diff --git a/src/ArduinoCloudProperty.cpp b/src/ArduinoCloudProperty.cpp index 2b8031c..64f9279 100644 --- a/src/ArduinoCloudProperty.cpp +++ b/src/ArduinoCloudProperty.cpp @@ -47,7 +47,10 @@ ArduinoCloudProperty::ArduinoCloudProperty() _update_interval_millis(0), _last_local_change_timestamp(0), _last_cloud_change_timestamp(0), - _map_data_list(nullptr) { + _map_data_list(nullptr), + _identifier(0), + _attributeIdentifier(0), + _lightPayload(false) { } /****************************************************************************** @@ -115,7 +118,9 @@ void ArduinoCloudProperty::execCallbackOnSync() { } } -void ArduinoCloudProperty::append(CborEncoder *encoder) { +void ArduinoCloudProperty::append(CborEncoder *encoder, bool lightPayload) { + _lightPayload = lightPayload; + _attributeIdentifier = 0; appendAttributesToCloudReal(encoder); fromLocalToCloud(); _has_been_updated_once = true; @@ -151,20 +156,35 @@ void ArduinoCloudProperty::appendAttributeReal(String value, String attributeNam } void ArduinoCloudProperty::appendAttributeName(String attributeName, std::functionappendValue, CborEncoder *encoder) { + if (attributeName != "") { + // when the attribute name string is not empty, the attribute identifier is incremented in order to be encoded in the message if the _lightPayload flag is set + _attributeIdentifier++; + } CborEncoder mapEncoder; cbor_encoder_create_map(encoder, &mapEncoder, 2); cbor_encode_int(&mapEncoder, static_cast(CborIntegerMapKey::Name)); - String completeName = _name; - if (attributeName != "") { - completeName += ":" + attributeName; + + // if _lightPayload is true, the property and attribute identifiers will be encoded instead of the property name + if (_lightPayload) { + // the most significant byte of the identifier to be encoded represent the property identifier + int completeIdentifier = _attributeIdentifier * 256; + // the least significant byte of the identifier to be encoded represent the attribute identifier + completeIdentifier += _identifier; + cbor_encode_int(&mapEncoder, completeIdentifier); + } else { + String completeName = _name; + if (attributeName != "") { + completeName += ":" + attributeName; + } + cbor_encode_text_stringz(&mapEncoder, completeName.c_str()); } - cbor_encode_text_stringz(&mapEncoder, completeName.c_str()); appendValue(mapEncoder); cbor_encoder_close_container(encoder, &mapEncoder); } void ArduinoCloudProperty::setAttributesFromCloud(LinkedList *map_data_list) { _map_data_list = map_data_list; + _attributeIdentifier = 0; setAttributesFromCloud(); } @@ -193,16 +213,30 @@ void ArduinoCloudProperty::setAttributeReal(String& value, String attributeName) } void ArduinoCloudProperty::setAttributeReal(String attributeName, std::functionsetValue) { + if (attributeName != "") { + _attributeIdentifier++; + } for (int i = 0; i < _map_data_list->size(); i++) { CborMapData *map = _map_data_list->get(i); if (map != nullptr) { - String an = map->attribute_name.get(); - if (an == attributeName) { - setValue(map); - break; + if (map->light_payload.isSet() && map->light_payload.get()) { + // if a light payload is detected, the attribute identifier is retrieved from the cbor map and the corresponding attribute is updated + int attid = map->attribute_identifier.get(); + if (attid == _attributeIdentifier) { + setValue(map); + break; + } + } else { + // if a normal payload is detected, the name of the attribute to be updated is extracted directly from the cbor map + String an = map->attribute_name.get(); + if (an == attributeName) { + setValue(map); + break; + } } } } + } String ArduinoCloudProperty::getAttributeName(String propertyName, char separator) { @@ -233,3 +267,7 @@ unsigned long ArduinoCloudProperty::getLastCloudChangeTimestamp() { unsigned long ArduinoCloudProperty::getLastLocalChangeTimestamp() { return _last_local_change_timestamp; } + +void ArduinoCloudProperty::setIdentifier(int identifier) { + _identifier = identifier; +} diff --git a/src/ArduinoCloudProperty.h b/src/ArduinoCloudProperty.h index 57f92ab..d2c1991 100644 --- a/src/ArduinoCloudProperty.h +++ b/src/ArduinoCloudProperty.h @@ -98,7 +98,11 @@ class CborMapData { MapEntry base_name; MapEntry base_time; MapEntry name; + MapEntry name_identifier; + MapEntry light_payload; MapEntry attribute_name; + MapEntry attribute_identifier; + MapEntry property_identifier; MapEntry val; MapEntry str_val; MapEntry bool_val; @@ -138,6 +142,9 @@ class ArduinoCloudProperty { inline String name() const { return _name; } + inline int identifier() const { + return _identifier; + } inline bool isReadableByCloud() const { return (_permission == Permission::Read) || (_permission == Permission::ReadWrite); } @@ -152,9 +159,10 @@ class ArduinoCloudProperty { void setLastLocalChangeTimestamp(unsigned long localChangeTime); unsigned long getLastCloudChangeTimestamp(); unsigned long getLastLocalChangeTimestamp(); + void setIdentifier(int identifier); void updateLocalTimestamp(); - void append(CborEncoder * encoder); + void append(CborEncoder * encoder, bool lightPayload); void appendAttributeReal(bool value, String attributeName = "", CborEncoder *encoder = nullptr); void appendAttributeReal(int value, String attributeName = "", CborEncoder *encoder = nullptr); void appendAttributeReal(float value, String attributeName = "", CborEncoder *encoder = nullptr); @@ -197,6 +205,11 @@ class ArduinoCloudProperty { unsigned long _last_local_change_timestamp; unsigned long _last_cloud_change_timestamp; LinkedList * _map_data_list; + /* Store the identifier of the property in the array list */ + int _identifier; + int _attributeIdentifier; + /* Indicates if the property shall be encoded using the identifier instead of the name */ + bool _lightPayload; }; /****************************************************************************** diff --git a/src/ArduinoCloudThing.cpp b/src/ArduinoCloudThing.cpp index 1cdb8b6..32dd596 100644 --- a/src/ArduinoCloudThing.cpp +++ b/src/ArduinoCloudThing.cpp @@ -45,6 +45,7 @@ void PrintFreeRam(void) { ArduinoCloudThing::ArduinoCloudThing() : _numPrimitivesProperties(0), + _numProperties(0), _isSyncMessage(false), _currentPropertyName(""), _currentPropertyBaseTime(0), @@ -58,7 +59,8 @@ ArduinoCloudThing::ArduinoCloudThing() : void ArduinoCloudThing::begin() { } -int ArduinoCloudThing::encode(uint8_t * data, size_t const size) { +int ArduinoCloudThing::encode(uint8_t * data, size_t const size, bool lightPayload) { + // check if backing storage and cloud has diverged // time interval may be elapsed or property may be changed CborEncoder encoder, arrayEncoder; @@ -69,7 +71,7 @@ int ArduinoCloudThing::encode(uint8_t * data, size_t const size) { return -1; } - if (appendChangedProperties(&arrayEncoder) < 1) { + if (appendChangedProperties(&arrayEncoder, lightPayload) < 1) { return -1; } @@ -84,7 +86,7 @@ int ArduinoCloudThing::encode(uint8_t * data, size_t const size) { return bytes_encoded; } -ArduinoCloudProperty& ArduinoCloudThing::addPropertyReal(ArduinoCloudProperty & property, String const & name, Permission const permission) { +ArduinoCloudProperty& ArduinoCloudThing::addPropertyReal(ArduinoCloudProperty & property, String const & name, Permission const permission, int propertyIdentifier) { property.init(name, permission); if (isPropertyInContainer(name)) { return (*getProperty(name)); @@ -92,9 +94,11 @@ ArduinoCloudProperty& ArduinoCloudThing::addPropertyReal(ArduinoCloudProperty & if (property.isPrimitive()) { _numPrimitivesProperties++; } - addProperty(&property); + _numProperties++; + addProperty(&property, propertyIdentifier); return (property); } + } void ArduinoCloudThing::decode(uint8_t const * const payload, size_t const length, bool isSyncMessage) { @@ -158,18 +162,19 @@ bool ArduinoCloudThing::isPropertyInContainer(String const & name) { return false; } -int ArduinoCloudThing::appendChangedProperties(CborEncoder * arrayEncoder) { +int ArduinoCloudThing::appendChangedProperties(CborEncoder * arrayEncoder, bool lightPayload) { int appendedProperties = 0; for (int i = 0; i < _property_list.size(); i++) { ArduinoCloudProperty * p = _property_list.get(i); if (p->shouldBeUpdated() && p->isReadableByCloud()) { - p->append(arrayEncoder); + p->append(arrayEncoder, lightPayload); appendedProperties++; } } return appendedProperties; } +//retrieve property by name ArduinoCloudProperty * ArduinoCloudThing::getProperty(String const & name) { for (int i = 0; i < _property_list.size(); i++) { ArduinoCloudProperty * p = _property_list.get(i); @@ -180,6 +185,17 @@ ArduinoCloudProperty * ArduinoCloudThing::getProperty(String const & name) { return NULL; } +//retrieve property by identifier +ArduinoCloudProperty * ArduinoCloudThing::getProperty(int const & pos) { + for (int i = 0; i < _property_list.size(); i++) { + ArduinoCloudProperty * p = _property_list.get(i); + if (p->identifier() == pos) { + return p; + } + } + return NULL; +} + // this function updates the timestamps on the primitive properties that have been modified locally since last cloud synchronization void ArduinoCloudThing::updateTimestampOnLocallyChangedProperties() { if (_numPrimitivesProperties == 0) { @@ -312,6 +328,7 @@ ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_Name(CborValue * val MapParserState next_state = MapParserState::Error; if (cbor_value_is_text_string(value_iter)) { + // if the value in the cbor message is a string, it corresponds to the name of the property to be updated (int the form [property_name]:[attribute_name]) char * val = nullptr; size_t val_size = 0; if (cbor_value_dup_text_string(value_iter, &val, &val_size, value_iter) == CborNoError) { @@ -326,8 +343,26 @@ ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_Name(CborValue * val map_data->attribute_name.set(attribute_name); next_state = MapParserState::MapKey; } + } else if (cbor_value_is_integer(value_iter)) { + // if the value in the cbor message is an integer, a light payload has been used and an integer identifier should be decode in order to retrieve the corresponding property and attribute name to be updated + int val = 0; + if (cbor_value_get_int(value_iter, &val) == CborNoError) { + map_data->light_payload.set(true); + map_data->name_identifier.set(val & 255); + map_data->attribute_identifier.set(val >> 8); + map_data->light_payload.set(true); + String name = getPropertyNameByIdentifier(val); + map_data->name.set(name); + + + if (cbor_value_advance(value_iter) == CborNoError) { + next_state = MapParserState::MapKey; + } + } } + + return next_state; } @@ -404,7 +439,6 @@ ArduinoCloudThing::MapParserState ArduinoCloudThing::handle_LeaveMap(CborValue * } if (_currentPropertyName != "" && propertyName != _currentPropertyName) { - /* Update the property containers depending on the parsed data */ updateProperty(_currentPropertyName, _currentPropertyBaseTime + _currentPropertyTime); /* Reset current property data */ @@ -461,6 +495,18 @@ void ArduinoCloudThing::updateProperty(String propertyName, unsigned long cloudC } } } + +// retrieve the property name by the identifier +String ArduinoCloudThing::getPropertyNameByIdentifier(int propertyIdentifier) { + ArduinoCloudProperty* property; + if (propertyIdentifier > 255) { + property = getProperty(propertyIdentifier & 255); + } else { + property = getProperty(propertyIdentifier); + } + return property->name(); +} + bool ArduinoCloudThing::ifNumericConvertToDouble(CborValue * value_iter, double * numeric_val) { if (cbor_value_is_integer(value_iter)) { diff --git a/src/ArduinoCloudThing.h b/src/ArduinoCloudThing.h index 9919573..c44eea6 100644 --- a/src/ArduinoCloudThing.h +++ b/src/ArduinoCloudThing.h @@ -77,23 +77,26 @@ class ArduinoCloudThing { ArduinoCloudThing(); void begin(); - - ArduinoCloudProperty & addPropertyReal(ArduinoCloudProperty & property, String const & name, Permission const permission); + //if propertyIdentifier is different from -1, an integer identifier is associated to the added property to be use instead of the property name when the parameter lightPayload is true in the encode method + ArduinoCloudProperty & addPropertyReal(ArduinoCloudProperty & property, String const & name, Permission const permission, int propertyIdentifier = -1); /* encode return > 0 if a property has changed and encodes the changed properties in CBOR format into the provided buffer */ - int encode(uint8_t * data, size_t const size); + /* if lightPayload is true the integer identifier of the property will be encoded in the message instead of the property name in order to reduce the size of the message payload*/ + int encode(uint8_t * data, size_t const size, bool lightPayload = false); /* decode a CBOR payload received from the cloud */ void decode(uint8_t const * const payload, size_t const length, bool isSyncMessage = false); bool isPropertyInContainer(String const & name); - int appendChangedProperties(CborEncoder * arrayEncoder); + int appendChangedProperties(CborEncoder * arrayEncoder, bool lightPayload); void updateTimestampOnLocallyChangedProperties(); void updateProperty(String propertyName, unsigned long cloudChangeEventTime); + String getPropertyNameByIdentifier(int propertyIdentifier); private: LinkedList _property_list; /* Keep track of the number of primitive properties in the Thing. If 0 it allows the early exit in updateTimestampOnLocallyChangedProperties() */ int _numPrimitivesProperties; + int _numProperties; /* Indicates the if the message received to be decoded is a response to the getLastValues inquiry */ bool _isSyncMessage; /* List of map data that will hold all the attributes of a property */ @@ -135,11 +138,18 @@ class ArduinoCloudThing { static bool ifNumericConvertToDouble(CborValue * value_iter, double * numeric_val); static double convertCborHalfFloatToDouble(uint16_t const half_val); - void freeMapDataList(LinkedList *map_data_list); - inline void addProperty(ArduinoCloudProperty * property_obj) { + void freeMapDataList(LinkedList * map_data_list); + inline void addProperty(ArduinoCloudProperty * property_obj, int propertyIdentifier) { + if (propertyIdentifier != -1) { + property_obj->setIdentifier(propertyIdentifier); + } else { + // if property identifier is -1, an incremental value will be assigned as identifier. + property_obj->setIdentifier(_numProperties); + } _property_list.add(property_obj); } ArduinoCloudProperty * getProperty(String const & name); + ArduinoCloudProperty * getProperty(int const & identifier); }; diff --git a/src/types/CloudLocation.h b/src/types/CloudLocation.h index b673d6f..79d6554 100644 --- a/src/types/CloudLocation.h +++ b/src/types/CloudLocation.h @@ -83,7 +83,6 @@ class CloudLocation : public ArduinoCloudProperty { return _value; } - virtual void fromCloudToLocal() { _value = _cloud_value; } diff --git a/test/include/TestUtil.h b/test/include/TestUtil.h index beb6a09..ea5e588 100644 --- a/test/include/TestUtil.h +++ b/test/include/TestUtil.h @@ -17,7 +17,7 @@ PROTOTYPES **************************************************************************************/ -std::vector encode(ArduinoCloudThing & thing); +std::vector encode(ArduinoCloudThing & thing, bool lightPayload = false); void print(std::vector const & vect); #endif /* INCLUDE_TESTUTIL_H_ */ diff --git a/test/src/TestUtil.cpp b/test/src/TestUtil.cpp index ab54c00..b372388 100644 --- a/test/src/TestUtil.cpp +++ b/test/src/TestUtil.cpp @@ -15,9 +15,9 @@ PUBLIC FUNCTIONS **************************************************************************************/ -std::vector encode(ArduinoCloudThing & thing) { +std::vector encode(ArduinoCloudThing & thing, bool lightPayload) { uint8_t buf[200] = {0}; - int const bytes_buf = thing.encode(buf, 200); + int const bytes_buf = thing.encode(buf, 200, lightPayload); if (bytes_buf == -1) { return std::vector(); } else { diff --git a/test/src/test_decode.cpp b/test/src/test_decode.cpp index 3fff8a4..5f521f9 100644 --- a/test/src/test_decode.cpp +++ b/test/src/test_decode.cpp @@ -50,6 +50,27 @@ SCENARIO("Arduino Cloud Properties are decoded", "[ArduinoCloudThing::decode]") /************************************************************************************/ + WHEN("A boolean property is changed via CBOR message - light payload") { + /*An integer identifier has been encoded instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudBool test = true; + /*The property is added with identifier 1 that will be used instead of the string "test" as property identifier*/ + thing.addPropertyReal(test, "test", Permission::ReadWrite, 1); + + /* [{0: 1, 4: false}] = 81 A2 00 01 04 F4 */ + uint8_t const payload[] = {0x81, 0xA2, 0x00, 0x01, 0x04, 0xF4}; + int const payload_length = sizeof(payload) / sizeof(uint8_t); + thing.decode(payload, payload_length); + + REQUIRE(test == false); + } + } + + /************************************************************************************/ + WHEN("A int property is changed via CBOR message") { GIVEN("CloudProtocol::V2") { ArduinoCloudThing thing; @@ -166,6 +187,33 @@ SCENARIO("Arduino Cloud Properties are decoded", "[ArduinoCloudThing::decode]") /************************************************************************************/ + WHEN("A Color property is changed via CBOR message - light payload") { + /*An integer identifier has been encoded instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + + CloudColor color_test = CloudColor(0.0, 0.0, 0.0); + + /*The property is added with identifier 1 that will be used instead of the string "test" as property identifier*/ + thing.addPropertyReal(color_test, "test", Permission::ReadWrite, 1); + + /* [{0: 257, 2: 2.0},{0: 513, 2: 2.0},{0: 769, 2: 2.0}] = 83 A2 00 19 01 01 02 FA 40 00 00 00 A2 00 19 02 01 02 FA 40 00 00 00 A2 00 19 03 01 02 FA 40 00 00 00 */ + uint8_t const payload[] = {0x83, 0xA2, 0x00, 0x19, 0x01, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x02, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x03, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00 }; + thing.decode(payload, sizeof(payload) / sizeof(uint8_t)); + + Color color_compare = Color(2.0, 2.0, 2.0); + Color value_color_test = color_test.getValue(); + bool verify = (value_color_test == color_compare); + REQUIRE(verify); + REQUIRE(value_color_test.hue == color_compare.hue); + REQUIRE(value_color_test.sat == color_compare.sat); + REQUIRE(value_color_test.bri == color_compare.bri); + } + } + + /************************************************************************************/ + WHEN("A ColoredLight property is changed via CBOR message") { GIVEN("CloudProtocol::V2") { ArduinoCloudThing thing; diff --git a/test/src/test_encode.cpp b/test/src/test_encode.cpp index 9640224..a9a5c5e 100644 --- a/test/src/test_encode.cpp +++ b/test/src/test_encode.cpp @@ -40,6 +40,26 @@ SCENARIO("Arduino Cloud Properties are encoded", "[ArduinoCloudThing::encode]") /************************************************************************************/ + WHEN("A 'bool' property is added - light payload") { + /*An integer identifier must be instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + encode(thing); + + CloudBool test = true; + /*The property is added with identifier 1 that will be used instead of the string "test" as property identifier*/ + thing.addPropertyReal(test, "test", Permission::ReadWrite, 1); + + /* [{0: 1, 4: true}] = 9F A2 00 01 04 F5 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x01, 0x04, 0xF5, 0xFF}; + std::vector const actual = encode(thing, true); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + WHEN("A 'int' property is added") { GIVEN("CloudProtocol::V2") { ArduinoCloudThing thing; @@ -131,6 +151,26 @@ SCENARIO("Arduino Cloud Properties are encoded", "[ArduinoCloudThing::encode]") /************************************************************************************/ + WHEN("A 'Color' property is added - light payload") { + /*An integer identifier must be encoded instead of the name of the property in order to have a shorter payload*/ + GIVEN("CloudProtocol::V2") { + ArduinoCloudThing thing; + thing.begin(); + encode(thing); + + CloudColor color_test = CloudColor(2.0, 2.0, 2.0); + /*The property is added with identifier 1 that will be used instead of the string "name" as property identifier */ + thing.addPropertyReal(color_test, "test", Permission::ReadWrite, 1); + + /* [{0: 257, 2: 2.0},{0: 513, 2: 2.0},{0: 769, 2: 2.0}] = 9F A2 00 19 01 01 02 FA 40 00 00 00 A2 00 19 02 01 02 FA 40 00 00 00 A2 00 19 03 01 02 FA 40 00 00 00 FF*/ + std::vector const expected = {0x9F, 0xA2, 0x00, 0x19, 0x01, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x02, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xA2, 0x00, 0x19, 0x03, 0x01, 0x02, 0xFA, 0x40, 0x00, 0x00, 0x00, 0xFF }; + std::vector const actual = encode(thing, true); + REQUIRE(actual == expected); + } + } + + /************************************************************************************/ + WHEN("A 'ColoredLight' property is added") { GIVEN("CloudProtocol::V2") { ArduinoCloudThing thing;