diff --git a/src/AIoTC_Types.h b/src/AIoTC_Types.h new file mode 100644 index 00000000..fe544b12 --- /dev/null +++ b/src/AIoTC_Types.h @@ -0,0 +1,51 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +#ifndef ARDUINO_AIOTC_TYPES_H_ +#define ARDUINO_AIOTC_TYPES_H_ + +/****************************************************************************** + TYPEDEF + ******************************************************************************/ + +typedef enum +{ + READ = 0x01, + WRITE = 0x02, + READWRITE = READ | WRITE +} permissionType; + +enum class ArduinoIoTConnectionStatus +{ + IDLE, + CONNECTING, + CONNECTED, + DISCONNECTED, + RECONNECTING, + ERROR, +}; + +enum class ArduinoIoTCloudEvent +{ + SYNC = 0, CONNECT = 1, DISCONNECT = 2 +}; + +typedef void (*OnCloudEventCallback)(void); + +typedef void (*ExecCloudEventCallback)(ArduinoIoTCloudEvent); + +#endif /* ARDUINO_AIOTC_TYPES_H_ */ \ No newline at end of file diff --git a/src/ArduinoIoTCloud.cpp b/src/ArduinoIoTCloud.cpp index 58052104..c0cac752 100644 --- a/src/ArduinoIoTCloud.cpp +++ b/src/ArduinoIoTCloud.cpp @@ -27,15 +27,10 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() : _connection{nullptr} -, _last_checked_property_index{0} , _time_service(TimeService) -, _tz_offset{0} -, _tz_dst_until{0} -, _thing_id{""} , _lib_version{AIOT_CONFIG_LIB_VERSION} , _device_id{""} , _cloud_event_callback{nullptr} -, _thing_id_outdated{false} { } @@ -44,23 +39,6 @@ ArduinoIoTCloudClass::ArduinoIoTCloudClass() * PUBLIC MEMBER FUNCTIONS ******************************************************************************/ -void ArduinoIoTCloudClass::push() -{ - requestUpdateForAllProperties(_thing_property_container); -} - -bool ArduinoIoTCloudClass::setTimestamp(String const & prop_name, unsigned long const timestamp) -{ - Property * p = getProperty(_thing_property_container, prop_name); - - if (p == nullptr) - return false; - - p->setTimestamp(timestamp); - - return true; -} - void ArduinoIoTCloudClass::addCallback(ArduinoIoTCloudEvent const event, OnCloudEventCallback callback) { _cloud_event_callback[static_cast(event)] = callback; @@ -120,93 +98,71 @@ Property& ArduinoIoTCloudClass::addPropertyReal(String& property, String name, i } Property& ArduinoIoTCloudClass::addPropertyReal(Property& property, String name, int tag, Permission const permission) { - return addPropertyToContainer(_thing_property_container, property, name, permission, tag); + return addInternalPropertyReal(property, name, tag, permission); } /* The following methods are deprecated but still used for non-LoRa boards */ void ArduinoIoTCloudClass::addPropertyReal(bool& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperBool(property); - addPropertyRealInternal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(float& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperFloat(property); - addPropertyRealInternal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(int& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperInt(property); - addPropertyRealInternal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperUnsignedInt(property); - addPropertyRealInternal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(String& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperString(property); - addPropertyRealInternal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, -1, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(Property& property, String name, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { - addPropertyRealInternal(property, name, -1, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(property, name, -1, permission_type, seconds, fn, minDelta, synFn); } /* The following methods are deprecated but still used for both LoRa and non-LoRa boards */ void ArduinoIoTCloudClass::addPropertyReal(bool& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperBool(property); - addPropertyRealInternal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(float& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperFloat(property); - addPropertyRealInternal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(int& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperInt(property); - addPropertyRealInternal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(unsigned int& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperUnsignedInt(property); - addPropertyRealInternal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(String& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { Property* p = new CloudWrapperString(property); - addPropertyRealInternal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(*p, name, tag, permission_type, seconds, fn, minDelta, synFn); } void ArduinoIoTCloudClass::addPropertyReal(Property& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) { - addPropertyRealInternal(property, name, tag, permission_type, seconds, fn, minDelta, synFn); + addInternalPropertyReal(property, name, tag, permission_type, seconds, fn, minDelta, synFn); } -void ArduinoIoTCloudClass::addPropertyRealInternal(Property& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) -{ - Permission permission = Permission::ReadWrite; - if (permission_type == READ) { - permission = Permission::Read; - } else if (permission_type == WRITE) { - permission = Permission::Write; - } else { - permission = Permission::ReadWrite; - } - - if (seconds == ON_CHANGE) { - addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); - } else { - addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishEvery(seconds).onUpdate(fn).onSync(synFn); - } -} - -/****************************************************************************** - * PROTECTED MEMBER FUNCTIONS - ******************************************************************************/ - void ArduinoIoTCloudClass::execCloudEventCallback(ArduinoIoTCloudEvent const event) { OnCloudEventCallback callback = _cloud_event_callback[static_cast(event)]; @@ -215,6 +171,10 @@ void ArduinoIoTCloudClass::execCloudEventCallback(ArduinoIoTCloudEvent const eve } } +/****************************************************************************** + * PROTECTED MEMBER FUNCTIONS + ******************************************************************************/ + __attribute__((weak)) void setDebugMessageLevel(int const /* level */) { /* do nothing */ diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 6154f25e..4897acab 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -31,6 +31,7 @@ #endif #include "AIoTC_Const.h" +#include "AIoTC_Types.h" #include "cbor/CBORDecoder.h" @@ -44,34 +45,6 @@ #include "utility/time/TimeService.h" -/****************************************************************************** - TYPEDEF - ******************************************************************************/ - -typedef enum -{ - READ = 0x01, - WRITE = 0x02, - READWRITE = READ | WRITE -} permissionType; - -enum class ArduinoIoTConnectionStatus -{ - IDLE, - CONNECTING, - CONNECTED, - DISCONNECTED, - RECONNECTING, - ERROR, -}; - -enum class ArduinoIoTCloudEvent : size_t -{ - SYNC = 0, CONNECT = 1, DISCONNECT = 2 -}; - -typedef void (*OnCloudEventCallback)(void); - /****************************************************************************** * CLASS DECLARATION ******************************************************************************/ @@ -87,26 +60,26 @@ class ArduinoIoTCloudClass virtual void update () = 0; virtual int connected () = 0; virtual void printDebugInfo() = 0; + virtual bool deviceNotAttached() = 0; + virtual void setThingId (String const thing_id) = 0; + virtual String & getThingId () = 0; + virtual void setThingIdOutdatedFlag() = 0; + virtual void clrThingIdOutdatedFlag() = 0; + virtual bool getThingIdOutdatedFlag() = 0; - void push(); - bool setTimestamp(String const & prop_name, unsigned long const timestamp); + virtual void push() = 0; + virtual bool setTimestamp(String const & prop_name, unsigned long const timestamp) = 0; - inline void setThingId (String const thing_id) { _thing_id = thing_id; }; - inline String & getThingId () { return _thing_id; }; 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(); } inline unsigned long getLocalTime() { return _time_service.getLocalTime(); } inline void updateInternalTimezoneInfo() { _time_service.setTimeZoneData(_tz_offset, _tz_dst_until); } + inline int tzOffset() { return _tz_offset; } + inline unsigned int getTzDstUntil() { return _tz_dst_until; } void addCallback(ArduinoIoTCloudEvent const event, OnCloudEventCallback callback); @@ -135,7 +108,6 @@ class ArduinoIoTCloudClass * This approach reduces the required amount of data which is of great * important when using LoRa. */ - void addPropertyReal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS) __attribute__((deprecated("Use addProperty(property, Permission::ReadWrite) instead."))); void addPropertyReal(bool& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS) __attribute__((deprecated("Use addProperty(property, Permission::ReadWrite) instead."))); void addPropertyReal(float& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS) __attribute__((deprecated("Use addProperty(property, Permission::ReadWrite) instead."))); @@ -150,27 +122,25 @@ class ArduinoIoTCloudClass Property& addPropertyReal(unsigned int& property, String name, int tag, Permission const permission); Property& addPropertyReal(String& property, String name, int tag, Permission const permission); + virtual void addInternalPropertyReal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS) = 0; + virtual Property& addInternalPropertyReal(Property& property, String name, int tag, Permission const permission) = 0; + + void execCloudEventCallback(ArduinoIoTCloudEvent const event); + protected: ConnectionHandler * _connection; PropertyContainer _device_property_container; - PropertyContainer _thing_property_container; - unsigned int _last_checked_property_index; TimeServiceClass & _time_service; int _tz_offset; unsigned int _tz_dst_until; - String _thing_id; String _lib_version; - void execCloudEventCallback(ArduinoIoTCloudEvent const event); - private: - void addPropertyRealInternal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS); - 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 0f96d36d..d2f2bd53 100644 --- a/src/ArduinoIoTCloudLPWAN.cpp +++ b/src/ArduinoIoTCloudLPWAN.cpp @@ -51,6 +51,7 @@ ArduinoIoTCloudLPWAN::ArduinoIoTCloudLPWAN() , _retryEnable{false} , _maxNumRetry{5} , _intervalRetry{1000} +, _last_checked_property_index{0} { } @@ -91,6 +92,46 @@ void ArduinoIoTCloudLPWAN::printDebugInfo() DEBUG_INFO("Thing ID: %s", getThingId().c_str()); } +Property& ArduinoIoTCloudLPWAN::addInternalPropertyReal(Property& property, String name, int tag, Permission const permission) +{ + return addPropertyToContainer(_thing_property_container, property, name, permission, tag); +} + +void ArduinoIoTCloudLPWAN::addInternalPropertyReal(Property& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) +{ + Permission permission = Permission::ReadWrite; + if (permission_type == READ) { + permission = Permission::Read; + } else if (permission_type == WRITE) { + permission = Permission::Write; + } else { + permission = Permission::ReadWrite; + } + + if (seconds == ON_CHANGE) { + addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); + } else { + addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishEvery(seconds).onUpdate(fn).onSync(synFn); + } +} + +void ArduinoIoTCloudLPWAN::push() +{ + requestUpdateForAllProperties(_thing_property_container); +} + +bool ArduinoIoTCloudLPWAN::setTimestamp(String const & prop_name, unsigned long const timestamp) +{ + Property * p = getProperty(_thing_property_container, prop_name); + + if (p == nullptr) + return false; + + p->setTimestamp(timestamp); + + return true; +} + /****************************************************************************** * PRIVATE MEMBER FUNCTIONS ******************************************************************************/ diff --git a/src/ArduinoIoTCloudLPWAN.h b/src/ArduinoIoTCloudLPWAN.h index 8d1f42d2..5a413252 100644 --- a/src/ArduinoIoTCloudLPWAN.h +++ b/src/ArduinoIoTCloudLPWAN.h @@ -23,6 +23,7 @@ ******************************************************************************/ #include +#include "property/PropertyContainer.h" /****************************************************************************** * CLASS DECLARATION @@ -39,6 +40,14 @@ class ArduinoIoTCloudLPWAN : public ArduinoIoTCloudClass virtual int connected () override; virtual void printDebugInfo() override; + virtual inline void setThingIdOutdatedFlag() { _thing_id_outdated = true ; } + virtual inline void clrThingIdOutdatedFlag() { _thing_id_outdated = false ; } + virtual inline bool getThingIdOutdatedFlag() { return _thing_id_outdated; } + + virtual inline void setThingId (String const thing_id) { _thing_id = thing_id; }; + virtual inline String & getThingId () { return _thing_id; }; + virtual inline bool deviceNotAttached() { return _thing_id == ""; } + int begin(ConnectionHandler& connection, bool retry = false); inline bool isRetryEnabled () const { return _retryEnable; } @@ -49,6 +58,11 @@ class ArduinoIoTCloudLPWAN : public ArduinoIoTCloudClass inline void setMaxRetry (int val) { _maxNumRetry = val; } inline void setIntervalRetry(long val) { _intervalRetry = val; } + virtual void addInternalPropertyReal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS) override; + virtual Property& addInternalPropertyReal(Property& property, String name, int tag, Permission const permission) override; + + virtual void push() override; + virtual bool setTimestamp(String const & prop_name, unsigned long const timestamp) override; private: @@ -59,6 +73,13 @@ class ArduinoIoTCloudLPWAN : public ArduinoIoTCloudClass Connected, }; + String _thing_id; + bool _thing_id_outdated; + + unsigned int _last_checked_property_index; + + PropertyContainer _thing_property_container; + State _state; bool _retryEnable; int _maxNumRetry; diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index fe2c473e..43ccfbfa 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -70,6 +70,11 @@ void setThingIdOutdated() ArduinoCloud.setThingIdOutdatedFlag(); } +void executeEventCallback(ArduinoIoTCloudEvent const event) +{ + ArduinoCloud.execCloudEventCallback(event); +} + /****************************************************************************** CTOR/DTOR ******************************************************************************/ @@ -80,10 +85,6 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _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} -, _last_subscribe_request_cnt{0} , _mqtt_data_buf{0} , _mqtt_data_len{0} , _mqtt_data_request_retransmit{false} @@ -94,13 +95,11 @@ ArduinoIoTCloudTCP::ArduinoIoTCloudTCP() , _password("") #endif , _mqttClient{nullptr} +, _arduinoCloudThing() , _deviceTopicOut("") , _deviceTopicIn("") -, _shadowTopicOut("") , _shadowTopicIn("") -, _dataTopicOut("") , _dataTopicIn("") -, _deviceSubscribedToThing{false} #if OTA_ENABLED , _ota_cap{false} , _ota_error{static_cast(OTAError::None)} @@ -124,6 +123,7 @@ int ArduinoIoTCloudTCP::begin(ConnectionHandler & connection, bool const enable_ _brokerAddress = brokerAddress; _brokerPort = brokerPort; _time_service.begin(&connection); + _arduinoCloudThing.begin(&_mqttClient, &_time_service, executeEventCallback); return begin(enable_watchdog, _brokerAddress, _brokerPort); } @@ -203,9 +203,8 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, p = new CloudWrapperBool(_ota_req); addPropertyToContainer(_device_property_container, *p, "OTA_REQ", Permission::ReadWrite, -1); #endif /* OTA_ENABLED */ - p = new CloudWrapperString(_thing_id); + p = new CloudWrapperString(_arduinoCloudThing.getThingId()); addPropertyToContainer(_device_property_container, *p, "thing_id", Permission::ReadWrite, -1).onUpdate(setThingIdOutdated); - 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); @@ -243,6 +242,64 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, return 1; } +Property& ArduinoIoTCloudTCP::addInternalPropertyReal(Property& property, String name, int tag, Permission const permission) +{ + return _arduinoCloudThing.addPropertyReal(property, name, tag, permission); +} + +void ArduinoIoTCloudTCP::addInternalPropertyReal(Property& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) +{ + Permission permission = Permission::ReadWrite; + if (permission_type == READ) { + permission = Permission::Read; + } else if (permission_type == WRITE) { + permission = Permission::Write; + } else { + permission = Permission::ReadWrite; + } + _arduinoCloudThing.addPropertyReal(property, name, tag, permission).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); +} + +void ArduinoIoTCloudTCP::push() +{ + _arduinoCloudThing.push(); +} + +bool ArduinoIoTCloudTCP::setTimestamp(String const & prop_name, unsigned long const timestamp) +{ + return _arduinoCloudThing.setTimestamp(prop_name, timestamp); +} + +bool ArduinoIoTCloudTCP::deviceNotAttached() +{ + return _arduinoCloudThing.getThingId().length() == 0; +} + +void ArduinoIoTCloudTCP::setThingId(String const thing_id) +{ + _arduinoCloudThing.setThingId(thing_id); +} + +String& ArduinoIoTCloudTCP::getThingId() +{ + return _arduinoCloudThing.getThingId(); +} + +void ArduinoIoTCloudTCP::setThingIdOutdatedFlag() +{ + _arduinoCloudThing.setThingIdOutdatedFlag(); +} + +void ArduinoIoTCloudTCP::clrThingIdOutdatedFlag() +{ + _arduinoCloudThing.clrThingIdOutdatedFlag(); +} + +bool ArduinoIoTCloudTCP::getThingIdOutdatedFlag() +{ + return _arduinoCloudThing.getThingIdOutdatedFlag(); +} + void ArduinoIoTCloudTCP::update() { /* Feed the watchdog. If any of the functions called below @@ -262,10 +319,6 @@ void ArduinoIoTCloudTCP::update() 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; } @@ -280,8 +333,10 @@ void ArduinoIoTCloudTCP::update() #endif /* Check for new data from the MQTT client. */ - if (_mqttClient.connected()) + if (_mqttClient.connected()) + { _mqttClient.poll(); + } } int ArduinoIoTCloudTCP::connected() @@ -326,6 +381,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_ConnectMqttBroker() { if (_mqttClient.connect(_brokerAddress.c_str(), _brokerPort)) { + _arduinoCloudThing.clrLastValueReceived(); _last_connection_attempt_cnt = 0; return State::SendDeviceProperties; } @@ -379,161 +435,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_SubscribeDeviceTopic() _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 subscription */ - _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_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::SubscribeThingTopics; - } - - if (_last_subscribe_request_cnt > AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_MAX_RETRY_CNT) - { - _last_subscribe_request_cnt = 0; - _last_subscribe_request_tick = 0; - _mqttClient.stop(); - execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); - return State::ConnectPhy; - } - - _last_subscribe_request_tick = now; - _last_subscribe_request_cnt++; - - if (!_mqttClient.subscribe(_dataTopicIn)) - { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _dataTopicIn.c_str()); - DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); - return State::SubscribeThingTopics; - } - - if (!_mqttClient.subscribe(_shadowTopicIn)) - { - DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _shadowTopicIn.c_str()); - DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); - return State::SubscribeThingTopics; - } - - DEBUG_INFO("Connected to Arduino IoT Cloud"); - DEBUG_INFO("Thing ID: %s", getThingId().c_str()); - execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT); - _deviceSubscribedToThing = true; - - /*Add retry wait time otherwise we are trying to reconnect every 250 ms...*/ - return State::RequestLastValues; -} - -ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_RequestLastValues() -{ - if (!_mqttClient.connected()) - { - return State::Disconnect; - } - - if (getThingIdOutdatedFlag()) - { - return State::CheckDeviceConfig; - } - - /* Check whether or not we need to send a new request. */ - unsigned long const now = millis(); - bool const is_sync_request_timeout = (now - _last_sync_request_tick) > AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms; - bool const is_first_sync_request = (_last_sync_request_cnt == 0); - if (is_first_sync_request || is_sync_request_timeout) - { - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s [%d] last values requested", __FUNCTION__, now); - requestLastValue(); - _last_sync_request_tick = now; - /* Track the number of times a get-last-values request was sent to the cloud. - * If no data is received within a certain number of retry-requests it's a better - * strategy to disconnect and re-establish connection from the ground up. - */ - _last_sync_request_cnt++; - if (_last_sync_request_cnt > AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT) - { - _last_sync_request_cnt = 0; - _last_sync_request_tick = 0; - _mqttClient.stop(); - execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); - return State::ConnectPhy; - } - } - - return State::RequestLastValues; + return State::Connected; } ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() @@ -541,6 +443,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() if (!_mqttClient.connected()) { /* The last message was definitely lost, trigger a retransmit. */ + //TODO set the variable in the thing class _mqtt_data_request_retransmit = true; return State::Disconnect; } @@ -549,24 +452,14 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() { if (getThingIdOutdatedFlag()) { - return State::CheckDeviceConfig; + _arduinoCloudThing.setThingIdOutdatedFlag(); } - /* 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 - * leads to a wrong time set in the time service which inhibits - * the connection from being established due to a wrong data - * in the reconstructed certificate. - */ - updateTimestampOnLocallyChangedProperties(_thing_property_container); - /* Retransmit data in case there was a lost transaction due * to phy layer or MQTT connectivity loss. */ - if(_mqtt_data_request_retransmit && (_mqtt_data_len > 0)) { - write(_dataTopicOut, _mqtt_data_buf, _mqtt_data_len); - _mqtt_data_request_retransmit = false; + if(_mqtt_data_request_retransmit) { + _arduinoCloudThing.setMqttDataRequestRetransmitFlag(); } #if OTA_ENABLED @@ -599,17 +492,10 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() #endif /* OTA_ENABLED */ - /* Check if any properties need encoding and send them to - * the cloud if necessary. - */ - sendThingPropertiesToCloud(); + // update the thing state machine + _arduinoCloudThing.update(); - unsigned long const internal_posix_time = _time_service.getTime(); - if(internal_posix_time < _tz_dst_until) { - return State::Connected; - } else { - return State::RequestLastValues; - } + return State::Connected; } } @@ -629,7 +515,6 @@ void ArduinoIoTCloudTCP::onMessage(int length) void ArduinoIoTCloudTCP::handleMessage(int length) { String topic = _mqttClient.messageTopic(); - byte bytes[length]; for (int i = 0; i < length; i++) { @@ -643,21 +528,11 @@ void ArduinoIoTCloudTCP::handleMessage(int length) _next_device_subscribe_attempt_tick = 0; } - /* Topic for user input data */ - if (_dataTopicIn == topic) { - 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(_thing_property_container, (uint8_t*)bytes, length, true); - _time_service.setTimeZoneData(_tz_offset, _tz_dst_until); - execCloudEventCallback(ArduinoIoTCloudEvent::SYNC); - _last_sync_request_cnt = 0; - _last_sync_request_tick = 0; - _state = State::Connected; + /* Topics for thing input data */ + updateThingTopics(); + if (_dataTopicIn == topic || _shadowTopicIn == topic) { + updateTimezoneInfo(); + _arduinoCloudThing.handleMessage(topic, (uint8_t*)bytes, length); } } @@ -679,11 +554,6 @@ void ArduinoIoTCloudTCP::sendPropertyContainerToCloud(String const topic, Proper } } -void ArduinoIoTCloudTCP::sendThingPropertiesToCloud() -{ - sendPropertyContainerToCloud(_dataTopicOut, _thing_property_container, _last_checked_property_index); -} - void ArduinoIoTCloudTCP::sendDevicePropertiesToCloud() { PropertyContainer ro_device_property_container; @@ -718,15 +588,6 @@ void ArduinoIoTCloudTCP::sendDevicePropertyToCloud(String const name) } #endif -void ArduinoIoTCloudTCP::requestLastValue() -{ - // Send the getLastValues CBOR message to the cloud - // [{0: "r:m", 3: "getLastValues"}] = 81 A2 00 63 72 3A 6D 03 6D 67 65 74 4C 61 73 74 56 61 6C 75 65 73 - // Use http://cbor.me to easily generate CBOR encoding - const uint8_t CBOR_REQUEST_LAST_VALUE_MSG[] = { 0x81, 0xA2, 0x00, 0x63, 0x72, 0x3A, 0x6D, 0x03, 0x6D, 0x67, 0x65, 0x74, 0x4C, 0x61, 0x73, 0x74, 0x56, 0x61, 0x6C, 0x75, 0x65, 0x73 }; - write(_shadowTopicOut, CBOR_REQUEST_LAST_VALUE_MSG, sizeof(CBOR_REQUEST_LAST_VALUE_MSG)); -} - int ArduinoIoTCloudTCP::write(String const topic, byte const data[], int const length) { if (_mqttClient.beginMessage(topic, length, false, 0)) { @@ -741,9 +602,7 @@ int ArduinoIoTCloudTCP::write(String const topic, byte const data[], int const l void ArduinoIoTCloudTCP::updateThingTopics() { - _shadowTopicOut = getTopic_shadowout(); _shadowTopicIn = getTopic_shadowin(); - _dataTopicOut = getTopic_dataout(); _dataTopicIn = getTopic_datain(); clrThingIdOutdatedFlag(); diff --git a/src/ArduinoIoTCloudTCP.h b/src/ArduinoIoTCloudTCP.h index e50f9a07..ffd89760 100644 --- a/src/ArduinoIoTCloudTCP.h +++ b/src/ArduinoIoTCloudTCP.h @@ -23,6 +23,7 @@ ******************************************************************************/ #include +#include #include @@ -47,6 +48,7 @@ #endif #include +#include "ArduinoIoTCloudTCPThing.h" /****************************************************************************** CONSTANTS @@ -78,6 +80,18 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass virtual void update () override; virtual int connected () override; virtual void printDebugInfo() override; + virtual bool deviceNotAttached() override; + virtual void setThingId (String const thing_id) override; + virtual String & getThingId () override; + virtual void setThingIdOutdatedFlag() override; + virtual void clrThingIdOutdatedFlag() override; + virtual bool getThingIdOutdatedFlag() override; + + virtual void addInternalPropertyReal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS) override; + virtual Property& addInternalPropertyReal(Property& property, String name, int tag, Permission const permission) override; + + virtual void push() override; + virtual bool setTimestamp(String const & prop_name, unsigned long const timestamp) override; #if defined(BOARD_HAS_ECCX08) || defined(BOARD_HAS_OFFLOADED_ECCX08) || defined(BOARD_HAS_SE050) int begin(ConnectionHandler & connection, bool const enable_watchdog = true, String brokerAddress = DEFAULT_BROKER_ADDRESS_SECURE_AUTH, uint16_t brokerPort = DEFAULT_BROKER_PORT_SECURE_AUTH); @@ -114,11 +128,9 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass SyncTime, ConnectMqttBroker, SendDeviceProperties, + CheckDeviceConfig, SubscribeDeviceTopic, WaitDeviceConfig, - CheckDeviceConfig, - SubscribeThingTopics, - RequestLastValues, Connected, Disconnect, }; @@ -130,10 +142,7 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass 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; - unsigned int _last_subscribe_request_cnt; + String _brokerAddress; uint16_t _brokerPort; uint8_t _mqtt_data_buf[MQTT_TRANSMIT_BUFFER_SIZE]; @@ -169,16 +178,13 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass #endif MqttClient _mqttClient; + ArduinoIoTCloudTCPThing _arduinoCloudThing; String _deviceTopicOut; String _deviceTopicIn; - String _shadowTopicOut; String _shadowTopicIn; - String _dataTopicOut; String _dataTopicIn; - bool _deviceSubscribedToThing; - #if OTA_ENABLED bool _ota_cap; int _ota_error; @@ -200,20 +206,15 @@ class ArduinoIoTCloudTCP: public ArduinoIoTCloudClass State handle_SyncTime(); State handle_ConnectMqttBroker(); 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(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 diff --git a/src/ArduinoIoTCloudTCPThing.cpp b/src/ArduinoIoTCloudTCPThing.cpp new file mode 100644 index 00000000..7528f489 --- /dev/null +++ b/src/ArduinoIoTCloudTCPThing.cpp @@ -0,0 +1,405 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2019 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include +#include "ArduinoIoTCloudTCPThing.h" + +#include +#include "cbor/CBOREncoder.h" + +/****************************************************************************** + CTOR/DTOR + ******************************************************************************/ + +ArduinoIoTCloudTCPThing::ArduinoIoTCloudTCPThing() +: _state{State::WaitDeviceConfig} +, _last_sync_request_tick{0} +, _last_sync_request_cnt{0} +, _last_subscribe_request_tick{0} +, _last_subscribe_request_cnt{0} +, _mqtt_data_buf{0} +, _mqtt_data_len{0} +, _mqtt_data_request_retransmit{false} +, _mqttClient() +, _time_service() +, _thing_id("") +, _thing_id_outdated{false} +, _deviceSubscribedToThing{false} +, _last_checked_property_index{0} +, _last_values_received{false} +, _shadowTopicOut("") +, _shadowTopicIn("") +, _dataTopicOut("") +, _dataTopicIn("") +{ + +} + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +int ArduinoIoTCloudTCPThing::begin(MqttClient *mqttClient, TimeServiceClass *time_service, ExecCloudEventCallback event_callback) +{ + _mqttClient = mqttClient; + _time_service = time_service; + _event_callback = event_callback; + return 1; +} + +void ArduinoIoTCloudTCPThing::update() +{ + /* Run through the state machine. */ + State next_state = _state; + switch (_state) + { + 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; +} + +void ArduinoIoTCloudTCPThing::handleMessage(String topic,uint8_t const * const bytes, int length) +{ + /* Topic for user input data */ + if (_dataTopicIn == topic) { + CBORDecoder::decode(_thing_property_container, (uint8_t*)bytes, length); + } + + /* Topic for sync Thing last values on connect */ + if ((_shadowTopicIn == topic) && (_state == State::RequestLastValues)) + { + DEBUG_INFO("ArduinoIoTCloudTCP::%s [%d] last values received", __FUNCTION__, millis()); + CBORDecoder::decode(_thing_property_container, (uint8_t*)bytes, length, true); + _event_callback(ArduinoIoTCloudEvent::SYNC); + _last_sync_request_cnt = 0; + _last_sync_request_tick = 0; + _last_values_received = true; + } +} + +int ArduinoIoTCloudTCPThing::connected() +{ + return _mqttClient->connected(); +} + +Property& ArduinoIoTCloudTCPThing::addPropertyReal(Property& property, String name, int tag, Permission const permission) +{ + return addPropertyToContainer(_thing_property_container, property, name, permission, tag); +} + +void ArduinoIoTCloudTCPThing::addPropertyReal(Property& property, String name, int tag, permissionType permission_type, long seconds, void(*fn)(void), float minDelta, void(*synFn)(Property & property)) +{ + Permission permission = Permission::ReadWrite; + if (permission_type == READ) { + permission = Permission::Read; + } else if (permission_type == WRITE) { + permission = Permission::Write; + } else { + permission = Permission::ReadWrite; + } + + if (seconds == ON_CHANGE) { + addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishOnChange(minDelta, Property::DEFAULT_MIN_TIME_BETWEEN_UPDATES_MILLIS).onUpdate(fn).onSync(synFn); + } else { + addPropertyToContainer(_thing_property_container, property, name, permission, tag).publishEvery(seconds).onUpdate(fn).onSync(synFn); + } +} + +void ArduinoIoTCloudTCPThing::push() +{ + requestUpdateForAllProperties(_thing_property_container); +} + +bool ArduinoIoTCloudTCPThing::setTimestamp(String const & prop_name, unsigned long const timestamp) +{ + Property * p = getProperty(_thing_property_container, prop_name); + + if (p == nullptr) + return false; + + p->setTimestamp(timestamp); + + return true; +} + +/****************************************************************************** + * PRIVATE MEMBER FUNCTIONS + ******************************************************************************/ + +ArduinoIoTCloudTCPThing::State ArduinoIoTCloudTCPThing::handle_WaitDeviceConfig() +{ + if (!_mqttClient->connected()) + { + return State::Disconnect; + } + // If the thing id is not set, we need to wait for it + if (deviceNotAttached()) + { + return State::WaitDeviceConfig; + } + + if (getThingIdOutdatedFlag()) + { + return State::CheckDeviceConfig; + } + + return State::CheckDeviceConfig; +} + +ArduinoIoTCloudTCPThing::State ArduinoIoTCloudTCPThing::handle_CheckDeviceConfig() +{ + if (!_mqttClient->connected()) + { + return State::Disconnect; + } + + if(_deviceSubscribedToThing == true) + { + /* Unsubscribe from old things topics and go on with a new subscription */ + _mqttClient->unsubscribe(_shadowTopicIn); + _mqttClient->unsubscribe(_dataTopicIn); + _deviceSubscribedToThing = false; + DEBUG_INFO("Detached from the Thing"); + _event_callback(ArduinoIoTCloudEvent::DISCONNECT); + } + + updateThingTopics(); + + if (deviceNotAttached()) + { + return State::WaitDeviceConfig; + } + + DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s Device attached to a new valid Thing %s", __FUNCTION__, getThingId().c_str()); + + return State::SubscribeThingTopics; +} + +ArduinoIoTCloudTCPThing::State ArduinoIoTCloudTCPThing::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_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::SubscribeThingTopics; + } + + if (_last_subscribe_request_cnt > AIOT_CONFIG_THING_TOPICS_SUBSCRIBE_MAX_RETRY_CNT) + { + _last_subscribe_request_cnt = 0; + _last_subscribe_request_tick = 0; + _mqttClient->stop(); + return State::WaitDeviceConfig; + } + + _last_subscribe_request_tick = now; + _last_subscribe_request_cnt++; + + if (!_mqttClient->subscribe(_dataTopicIn)) + { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _dataTopicIn.c_str()); + DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); + return State::SubscribeThingTopics; + } + + if (!_mqttClient->subscribe(_shadowTopicIn)) + { + DEBUG_ERROR("ArduinoIoTCloudTCP::%s could not subscribe to %s", __FUNCTION__, _shadowTopicIn.c_str()); + DEBUG_ERROR("Check your thing configuration, and press the reset button on your board."); + return State::SubscribeThingTopics; + } + + DEBUG_INFO("Connected to Arduino IoT Cloud"); + DEBUG_INFO("Thing ID: %s", getThingId().c_str()); + _event_callback(ArduinoIoTCloudEvent::CONNECT); + _deviceSubscribedToThing = true; + + /*Add retry wait time otherwise we are trying to reconnect every 250 ms...*/ + return State::RequestLastValues; +} + +ArduinoIoTCloudTCPThing::State ArduinoIoTCloudTCPThing::handle_RequestLastValues() +{ + if (!_mqttClient->connected()) + { + return State::Disconnect; + } + + if (getThingIdOutdatedFlag()) + { + return State::CheckDeviceConfig; + } + + if (_last_values_received) { + return State::Connected; + } + + /* Check whether or not we need to send a new request. */ + unsigned long const now = millis(); + bool const is_sync_request_timeout = (now - _last_sync_request_tick) > AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms; + bool const is_first_sync_request = (_last_sync_request_cnt == 0); + + if (is_first_sync_request || is_sync_request_timeout) + { + DEBUG_INFO("ArduinoIoTCloudTCP::%s [%d] last values requested", __FUNCTION__, now); + requestLastValue(); + _last_sync_request_tick = now; + /* Track the number of times a get-last-values request was sent to the cloud. + * If no data is received within a certain number of retry-requests it's a better + * strategy to disconnect and re-establish connection from the ground up. + */ + _last_sync_request_cnt++; + if (_last_sync_request_cnt > AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT) + { + _last_sync_request_cnt = 0; + _last_sync_request_tick = 0; + _mqttClient->stop(); + return State::WaitDeviceConfig; + } + } + + return State::RequestLastValues; +} + +ArduinoIoTCloudTCPThing::State ArduinoIoTCloudTCPThing::handle_Connected() +{ + if (!_mqttClient->connected()) + { + /* The last message was definitely lost, trigger a retransmit. */ + _mqtt_data_request_retransmit = true; + 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 + * leads to a wrong time set in the time service which inhibits + * the connection from being established due to a wrong data + * in the reconstructed certificate. + */ + updateTimestampOnLocallyChangedProperties(_thing_property_container); + /* Retransmit data in case there was a lost transaction due + * to phy layer or MQTT connectivity loss. + */ + if(_mqtt_data_request_retransmit) { + write(_dataTopicOut, _mqtt_data_buf, _mqtt_data_len); + _mqtt_data_request_retransmit = false; + } + + /* Check if any properties need encoding and send them to + * the cloud if necessary. + */ + sendThingPropertiesToCloud(); + + unsigned int tz_dst_until = _time_service->getTimeZoneUntil(); + unsigned long const internal_posix_time = _time_service->getTime(); + if(internal_posix_time < tz_dst_until) { + return State::Connected; + } else { + return State::RequestLastValues; + } + } +} + +ArduinoIoTCloudTCPThing::State ArduinoIoTCloudTCPThing::handle_Disconnect() +{ + DEBUG_ERROR("ArduinoIoTCloudTCP::%s MQTT client connection lost", __FUNCTION__); + _mqttClient->stop(); + return State::WaitDeviceConfig; +} + +void ArduinoIoTCloudTCPThing::sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index) +{ + int bytes_encoded = 0; + uint8_t data[MQTT_TRANSMIT_BUFFER_SIZE]; + + if (CBOREncoder::encode(property_container, data, sizeof(data), bytes_encoded, current_property_index, false) == CborNoError) + if (bytes_encoded > 0) + { + /* If properties have been encoded store them in the back-up buffer + * in order to allow retransmission in case of failure. + */ + _mqtt_data_len = bytes_encoded; + memcpy(_mqtt_data_buf, data, _mqtt_data_len); + /* Transmit the properties to the MQTT broker */ + write(topic, _mqtt_data_buf, _mqtt_data_len); + } +} + +void ArduinoIoTCloudTCPThing::sendThingPropertiesToCloud() +{ + sendPropertyContainerToCloud(_dataTopicOut, _thing_property_container, _last_checked_property_index); +} + +void ArduinoIoTCloudTCPThing::requestLastValue() +{ + // Send the getLastValues CBOR message to the cloud + // [{0: "r:m", 3: "getLastValues"}] = 81 A2 00 63 72 3A 6D 03 6D 67 65 74 4C 61 73 74 56 61 6C 75 65 73 + // Use http://cbor.me to easily generate CBOR encoding + const uint8_t CBOR_REQUEST_LAST_VALUE_MSG[] = { 0x81, 0xA2, 0x00, 0x63, 0x72, 0x3A, 0x6D, 0x03, 0x6D, 0x67, 0x65, 0x74, 0x4C, 0x61, 0x73, 0x74, 0x56, 0x61, 0x6C, 0x75, 0x65, 0x73 }; + write(_shadowTopicOut, CBOR_REQUEST_LAST_VALUE_MSG, sizeof(CBOR_REQUEST_LAST_VALUE_MSG)); +} + +int ArduinoIoTCloudTCPThing::write(String const topic, byte const data[], int const length) +{ + if (_mqttClient->beginMessage(topic, length, false, 0)) { + if (_mqttClient->write(data, length)) { + if (_mqttClient->endMessage()) { + return 1; + } + } + } + return 0; +} + +void ArduinoIoTCloudTCPThing::updateThingTopics() +{ + _shadowTopicOut = getTopic_shadowout(); + _shadowTopicIn = getTopic_shadowin(); + _dataTopicOut = getTopic_dataout(); + _dataTopicIn = getTopic_datain(); + + clrThingIdOutdatedFlag(); +} \ No newline at end of file diff --git a/src/ArduinoIoTCloudTCPThing.h b/src/ArduinoIoTCloudTCPThing.h new file mode 100644 index 00000000..3d66ae1c --- /dev/null +++ b/src/ArduinoIoTCloudTCPThing.h @@ -0,0 +1,138 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2019 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +#ifndef ARDUINO_IOT_CLOUD_TCP_THING_H +#define ARDUINO_IOT_CLOUD_TCP_THING_H + + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include +#include +#include + +#include "utility/time/TimeService.h" +#include "property/PropertyContainer.h" +#include "cbor/CBOREncoder.h" +#include "cbor/CBORDecoder.h" + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class ArduinoIoTCloudTCPThing +{ + public: + + ArduinoIoTCloudTCPThing(); + virtual ~ArduinoIoTCloudTCPThing() { } + + + void update(); + void handleMessage(String topic,uint8_t const * const bytes, int length); + + // begin takes an mqtt client + int begin(MqttClient *mqttClient, TimeServiceClass *time_service, ExecCloudEventCallback event_callback); + int connected(); + void updateTimezoneInfo(); + + void addPropertyReal(Property& property, String name, int tag, permissionType permission_type = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, float minDelta = 0.0f, void(*synFn)(Property & property) = CLOUD_WINS) __attribute__((deprecated("Use addProperty(property, Permission::ReadWrite) instead."))); + Property& addPropertyReal(Property& property, String name, int tag, Permission const permission); + + void push(); + bool setTimestamp(String const & prop_name, unsigned long const timestamp); + + inline void setThingId (String const thing_id) { _thing_id = thing_id; }; + inline String & getThingId () { return _thing_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 void setMqttDataRequestRetransmitFlag() { _mqtt_data_request_retransmit = true; } + inline void clrMqttDataRequestRetransmitFlag() { _mqtt_data_request_retransmit = false; } + + inline void setLastValueReceived() {_last_values_received = true;} + inline void clrLastValueReceived() {_last_values_received = false;} + + private: + static const int MQTT_TRANSMIT_BUFFER_SIZE = 256; + + enum class State + { + WaitDeviceConfig, + CheckDeviceConfig, + SubscribeThingTopics, + RequestLastValues, + Connected, + Disconnect, + }; + + State _state; + + unsigned long _last_sync_request_tick; + unsigned int _last_sync_request_cnt; + unsigned long _last_subscribe_request_tick; + unsigned int _last_subscribe_request_cnt; + String _brokerAddress; + uint16_t _brokerPort; + uint8_t _mqtt_data_buf[MQTT_TRANSMIT_BUFFER_SIZE]; + int _mqtt_data_len; + bool _mqtt_data_request_retransmit; + + MqttClient *_mqttClient; + TimeServiceClass *_time_service; + ExecCloudEventCallback _event_callback; + + String _thing_id; + bool _thing_id_outdated; + bool _deviceSubscribedToThing; + unsigned int _last_checked_property_index; + bool _last_values_received; + + String _shadowTopicOut; + String _shadowTopicIn; + String _dataTopicOut; + String _dataTopicIn; + + PropertyContainer _thing_property_container; + + 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_WaitDeviceConfig(); + State handle_CheckDeviceConfig(); + State handle_SubscribeThingTopics(); + State handle_RequestLastValues(); + State handle_Connected(); + State handle_Disconnect(); + + void sendPropertyContainerToCloud(String const topic, PropertyContainer & property_container, unsigned int & current_property_index); + void sendThingPropertiesToCloud(); + void requestLastValue(); + int write(String const topic, byte const data[], int const length); + + void updateThingTopics(); +}; + +#endif diff --git a/src/utility/time/TimeService.cpp b/src/utility/time/TimeService.cpp index cc387215..0ae47399 100644 --- a/src/utility/time/TimeService.cpp +++ b/src/utility/time/TimeService.cpp @@ -203,6 +203,11 @@ void TimeServiceClass::setTimeZoneData(long offset, unsigned long dst_until) } } +unsigned int TimeServiceClass::getTimeZoneUntil() +{ + return _timezone_dst_until; +} + unsigned long TimeServiceClass::getLocalTime() { unsigned long utc = getTime(); diff --git a/src/utility/time/TimeService.h b/src/utility/time/TimeService.h index 6e6af6bb..dee5ac95 100644 --- a/src/utility/time/TimeService.h +++ b/src/utility/time/TimeService.h @@ -47,6 +47,7 @@ class TimeServiceClass void setTime(unsigned long time); unsigned long getLocalTime(); void setTimeZoneData(long offset, unsigned long valid_until); + unsigned int getTimeZoneUntil(); bool sync(); void setSyncInterval(unsigned long seconds); void setSyncFunction(syncTimeFunctionPtr sync_func);