diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index 6949524d6..eed12f5fc 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -140,14 +140,19 @@ * CONSTANTS ******************************************************************************/ -#define AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms (1000UL) -#define AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms (32000UL) -#define AIOT_CONFIG_SUBSCRIBE_RETRY_DELAY_ms (1000UL) -#define AIOT_CONFIG_SUBSCRIBE_MAX_RETRY_CNT (10UL) -#define AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms (30000UL) -#define AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT (10UL) - -#define AIOT_CONFIG_RP2040_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms (10*1000UL) +#define AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms (1000UL) +#define AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms (32000UL) +#define AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms (5*1000UL) +#define AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms (32000UL) +#define AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_RETRY_DELAY_ms (1000UL) +#define AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_MAX_RETRY_CNT (10UL) +#define AIOT_CONFIG_MAX_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms (1280000UL) +#define AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms (30000UL) +#define AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT (10UL) + +#define AIOT_CONFIG_RP2040_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms (10*1000UL) #define AIOT_CONFIG_RP2040_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms (4*60*1000UL) +#define AIOT_CONFIG_LIB_VERSION "1.5.0" + #endif /* ARDUINO_AIOTC_CONFIG_H_ */ diff --git a/src/ArduinoIoTCloud.cpp b/src/ArduinoIoTCloud.cpp index 115ce3a0d..0b2794ade 100644 --- a/src/ArduinoIoTCloud.cpp +++ b/src/ArduinoIoTCloud.cpp @@ -33,7 +33,9 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() , _tz_dst_until{0} , _thing_id{""} , _device_id{""} +, _lib_version{AIOT_CONFIG_LIB_VERSION} , _cloud_event_callback{nullptr} +, _thing_id_outdated{false} { } @@ -44,12 +46,12 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() void ArduinoIoTCloudClass::push() { - requestUpdateForAllProperties(_property_container); + requestUpdateForAllProperties(_thing_property_container); } bool ArduinoIoTCloudClass::setTimestamp(String const & prop_name, unsigned long const timestamp) { - Property * p = getProperty(_property_container, prop_name); + Property * p = getProperty(_thing_property_container, prop_name); if (p == nullptr) return false; @@ -81,20 +83,30 @@ void ArduinoIoTCloudClass::addPropertyReal(Property& property, String name, int } if (seconds == ON_CHANGE) { - addPropertyToContainer(_property_container, property, name, permission, tag).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); + addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); } else { - addPropertyToContainer(_property_container, property, name, permission, tag).publishEvery(seconds).onUpdate(fn).onSync(synFn); + addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishEvery(seconds).onUpdate(fn).onSync(synFn); } } Property& ArduinoIoTCloudClass::addPropertyReal(Property& property, String name, Permission const permission) { - return addPropertyToContainer(_property_container, property, name, permission); + return addPropertyToContainer(_thing_property_container, property, name, permission); } Property& ArduinoIoTCloudClass::addPropertyReal(Property& property, String name, int tag, Permission const permission) { - return addPropertyToContainer(_property_container, property, name, permission, tag); + return addPropertyToContainer(_thing_property_container, property, name, permission, tag); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(Property& property, PropertyContainer &prop_cont, String name, Permission const permission) +{ + return addPropertyToContainer(prop_cont, property, name, permission, -1); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(Property& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission) +{ + return addPropertyToContainer(prop_cont, property, name, permission, tag); } void ArduinoIoTCloudClass::addPropertyReal(bool& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) @@ -110,13 +122,23 @@ void ArduinoIoTCloudClass::addPropertyReal(bool& property, String name, int tag, Property& ArduinoIoTCloudClass::addPropertyReal(bool& property, String name, Permission const permission) { - return addPropertyReal(property, name, -1, permission); + return addPropertyReal(property, _thing_property_container, name, -1, permission); } Property& ArduinoIoTCloudClass::addPropertyReal(bool& property, String name, int tag, Permission const permission) +{ + return addPropertyReal(property, _thing_property_container, name, tag, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(bool& property, PropertyContainer &prop_cont, String name, Permission const permission) +{ + return addPropertyReal(property, prop_cont, name, -1, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(bool& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission) { Property* p = new CloudWrapperBool(property); - return addPropertyToContainer(_property_container, *p, name, permission, tag); + return addPropertyToContainer(prop_cont, *p, name, permission, tag); } void ArduinoIoTCloudClass::addPropertyReal(float& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) @@ -132,13 +154,23 @@ void ArduinoIoTCloudClass::addPropertyReal(float& property, String name, int tag Property& ArduinoIoTCloudClass::addPropertyReal(float& property, String name, Permission const permission) { - return addPropertyReal(property, name, -1, permission); + return addPropertyReal(property, _thing_property_container, name, -1, permission); } Property& ArduinoIoTCloudClass::addPropertyReal(float& property, String name, int tag, Permission const permission) +{ + return addPropertyReal(property, _thing_property_container, name, tag, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(float& property, PropertyContainer &prop_cont, String name, Permission const permission) +{ + return addPropertyReal(property, prop_cont, name, -1, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(float& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission) { Property* p = new CloudWrapperFloat(property); - return addPropertyToContainer(_property_container, *p, name, permission, tag); + return addPropertyToContainer(prop_cont, *p, name, permission, tag); } void ArduinoIoTCloudClass::addPropertyReal(int& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) @@ -154,13 +186,23 @@ void ArduinoIoTCloudClass::addPropertyReal(int& property, String name, int tag, Property& ArduinoIoTCloudClass::addPropertyReal(int& property, String name, Permission const permission) { - return addPropertyReal(property, name, -1, permission); + return addPropertyReal(property, _thing_property_container, name, -1, permission); } Property& ArduinoIoTCloudClass::addPropertyReal(int& property, String name, int tag, Permission const permission) +{ + return addPropertyReal(property, _thing_property_container, name, tag, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(int& property, PropertyContainer &prop_cont, String name, Permission const permission) +{ + return addPropertyReal(property, prop_cont, name, -1, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(int& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission) { Property* p = new CloudWrapperInt(property); - return addPropertyToContainer(_property_container, *p, name, permission, tag); + return addPropertyToContainer(prop_cont, *p, name, permission, tag); } void ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) @@ -176,13 +218,23 @@ void ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, String name, Property& ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, String name, Permission const permission) { - return addPropertyReal(property, name, -1, permission); + return addPropertyReal(property, _thing_property_container, name, -1, permission); } Property& ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, String name, int tag, Permission const permission) +{ + return addPropertyReal(property, _thing_property_container, name, tag, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, PropertyContainer &prop_cont, String name, Permission const permission) +{ + return addPropertyReal(property, prop_cont, name, -1, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission) { Property* p = new CloudWrapperUnsignedInt(property); - return addPropertyToContainer(_property_container, *p, name, permission, tag); + return addPropertyToContainer(prop_cont, *p, name, permission, tag); } void ArduinoIoTCloudClass::addPropertyReal(String& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) @@ -198,13 +250,23 @@ void ArduinoIoTCloudClass::addPropertyReal(String& property, String name, int ta Property& ArduinoIoTCloudClass::addPropertyReal(String& property, String name, Permission const permission) { - return addPropertyReal(property, name, -1, permission); + return addPropertyReal(property, _thing_property_container, name, -1, permission); } Property& ArduinoIoTCloudClass::addPropertyReal(String& property, String name, int tag, Permission const permission) +{ + return addPropertyReal(property, _thing_property_container, name, tag, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(String& property, PropertyContainer &prop_cont, String name, Permission const permission) +{ + return addPropertyReal(property, prop_cont, name, -1, permission); +} + +Property& ArduinoIoTCloudClass::addPropertyReal(String& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission) { Property* p = new CloudWrapperString(property); - return addPropertyToContainer(_property_container, *p, name, permission, tag); + return addPropertyToContainer(prop_cont, *p, name, permission, tag); } /****************************************************************************** diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 565a91501..8be318b25 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -96,6 +96,12 @@ class ArduinoIoTCloudClass inline void setDeviceId(String const device_id) { _device_id = device_id; }; inline String & getDeviceId() { return _device_id; }; + inline void setThingIdOutdatedFlag() { _thing_id_outdated = true ; } + inline void clrThingIdOutdatedFlag() { _thing_id_outdated = false ; } + inline bool getThingIdOutdatedFlag() { return _thing_id_outdated; } + + inline bool deviceNotAttached() { return _thing_id == ""; } + inline ConnectionHandler * getConnection() { return _connection; } inline unsigned long getInternalTime() { return _time_service.getTime(); } @@ -124,6 +130,20 @@ class ArduinoIoTCloudClass Property& addPropertyReal(unsigned int& property, String name, Permission const permission); Property& addPropertyReal(String& property, String name, Permission const permission); + Property& addPropertyReal(Property& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission); + Property& addPropertyReal(bool& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission); + Property& addPropertyReal(float& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission); + Property& addPropertyReal(int& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission); + Property& addPropertyReal(unsigned int& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission); + Property& addPropertyReal(String& property, PropertyContainer &prop_cont, String name, int tag, Permission const permission); + + Property& addPropertyReal(Property& property, PropertyContainer &prop_cont, String name, Permission const permission); + Property& addPropertyReal(bool& property, PropertyContainer &prop_cont, String name, Permission const permission); + Property& addPropertyReal(float& property, PropertyContainer &prop_cont, String name, Permission const permission); + Property& addPropertyReal(int& property, PropertyContainer &prop_cont, String name, Permission const permission); + Property& addPropertyReal(unsigned int& property, PropertyContainer &prop_cont, String name, Permission const permission); + Property& addPropertyReal(String& property, PropertyContainer &prop_cont, String name, Permission const permission); + /* The following methods are for MKR WAN 1300/1310 LoRa boards since * they use a number to identify a given property within a CBOR message. * This approach reduces the required amount of data which is of great @@ -147,19 +167,22 @@ class ArduinoIoTCloudClass protected: ConnectionHandler * _connection; - PropertyContainer _property_container; + PropertyContainer _device_property_container; + PropertyContainer _thing_property_container; unsigned int _last_checked_property_index; TimeService & _time_service; int _tz_offset; unsigned int _tz_dst_until; + String _thing_id; + String _lib_version; void execCloudEventCallback(ArduinoIoTCloudEvent const event); private: - String _thing_id; String _device_id; OnCloudEventCallback _cloud_event_callback[3]; + bool _thing_id_outdated; }; #ifdef HAS_TCP diff --git a/src/ArduinoIoTCloudLPWAN.cpp b/src/ArduinoIoTCloudLPWAN.cpp index fc85d0471..0f96d36db 100644 --- a/src/ArduinoIoTCloudLPWAN.cpp +++ b/src/ArduinoIoTCloudLPWAN.cpp @@ -37,7 +37,7 @@ static size_t const CBOR_LORA_MSG_MAX_SIZE = 255; LOCAL MODULE FUNCTIONS ******************************************************************************/ -extern "C" unsigned long getTime() +unsigned long getTime() { return ArduinoCloud.getInternalTime(); } @@ -120,8 +120,8 @@ ArduinoIoTCloudLPWAN::State ArduinoIoTCloudLPWAN::handle_Connected() } /* Check if a primitive property wrapper is locally changed. */ - updateTimestampOnLocallyChangedProperties(_property_container); - + updateTimestampOnLocallyChangedProperties(_thing_property_container); + /* Decode available data. */ if (_connection->available()) decodePropertiesFromCloud(); @@ -142,7 +142,7 @@ void ArduinoIoTCloudLPWAN::decodePropertiesFromCloud() { lora_msg_buf[bytes_received] = _connection->read(); } - CBORDecoder::decode(_property_container, lora_msg_buf, bytes_received); + CBORDecoder::decode(_thing_property_container, lora_msg_buf, bytes_received); } void ArduinoIoTCloudLPWAN::sendPropertiesToCloud() @@ -150,7 +150,7 @@ void ArduinoIoTCloudLPWAN::sendPropertiesToCloud() int bytes_encoded = 0; uint8_t data[CBOR_LORA_MSG_MAX_SIZE]; - if (CBOREncoder::encode(_property_container, data, sizeof(data), bytes_encoded, _last_checked_property_index, true) == CborNoError) + if (CBOREncoder::encode(_thing_property_container, data, sizeof(data), bytes_encoded, _last_checked_property_index, true) == CborNoError) if (bytes_encoded > 0) writeProperties(data, bytes_encoded); } diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index e78011852..ed0e46cd4 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -59,16 +59,21 @@ extern RTC_HandleTypeDef RTCHandle; LOCAL MODULE FUNCTIONS ******************************************************************************/ -extern "C" unsigned long getTime() +unsigned long getTime() { return ArduinoCloud.getInternalTime(); } -extern "C" void updateTimezoneInfo() +void updateTimezoneInfo() { ArduinoCloud.updateInternalTimezoneInfo(); } +void setThingIdOutdated() +{ + ArduinoCloud.setThingIdOutdatedFlag(); +} + /****************************************************************************** CTOR/DTOR ******************************************************************************/ @@ -77,6 +82,8 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() : _state{State::ConnectPhy} , _next_connection_attempt_tick{0} , _last_connection_attempt_cnt{0} +, _next_device_subscribe_attempt_tick{0} +, _last_device_subscribe_cnt{0} , _last_sync_request_tick{0} , _last_sync_request_cnt{0} , _last_subscribe_request_tick{0} @@ -91,10 +98,13 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _password("") #endif , _mqttClient{nullptr} +, _deviceTopicOut("") +, _deviceTopicIn("") , _shadowTopicOut("") , _shadowTopicIn("") , _dataTopicOut("") , _dataTopicIn("") +, _deviceSubscribedToThing{false} #if OTA_ENABLED , _ota_cap{false} , _ota_error{static_cast(OTAError::None)} @@ -237,21 +247,21 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, _mqttClient.setConnectionTimeout(1500); _mqttClient.setId(getDeviceId().c_str()); - _shadowTopicOut = getTopic_shadowout(); - _shadowTopicIn = getTopic_shadowin(); - _dataTopicOut = getTopic_dataout(); - _dataTopicIn = getTopic_datain(); + _deviceTopicOut = getTopic_deviceout(); + _deviceTopicIn = getTopic_devicein(); + addPropertyReal(_lib_version, _device_property_container, "LIB_VERSION", Permission::Read); #if OTA_ENABLED - addPropertyReal(_ota_cap, "OTA_CAP", Permission::Read); - addPropertyReal(_ota_error, "OTA_ERROR", Permission::Read); - addPropertyReal(_ota_img_sha256, "OTA_SHA256", Permission::Read); - addPropertyReal(_ota_url, "OTA_URL", Permission::ReadWrite).onSync(CLOUD_WINS); - addPropertyReal(_ota_req, "OTA_REQ", Permission::ReadWrite).onSync(CLOUD_WINS); + addPropertyReal(_ota_cap, _device_property_container, "OTA_CAP", Permission::Read); + addPropertyReal(_ota_error, _device_property_container, "OTA_ERROR", Permission::Read); + addPropertyReal(_ota_img_sha256, _device_property_container, "OTA_SHA256", Permission::Read); + addPropertyReal(_ota_url, _device_property_container, "OTA_URL", Permission::ReadWrite); + addPropertyReal(_ota_req, _device_property_container, "OTA_REQ", Permission::ReadWrite); #endif /* OTA_ENABLED */ - addPropertyReal(_tz_offset, "tz_offset", Permission::ReadWrite).onSync(CLOUD_WINS).onUpdate(updateTimezoneInfo); - addPropertyReal(_tz_dst_until, "tz_dst_until", Permission::ReadWrite).onSync(CLOUD_WINS).onUpdate(updateTimezoneInfo); + addPropertyReal(_tz_offset, _thing_property_container, "tz_offset", Permission::ReadWrite).onSync(CLOUD_WINS).onUpdate(updateTimezoneInfo); + addPropertyReal(_tz_dst_until, _thing_property_container, "tz_dst_until", Permission::ReadWrite).onSync(CLOUD_WINS).onUpdate(updateTimezoneInfo); + addPropertyReal(_thing_id, _device_property_container, "thing_id", Permission::ReadWrite).onUpdate(setThingIdOutdated); #if OTA_STORAGE_PORTENTA_QSPI #define BOOTLOADER_ADDR (0x8000000) @@ -320,12 +330,17 @@ void ArduinoIoTCloudTCP::update() State next_state = _state; switch (_state) { - case State::ConnectPhy: next_state = handle_ConnectPhy(); break; - case State::SyncTime: next_state = handle_SyncTime(); break; - case State::ConnectMqttBroker: next_state = handle_ConnectMqttBroker(); break; - case State::SubscribeMqttTopics: next_state = handle_SubscribeMqttTopics(); break; - case State::RequestLastValues: next_state = handle_RequestLastValues(); break; - case State::Connected: next_state = handle_Connected(); break; + case State::ConnectPhy: next_state = handle_ConnectPhy(); break; + case State::SyncTime: next_state = handle_SyncTime(); break; + case State::ConnectMqttBroker: next_state = handle_ConnectMqttBroker(); break; + case State::SendDeviceProperties: next_state = handle_SendDeviceProperties(); break; + case State::SubscribeDeviceTopic: next_state = handle_SubscribeDeviceTopic(); break; + case State::WaitDeviceConfig: next_state = handle_WaitDeviceConfig(); break; + case State::CheckDeviceConfig: next_state = handle_CheckDeviceConfig(); break; + case State::SubscribeThingTopics: next_state = handle_SubscribeThingTopics(); break; + case State::RequestLastValues: next_state = handle_RequestLastValues(); break; + case State::Connected: next_state = handle_Connected(); break; + case State::Disconnect: next_state = handle_Disconnect(); break; } _state = next_state; @@ -351,7 +366,6 @@ void ArduinoIoTCloudTCP::printDebugInfo() { DEBUG_INFO("***** Arduino IoT Cloud - configuration info *****"); DEBUG_INFO("Device ID: %s", getDeviceId().c_str()); - DEBUG_INFO("Thing ID: %s", getThingId().c_str()); DEBUG_INFO("MQTT Broker: %s:%d", _brokerAddress.c_str(), _brokerPort); } @@ -383,7 +397,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() if (_mqttClient.connect(_brokerAddress.c_str(), _brokerPort)) { _last_connection_attempt_cnt = 0; - return State::SubscribeMqttTopics; + return State::SendDeviceProperties; } _last_connection_attempt_cnt++; @@ -396,26 +410,129 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() return State::ConnectPhy; } -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeMqttTopics() +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SendDeviceProperties() +{ + if (!_mqttClient.connected()) + { + return State::Disconnect; + } + + sendDevicePropertiesToCloud(); + return State::SubscribeDeviceTopic; +} + +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeDeviceTopic() { if (!_mqttClient.connected()) { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s MQTT client connection lost", __FUNCTION__); + return State::Disconnect; + } + + if (!_mqttClient.subscribe(_deviceTopicIn)) + { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _deviceTopicIn.c_str()); + return State::SubscribeDeviceTopic; + } + + if (_last_device_subscribe_cnt > AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT) + { + _last_device_subscribe_cnt = 0; + _next_device_subscribe_attempt_tick = 0; _mqttClient.stop(); execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); return State::ConnectPhy; } + /* No device configuration reply. Wait: 5s -> 10s -> 20s -> 30s */ + unsigned long subscribe_retry_delay = (1 << _last_device_subscribe_cnt) * AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms; + subscribe_retry_delay = min(subscribe_retry_delay, static_cast(AIOT_CONFIG_MAX_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms)); + _next_device_subscribe_attempt_tick = millis() + subscribe_retry_delay; + _last_device_subscribe_cnt++; + + return State::WaitDeviceConfig; +} + +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_WaitDeviceConfig() +{ + if (!_mqttClient.connected()) + { + return State::Disconnect; + } + + if (getThingIdOutdatedFlag()) + { + return State::CheckDeviceConfig; + } + + if (millis() > _next_device_subscribe_attempt_tick) + { + /* Configuration not received or device not attached to a valid thing. Try to resubscribe */ + if (_mqttClient.unsubscribe(_deviceTopicIn)) + { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s device waiting for valid thing_id", __FUNCTION__); + return State::SubscribeDeviceTopic; + } + } + return State::WaitDeviceConfig; +} + +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_CheckDeviceConfig() +{ + if (!_mqttClient.connected()) + { + return State::Disconnect; + } + + if(_deviceSubscribedToThing == true) + { + /* Unsubscribe from old things topics and go on with a new subsctiption */ + _mqttClient.unsubscribe(_shadowTopicIn); + _mqttClient.unsubscribe(_dataTopicIn); + _deviceSubscribedToThing = false; + DEBUG_INFO("Disconnected from Arduino IoT Cloud"); + execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); + } + + updateThingTopics(); + + if (deviceNotAttached()) + { + /* Configuration received but device not attached. Wait: 40s */ + unsigned long attach_retry_delay = (1 << _last_device_attach_cnt) * AIOT_CONFIG_DEVICE_TOPIC_SUBSCRIBE_RETRY_DELAY_ms; + attach_retry_delay = min(attach_retry_delay, static_cast(AIOT_CONFIG_MAX_DEVICE_TOPIC_ATTACH_RETRY_DELAY_ms)); + _next_device_subscribe_attempt_tick = millis() + attach_retry_delay; + _last_device_attach_cnt++; + return State::WaitDeviceConfig; + } + + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s Device attached to a new valid Thing %s", __FUNCTION__, getThingId().c_str()); + _last_device_attach_cnt = 0; + + return State::SubscribeThingTopics; +} + +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeThingTopics() +{ + if (!_mqttClient.connected()) + { + return State::Disconnect; + } + + if (getThingIdOutdatedFlag()) + { + return State::CheckDeviceConfig; + } + unsigned long const now = millis(); - bool const is_subscribe_retry_delay_expired = (now - _last_subscribe_request_tick) > AIOT_CONFIG_SUBSCRIBE_RETRY_DELAY_ms; + bool const is_subscribe_retry_delay_expired = (now - _last_subscribe_request_tick) > AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_RETRY_DELAY_ms; bool const is_first_subscribe_request = (_last_subscribe_request_cnt == 0); if (!is_first_subscribe_request && !is_subscribe_retry_delay_expired) { - return State::SubscribeMqttTopics; + return State::SubscribeThingTopics; } - if (_last_subscribe_request_cnt > AIOT_CONFIG_SUBSCRIBE_MAX_RETRY_CNT) + if (_last_subscribe_request_cnt > AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_MAX_RETRY_CNT) { _last_subscribe_request_cnt = 0; _last_subscribe_request_tick = 0; @@ -433,41 +550,37 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeMqttTopics() #if !defined(__AVR__) DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); #endif - return State::SubscribeMqttTopics; + return State::SubscribeThingTopics; } - if (_shadowTopicIn != "") + if (!_mqttClient.subscribe(_shadowTopicIn)) { - if (!_mqttClient.subscribe(_shadowTopicIn)) - { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _shadowTopicIn.c_str()); + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _shadowTopicIn.c_str()); #if !defined(__AVR__) - DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); + DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); #endif - return State::SubscribeMqttTopics; - } + return State::SubscribeThingTopics; } DEBUG_INFO("Connected to Arduino IoT Cloud"); + DEBUG_INFO("Thing ID: %s", getThingId().c_str()); execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT); - _last_subscribe_request_cnt = 0; - _last_subscribe_request_tick = 0; - - if (_shadowTopicIn != "") - return State::RequestLastValues; - else - return State::Connected; + _deviceSubscribedToThing = true; + /*Add retry wait time otherwise we are trying to reconnect every 250ms...*/ + return State::RequestLastValues; } ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_RequestLastValues() { if (!_mqttClient.connected()) { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s MQTT client connection lost", __FUNCTION__); - _mqttClient.stop(); - execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); - return State::ConnectPhy; + return State::Disconnect; + } + + if (getThingIdOutdatedFlag()) + { + return State::CheckDeviceConfig; } /* Check whether or not we need to send a new request. */ @@ -501,22 +614,18 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() { if (!_mqttClient.connected()) { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s MQTT client connection lost", __FUNCTION__); - - /* Forcefully disconnect MQTT client and trigger a reconnection. */ - _mqttClient.stop(); - /* The last message was definitely lost, trigger a retransmit. */ _mqtt_data_request_retransmit = true; - - /* We are not connected anymore, trigger the callback for a disconnected event. */ - execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); - - return State::ConnectPhy; + return State::Disconnect; } /* We are connected so let's to our stuff here. */ else { + if (getThingIdOutdatedFlag()) + { + return State::CheckDeviceConfig; + } + /* Check if a primitive property wrapper is locally changed. * This function requires an existing time service which in * turn requires an established connection. Not having that @@ -524,7 +633,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() * the connection from being established due to a wrong data * in the reconstructed certificate. */ - updateTimestampOnLocallyChangedProperties(_property_container); + updateTimestampOnLocallyChangedProperties(_thing_property_container); /* Retransmit data in case there was a lost transaction due * to phy layer or MQTT connectivity loss. @@ -548,18 +657,26 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() _ota_error = static_cast(OTAError::None); /* Clear the request flag. */ _ota_req = false; - /* Transmit the cleared error and request flags to the cloud. */ - sendOTAPropertiesToCloud(); + /* Transmit the cleared request flags to the cloud. */ + sendDevicePropertyToCloud("OTA_REQ"); /* Call member function to handle OTA request. */ onOTARequest(); + /* If something fails send the OTA error to the cloud */ + sendDevicePropertyToCloud("OTA_ERROR"); } } + + /* Check if we have received the OTA_URL property and provide + * echo to the cloud. + */ + sendDevicePropertyToCloud("OTA_URL"); + #endif /* OTA_ENABLED */ /* Check if any properties need encoding and send them to * the cloud if necessary. */ - sendPropertiesToCloud(); + sendThingPropertiesToCloud(); unsigned long const internal_posix_time = _time_service.getTime(); if(internal_posix_time < _tz_dst_until) { @@ -570,6 +687,14 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() } } +ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Disconnect() +{ + DEBUG_ERROR("ArduinoIoTCloudTCP::%s MQTT client connection lost", __FUNCTION__); + _mqttClient.stop(); + execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); + return State::ConnectPhy; +} + void ArduinoIoTCloudTCP::onMessage(int length) { ArduinoCloud.handleMessage(length); @@ -585,15 +710,23 @@ void ArduinoIoTCloudTCP::handleMessage(int length) bytes[i] = _mqttClient.read(); } + /* Topic for OTA properties and device configuration */ + if (_deviceTopicIn == topic) { + CBORDecoder::decode(_device_property_container, (uint8_t*)bytes, length); + _last_device_subscribe_cnt = 0; + _next_device_subscribe_attempt_tick = 0; + } + + /* Topic for user input data */ if (_dataTopicIn == topic) { - CBORDecoder::decode(_property_container, (uint8_t*)bytes, length); + CBORDecoder::decode(_thing_property_container, (uint8_t*)bytes, length); } + /* Topic for sync Thing last values on connect */ if ((_shadowTopicIn == topic) && (_state == State::RequestLastValues)) { DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values received", __FUNCTION__, millis()); - CBORDecoder::decode(_property_container, (uint8_t*)bytes, length, true); - sendPropertiesToCloud(); + CBORDecoder::decode(_thing_property_container, (uint8_t*)bytes, length, true); _time_service.setTimeZoneData(_tz_offset, _tz_dst_until); execCloudEventCallback(ArduinoIoTCloudEvent::SYNC); _last_sync_request_cnt = 0; @@ -602,7 +735,7 @@ void ArduinoIoTCloudTCP::handleMessage(int length) } } -void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(PropertyContainer & property_container, unsigned int & current_property_index) +void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index) { int bytes_encoded = 0; uint8_t data[MQTT_TRANSMIT_BUFFER_SIZE]; @@ -616,33 +749,46 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(PropertyContainer & proper _mqtt_data_len = bytes_encoded; memcpy(_mqtt_data_buf, data, _mqtt_data_len); /* Transmit the properties to the MQTT broker */ - write(_dataTopicOut, _mqtt_data_buf, _mqtt_data_len); + write(topic, _mqtt_data_buf, _mqtt_data_len); } } -void ArduinoIoTCloudTCP::sendPropertiesToCloud() +void ArduinoIoTCloudTCP::sendThingPropertiesToCloud() { - sendPropertyContainerToCloud(_property_container, _last_checked_property_index); + sendPropertyContainerToCloud(_dataTopicOut, _thing_property_container, _last_checked_property_index); } -#if OTA_ENABLED -void ArduinoIoTCloudTCP::sendOTAPropertiesToCloud() +void ArduinoIoTCloudTCP::sendDevicePropertiesToCloud() { - PropertyContainer ota_property_container; - unsigned int last_ota_property_index = 0; + PropertyContainer ro_device_property_container; + unsigned int last_device_property_index = 0; - std::list const ota_property_list {"OTA_CAP", "OTA_ERROR", "OTA_SHA256", "OTA_URL", "OTA_REQ"}; - std::for_each(ota_property_list.cbegin(), - ota_property_list.cend(), - [this, &ota_property_container ] (String const & name) + std::list ro_device_property_list {"LIB_VERSION", "OTA_CAP", "OTA_ERROR", "OTA_SHA256"}; + std::for_each(ro_device_property_list.begin(), + ro_device_property_list.end(), + [this, &ro_device_property_container ] (String const & name) { - Property* p = getProperty(this->_property_container, name); + Property* p = getProperty(this->_device_property_container, name); if(p != nullptr) - addPropertyToContainer(ota_property_container, *p, p->name(), p->isWriteableByCloud() ? Permission::ReadWrite : Permission::Read); + addPropertyToContainer(ro_device_property_container, *p, p->name(), p->isWriteableByCloud() ? Permission::ReadWrite : Permission::Read); } ); - sendPropertyContainerToCloud(ota_property_container, last_ota_property_index); + sendPropertyContainerToCloud(_deviceTopicOut, ro_device_property_container, last_device_property_index); +} + +#if OTA_ENABLED +void ArduinoIoTCloudTCP::sendDevicePropertyToCloud(String const name) +{ + PropertyContainer temp_device_property_container; + unsigned int last_device_property_index = 0; + + Property* p = getProperty(this->_device_property_container, name); + if(p != nullptr) + { + addPropertyToContainer(temp_device_property_container, *p, p->name(), p->isWriteableByCloud() ? Permission::ReadWrite : Permission::Read); + sendPropertyContainerToCloud(_deviceTopicOut, temp_device_property_container, last_device_property_index); + } } #endif @@ -686,6 +832,16 @@ void ArduinoIoTCloudTCP::onOTARequest() } #endif +void ArduinoIoTCloudTCP::updateThingTopics() +{ + _shadowTopicOut = getTopic_shadowout(); + _shadowTopicIn = getTopic_shadowin(); + _dataTopicOut = getTopic_dataout(); + _dataTopicIn = getTopic_datain(); + + clrThingIdOutdatedFlag(); +} + /****************************************************************************** * EXTERN DEFINITION ******************************************************************************/ diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index 59caba7a2..b704f6762 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -105,15 +105,23 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass ConnectPhy, SyncTime, ConnectMqttBroker, - SubscribeMqttTopics, + SendDeviceProperties, + SubscribeDeviceTopic, + WaitDeviceConfig, + CheckDeviceConfig, + SubscribeThingTopics, RequestLastValues, Connected, + Disconnect, }; State _state; unsigned long _next_connection_attempt_tick; unsigned int _last_connection_attempt_cnt; + unsigned long _next_device_subscribe_attempt_tick; + unsigned int _last_device_subscribe_cnt; + unsigned int _last_device_attach_cnt; unsigned long _last_sync_request_tick; unsigned int _last_sync_request_cnt; unsigned long _last_subscribe_request_tick; @@ -137,11 +145,15 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass MqttClient _mqttClient; + String _deviceTopicOut; + String _deviceTopicIn; String _shadowTopicOut; String _shadowTopicIn; String _dataTopicOut; String _dataTopicIn; + bool _deviceSubscribedToThing; + #if OTA_ENABLED bool _ota_cap; int _ota_error; @@ -152,30 +164,39 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass onOTARequestCallbackFunc _get_ota_confirmation; #endif /* OTA_ENABLED */ - inline String getTopic_shadowout() { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/shadow/o"); } - inline String getTopic_shadowin () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/shadow/i"); } - inline String getTopic_dataout () { return ( getThingId().length() == 0) ? String("/a/d/" + getDeviceId() + "/e/o") : String("/a/t/" + getThingId() + "/e/o"); } - inline String getTopic_datain () { return ( getThingId().length() == 0) ? String("/a/d/" + getDeviceId() + "/e/i") : String("/a/t/" + getThingId() + "/e/i"); } + inline String getTopic_deviceout() { return String("/a/d/" + getDeviceId() + "/e/o");} + inline String getTopic_devicein () { return String("/a/d/" + getDeviceId() + "/e/i");} + inline String getTopic_shadowout() { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/shadow/o"); } + inline String getTopic_shadowin () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/shadow/i"); } + inline String getTopic_dataout () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/e/o"); } + inline String getTopic_datain () { return ( getThingId().length() == 0) ? String("") : String("/a/t/" + getThingId() + "/e/i"); } State handle_ConnectPhy(); State handle_SyncTime(); State handle_ConnectMqttBroker(); - State handle_SubscribeMqttTopics(); + State handle_SendDeviceProperties(); + State handle_WaitDeviceConfig(); + State handle_CheckDeviceConfig(); + State handle_SubscribeDeviceTopic(); + State handle_SubscribeThingTopics(); State handle_RequestLastValues(); State handle_Connected(); + State handle_Disconnect(); static void onMessage(int length); void handleMessage(int length); - void sendPropertyContainerToCloud(PropertyContainer & property_container, unsigned int & current_property_index); - void sendPropertiesToCloud(); + void sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index); + void sendThingPropertiesToCloud(); + void sendDevicePropertiesToCloud(); void requestLastValue(); int write(String const topic, byte const data[], int const length); #if OTA_ENABLED void onOTARequest(); - void sendOTAPropertiesToCloud(); + void sendDevicePropertyToCloud(String const name); #endif + void updateThingTopics(); }; /****************************************************************************** @@ -184,4 +205,4 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass extern ArduinoIoTCloudTCP ArduinoCloud; -#endif \ No newline at end of file +#endif