From 84e8e9e282c1994ef40b64add1d29afbc523a860 Mon Sep 17 00:00:00 2001 From: "Zachary J. Fields" Date: Wed, 7 Aug 2024 22:35:51 -0500 Subject: [PATCH] feat: ArduinoIoTCloudNotecard --- .github/workflows/compile-examples.yml | 2 + README.md | 19 +- .../ArduinoIoTCloud-Advanced.ino | 4 +- .../thingProperties.h | 7 + .../ArduinoIoTCloud-Basic.ino | 4 +- .../ArduinoIoTCloud-Basic/thingProperties.h | 7 + .../ArduinoIoTCloud-Callbacks.ino | 4 +- .../thingProperties.h | 7 + .../ArduinoIoTCloud-DeferredOTA.ino | 4 +- .../ArduinoIoTCloud-Notecard.ino | 67 +++ examples/ArduinoIoTCloud-Notecard/README.md | 78 +++ .../arduino_secrets.h | 7 + .../thingProperties.h | 36 ++ .../ArduinoIoTCloud-Schedule.ino | 4 +- .../thingProperties.h | 7 + library.properties | 2 +- src/AIoTC_Config.h | 35 +- src/ArduinoIoTCloud.h | 4 +- src/ArduinoIoTCloudDevice.cpp | 4 +- src/ArduinoIoTCloudNotecard.cpp | 481 ++++++++++++++++++ src/ArduinoIoTCloudNotecard.h | 133 +++++ src/ArduinoIoTCloudThing.cpp | 4 +- src/utility/time/RTCMillis.cpp | 8 +- src/utility/time/RTCMillis.h | 4 +- src/utility/time/TimeService.cpp | 81 ++- src/utility/time/TimeService.h | 2 +- 26 files changed, 960 insertions(+), 55 deletions(-) create mode 100644 examples/ArduinoIoTCloud-Notecard/ArduinoIoTCloud-Notecard.ino create mode 100644 examples/ArduinoIoTCloud-Notecard/README.md create mode 100644 examples/ArduinoIoTCloud-Notecard/arduino_secrets.h create mode 100644 examples/ArduinoIoTCloud-Notecard/thingProperties.h create mode 100644 src/ArduinoIoTCloudNotecard.cpp create mode 100644 src/ArduinoIoTCloudNotecard.h diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 4e286b9f..f26c4c30 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -192,8 +192,10 @@ jobs: - name: arduino:mbed_opta libraries: | - name: ArduinoECCX08 + - name: Blues Wireless Notecard sketch-paths: | - examples/ArduinoIoTCloud-DeferredOTA + - examples/ArduinoIoTCloud-Notecard - examples/utility/Provisioning # GIGA - board: diff --git a/README.md b/README.md index 83a7e70c..f86a0280 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,27 @@ ### What? The `ArduinoIoTCloud` library is the central element of the firmware enabling certain Arduino boards to connect to the [Arduino IoT Cloud](https://www.arduino.cc/en/IoT/HomePage). The following boards are supported: -* **WiFi**: [`MKR 1000`](https://store.arduino.cc/arduino-mkr1000-wifi), [`MKR WiFi 1010`](https://store.arduino.cc/arduino-mkr-wifi-1010), [`Nano 33 IoT`](https://store.arduino.cc/arduino-nano-33-iot), [`Portenta H7`](https://store.arduino.cc/portenta-h7), [`Nano RP2040 Connect`](https://store.arduino.cc/products/arduino-nano-rp2040-connect), [`Nicla Vision`](https://store.arduino.cc/products/nicla-vision), [`OPTA WiFi`](https://store.arduino.cc/products/opta-wifi), [`GIGA R1 WiFi`](https://store.arduino.cc/products/giga-r1-wifi), [`Portenta C33`](https://store.arduino.cc/products/portenta-c33), [`UNO R4 WiFi`](https://store.arduino.cc/products/uno-r4-wifi), [`Nano ESP32`](https://store.arduino.cc/products/nano-esp32), [`ESP8266`](https://github.com/esp8266/Arduino/releases/tag/2.5.0), [`ESP32`](https://github.com/espressif/arduino-esp32/releases/tag/2.0.5) +* **Wi-Fi**: [`MKR 1000`](https://store.arduino.cc/arduino-mkr1000-wifi), [`MKR WiFi 1010`](https://store.arduino.cc/arduino-mkr-wifi-1010), [`Nano 33 IoT`](https://store.arduino.cc/arduino-nano-33-iot), [`Portenta H7`](https://store.arduino.cc/portenta-h7), [`Nano RP2040 Connect`](https://store.arduino.cc/products/arduino-nano-rp2040-connect), [`Nicla Vision`](https://store.arduino.cc/products/nicla-vision), [`OPTA WiFi`](https://store.arduino.cc/products/opta-wifi), [`GIGA R1 WiFi`](https://store.arduino.cc/products/giga-r1-wifi), [`Portenta C33`](https://store.arduino.cc/products/portenta-c33), [`UNO R4 WiFi`](https://store.arduino.cc/products/uno-r4-wifi), [`Nano ESP32`](https://store.arduino.cc/products/nano-esp32), [`ESP8266`](https://github.com/esp8266/Arduino/releases/tag/2.5.0), [`ESP32`](https://github.com/espressif/arduino-esp32/releases/tag/2.0.5) * **GSM**: [`MKR GSM 1400`](https://store.arduino.cc/arduino-mkr-gsm-1400-1415) * **5G**: [`MKR NB 1500`](https://store.arduino.cc/arduino-mkr-nb-1500-1413) * **LoRa**: [`MKR WAN 1300/1310`](https://store.arduino.cc/mkr-wan-1310) * **Ethernet**: [`Portenta H7`](https://store.arduino.cc/products/portenta-h7) + [`Vision Shield Ethernet`](https://store.arduino.cc/products/arduino-portenta-vision-shield-ethernet), [`Max Carrier`](https://store.arduino.cc/products/portenta-max-carrier), [`Breakout`](https://store.arduino.cc/products/arduino-portenta-breakout), [`Portenta Machine Control`](https://store.arduino.cc/products/arduino-portenta-machine-control), [`OPTA WiFi`](https://store.arduino.cc/products/opta-wifi), [`OPTA RS485`](https://store.arduino.cc/products/opta-rs485), [`OPTA Lite`](https://store.arduino.cc/products/opta-lite), [`Portenta C33`](https://store.arduino.cc/products/portenta-c33) + [`Vision Shield Ethernet`](https://store.arduino.cc/products/arduino-portenta-vision-shield-ethernet) +* **Notecard**: [Provides Cellular/LoRa/Satellite/Wi-Fi to any modern board/architecture](https://shop.blues.com/collections/notecard) + +#### More About Notecard Connectivity + +The Notecard is a wireless, secure abstraction for device connectivity, that can +be used to enable _ANY*_ device with I2C, or UART, to connect to the Arduino IoT +Cloud via cellular, LoRa, satellite or Wi-Fi (including the devices listed +above)! + +As a result, the STM32 architecture has now been added to this library. If you +have an STM32 device, you are now able to connect it to the Arduino IoT Cloud by +using a Notecard to provide a secure communication channel. + +> \*_Unfortunately, the AVR architecture is not supported by the Arduino IoT +> Cloud library. Therefore, those devices are ineligible for use with the +> Notecard._ ### How? 1) Register your Arduino IoT Cloud capable board via [Arduino IoT Cloud](https://create.arduino.cc/iot) (Devices Section). @@ -96,4 +112,3 @@ Boards can authenticate to the ArduinoIoTCloud servers using 3 methods: * `APP_EUI` and `APP_KEY`. This values are defined in the `thingProperties.h` file and included in the Sketch. Boards that are using this method are: [`MKR WAN 1300/1310`](https://store.arduino.cc/mkr-wan-1310) - \ No newline at end of file diff --git a/examples/ArduinoIoTCloud-Advanced/ArduinoIoTCloud-Advanced.ino b/examples/ArduinoIoTCloud-Advanced/ArduinoIoTCloud-Advanced.ino index 09585040..0354043e 100644 --- a/examples/ArduinoIoTCloud-Advanced/ArduinoIoTCloud-Advanced.ino +++ b/examples/ArduinoIoTCloud-Advanced/ArduinoIoTCloud-Advanced.ino @@ -17,13 +17,15 @@ void setup() { Serial.begin(9600); for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime <= 5000); ) { } + /* Specify the level of detail for debug messages */ + setDebugMessageLevel(DBG_INFO); + /* This function takes care of connecting your sketch variables to the ArduinoIoTCloud object */ initProperties(); /* Initialize Arduino IoT Cloud library */ ArduinoCloud.begin(ArduinoIoTPreferredConnection); - setDebugMessageLevel(DBG_INFO); ArduinoCloud.printDebugInfo(); } diff --git a/examples/ArduinoIoTCloud-Advanced/thingProperties.h b/examples/ArduinoIoTCloud-Advanced/thingProperties.h index 43187104..9f45a651 100644 --- a/examples/ArduinoIoTCloud-Advanced/thingProperties.h +++ b/examples/ArduinoIoTCloud-Advanced/thingProperties.h @@ -11,6 +11,10 @@ #define BOARD_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #endif +#if defined(BOARD_HAS_LORA) + #define THING_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +#endif + void onSwitchButtonChange(); void onColorChange(); @@ -23,6 +27,9 @@ void initProperties() { ArduinoCloud.setBoardId(BOARD_ID); ArduinoCloud.setSecretDeviceKey(SECRET_DEVICE_KEY); #endif +#if defined(BOARD_HAS_LORA) + ArduinoCloud.setThingId(THING_ID); +#endif #if defined(BOARD_HAS_WIFI) || defined(BOARD_HAS_GSM) || defined(BOARD_HAS_NB) || defined(BOARD_HAS_ETHERNET) || defined(BOARD_HAS_CATM1_NBIOT) ArduinoCloud.addProperty(switchButton, Permission::Write).onUpdate(onSwitchButtonChange); ArduinoCloud.addProperty(location, Permission::Read).publishOnChange(0.0f); diff --git a/examples/ArduinoIoTCloud-Basic/ArduinoIoTCloud-Basic.ino b/examples/ArduinoIoTCloud-Basic/ArduinoIoTCloud-Basic.ino index a37996b3..a4e160cf 100644 --- a/examples/ArduinoIoTCloud-Basic/ArduinoIoTCloud-Basic.ino +++ b/examples/ArduinoIoTCloud-Basic/ArduinoIoTCloud-Basic.ino @@ -25,6 +25,9 @@ void setup() { Serial.begin(9600); for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime <= 5000); ) { } + /* Specify the level of detail for debug messages */ + setDebugMessageLevel(DBG_INFO); + /* Configure LED pin as an output */ pinMode(LED_BUILTIN, OUTPUT); @@ -34,7 +37,6 @@ void setup() { /* Initialize Arduino IoT Cloud library */ ArduinoCloud.begin(ArduinoIoTPreferredConnection); - setDebugMessageLevel(DBG_INFO); ArduinoCloud.printDebugInfo(); } diff --git a/examples/ArduinoIoTCloud-Basic/thingProperties.h b/examples/ArduinoIoTCloud-Basic/thingProperties.h index 4193b440..0b758329 100644 --- a/examples/ArduinoIoTCloud-Basic/thingProperties.h +++ b/examples/ArduinoIoTCloud-Basic/thingProperties.h @@ -11,6 +11,10 @@ #define BOARD_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #endif +#if defined(BOARD_HAS_LORA) + #define THING_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +#endif + void onLedChange(); bool led; @@ -22,6 +26,9 @@ void initProperties() { ArduinoCloud.setBoardId(BOARD_ID); ArduinoCloud.setSecretDeviceKey(SECRET_DEVICE_KEY); #endif +#if defined(BOARD_HAS_LORA) + ArduinoCloud.setThingId(THING_ID); +#endif #if defined(BOARD_HAS_WIFI) || defined(BOARD_HAS_GSM) || defined(BOARD_HAS_NB) || defined(BOARD_HAS_ETHERNET) || defined(BOARD_HAS_CATM1_NBIOT) ArduinoCloud.addProperty(led, Permission::Write).onUpdate(onLedChange); ArduinoCloud.addProperty(potentiometer, Permission::Read).publishOnChange(10); diff --git a/examples/ArduinoIoTCloud-Callbacks/ArduinoIoTCloud-Callbacks.ino b/examples/ArduinoIoTCloud-Callbacks/ArduinoIoTCloud-Callbacks.ino index 1d39a2f5..019e8880 100644 --- a/examples/ArduinoIoTCloud-Callbacks/ArduinoIoTCloud-Callbacks.ino +++ b/examples/ArduinoIoTCloud-Callbacks/ArduinoIoTCloud-Callbacks.ino @@ -33,6 +33,9 @@ void setup() { Serial.begin(9600); for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime <= 5000); ) { } + /* Specify the level of detail for debug messages */ + setDebugMessageLevel(DBG_INFO); + /* This function takes care of connecting your sketch variables to the ArduinoIoTCloud object */ initProperties(); @@ -51,7 +54,6 @@ void setup() { ArduinoCloud.addCallback(ArduinoIoTCloudEvent::SYNC, doThisOnSync); ArduinoCloud.addCallback(ArduinoIoTCloudEvent::DISCONNECT, doThisOnDisconnect); - setDebugMessageLevel(DBG_INFO); ArduinoCloud.printDebugInfo(); } diff --git a/examples/ArduinoIoTCloud-Callbacks/thingProperties.h b/examples/ArduinoIoTCloud-Callbacks/thingProperties.h index 8f2b5055..59373323 100644 --- a/examples/ArduinoIoTCloud-Callbacks/thingProperties.h +++ b/examples/ArduinoIoTCloud-Callbacks/thingProperties.h @@ -11,11 +11,18 @@ #define BOARD_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #endif +#if defined(BOARD_HAS_LORA) + #define THING_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +#endif + void initProperties() { #if defined(BOARD_HAS_SECRET_KEY) ArduinoCloud.setBoardId(BOARD_ID); ArduinoCloud.setSecretDeviceKey(SECRET_DEVICE_KEY); #endif +#if defined(BOARD_HAS_LORA) + ArduinoCloud.setThingId(THING_ID); +#endif } #if defined(BOARD_HAS_WIFI) diff --git a/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino b/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino index 52fc8731..5cd27ba5 100644 --- a/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino +++ b/examples/ArduinoIoTCloud-DeferredOTA/ArduinoIoTCloud-DeferredOTA.ino @@ -58,6 +58,9 @@ void setup() { Serial.begin(9600); for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime <= 5000); ) { } + /* Specify the level of detail for debug messages */ + setDebugMessageLevel(DBG_INFO); + /* Configure LED pin as an output */ pinMode(LED_BUILTIN, OUTPUT); @@ -70,7 +73,6 @@ void setup() { /* Setup OTA callback */ ArduinoCloud.onOTARequestCb(onOTARequestCallback); - setDebugMessageLevel(DBG_INFO); ArduinoCloud.printDebugInfo(); } diff --git a/examples/ArduinoIoTCloud-Notecard/ArduinoIoTCloud-Notecard.ino b/examples/ArduinoIoTCloud-Notecard/ArduinoIoTCloud-Notecard.ino new file mode 100644 index 00000000..359f56b9 --- /dev/null +++ b/examples/ArduinoIoTCloud-Notecard/ArduinoIoTCloud-Notecard.ino @@ -0,0 +1,67 @@ +/* + This sketch demonstrates how to exchange data between your board and the + Arduino IoT Cloud, while using the Notecard for wireless communication. + + * Connect a potentiometer (or other analog sensor) to A0. + * When the potentiometer (or sensor) value changes the data is sent to the Cloud. + * When you flip the switch in the Cloud dashboard the onboard LED lights gets turned ON or OFF. + + IMPORTANT: + This sketch works with any Wi-Fi, Cellular, LoRa or Satellite enabled Notecard. + + The full list of compatible boards can be found here: + - https://github.com/arduino-libraries/ArduinoIoTCloud#what +*/ + +#include +#include "thingProperties.h" + +#if !defined(LED_BUILTIN) && !defined(ARDUINO_NANO_ESP32) +static int const LED_BUILTIN = 2; +#endif + +/* + * Choose an interrupt capable pin to reduce polling and improve + * the overall responsiveness of the ArduinoIoTCloud library + */ +// #define ATTN_PIN 9 + +void setup() { + /* Initialize serial and wait up to 5 seconds for port to open */ + Serial.begin(9600); + for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime <= 5000); ) { } + + /* Specify the level of detail for debug messages */ + setDebugMessageLevel(DBG_INFO); + + /* Configure LED pin as an output */ + pinMode(LED_BUILTIN, OUTPUT); + + /* This function takes care of connecting your sketch variables to the ArduinoIoTCloud object */ + initProperties(); + + /* Initialize Arduino IoT Cloud library */ +#ifndef ATTN_PIN + ArduinoCloud.begin(ArduinoIoTPreferredConnection); + ArduinoCloud.setNotecardPollingInterval(3000); // default: 1000ms, min: 250ms +#else + ArduinoCloud.begin(ArduinoIoTPreferredConnection, ATTN_PIN); +#endif + + ArduinoCloud.printDebugInfo(); +} + +void loop() { + ArduinoCloud.update(); + potentiometer = analogRead(A0); + seconds = millis() / 1000; +} + +/* + * 'onLedChange' is called when the "led" property of your Thing changes + */ +void onLedChange() { + Serial.print("LED set to "); + Serial.println(led); + digitalWrite(LED_BUILTIN, led); +} diff --git a/examples/ArduinoIoTCloud-Notecard/README.md b/examples/ArduinoIoTCloud-Notecard/README.md new file mode 100644 index 00000000..a17d954c --- /dev/null +++ b/examples/ArduinoIoTCloud-Notecard/README.md @@ -0,0 +1,78 @@ +Notecard Connectivity +===================== + +The Notecard is a wireless, secure abstraction for device connectivity, that can +be used to enable _ANY*_ device with I2C, or UART, to connect to the Arduino IoT +Cloud via cellular, LoRa, satellite or Wi-Fi! + +As a result, your existing device architecture can now have first class support +in the Arduino IoT Cloud, by using a Notecard as a secure communication channel. + +> \*_While any device with I2C/UART may use the Notecard, the Arduino IoT Cloud +> library is not supported by the AVR toolchain. Therefore, devices based on the +> AVR architecture cannot access the Arduino IoT Cloud via the Notecard._ +> +> _However, any device (including AVR), may use the Notecard library to send data +> to Notehub, then that data may be routed to any endpoint of your choosing. See the +> [Notecard Routing Guide](https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud) +> for more information..._ + +Wireless Connectivity Options +----------------------------- + +- [Cellular](https://shop.blues.com/collections/notecard/products/notecard-cellular) +- [Cellular + Wi-Fi](https://shop.blues.com/collections/notecard/products/notecard-cell-wifi) +- [Wi-Fi](https://shop.blues.com/collections/notecard/products/wifi-notecard) +- [LoRa](https://shop.blues.com/collections/notecard/products/notecard-lora) +- [Satellite](https://shop.blues.com/products/starnote) + +How it Works +------------ + +**Architecture Diagram:** + +``` +-------- ------------ ----------- ----------- +| | | | | | | | +| Host | | | Secure | | | Arduino | +| MCU |------| Notecard | ( ( Wireless ) ) | Notehub |------| IoT | +| | | | Protocol | | | Cloud | +|______| |__________| |_________| |_________| +``` + +Getting Started +--------------- + +### Setup a Notehub Account + +Using the Notecard only requires a couple of easy steps: + +1. [Purchase a Notecard](https://shop.blues.com/collections/notecard) (and +[Notecarrier](https://shop.blues.com/collections/notecarrier)) that fits the +needs of your device. + > _**NOTE:** We recommend starting with our [Dev Kit](https://shop.blues.com/products/blues-global-starter-kit) + > if you are unsure._ +1. [Setup a Notehub account](https://dev.blues.io/quickstart/notecard-quickstart/notecard-and-notecarrier-f/#set-up-notehub). + > _**NOTE:** Notehub accounts are free (no credit card required)._ +1. [Create a project on your Notehub account](https://dev.blues.io/quickstart/notecard-quickstart/notecard-and-notecarrier-f/#create-a-notehub-project). +1. In `thingProperties.h`, replace "com.domain.you:product" (from +`NOTECARD_PRODUCT_UID`) with the ProductUID of your new Notehub project. + +### Power-up the Device + +1. [Connect the Notecard to your Host MCU](https://dev.blues.io/quickstart/notecard-quickstart/notecard-and-notecarrier-f/#connect-your-notecard-and-notecarrier) +1. Flash the `ArduinoIoTCloud-Notecard` example sketch to your device. You +should see the device reporting itself as online in your [Notehub Project](https://notehub.io). + +### Associate Notecard to Arduino IoT Cloud + +1. Create a "MANUAL Device" in the Arduino IoT Cloud, then [add environment +variables for the "Device ID" and "Secret Key" to your Notecard in Notehub](https://dev.blues.io/guides-and-tutorials/notecard-guides/understanding-environment-variables/#setting-a-notehub-device-variable). + - `_sn`: \ + - `_secret_key`: \ + +### More Information + +For more information about the Notecard and Notehub in general, please see our +[Quickstart Guide](https://dev.blues.io/quickstart/) for a general overview of +how the Notecard and Notehub are designed to work. diff --git a/examples/ArduinoIoTCloud-Notecard/arduino_secrets.h b/examples/ArduinoIoTCloud-Notecard/arduino_secrets.h new file mode 100644 index 00000000..43e3b76e --- /dev/null +++ b/examples/ArduinoIoTCloud-Notecard/arduino_secrets.h @@ -0,0 +1,7 @@ +#include + +/* If provided, the Wi-Fi Credentials will be passed along to the Notecard. If + * the Notecard supports Wi-Fi, it will attempt to connect to the network using + * these credentials, if not, the Notecard will safely ignore these values. */ +#define SECRET_WIFI_SSID "YOUR_WIFI_NETWORK_NAME" +#define SECRET_WIFI_PASS "YOUR_WIFI_PASSWORD" diff --git a/examples/ArduinoIoTCloud-Notecard/thingProperties.h b/examples/ArduinoIoTCloud-Notecard/thingProperties.h new file mode 100644 index 00000000..cbcaf4da --- /dev/null +++ b/examples/ArduinoIoTCloud-Notecard/thingProperties.h @@ -0,0 +1,36 @@ +#include + +#include +#include +#include "arduino_secrets.h" + +/* The Notecard can provide connectivity to almost any board via ESLOV (I2C) + * or UART. An empty string (or the default value provided below) will not + * override the Notecard's existing configuration. + * Learn more at: https://dev.blues.io */ +#define NOTECARD_PRODUCT_UID "com.domain.you:product" + +/* Uncomment the following line to use the Notecard over UART */ +// #define UART_INTERFACE Serial1 + +void onLedChange(); + +bool led; +int potentiometer; +int seconds; + +#ifndef UART_INTERFACE +NotecardConnectionHandler ArduinoIoTPreferredConnection(NOTECARD_PRODUCT_UID); +#else +NotecardConnectionHandler ArduinoIoTPreferredConnection(NOTECARD_PRODUCT_UID, UART_INTERFACE); +#endif + +void initProperties() { + ArduinoCloud.addProperty(led, Permission::ReadWrite).onUpdate(onLedChange); + ArduinoCloud.addProperty(potentiometer, Permission::Read).publishOnChange(10); + ArduinoCloud.addProperty(seconds, Permission::Read).publishEvery(5 * MINUTES); + + if (::strncmp(SECRET_WIFI_SSID, "YOUR_WIFI_NETWORK_NAME", sizeof(SECRET_WIFI_SSID))) { + ArduinoIoTPreferredConnection.setWiFiCredentials(SECRET_WIFI_SSID, SECRET_WIFI_PASS); + } +} diff --git a/examples/ArduinoIoTCloud-Schedule/ArduinoIoTCloud-Schedule.ino b/examples/ArduinoIoTCloud-Schedule/ArduinoIoTCloud-Schedule.ino index 5b41d149..061423bd 100644 --- a/examples/ArduinoIoTCloud-Schedule/ArduinoIoTCloud-Schedule.ino +++ b/examples/ArduinoIoTCloud-Schedule/ArduinoIoTCloud-Schedule.ino @@ -17,6 +17,9 @@ void setup() { Serial.begin(9600); for(unsigned long const serialBeginTime = millis(); !Serial && (millis() - serialBeginTime <= 5000); ) { } + /* Specify the level of detail for debug messages */ + setDebugMessageLevel(DBG_INFO); + /* Configure LED pin as an output */ pinMode(LED_BUILTIN, OUTPUT); @@ -26,7 +29,6 @@ void setup() { /* Initialize Arduino IoT Cloud library */ ArduinoCloud.begin(ArduinoIoTPreferredConnection); - setDebugMessageLevel(DBG_INFO); ArduinoCloud.printDebugInfo(); /* Setup one shot schedule example */ diff --git a/examples/ArduinoIoTCloud-Schedule/thingProperties.h b/examples/ArduinoIoTCloud-Schedule/thingProperties.h index 8d2d7dfc..4a7eaab5 100644 --- a/examples/ArduinoIoTCloud-Schedule/thingProperties.h +++ b/examples/ArduinoIoTCloud-Schedule/thingProperties.h @@ -11,6 +11,10 @@ #define BOARD_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #endif +#if defined(BOARD_HAS_LORA) + #define THING_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +#endif + void onSwitchButtonChange(); bool switchButton; @@ -27,6 +31,9 @@ void initProperties() { ArduinoCloud.setBoardId(BOARD_ID); ArduinoCloud.setSecretDeviceKey(SECRET_DEVICE_KEY); #endif +#if defined(BOARD_HAS_LORA) + ArduinoCloud.setThingId(THING_ID); +#endif #if defined(BOARD_HAS_WIFI) || defined(BOARD_HAS_GSM) || defined(BOARD_HAS_NB) || defined(BOARD_HAS_ETHERNET) || defined(BOARD_HAS_CATM1_NBIOT) ArduinoCloud.addProperty(switchButton, Permission::Write); ArduinoCloud.addProperty(oneShot, Permission::ReadWrite); diff --git a/library.properties b/library.properties index 267c5b40..ac24f20f 100644 --- a/library.properties +++ b/library.properties @@ -6,6 +6,6 @@ sentence=This library allows connecting to the Arduino IoT Cloud service. paragraph=It provides a ConnectionManager to handle connection/disconnection, property-change updates and events callbacks. The supported boards are MKR GSM, MKR1000 and WiFi101. category=Communication url=https://github.com/arduino-libraries/ArduinoIoTCloud -architectures=mbed,samd,esp8266,mbed_nano,mbed_portenta,mbed_nicla,esp32,mbed_opta,mbed_giga,renesas_portenta,renesas_uno,mbed_edge +architectures=mbed,samd,esp8266,mbed_nano,mbed_portenta,mbed_nicla,esp32,mbed_opta,mbed_giga,renesas_portenta,renesas_uno,mbed_edge,stm32 includes=ArduinoIoTCloud.h depends=Arduino_ConnectionHandler,Arduino_DebugUtils,Arduino_SecureElement,ArduinoMqttClient,ArduinoECCX08,RTCZero,Adafruit SleepyDog Library,ArduinoHttpClient diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index 14137ae1..2412be10 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -18,6 +18,12 @@ #ifndef ARDUINO_AIOTC_CONFIG_H_ #define ARDUINO_AIOTC_CONFIG_H_ +#if defined __has_include + #if __has_include () + #define HAS_NOTECARD + #endif +#endif + #include /****************************************************************************** @@ -52,6 +58,8 @@ * AUTOMATICALLY CONFIGURED DEFINES ******************************************************************************/ +#if !defined(HAS_NOTECARD) + #if defined(ARDUINO_SAMD_MKRWIFI1010) || defined(ARDUINO_SAMD_NANO_33_IOT) #define OTA_STORAGE_SNU (1) #else @@ -114,11 +122,6 @@ #define HAS_TCP #endif -#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA) - #define BEAR_SSL_CLIENT_IBUF_SIZE (16384 + 325) // Allows download from storage API - #define BOARD_STM32H7 -#endif - #if defined(ARDUINO_NANO_RP2040_CONNECT) #define BEAR_SSL_CLIENT_IBUF_SIZE (16384 + 325) // Allows download from storage API #endif @@ -138,6 +141,13 @@ #define BOARD_HAS_SECURE_ELEMENT #endif +#endif // HAS_NOTECARD + +#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_NICLA_VISION) || defined(ARDUINO_OPTA) || defined(ARDUINO_GIGA) + #define BEAR_SSL_CLIENT_IBUF_SIZE (16384 + 325) // Allows download from storage API + #define BOARD_STM32H7 +#endif + /****************************************************************************** * CONSTANTS ******************************************************************************/ @@ -146,16 +156,23 @@ #define AIOT_CONFIG_LPWAN_UPDATE_RETRY_DELAY_ms (10000UL) #endif -#if defined(HAS_TCP) +#if defined(HAS_NOTECARD) || defined(HAS_TCP) #define AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms (1000UL) #define AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms (32000UL) - #define AIOT_CONFIG_THING_ID_REQUEST_RETRY_DELAY_ms (2000UL) - #define AIOT_CONFIG_MAX_THING_ID_REQUEST_RETRY_DELAY_ms (32000UL) + #if defined(HAS_NOTECARD) + // 10x the standard delays for Notecard + #define AIOT_CONFIG_THING_ID_REQUEST_RETRY_DELAY_ms (20000UL) + #define AIOT_CONFIG_MAX_THING_ID_REQUEST_RETRY_DELAY_ms (320000UL) + #else + #define AIOT_CONFIG_THING_ID_REQUEST_RETRY_DELAY_ms (2000UL) + #define AIOT_CONFIG_MAX_THING_ID_REQUEST_RETRY_DELAY_ms (32000UL) + #endif + #define AIOT_CONFIG_THING_ID_REQUEST_MAX_RETRY_CNT (10UL) #define AIOT_CONFIG_DEVICE_REGISTERED_RETRY_DELAY_k (10UL) - #define AIOT_CONFIG_MAX_DEVICE_REGISTERED_RETRY_DELAY_k (4UL) + #define AIOT_CONFIG_MAX_DEVICE_REGISTERED_RETRY_DELAY_k (40UL) #define AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms (30000UL) #define AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT (10UL) diff --git a/src/ArduinoIoTCloud.h b/src/ArduinoIoTCloud.h index 7c0bcc5f..7cea9382 100644 --- a/src/ArduinoIoTCloud.h +++ b/src/ArduinoIoTCloud.h @@ -161,7 +161,9 @@ class ArduinoIoTCloudClass OnCloudEventCallback _cloud_event_callback[3]; }; -#ifdef HAS_TCP +#if defined(HAS_NOTECARD) + #include "ArduinoIoTCloudNotecard.h" +#elif defined(HAS_TCP) #include "ArduinoIoTCloudTCP.h" #elif defined(HAS_LORA) #include "ArduinoIoTCloudLPWAN.h" diff --git a/src/ArduinoIoTCloudDevice.cpp b/src/ArduinoIoTCloudDevice.cpp index cfef518e..574ad3a7 100644 --- a/src/ArduinoIoTCloudDevice.cpp +++ b/src/ArduinoIoTCloudDevice.cpp @@ -14,7 +14,7 @@ #include -#ifdef HAS_TCP +#if defined(HAS_NOTECARD) || defined(HAS_TCP) #include "ArduinoIoTCloudDevice.h" #include "interfaces/CloudProcess.h" @@ -146,4 +146,4 @@ ArduinoCloudDevice::State ArduinoCloudDevice::handleDisconnected() { return State::Disconnected; } -#endif /* HAS_TCP */ +#endif /* HAS_NOTECARD || HAS_TCP */ diff --git a/src/ArduinoIoTCloudNotecard.cpp b/src/ArduinoIoTCloudNotecard.cpp new file mode 100644 index 00000000..79731ce8 --- /dev/null +++ b/src/ArduinoIoTCloudNotecard.cpp @@ -0,0 +1,481 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright 2024 Blues (http://www.blues.com/) + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "AIoTC_Config.h" + +#if defined(HAS_NOTECARD) + +#include "ArduinoIoTCloudNotecard.h" + +#include +#include + +#include +#include + +#include "cbor/CBOREncoder.h" +#include "cbor/MessageDecoder.h" +#include "cbor/MessageEncoder.h" + +/****************************************************************************** + * CONSTANTS + ******************************************************************************/ + +static size_t const CBOR_MSG_BUFFER_SIZE = 256; +static size_t const CBOR_LORA_PAYLOAD_MAX_SIZE = 236; +static size_t const DEFAULT_READ_INTERVAL_MS = 1000; // 1 second +static size_t const FAILSAFE_READ_INTERVAL_MS = 15000; // 15 seconds + +/****************************************************************************** + * LOCAL MODULE FUNCTIONS + ******************************************************************************/ + +unsigned long getTime() +{ + return ArduinoCloud.getInternalTime(); +} + +void ISR_dataAvailable(void) +{ + ArduinoCloud._data_available = true; +} + +/****************************************************************************** + * CTOR/DTOR + ******************************************************************************/ + +ArduinoIoTCloudNotecard::ArduinoIoTCloudNotecard() +: + _state{State::ConnectPhy} + ,_connection_attempt(0,0) + ,_message_stream(std::bind(&ArduinoIoTCloudNotecard::sendMessage, this, std::placeholders::_1)) + ,_thing(&_message_stream) + ,_device(&_message_stream) + ,_notecard_last_poll_ms{static_cast(-DEFAULT_READ_INTERVAL_MS)} + ,_notecard_polling_interval_ms{DEFAULT_READ_INTERVAL_MS} + ,_interrupt_pin{-1} + ,_data_available{false} +{ + +} + +/****************************************************************************** + * PUBLIC MEMBER FUNCTIONS + ******************************************************************************/ + +int ArduinoIoTCloudNotecard::begin(ConnectionHandler &connection_, int interrupt_pin_) +{ + _connection = &connection_; + NotecardConnectionHandler *notecard_connection = reinterpret_cast(_connection); + + // Configure the interrupt pin + if (interrupt_pin_ >= 0) { + _interrupt_pin = interrupt_pin_; + ::pinMode(_interrupt_pin, INPUT); + ::attachInterrupt(digitalPinToInterrupt(_interrupt_pin), ISR_dataAvailable, RISING); + notecard_connection->enableHardwareInterrupts(); + } + + // Initialize the connection to the Notecard + if (NetworkConnectionState::ERROR == _connection->check()) { + DEBUG_ERROR("ArduinoIoTCloudNotecard::%s encountered fatal connection error!", __FUNCTION__); + return 0; // (false -> failure) + } + + // Pull the Arduino IoT Cloud Device ID from the Notecard + setDeviceId(notecard_connection->getDeviceId()); + + // Begin the Notecard time service + _time_service.begin(&connection_); + + /* Setup retry timers */ + _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); + + // Configure the Device and Thing property containers + _thing.begin(); + _device.begin(); + + return 1; // (true -> success) +} + +int ArduinoIoTCloudNotecard::connected() +{ + return (_connection->check() == NetworkConnectionState::CONNECTED); +} + +void ArduinoIoTCloudNotecard::printDebugInfo() +{ + // Print the debug information + NetworkConnectionState conn_state = _connection->check(); + DEBUG_INFO("***** Arduino IoT Cloud Notecard - configuration info *****"); + DEBUG_INFO("Notecard UID: %s", reinterpret_cast(_connection)->getNotecardUid().c_str()); + DEBUG_INFO("Arduino Device ID: %s", getDeviceId().c_str()); + if (NetworkConnectionState::CONNECTED == conn_state) + { + DEBUG_INFO("Arduino Thing ID: %s", getThingId().c_str()); + } + else + { + DEBUG_INFO("Arduino Thing ID: awaiting connection..."); + } +} + +void ArduinoIoTCloudNotecard::update() +{ + /* Run through the state machine. */ + State next_state = _state; + switch (_state) + { + case State::ConnectPhy: next_state = handle_ConnectPhy(); break; + case State::SyncTime: next_state = handle_SyncTime(); break; + case State::Connected: next_state = handle_Connected(); break; + case State::Disconnect: next_state = handle_Disconnect(); break; + } + _state = next_state; +} + +/****************************************************************************** + * PRIVATE STATE MACHINE FUNCTIONS + ******************************************************************************/ + +ArduinoIoTCloudNotecard::State ArduinoIoTCloudNotecard::handle_ConnectPhy() +{ + if (_connection->check() == NetworkConnectionState::CONNECTED + && (!_connection_attempt.isRetry() || (_connection_attempt.isRetry() && _connection_attempt.isExpired()))) + return State::SyncTime; + else + return State::ConnectPhy; +} + +ArduinoIoTCloudNotecard::State ArduinoIoTCloudNotecard::handle_SyncTime() +{ + const uint32_t current_time = ArduinoCloud.getInternalTime(); + if (TimeServiceClass::isTimeValid(current_time)) + { + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] internal clock configured to posix timestamp %d", __FUNCTION__, millis(), current_time); + return State::Connected; + } + + DEBUG_ERROR("ArduinoIoTCloudNotecard::%s could not get valid time. Retrying now.", __FUNCTION__); + return State::ConnectPhy; +} + +ArduinoIoTCloudNotecard::State ArduinoIoTCloudNotecard::handle_Connected() +{ + if (!connected() || !_thing.connected() || !_device.connected()) + { + return State::Disconnect; + } + + /* Poll Notecard for new messages */ + pollNotecard(); + + /* Call CloudDevice process to get configuration */ + _device.update(); + +#if OTA_ENABLED + if(_get_ota_confirmation != nullptr && + _ota.getState() == OTACloudProcessInterface::State::OtaAvailable && + _get_ota_confirmation()) { + _ota.approveOta(); + } + + _ota.update(); +#endif // OTA_ENABLED + + + if (_device.isAttached()) { + /* Call CloudThing process to synchronize properties */ + _thing.update(); + } + + return State::Connected; +} + +ArduinoIoTCloudNotecard::State ArduinoIoTCloudNotecard::handle_Disconnect() +{ + if (!connected()) { + DEBUG_ERROR("ArduinoIoTCloudNotecard::%s connection to Notehub lost", __FUNCTION__); + } + + // Reset the Thing and Device property containers + Message message = { ResetCmdId }; + _thing.handleMessage(&message); + _device.handleMessage(&message); + + DEBUG_INFO("Disconnected from Arduino IoT Cloud"); + + // Execute the user-defined disconnect callback + execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); + + /* Setup timer for broker connection and restart */ + _connection_attempt.begin(AIOT_CONFIG_RECONNECTION_RETRY_DELAY_ms, AIOT_CONFIG_MAX_RECONNECTION_RETRY_DELAY_ms); + return State::ConnectPhy; +} + +/****************************************************************************** + * PRIVATE SUPPORT FUNCTIONS + ******************************************************************************/ + +void ArduinoIoTCloudNotecard::attachThing(String thingId) +{ + _thing_id = thingId; + + Message message; + message = { DeviceAttachedCmdId }; + _device.handleMessage(&message); + + DEBUG_INFO("Connected to Arduino IoT Cloud"); + DEBUG_INFO("Thing ID: %s", getThingId().c_str()); + execCloudEventCallback(ArduinoIoTCloudEvent::CONNECT); +} + +bool ArduinoIoTCloudNotecard::available(void) +{ + bool result; + + const bool interrupts_enabled = (_interrupt_pin >= 0); + const uint32_t now_ms = ::millis(); + + bool check_data = ((now_ms - _notecard_last_poll_ms) > _notecard_polling_interval_ms); + if (interrupts_enabled) { + check_data = (_data_available || ((now_ms - _notecard_last_poll_ms) > FAILSAFE_READ_INTERVAL_MS)); + } + + if (check_data) { + result = _connection->available(); + _data_available = (interrupts_enabled && ::digitalRead(_interrupt_pin)); + _notecard_last_poll_ms = now_ms; + } else { + result = false; + } + + return result; +} + +void ArduinoIoTCloudNotecard::detachThing() +{ + Message message; + message = { DeviceDetachedCmdId }; + _device.handleMessage(&message); + + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + DEBUG_INFO("Disconnected from Arduino IoT Cloud"); + execCloudEventCallback(ArduinoIoTCloudEvent::DISCONNECT); +} + +void ArduinoIoTCloudNotecard::fetchIncomingBytes(uint8_t *buf, size_t &len) +{ + size_t bytes_received = 0; + for (; + _connection->available() && (bytes_received < len); + ++bytes_received) + { + buf[bytes_received] = _connection->read(); + } + if (bytes_received == len && _connection->available()) { + DEBUG_ERROR("ArduinoIoTCloudNotecard::%s buffer overflow on inbound message", __FUNCTION__); + } else { + DEBUG_DEBUG("ArduinoIoTCloudNotecard::%s received %d bytes from cloud", __FUNCTION__, bytes_received); + len = bytes_received; + } +} + +void ArduinoIoTCloudNotecard::pollNotecard(void) +{ + /* Decode available data. */ + if (available()) { + size_t note_len = CBOR_MSG_BUFFER_SIZE; + uint8_t note_buf[CBOR_MSG_BUFFER_SIZE]; + fetchIncomingBytes(note_buf, note_len); + processMessage(note_buf, note_len); + } +} + +void ArduinoIoTCloudNotecard::processCommand(const uint8_t *buf, size_t len) +{ + CommandDown command; + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] received %d bytes", __FUNCTION__, millis(), len); + CBORMessageDecoder decoder; + + if (decoder.decode((Message*)&command, buf, len) != Decoder::Status::Error) { + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] received command id %d", __FUNCTION__, millis(), command.c.id); + switch (command.c.id) + { + case CommandId::ThingUpdateCmdId: + { + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] device configuration received", __FUNCTION__, millis()); + String new_thing_id = String(command.thingUpdateCmd.params.thing_id); + + if (!new_thing_id.length()) { + DEBUG_DEBUG("ArduinoIoTCloudNotecard::%s received null Thing ID.", __FUNCTION__); + _thing_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; + + /* Send message to device state machine to inform we have received a null thing-id */ + Message message; + message = { DeviceRegisteredCmdId }; + _device.handleMessage(&message); + } else { + if (_device.isAttached() && _thing_id != new_thing_id) { + DEBUG_DEBUG("ArduinoIoTCloudNotecard::%s detaching Thing ID: %s", __FUNCTION__, _thing_id.c_str()); + detachThing(); + } + if (!_device.isAttached()) { + DEBUG_DEBUG("ArduinoIoTCloudNotecard::%s attaching Thing ID: %s", __FUNCTION__, new_thing_id.c_str()); + attachThing(new_thing_id); + } + } + } + break; + + case CommandId::ThingDetachCmdId: + { + if (!_device.isAttached() || _thing_id != String(command.thingDetachCmd.params.thing_id)) { + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] thing detach rejected", __FUNCTION__, millis()); + } + + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] thing detach received", __FUNCTION__, millis()); + detachThing(); + } + break; + + case CommandId::TimezoneCommandDownId: + { + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] timezone update received", __FUNCTION__, millis()); + _thing.handleMessage((Message*)&command); + } + break; + + case CommandId::LastValuesUpdateCmdId: + { + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] last values received", __FUNCTION__, millis()); + CBORDecoder::decode(_thing.getPropertyContainer(), + (uint8_t*)command.lastValuesUpdateCmd.params.last_values, + command.lastValuesUpdateCmd.params.length, true); + _thing.handleMessage((Message*)&command); + execCloudEventCallback(ArduinoIoTCloudEvent::SYNC); + + /* + * NOTE: In this current version properties are not properly integrated + * with the new paradigm of modeling the messages with C structs. The + * current CBOR library allocates an array in the heap thus we need to + * delete it after decoding it with the old CBORDecoder + */ + free(command.lastValuesUpdateCmd.params.last_values); + } + break; + +#if OTA_ENABLED + case CommandId::OtaUpdateCmdDownId: + { + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] ota update received", __FUNCTION__, millis()); + _ota.handleMessage((Message*)&command); + } +#endif + + default: + break; + } + } +} + +void ArduinoIoTCloudNotecard::processMessage(const uint8_t *buf, size_t len) +{ + DEBUG_VERBOSE("ArduinoIoTCloudNotecard::%s [%d] decoding %d bytes from cloud...", __FUNCTION__, millis(), len); + NotecardConnectionHandler *notecard_connection = reinterpret_cast(_connection); + switch (notecard_connection->getTopicType()) { + // Commands + case NotecardConnectionHandler::TopicType::Command: + processCommand(buf, len); + break; + // Telemetry + case NotecardConnectionHandler::TopicType::Thing: + CBORDecoder::decode(_thing.getPropertyContainer(), buf, len); + break; + default: + DEBUG_WARNING("Unable to decode unknown topic type: 0x%2X", notecard_connection->getTopicType()); + break; + } +} + +void ArduinoIoTCloudNotecard::sendMessage(Message * msg) +{ + switch (msg->id) { + case PropertiesUpdateCmdId: + sendThingPropertyContainerToCloud(); + break; + + default: + sendCommandMsgToCloud(msg); + break; + } +} + +void ArduinoIoTCloudNotecard::sendCommandMsgToCloud(Message * msg_) +{ + size_t bytes_encoded = CBOR_MSG_BUFFER_SIZE; + uint8_t data[CBOR_MSG_BUFFER_SIZE]; + CBORMessageEncoder encoder; + NotecardConnectionHandler *notecard_connection = reinterpret_cast(_connection); + + if (encoder.encode(msg_, data, bytes_encoded) == Encoder::Status::Complete) { + if (CBOR_LORA_PAYLOAD_MAX_SIZE < bytes_encoded) { + DEBUG_WARNING("Encoded %d bytes for Command Message. Exceeds maximum payload size of %d bytes, and cannot be sent to cloud.", bytes_encoded, CBOR_LORA_PAYLOAD_MAX_SIZE); + } else if (bytes_encoded > 0) { + DEBUG_DEBUG("ArduinoIoTCloudNotecard::%s encoded %d bytes for Command Message", __FUNCTION__, bytes_encoded); + notecard_connection->setTopicType(NotecardConnectionHandler::TopicType::Command); + if (notecard_connection->write(data, bytes_encoded)) { + DEBUG_ERROR("Failed to send Command Message to cloud"); + } else { + notecard_connection->initiateNotehubSync(NotecardConnectionHandler::SyncType::Inbound); + } + } else { + DEBUG_DEBUG("ArduinoIoTCloudNotecard::%s encoded zero (0) bytes for Command Message", __FUNCTION__); + } + } else { + DEBUG_ERROR("Failed to encode Command Message"); + } +} + +void ArduinoIoTCloudNotecard::sendThingPropertyContainerToCloud() +{ + int bytes_encoded = 0; + uint8_t data[CBOR_MSG_BUFFER_SIZE]; + NotecardConnectionHandler *notecard_connection = reinterpret_cast(_connection); + + // Check if any property needs encoding and send them to the cloud + if (CBOREncoder::encode(_thing.getPropertyContainer(), data, sizeof(data), bytes_encoded, _thing.getPropertyContainerIndex(), USE_LIGHT_PAYLOADS) == CborNoError) { + if (static_cast(CBOR_LORA_PAYLOAD_MAX_SIZE) < bytes_encoded) { + DEBUG_ERROR("Encoded %d bytes for Thing properties. Exceeds maximum encoded payload size of %d bytes, and cannot sync with cloud.", bytes_encoded, CBOR_LORA_PAYLOAD_MAX_SIZE); + } else if (bytes_encoded < 0) { + DEBUG_ERROR("Encoding Thing properties resulted in error: %d.", bytes_encoded); + } else if (bytes_encoded > 0) { + DEBUG_DEBUG("ArduinoIoTCloudNotecard::%s encoded %d bytes of Thing properties", __FUNCTION__, bytes_encoded); + notecard_connection->setTopicType(NotecardConnectionHandler::TopicType::Thing); + if (notecard_connection->write(data, bytes_encoded)) { + DEBUG_ERROR("Failed to sync Thing properties with cloud"); + } + } + } else { + DEBUG_ERROR("Failed to encode Thing properties"); + } +} + +/****************************************************************************** + * EXTERN DEFINITION + ******************************************************************************/ + +ArduinoIoTCloudNotecard ArduinoCloud; + +#endif // HAS_NOTECARD diff --git a/src/ArduinoIoTCloudNotecard.h b/src/ArduinoIoTCloudNotecard.h new file mode 100644 index 00000000..c509c0d8 --- /dev/null +++ b/src/ArduinoIoTCloudNotecard.h @@ -0,0 +1,133 @@ +/* + This file is part of the ArduinoIoTCloud library. + + Copyright 2024 Blues (http://www.blues.com/) + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef ARDUINO_IOT_CLOUD_NOTECARD_H +#define ARDUINO_IOT_CLOUD_NOTECARD_H + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "ArduinoIoTCloud.h" +#include "ArduinoIoTCloudThing.h" +#include "ArduinoIoTCloudDevice.h" + +/****************************************************************************** + * DEFINES + ******************************************************************************/ + +#define USE_LIGHT_PAYLOADS (false) + +/****************************************************************************** + * CONSTANTS + ******************************************************************************/ + +/****************************************************************************** + * TYPEDEF + ******************************************************************************/ + +/****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +/** + * @brief The ArduinoIoTCloudNotecard class + * + * This class is used to connect to the Arduino IoT Cloud using a Notecard. + */ +class ArduinoIoTCloudNotecard : public ArduinoIoTCloudClass +{ + public: + ArduinoIoTCloudNotecard(); + virtual ~ArduinoIoTCloudNotecard() { } + + virtual void update () override; + virtual int connected () override; + virtual void printDebugInfo() override; + inline virtual PropertyContainer &getThingPropertyContainer() override { return _thing.getPropertyContainer(); } + + /** + * @brief Begin the connection to the Arduino IoT Cloud. + * + * @param connection The connection handler to use. + * @param interrupt_pin The interrupt pin to use for the Notecard. + * + * @note The interrupt pin is optional and only required if you want to + * eliminate the need to poll the Notecard for new data. The pin only needs + * to be specified, and will otherwise be configured by the library. + * + * @return 1 on success, 0 on failure. + */ + int begin(ConnectionHandler &connection, int interrupt_pin = -1); + + /** + * @brief Set the Notecard polling interval. + * + * The interval at which the Notecard is polled (via I2C/UART) for new data. + * This is not a network transaction, but a local polling of the Notecard. + * + * @param interval_ms The interval in milliseconds. + * @par + * - Minimum: 250ms + * - Default: 1000ms + * + * @note The Notecard poll interval is ignored if an interrupt pin is + * provided to the `begin()` function. + */ + inline void setNotecardPollingInterval(uint32_t interval_ms) { _notecard_polling_interval_ms = ((interval_ms < 250) ? 250 : interval_ms); } + + private: + + enum class State + { + ConnectPhy, + SyncTime, + Connected, + Disconnect, + }; + + State _state; + TimedAttempt _connection_attempt; + MessageStream _message_stream; + ArduinoCloudThing _thing; + ArduinoCloudDevice _device; + + // Notecard member variables + uint32_t _notecard_last_poll_ms; + uint32_t _notecard_polling_interval_ms; + int _interrupt_pin; + volatile bool _data_available; + + State handle_ConnectPhy(); + State handle_SyncTime(); + State handle_Connected(); + State handle_Disconnect(); + + void attachThing(String thingId); + bool available (void); + void detachThing(); + void fetchIncomingBytes(uint8_t *buf, size_t &len); + void pollNotecard(void); + void processCommand(const uint8_t *buf, size_t len); + void processMessage(const uint8_t *buf, size_t len); + void sendMessage(Message * msg); + void sendCommandMsgToCloud(Message * msg_); + void sendThingPropertyContainerToCloud(void); + + friend void ISR_dataAvailable (void); +}; + +/****************************************************************************** + * EXTERN DECLARATION + ******************************************************************************/ + +extern ArduinoIoTCloudNotecard ArduinoCloud; + +#endif // ARDUINO_IOT_CLOUD_NOTECARD_H diff --git a/src/ArduinoIoTCloudThing.cpp b/src/ArduinoIoTCloudThing.cpp index 98ebb0fb..5b07d0a5 100644 --- a/src/ArduinoIoTCloudThing.cpp +++ b/src/ArduinoIoTCloudThing.cpp @@ -15,7 +15,7 @@ #include -#ifdef HAS_TCP +#if defined(HAS_NOTECARD) || defined(HAS_TCP) #include "ArduinoIoTCloudThing.h" #include "interfaces/CloudProcess.h" @@ -180,4 +180,4 @@ ArduinoCloudThing::State ArduinoCloudThing::handleDisconnect() { return State::Disconnect; } -#endif /* HAS_TCP */ +#endif /* HAS_NOTECARD || HAS_TCP */ diff --git a/src/utility/time/RTCMillis.cpp b/src/utility/time/RTCMillis.cpp index 419236d1..d7debd26 100644 --- a/src/utility/time/RTCMillis.cpp +++ b/src/utility/time/RTCMillis.cpp @@ -15,12 +15,14 @@ a commercial license, send an email to license@arduino.cc. */ -#ifdef ARDUINO_ARCH_ESP8266 - /************************************************************************************** * INCLUDE **************************************************************************************/ +#include "AIoTC_Config.h" + +#if defined(HAS_NOTECARD) || defined(ARDUINO_ARCH_ESP8266) + #include #include "RTCMillis.h" @@ -59,4 +61,4 @@ unsigned long RTCMillis::get() return _last_rtc_update_value; } -#endif /* ARDUINO_ARCH_ESP8266 */ +#endif /* HAS_NOTECARD || ARDUINO_ARCH_ESP8266 */ diff --git a/src/utility/time/RTCMillis.h b/src/utility/time/RTCMillis.h index 1fa7e0f4..ab35b953 100644 --- a/src/utility/time/RTCMillis.h +++ b/src/utility/time/RTCMillis.h @@ -18,7 +18,7 @@ #ifndef ARDUINO_IOT_CLOUD_RTC_MILLIS_H_ #define ARDUINO_IOT_CLOUD_RTC_MILLIS_H_ -#ifdef ARDUINO_ARCH_ESP8266 +#if defined(HAS_NOTECARD) || defined(ARDUINO_ARCH_ESP8266) /************************************************************************************** * INCLUDE @@ -45,6 +45,6 @@ class RTCMillis }; -#endif /* ARDUINO_ARCH_ESP8266 */ +#endif /* HAS_NOTECARD || ARDUINO_ARCH_ESP8266 */ #endif /* ARDUINO_IOT_CLOUD_RTC_MILLIS_H_ */ diff --git a/src/utility/time/TimeService.cpp b/src/utility/time/TimeService.cpp index 5bf081de..6cae34b2 100644 --- a/src/utility/time/TimeService.cpp +++ b/src/utility/time/TimeService.cpp @@ -19,26 +19,21 @@ * INCLUDE **************************************************************************************/ -#include #include -#include "TimeService.h" -#include "NTPUtils.h" + +#include "AIoTC_Config.h" #include "AIoTC_Const.h" +#include "NTPUtils.h" +#include "TimeService.h" -#ifdef ARDUINO_ARCH_SAMD +#if defined(HAS_NOTECARD) || defined(ARDUINO_ARCH_ESP8266) + #include "RTCMillis.h" +#elif defined(ARDUINO_ARCH_SAMD) #include -#endif - -#ifdef ARDUINO_ARCH_MBED +#elif defined(ARDUINO_ARCH_MBED) #include -#endif - -#ifdef ARDUINO_ARCH_ESP8266 - #include "RTCMillis.h" -#endif - -#ifdef ARDUINO_ARCH_RENESAS +#elif defined(ARDUINO_ARCH_RENESAS) #include "RTC.h" #endif @@ -46,12 +41,10 @@ * GLOBAL VARIABLES **************************************************************************************/ -#ifdef ARDUINO_ARCH_SAMD -RTCZero rtc; -#endif - -#ifdef ARDUINO_ARCH_ESP8266 +#if defined(HAS_NOTECARD) || defined(ARDUINO_ARCH_ESP8266) RTCMillis rtc; +#elif defined(ARDUINO_ARCH_SAMD) +RTCZero rtc; #endif /************************************************************************************** @@ -60,6 +53,12 @@ RTCMillis rtc; time_t cvt_time(char const * time); +#if defined(HAS_NOTECARD) +void notecard_initRTC(); +void notecard_setRTC(unsigned long time); +unsigned long notecard_getRTC(); +#else + #ifdef ARDUINO_ARCH_SAMD void samd_initRTC(); void samd_setRTC(unsigned long time); @@ -90,6 +89,8 @@ void renesas_setRTC(unsigned long time); unsigned long renesas_getRTC(); #endif +#endif /* HAS_NOTECARD */ + /************************************************************************************** * DEFINES **************************************************************************************/ @@ -161,10 +162,9 @@ bool TimeServiceClass::sync() if(_sync_func) { utc = _sync_func(); } else { -#ifdef HAS_TCP +#if defined(HAS_NOTECARD) || defined(HAS_TCP) utc = getRemoteTime(); -#endif -#ifdef HAS_LORA +#elif defined(HAS_LORA) /* Just keep incrementing stored RTC value*/ utc = getRTC(); #endif @@ -275,7 +275,7 @@ unsigned long TimeServiceClass::getTimeFromString(const String& input) * PRIVATE MEMBER FUNCTIONS **************************************************************************************/ -#ifdef HAS_TCP +#if defined(HAS_NOTECARD) || defined(HAS_TCP) bool TimeServiceClass::connected() { if(_con_hdl == nullptr) { @@ -288,6 +288,7 @@ bool TimeServiceClass::connected() unsigned long TimeServiceClass::getRemoteTime() { if(connected()) { +#ifdef HAS_TCP /* At first try to obtain a valid time via NTP. * This is the most reliable time source and it will * ensure a correct behaviour of the library. @@ -298,6 +299,7 @@ unsigned long TimeServiceClass::getRemoteTime() return ntp_time; } } +#endif /* HAS_TCP */ /* As fallback if NTP request fails try to obtain the * network time using the connection handler. @@ -316,7 +318,7 @@ unsigned long TimeServiceClass::getRemoteTime() return EPOCH_AT_COMPILE_TIME; } -#endif /* HAS_TCP */ +#endif /* HAS_NOTECARD || HAS_TCP */ bool TimeServiceClass::isTimeValid(unsigned long const time) { @@ -331,7 +333,9 @@ bool TimeServiceClass::isTimeZoneOffsetValid(long const offset) void TimeServiceClass::initRTC() { -#if defined (ARDUINO_ARCH_SAMD) +#if defined (HAS_NOTECARD) + notecard_initRTC(); +#elif defined (ARDUINO_ARCH_SAMD) samd_initRTC(); #elif defined (ARDUINO_ARCH_MBED) mbed_initRTC(); @@ -348,7 +352,9 @@ void TimeServiceClass::initRTC() void TimeServiceClass::setRTC(unsigned long time) { -#if defined (ARDUINO_ARCH_SAMD) +#if defined (HAS_NOTECARD) + notecard_setRTC(time); +#elif defined (ARDUINO_ARCH_SAMD) samd_setRTC(time); #elif defined (ARDUINO_ARCH_MBED) mbed_setRTC(time); @@ -365,7 +371,9 @@ void TimeServiceClass::setRTC(unsigned long time) unsigned long TimeServiceClass::getRTC() { -#if defined (ARDUINO_ARCH_SAMD) +#if defined (HAS_NOTECARD) + return notecard_getRTC(); +#elif defined (ARDUINO_ARCH_SAMD) return samd_getRTC(); #elif defined (ARDUINO_ARCH_MBED) return mbed_getRTC(); @@ -420,6 +428,23 @@ time_t cvt_time(char const * time) return build_time; } +#ifdef HAS_NOTECARD +void notecard_initRTC() +{ + rtc.begin(); +} + +void notecard_setRTC(unsigned long time) +{ + rtc.set(time); +} + +unsigned long notecard_getRTC() +{ + return rtc.get(); +} +#else + #ifdef ARDUINO_ARCH_SAMD void samd_initRTC() { @@ -509,6 +534,8 @@ unsigned long renesas_getRTC() } #endif +#endif /* HAS_NOTECARD */ + /****************************************************************************** * EXTERN DEFINITION ******************************************************************************/ diff --git a/src/utility/time/TimeService.h b/src/utility/time/TimeService.h index 794ec344..71656d94 100644 --- a/src/utility/time/TimeService.h +++ b/src/utility/time/TimeService.h @@ -69,7 +69,7 @@ class TimeServiceClass unsigned long _sync_interval_ms; syncTimeFunctionPtr _sync_func; -#ifdef HAS_TCP +#if defined(HAS_NOTECARD) || defined(HAS_TCP) unsigned long getRemoteTime(); bool connected(); #endif