diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..22a51dd --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,139 @@ +name: CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + workflow_dispatch: + +jobs: + + build: + strategy: + matrix: + board: + - esp32:esp32@1.0.6 + - esp32:esp32:esp32:DebugLevel=none + - esp32:esp32:esp32:DebugLevel=verbose + #- esp32:esp32:firebeetle32 + #- esp32:esp32:ttgo-lora32:Revision=TTGO_LoRa32_V1 + #- esp32:esp32:ttgo-lora32:Revision=TTGO_LoRa32_V2 + #- esp32:esp32:ttgo-lora32:Revision=TTGO_LoRa32_v21new + #- esp32:esp32:heltec_wireless_stick:PSRAM=disabled + #- esp32:esp32:featheresp32 + #- esp32:esp32:adafruit_feather_esp32s2 + + runs-on: ubuntu-latest + name: ${{ matrix.board }} + env: + GH_TOKEN: ${{ github.token }} + run-build: ${{ contains(matrix.board, 'esp32:esp32') || contains(github.event.head_commit.message, 'CI_BUILD_ALL') || contains(github.event.head_commit.message, 'Bump version to') || contains(github.event.head_commit.message, format('{0}', matrix.board)) }} + + steps: + - name: Install arduino-cli + if: ${{ env.run-build == 'true' }} + run: + | + mkdir -p ~/.local/bin + echo "~/.local/bin" >> $GITHUB_PATH + curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh + + - name: Get platform name + if: ${{ env.run-build == 'true' }} + uses: jungwinter/split@v2 + id: split + with: + msg: ${{ matrix.board }} + separator: ':' + + - name: Prepare platform-specific settings + if: ${{ env.run-build == 'true' }} + id: prep + run: + | + # common settings - no extra options, all warnings + echo "skip-pattern='simple_sensor_bme280'" >> $GITHUB_OUTPUT + echo "warnings='all'" >> $GITHUB_OUTPUT + + # platform-dependent settings - extra board options, board index URLs, skip patterns etc. + if [[ "${{ contains(matrix.board, 'esp32:esp32') }}" == "true" ]]; then + # ESP32 + python -m pip install pyserial + echo "index-url=--additional-urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json" >> $GITHUB_OUTPUT + fi + + - name: Install libraries + if: ${{ env.run-build == 'true' }} + run: + | + declare -a required_libs=( + "MCCI LoRaWAN LMIC library@4.1.1" + "MCCI Arduino LoRaWAN Library@0.9.2" + "MCCI Arduino Development Kit ADK@0.2.2" + "LoRa Serialization@3.2.1" + "ESP32Time@2.0.0") + for i in "${required_libs[@]}" + do + arduino-cli lib install "$i" + done + # Clone mcci-catena's fork of Adafruit_BME280_Library used in the example simple_sensor_bme280 + #git clone https://github.com/mcci-catena/Adafruit_BME280_Library.git + #cp -r Adafruit_BME280_Library /home/runner/Arduino/libraries/ + + - name: Install platform + if: ${{ env.run-build == 'true' }} + run: + | + arduino-cli core update-index ${{ format('{0}', steps.prep.outputs.index-url) }} + arduino-cli core install ${{ format('{0}:{1} {2}', steps.split.outputs._0, steps.split.outputs._1, steps.prep.outputs.index-url) }} + + - name: Checkout repository + if: ${{ env.run-build == 'true' }} + uses: actions/checkout@v3 + + - name: Customizing lmic_project_config.h (LMIC_ENABLE_DeviceTimeReq) + if: ${{ env.run-build == 'true' }} + run: + | + echo "#define LMIC_ENABLE_DeviceTimeReq 1" >> /home/runner/Arduino/libraries/MCCI_LoRaWAN_LMIC_library/project_config/lmic_project_config.h + # Fix for linker error with Arduino core for ESP32 v2.0.x as suggested in https://github.com/mcci-catena/arduino-lmic/issues/714#issuecomment-822051171 + echo "#define hal_init LMICHAL_init" >> /home/runner/Arduino/libraries/MCCI_LoRaWAN_LMIC_library/project_config/lmic_project_config.h + + - name: Copy secrets.h from secrets.h.template + if: ${{ env.run-build == 'true' }} + run: + | + pwd + ls + cp examples/arduino_lorawan_esp32_example/secrets.h.template examples/arduino_lorawan_esp32_example/secrets.h + + - name: Build sketch + if: ${{ env.run-build == 'true' }} + run: + | + #for example in $(find $PWD/examples -name '*.ino' | sort); do + # modified to compile a singe sketch (instead of a library's examples) + for example in $(find $PWD -name '*.ino' | sort); do + # check whether to skip this sketch + if [ ! -z '${{ steps.prep.outputs.skip-pattern }}' ] && [[ ${example} =~ ${{ steps.prep.outputs.skip-pattern }} ]]; then + # skip sketch + echo -e "\n\033[1;33mSkipped ${example##*/} (matched with ${{ steps.prep.outputs.skip-pattern }})\033[0m"; + else + # build sketch + echo -e "\n\033[1;33mBuilding ${example##*/} ... \033[0m"; + if [[ "${{ contains(matrix.board, '@1.0.6') }}" == "true" ]]; then + # hardcoded fqbn, because 'esp32:esp32@1.0.6' did not work + arduino-cli compile --libraries /home/runner/work/arduino-lorawan --fqbn esp32:esp32:esp32${{ steps.prep.outputs.options }} $example --warnings=${{ steps.prep.outputs.warnings }} + else + arduino-cli compile --libraries /home/runner/work/arduino-lorawan --fqbn ${{ matrix.board }}${{ steps.prep.outputs.options }} $example --warnings=${{ steps.prep.outputs.warnings }} + fi + + if [ $? -ne 0 ]; then + echo -e "\033[1;31m${example##*/} build FAILED\033[0m\n"; + exit 1; + else + echo -e "\033[1;32m${example##*/} build PASSED\033[0m\n"; + fi + fi + done diff --git a/README.md b/README.md index 64b3cf3..3c08237 100644 --- a/README.md +++ b/README.md @@ -428,9 +428,13 @@ Much more elaborate uses can be found in the MCCI [Catena-Arduino-Platform](http ## Release History +- v0.10.0-pre2 includes the following changes. + + - examples/arduino_lorawan_esp32_example: [@matthias-bs](https://github.com/matthias-bs) Added option for setting ESP32 internal RTC from LoRaWAN network time + - v0.10.0-pre1 includes the following changes. - - examples/arduino_lorawan_esp32_example: @matthias-bs Added pin mappings for some common ESP32 LoRaWAN boards + - examples/arduino_lorawan_esp32_example: [@matthias-bs](https://github.com/matthias-bs) Added pin mappings for some common ESP32 LoRaWAN boards - v0.9.2 includes the following changes. diff --git a/examples/arduino_lorawan_esp32_example/arduino_lorawan_esp32_example.ino b/examples/arduino_lorawan_esp32_example/arduino_lorawan_esp32_example.ino index 1f78803..170896e 100644 --- a/examples/arduino_lorawan_esp32_example/arduino_lorawan_esp32_example.ino +++ b/examples/arduino_lorawan_esp32_example/arduino_lorawan_esp32_example.ino @@ -10,6 +10,7 @@ // - implements fast re-joining after sleep by storing network session data // in the ESP32 RTC RAM // - LoRa_Serialization is used for encoding various data types into bytes +// - internal Real-Time Clock (RTC) set from LoRaWAN network time (optional) // // // Based on: @@ -27,6 +28,7 @@ // MCCI LoRaWAN LMIC library 4.1.1 // MCCI Arduino LoRaWAN Library 0.9.2 // LoRa_Serialization 3.2.1 +// ESP32Time 2.0.0 // // // created: 07/2022 @@ -61,6 +63,7 @@ // 20230307 Changed cMyLoRaWAN to inherit from Arduino_LoRaWAN_network // instead of Arduino_LoRaWAN_ttn // Added Pin mappings for some common ESP32 LoRaWAN boards +// 20230308 Added option for setting RTC from LoRaWAN network time // // Notes: // - After a successful transmission, the controller can go into deep sleep @@ -73,6 +76,11 @@ // - The ESP32's RTC RAM is used to store information about the LoRaWAN // network session; this speeds up the connection after a restart // significantly +// - To enable Network Time Requests: +// #define LMIC_ENABLE_DeviceTimeReq 1 +// - settimeofday()/gettimeofday() must be used to access the ESP32's RTC time +// - Arduino ESP32 package has built-in time zone handling, see +// https://github.com/SensorsIot/NTP-time-for-ESP8266-and-ESP32/blob/master/NTP_Example/NTP_Example.ino // /////////////////////////////////////////////////////////////////////////////// @@ -92,6 +100,12 @@ #include #include +// NOTE: Add #define LMIC_ENABLE_DeviceTimeReq 1 +// in ~/Arduino/libraries/MCCI_LoRaWAN_LMIC_library/project_config/lmic_project_config.h +#if (not(LMIC_ENABLE_DeviceTimeReq)) + #warning "LMIC_ENABLE_DeviceTimeReq is not set - will not be able to retrieve network time!" +#endif + //----------------------------------------------------------------------------- // // User Configuration @@ -103,6 +117,17 @@ // Enable sleep mode - sleep after successful transmission to TTN (recommended!) #define SLEEP_EN +// Enable setting RTC from LoRaWAN network time +#define GET_NETWORKTIME + +#if defined(GET_NETWORKTIME) + // Enter your time zone (https://remotemonitoringsystems.ca/time-zone-abbreviations.php) + const char* TZ_INFO = "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"; + + // RTC to network time sync interval (in minutes) + #define CLOCK_SYNC_INTERVAL 24 * 60 +#endif + // If SLEEP_EN is defined, MCU will sleep for SLEEP_INTERVAL seconds after succesful transmission #define SLEEP_INTERVAL 360 @@ -117,6 +142,10 @@ //----------------------------------------------------------------------------- +#if defined(GET_NETWORKTIME) + #include +#endif + // LoRa_Serialization #include @@ -190,8 +219,9 @@ #endif -// Uplink message payload size (calculate from assignments to 'encoder' object) -const uint8_t PAYLOAD_SIZE = 8; +// Uplink message payload size +// The maximum allowed for all data rates is 51 bytes. +const uint8_t PAYLOAD_SIZE = 51; // RTC Memory Handling #define MAGIC1 (('m' << 24) | ('g' < 16) | ('c' << 8) | '1') @@ -205,11 +235,14 @@ const uint8_t PAYLOAD_SIZE = 8; #define DEBUG_PRINTF_TS(...) { DEBUG_PORT.printf("%d ms: ", osticks2ms(os_getTime())); \ DEBUG_PORT.printf(__VA_ARGS__); } #else - #define DEBUG_PRINTF(...) {} - #define DEBUG_PRINTF_TS(...) {} + #define DEBUG_PRINTF(...) {} + #define DEBUG_PRINTF_TS(...) {} +#endif + +#if defined(GET_NETWORKTIME) + void printDateTime(void); #endif - /****************************************************************************\ | | The LoRaWAN object @@ -222,6 +255,15 @@ public: using Super = Arduino_LoRaWAN_network; void setup(); + #if defined(GET_NETWORKTIME) + /*! + * \fn requestNetworkTime + * + * \brief Wrapper function for LMIC_requestNetworkTime() + */ + void requestNetworkTime(void); + #endif + protected: // you'll need to provide implementation for this. virtual bool GetOtaaProvisioningInfo(Arduino_LoRaWAN::OtaaProvisioningInfo*) override; @@ -259,7 +301,7 @@ public: uint16_t getVoltageBattery(void); uint16_t getVoltageSupply(void); - bool uplinkRequest(void) { + void uplinkRequest(void) { m_fUplinkRequest = true; }; /// @@ -338,12 +380,26 @@ RTC_DATA_ATTR size_t rtcSavedNExtraInfo; RTC_DATA_ATTR uint8_t rtcSavedExtraInfo[EXTRA_INFO_MEM_SIZE]; RTC_DATA_ATTR bool runtimeExpired = 0; +#if defined(GET_NETWORKTIME) + RTC_DATA_ATTR time_t rtcLastClockSync = 0; //!< timestamp of last RTC synchonization to network time +#endif + // Uplink payload buffer static uint8_t loraData[PAYLOAD_SIZE]; // Force sleep mode after has been reached (if FORCE_SLEEP is defined) ostime_t sleepTimeout; +/// RTC sync request flag - set (if due) in setup() / cleared in UserRequestNetworkTimeCb() +bool rtcSyncReq = false; + +#if defined(GET_NETWORKTIME) + /// Seconds since the UTC epoch + uint32_t userUTCTime; + + /// Real time clock + ESP32Time rtc; +#endif /****************************************************************************\ | @@ -408,6 +464,18 @@ void setup() { DEBUG_PRINTF_TS("setup()\n"); + #if defined(GET_NETWORKTIME) + // Set time zone + setenv("TZ", TZ_INFO, 1); + printDateTime(); + + // Check if clock was never synchronized or sync interval has expired + if ((rtcLastClockSync == 0) || ((rtc.getLocalEpoch() - rtcLastClockSync) > (CLOCK_SYNC_INTERVAL * 60))) { + DEBUG_PRINTF("RTC sync required\n"); + rtcSyncReq = true; + } + #endif + // set up the log; do this first. myEventLog.setup(); DEBUG_PRINTF("myEventlog.setup() - done\n"); @@ -436,7 +504,7 @@ void loop() { myEventLog.loop(); #ifdef FORCE_SLEEP - if (os_getTime() > sleepTimeout) { + if ((os_getTime() > sleepTimeout) & !rtcSyncReq) { DEBUG_PRINTF_TS("Sleep timer expired!\n"); DEBUG_PRINTF("Shutdown()\n"); runtimeExpired = true; @@ -460,9 +528,7 @@ void cMyLoRaWAN::setup() { // simply call begin() w/o parameters, and the LMIC's built-in // configuration for this board will be used. - bool res = this->Super::begin(myPinMap); - DEBUG_PRINTF("Arduino_LoRaWAN::begin(): %d\n", res); - + this->Super::begin(myPinMap); // LMIC_selectSubBand(0); LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); @@ -723,6 +789,82 @@ cMyLoRaWAN::GetAbpProvisioningInfo(AbpProvisioningInfo *pAbpInfo) { return true; } +#if defined(GET_NETWORKTIME) + /// Print date and time (i.e. local time) + void printDateTime(void) { + struct tm timeinfo; + char tbuf[26]; + + time_t tnow = rtc.getLocalEpoch(); + localtime_r(&tnow, &timeinfo); + strftime(tbuf, 25, "%Y-%m-%d %H:%M:%S\n", &timeinfo); + DEBUG_PRINTF("%s", tbuf); + } + + /** + * \fn UserRequestNetworkTimeCb + * + * \brief Callback function for setting RTC from LoRaWAN network time + * + * \param pVoidUserUTCTime user supplied buffer for UTC time + * + * \param flagSuccess flag indicating if network time request was succesful + */ + void UserRequestNetworkTimeCb(void *pVoidUserUTCTime, int flagSuccess) { + // Explicit conversion from void* to uint32_t* to avoid compiler errors + uint32_t *pUserUTCTime = (uint32_t *) pVoidUserUTCTime; + + // A struct that will be populated by LMIC_getNetworkTimeReference. + // It contains the following fields: + // - tLocal: the value returned by os_GetTime() when the time + // request was sent to the gateway, and + // - tNetwork: the seconds between the GPS epoch and the time + // the gateway received the time request + lmic_time_reference_t lmicTimeReference; + + if (flagSuccess != 1) { + // Most likely the service is not provided by the gateway. No sense in trying again... + DEBUG_PRINTF_TS("didn't succeed\n"); + rtcSyncReq = false; + return; + } + + // Populate "lmic_time_reference" + flagSuccess = LMIC_getNetworkTimeReference(&lmicTimeReference); + if (flagSuccess != 1) { + DEBUG_PRINTF_TS("LMIC_getNetworkTimeReference didn't succeed\n"); + return; + } + + // Update userUTCTime, considering the difference between the GPS and UTC + // epoch, and the leap seconds + *pUserUTCTime = lmicTimeReference.tNetwork + 315964800; + + // Add the delay between the instant the time was transmitted and + // the current time + + // Current time, in ticks + ostime_t ticksNow = os_getTime(); + // Time when the request was sent, in ticks + ostime_t ticksRequestSent = lmicTimeReference.tLocal; + uint32_t requestDelaySec = osticks2ms(ticksNow - ticksRequestSent) / 1000; + *pUserUTCTime += requestDelaySec; + + // Update the system time with the time read from the network + rtc.setTime(*pUserUTCTime); + + // Save clock sync timestamp and clear flag + rtcLastClockSync = rtc.getLocalEpoch(); + rtcSyncReq = false; + DEBUG_PRINTF_TS("RTC sync completed\n"); + printDateTime(); + } + + void + cMyLoRaWAN::requestNetworkTime(void) { + LMIC_requestNetworkTime(UserRequestNetworkTimeCb, &userUTCTime); + } +#endif /****************************************************************************\ | @@ -829,6 +971,15 @@ cSensor::doUplink(void) { return; } + #if defined(GET_NETWORKTIME) + // + // Request time and date + // + if (rtcSyncReq) { + myLoRaWAN.requestNetworkTime(); + } + #endif + // Call sensor data function stubs temperature_deg_c = getTemperature(); humidity_percent = getHumidity(); @@ -850,7 +1001,7 @@ cSensor::doUplink(void) { DEBUG_PRINTF(" runtimeExpired: %d\n", runtimeExpired); DEBUG_PRINTF("\n"); - // Serialize data into byte array + // Serialize data into byte array (max. PAYLOAD_SIZE) // NOTE: // For TTN MQTT integration, ttn_decoder.js must be adjusted accordingly LoraEncoder encoder(loraData); @@ -867,7 +1018,7 @@ cSensor::doUplink(void) { this->m_fBusy = true; if (! myLoRaWAN.SendBuffer( - loraData, sizeof(loraData), + loraData, encoder.getLength(), // this is the completion function: [](void *pClientData, bool fSucccess) -> void { auto const pThis = (cSensor *)pClientData; diff --git a/library.json b/library.json index c1b7789..5258a8c 100644 --- a/library.json +++ b/library.json @@ -31,7 +31,7 @@ "authors": ["Terry Moore "] } ], - "version": "0.9.2", + "version": "0.10.0-pre2", "frameworks": "arduino", "platforms": "*" } diff --git a/library.properties b/library.properties index f871188..d1dbbab 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=MCCI Arduino LoRaWAN Library -version=0.10.0-pre1 +version=0.10.0-pre2 author=Terry Moore, ChaeHee Won maintainer=Terry Moore sentence=High-level library for LoRaWAN-based Arduino end-devices. diff --git a/src/Arduino_LoRaWAN.h b/src/Arduino_LoRaWAN.h index 3ef11db..22af5a9 100644 --- a/src/Arduino_LoRaWAN.h +++ b/src/Arduino_LoRaWAN.h @@ -34,7 +34,7 @@ Copyright notice: /// \ref ARDUINO_LORAWAN_VERSION_COMPARE_LT() to compare relative versions. /// #define ARDUINO_LORAWAN_VERSION \ - ARDUINO_LORAWAN_VERSION_CALC(0, 10, 0, 1) /* v0.10.0-pre1 */ + ARDUINO_LORAWAN_VERSION_CALC(0, 10, 0, 2) /* v0.10.0-pre2 */ #define ARDUINO_LORAWAN_VERSION_GET_MAJOR(v) \ (((v) >> 24u) & 0xFFu)