diff --git a/.gitignore b/.gitignore index aa33e14f..875ecd9f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,4 @@ .clang_complete .gcc-flags.json src/loraconf.h -src/ota.conf -src/DBtimesync.cpp -include/DBtimesync.h \ No newline at end of file +src/ota.conf \ No newline at end of file diff --git a/README.md b/README.md index aac9c1a1..9ba51050 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Depending on board hardware following features are supported: - Silicon unique ID - Battery voltage monitoring - GPS (Generic serial NMEA, or Quectel L76 I2C) -- Environmental sensor (Bosch BME680 I2C) +- Environmental sensor (Bosch BME280/BME680 I2C) - Real Time Clock (Maxim DS3231 I2C) - IF482 (serial) and DCF77 (gpio) time telegram generator @@ -86,7 +86,7 @@ If your device has a fixed DEVEUI enter this in your local loraconf.h file. Duri If your device has silicon **Unique ID** which is stored in serial EEPROM Microchip 24AA02E64 you don't need to change anything. The Unique ID will be read during startup and DEVEUI will be generated from it, overriding settings in loraconf.h. -If your device has a **real time clock** it can be updated bei either LoRaWAN network or GPS time, according to settings *TIME_SYNC_INTERVAL* and *TIME_SYNC_LORA* in paxcounter.conf. +If your device has a **real time clock** it can be updated bei either LoRaWAN network or GPS time, according to settings *TIME_SYNC_INTERVAL* and *TIME_SYNC_LORAWAN* in paxcounter.conf. # Building @@ -366,6 +366,10 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts. Device answers with it's local time/date (UTC Unix epoch) on Port 9. +0x87 set time/date + + Device synchronizes it's time/date by calling the preconfigured time source. + # License diff --git a/include/bme680mems.h b/include/bmesensor.h similarity index 94% rename from include/bme680mems.h rename to include/bmesensor.h index a97e0605..fb7d66b9 100644 --- a/include/bme680mems.h +++ b/include/bmesensor.h @@ -1,9 +1,15 @@ -#ifndef _BME680MEMS_H -#define _BME680MEMS_H +#ifndef _BMESENSOR_H +#define _BMESENSOR_H #include "globals.h" #include + +#ifdef HAS_BME680 #include "../lib/Bosch-BSEC/src/bsec.h" +#elif defined HAS_BME280 +#include +#include +#endif extern bmeStatus_t bme_status; // Make struct for storing gps data globally available diff --git a/include/cyclic.h b/include/cyclic.h index ffdee8f5..b9512c53 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -5,10 +5,12 @@ #include "senddata.h" #include "rcommand.h" #include "spislave.h" +#if(HAS_LORA) #include +#endif -#ifdef HAS_BME -#include "bme680mems.h" +#if (HAS_BME) +#include "bmesensor.h" #endif extern Ticker housekeeper; @@ -19,4 +21,4 @@ uint64_t uptime(void); void reset_counters(void); uint32_t getFreeRAM(); -#endif \ No newline at end of file +#endif diff --git a/include/globals.h b/include/globals.h index 24c83580..d88a310f 100644 --- a/include/globals.h +++ b/include/globals.h @@ -5,7 +5,7 @@ #include // Time functions -#include +#include "microTime.h" #include #include #include @@ -123,11 +123,11 @@ extern time_t userUTCTime; #include "payload.h" #include "blescan.h" -#ifdef HAS_GPS +#if(HAS_GPS) #include "gpsread.h" #endif -#ifdef HAS_LORA +#if(HAS_LORA) #include "lorawan.h" #endif @@ -147,12 +147,12 @@ extern time_t userUTCTime; #include "antenna.h" #endif -#ifdef HAS_SENSORS +#if(HAS_SENSORS) #include "sensor.h" #endif -#ifdef HAS_BME -#include "bme680mems.h" +#if (HAS_BME) +#include "bmesensor.h" #endif #endif \ No newline at end of file diff --git a/include/lorawan.h b/include/lorawan.h index 58aa362c..3c207c02 100644 --- a/include/lorawan.h +++ b/include/lorawan.h @@ -4,8 +4,8 @@ #include "globals.h" #include "rcommand.h" #include "timekeeper.h" -#if(DBTIMESYNC) -#include "DBtimesync.h" +#if(TIME_SYNC_TIMESERVER) +#include "timesync.h" #endif // LMIC-Arduino LoRaWAN Stack diff --git a/include/main.h b/include/main.h index b60ab7b9..bce12e51 100644 --- a/include/main.h +++ b/include/main.h @@ -16,6 +16,8 @@ #include "irqhandler.h" #include "led.h" #include "spislave.h" +#if(HAS_LORA) #include "lorawan.h" +#endif #include "timekeeper.h" -#endif \ No newline at end of file +#endif diff --git a/include/payload.h b/include/payload.h index 159dcfc4..e2356764 100644 --- a/include/payload.h +++ b/include/payload.h @@ -16,11 +16,12 @@ #define LPP_MSG_CHANNEL 28 #define LPP_HUMIDITY_CHANNEL 29 #define LPP_BAROMETER_CHANNEL 30 -#define LPP_AIR_CHANNEL 31 +#define LPP_AIR_CHANNEL 31 #endif -// MyDevices CayenneLPP 2.0 types for Packed Sensor Payload, not using channels, but different FPorts +// MyDevices CayenneLPP 2.0 types for Packed Sensor Payload, not using channels, +// but different FPorts #define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01m #define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed MSB #define LPP_DIGITAL_INPUT 0 // 1 byte @@ -40,6 +41,7 @@ public: void reset(void); uint8_t getSize(void); uint8_t *getBuffer(void); + void addByte(uint8_t value); void addCount(uint16_t value, uint8_t sniffytpe); void addConfig(configData_t value); void addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem, @@ -72,7 +74,7 @@ private: void writeFloat(float value); void writeUFloat(float value); void writePressure(float value); - void writeVersion(char * version); + void writeVersion(char *version); void writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f, bool g, bool h); diff --git a/include/rcommand.h b/include/rcommand.h index 9eb06384..d708827b 100644 --- a/include/rcommand.h +++ b/include/rcommand.h @@ -4,13 +4,15 @@ #include "senddata.h" #include "cyclic.h" #include "configmanager.h" +#if(HAS_LORA) #include "lorawan.h" +#endif #include "macsniff.h" #include #include "cyclic.h" #include "timekeeper.h" -#if(DBTIMESYNC) -#include "DBtimesync.h" +#if(TIME_SYNC_TIMESERVER) +#include "timesync.h" #endif // table of remote commands and assigned functions @@ -24,4 +26,4 @@ typedef struct { void rcommand(uint8_t cmd[], uint8_t cmdlength); void do_reset(); -#endif \ No newline at end of file +#endif diff --git a/include/senddata.h b/include/senddata.h index e0eb4ecf..d81b852b 100644 --- a/include/senddata.h +++ b/include/senddata.h @@ -2,7 +2,9 @@ #define _SENDDATA_H #include "spislave.h" +#if(HAS_LORA) #include "lorawan.h" +#endif #include "cyclic.h" extern Ticker sendcycler; @@ -13,4 +15,4 @@ void checkSendQueues(void); void flushQueues(); void sendcycle(void); -#endif // _SENDDATA_H_ \ No newline at end of file +#endif // _SENDDATA_H_ diff --git a/include/timekeeper.h b/include/timekeeper.h index ef83b11b..fc12e4a9 100644 --- a/include/timekeeper.h +++ b/include/timekeeper.h @@ -6,7 +6,7 @@ #include "TimeLib.h" #include "irqhandler.h" -#ifdef HAS_GPS +#if(HAS_GPS) #include "gpsread.h" #endif #ifdef HAS_IF482 diff --git a/include/timesync.h b/include/timesync.h new file mode 100644 index 00000000..e854301e --- /dev/null +++ b/include/timesync.h @@ -0,0 +1,20 @@ +#ifndef _TIME_SYNC_TIMESERVER_H +#define _TIME_SYNC_TIMESERVER_H + +#include +#include "globals.h" +#include "timesync.h" +#include "timekeeper.h" + +#define TIME_SYNC_SAMPLES 5 // number of time requests for averaging +#define TIME_SYNC_CYCLE 60 // seconds between two time requests +#define TIME_SYNC_TIMEOUT 120 // timeout seconds waiting for timeserver answer +#define TIME_SYNC_TRIGGER 100 // time deviation in millisec triggering a sync +#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length + +void send_timesync_req(void); +int recv_timesync_ans(uint8_t buf[], uint8_t buf_len); +void process_timesync_req(void *taskparameter); +void store_time_sync_req(time_t t_millisec); + +#endif \ No newline at end of file diff --git a/lib/microTime/src/Time.cpp b/lib/microTime/src/microTime.cpp similarity index 100% rename from lib/microTime/src/Time.cpp rename to lib/microTime/src/microTime.cpp diff --git a/lib/microTime/src/Time.h b/lib/microTime/src/microTime.h similarity index 100% rename from lib/microTime/src/Time.h rename to lib/microTime/src/microTime.h diff --git a/platformio.ini b/platformio.ini index 47ca6e73..74f6787b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,10 +30,10 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.7.36 +release_version = 1.7.38 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose -debug_level = 3 +debug_level = 4 ; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA upload_protocol = esptool ;upload_protocol = custom @@ -53,9 +53,11 @@ lib_deps_gps = TinyGPSPlus@>=1.0.2 lib_deps_rtc = RTC@^2.3.0 +lib_deps_sensors = + Adafruit Unified Sensor@^1.0.3 + Adafruit BME280 Library@1.0.8 lib_deps_basic = ArduinoJson@^5.13.1 - Time@>=1.5 Timezone@^1.2.2 lib_deps_all = ${common.lib_deps_basic} @@ -218,7 +220,7 @@ lib_deps = ${common.lib_deps_lora} ${common.lib_deps_display} ${common.lib_deps_rtc} -build_flags = +build_flags = ${common.build_flags_basic} upload_protocol = ${common.upload_protocol} extra_scripts = ${common.extra_scripts} diff --git a/src/TTN/Nodered-Timeserver.json b/src/TTN/Nodered-Timeserver.json new file mode 100644 index 00000000..f2aac01c --- /dev/null +++ b/src/TTN/Nodered-Timeserver.json @@ -0,0 +1,249 @@ +[ + { + "id": "49e3c067.e782e", + "type": "change", + "z": "449c1517.e25f4c", + "name": "Payload", + "rules": [ + { + "t": "change", + "p": "topic", + "pt": "msg", + "from": "up", + "fromt": "str", + "to": "down", + "tot": "str" + }, + { + "t": "move", + "p": "payload", + "pt": "msg", + "to": "payload.payload_raw", + "tot": "msg" + }, + { + "t": "set", + "p": "payload.port", + "pt": "msg", + "to": "9", + "tot": "num" + }, + { + "t": "set", + "p": "payload.confirmed", + "pt": "msg", + "to": "false", + "tot": "bool" + }, + { + "t": "set", + "p": "payload.schedule", + "pt": "msg", + "to": "replace", + "tot": "str" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 220, + "y": 360, + "wires": [ + [ + "84f1cda2.069e7" + ] + ] + }, + { + "id": "cc140589.dea168", + "type": "mqtt in", + "z": "449c1517.e25f4c", + "name": "listen", + "topic": "+/devices/+/up", + "qos": "2", + "broker": "2a15ab6f.ab2244", + "x": 70, + "y": 120, + "wires": [ + [ + "9f4b8dd3.2f0d2" + ] + ] + }, + { + "id": "72d5e7ee.d1eba8", + "type": "mqtt out", + "z": "449c1517.e25f4c", + "name": "send", + "topic": "", + "qos": "", + "retain": "", + "broker": "2a15ab6f.ab2244", + "x": 690, + "y": 360, + "wires": [] + }, + { + "id": "4f97d75.6c87528", + "type": "json", + "z": "449c1517.e25f4c", + "name": "Convert", + "property": "payload", + "action": "", + "pretty": false, + "x": 220, + "y": 200, + "wires": [ + [ + "8ed813a9.a9319" + ] + ] + }, + { + "id": "9f4b8dd3.2f0d2", + "type": "switch", + "z": "449c1517.e25f4c", + "name": "Timeport", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "cont", + "v": "\"port\":9", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 220, + "y": 120, + "wires": [ + [ + "4f97d75.6c87528" + ] + ] + }, + { + "id": "f4c5b6de.f95148", + "type": "function", + "z": "449c1517.e25f4c", + "name": "Time_Sync_Ans", + "func": "/* LoRaWAN Timeserver\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n0 sequence number (taken from node's time_sync_req)\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\n*/\n\n let buf = new ArrayBuffer(6);\n let timestamp = (+new Date(msg.payload.metadata.gateways[0].time));\n \n var seconds = Math.floor(timestamp/1000);\n var fractions = (timestamp % 1000) / 4;\n var seqno = msg.payload.payload_raw[0];\n\n new DataView(buf).setUint8(0, seqno);\n new DataView(buf).setUint32(1, seconds);\n new DataView(buf).setUint8(5, fractions);\n\n msg.payload = new Buffer(new Uint8Array(buf));\n \n return msg;", + "outputs": 1, + "noerr": 0, + "x": 400, + "y": 280, + "wires": [ + [ + "49e3c067.e782e" + ] + ] + }, + { + "id": "dac8aafa.389298", + "type": "json", + "z": "449c1517.e25f4c", + "name": "Convert", + "property": "payload", + "action": "", + "pretty": false, + "x": 540, + "y": 360, + "wires": [ + [ + "72d5e7ee.d1eba8" + ] + ] + }, + { + "id": "8ed813a9.a9319", + "type": "base64", + "z": "449c1517.e25f4c", + "name": "Decode", + "action": "", + "property": "payload.payload_raw", + "x": 380, + "y": 200, + "wires": [ + [ + "f868bce2.dde67" + ] + ] + }, + { + "id": "84f1cda2.069e7", + "type": "base64", + "z": "449c1517.e25f4c", + "name": "Encode", + "action": "", + "property": "payload.payload_raw", + "x": 380, + "y": 360, + "wires": [ + [ + "dac8aafa.389298" + ] + ] + }, + { + "id": "6190967b.01f758", + "type": "comment", + "z": "449c1517.e25f4c", + "name": "LoRaWAN Timeserver", + "info": "PLEASE NOTE: There is a patent filed for the time sync algorithm used in the\ncode of this file. The shown implementation example is covered by the\nrepository's licencse, but you may not be eligible to deploy the applied\nalgorithm in applications without granted license by the patent holder.", + "x": 120, + "y": 40, + "wires": [] + }, + { + "id": "f868bce2.dde67", + "type": "switch", + "z": "449c1517.e25f4c", + "name": "Timechecker", + "property": "payload.metadata.gateways[0].time", + "propertyType": "msg", + "rules": [ + { + "t": "lte", + "v": "payload.metadata.time", + "vt": "msg" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 550, + "y": 200, + "wires": [ + [ + "f4c5b6de.f95148" + ] + ] + }, + { + "id": "2a15ab6f.ab2244", + "type": "mqtt-broker", + "z": "", + "name": "eu.thethings.network:1883", + "broker": "eu.thethings.network", + "port": "1883", + "tls": "", + "clientid": "", + "usetls": false, + "compatmode": true, + "keepalive": "60", + "cleansession": true, + "birthTopic": "", + "birthQos": "0", + "birthPayload": "", + "closeTopic": "", + "closeQos": "0", + "closePayload": "", + "willTopic": "", + "willQos": "0", + "willPayload": "" + } +] \ No newline at end of file diff --git a/src/TTN/packed_decoder.js b/src/TTN/packed_decoder.js index a99f44c6..49f1b5b9 100644 --- a/src/TTN/packed_decoder.js +++ b/src/TTN/packed_decoder.js @@ -63,6 +63,14 @@ function Decoder(bytes, port) { return decode(bytes, [uint16], ['voltage']); } + if (port === 9) { + // timesync request + if (bytes.length === 1) { + decoded.timesync_seqno = bytes[0]; + } + return decoded; + } + } diff --git a/src/TTN/plain_decoder.js b/src/TTN/plain_decoder.js index ca43a5e4..62249d08 100644 --- a/src/TTN/plain_decoder.js +++ b/src/TTN/plain_decoder.js @@ -67,6 +67,12 @@ function Decoder(bytes, port) { decoded.battery = (bytes[i++] << 8) | bytes[i++];} } + if (port === 9) { + if (bytes.length === 1) { + decoded.timesync_seqno = bytes[0]; + } + } + return decoded; } diff --git a/src/bme680mems.cpp b/src/bmesensor.cpp similarity index 78% rename from src/bme680mems.cpp rename to src/bmesensor.cpp index d0679507..19111d32 100644 --- a/src/bme680mems.cpp +++ b/src/bmesensor.cpp @@ -1,6 +1,6 @@ -#ifdef HAS_BME +#if (HAS_BME) -#include "bme680mems.h" +#include "bmesensor.h" // Local logging tag static const char TAG[] = __FILE__; @@ -8,6 +8,9 @@ static const char TAG[] = __FILE__; bmeStatus_t bme_status; TaskHandle_t BmeTask; +#define SEALEVELPRESSURE_HPA (1013.25) + +#ifdef HAS_BME680 bsec_virtual_sensor_t sensorList[10] = { BSEC_OUTPUT_RAW_TEMPERATURE, BSEC_OUTPUT_RAW_PRESSURE, @@ -26,16 +29,25 @@ uint16_t stateUpdateCounter = 0; Bsec iaqSensor; +#elif defined HAS_BME280 + +Adafruit_BME280 bme; // I2C + // Adafruit_BME280 bme(BME_CS); // hardware SPI +// Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI + +#endif + // initialize BME680 sensor int bme_init(void) { // return = 0 -> error / return = 1 -> success +#ifdef HAS_BME680 // block i2c bus access if (I2C_MUTEX_LOCK()) { - Wire.begin(HAS_BME); - iaqSensor.begin(BME_ADDR, Wire); + Wire.begin(HAS_BME680); + iaqSensor.begin(BME680_ADDR, Wire); ESP_LOGI(TAG, "BSEC v%d.%d.%d.%d", iaqSensor.version.major, iaqSensor.version.minor, iaqSensor.version.major_bugfix, @@ -66,6 +78,26 @@ int bme_init(void) { goto error; } +#elif defined HAS_BME280 + + bool status; + // return = 0 -> error / return = 1 -> success + + // block i2c bus access + if (I2C_MUTEX_LOCK()) { + status = bme.begin(BME280_ADDR); + if (!status) { + ESP_LOGE(TAG, "BME280 sensor not found"); + goto error; + } + ESP_LOGI(TAG, "BME280 sensor found and initialized"); + } else { + ESP_LOGE(TAG, "I2c bus busy - BME280 initialization error"); + goto error; + } + +#endif + I2C_MUTEX_UNLOCK(); // release i2c bus access return 1; @@ -75,6 +107,8 @@ error: } // bme_init() +#ifdef HAS_BME680 + // Helper function definitions int checkIaqSensorStatus(void) { int rslt = 1; // true = 1 = no error, false = 0 = error @@ -97,13 +131,14 @@ int checkIaqSensorStatus(void) { return rslt; } // checkIaqSensorStatus() +#endif // loop function which reads and processes data based on sensor settings void bme_loop(void *pvParameters) { configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check -#ifdef HAS_BME +#ifdef HAS_BME680 while (1) { // block i2c bus access if (I2C_MUTEX_LOCK()) { @@ -122,12 +157,24 @@ void bme_loop(void *pvParameters) { I2C_MUTEX_UNLOCK(); } } +#elif defined HAS_BME280 + while (1) { + if (I2C_MUTEX_LOCK()) { + bme_status.temperature = bme.readTemperature(); + bme_status.pressure = + (bme.readPressure() / 100.0); // conversion Pa -> hPa + // bme.readAltitude(SEALEVELPRESSURE_HPA); + bme_status.humidity = bme.readHumidity(); + I2C_MUTEX_UNLOCK(); + } + } #endif ESP_LOGE(TAG, "BME task ended"); vTaskDelete(BmeTask); // should never be reached } // bme_loop() +#ifdef HAS_BME680 void loadState(void) { if (cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE] == BSEC_MAX_STATE_BLOB_SIZE) { // Existing state in NVS stored @@ -167,5 +214,6 @@ void updateState(void) { saveConfig(); } } +#endif #endif // HAS_BME \ No newline at end of file diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 8899d05b..a2d9a579 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -24,7 +24,7 @@ void doHousekeeping() { #ifdef HAS_SPI spi_housekeeping(); #endif -#ifdef HAS_LORA +#if(HAS_LORA) lora_housekeeping(); #endif @@ -32,11 +32,11 @@ void doHousekeeping() { ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(irqHandlerTask), eTaskGetState(irqHandlerTask)); -#ifdef HAS_GPS +#if(HAS_GPS) ESP_LOGD(TAG, "Gpsloop %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(GpsTask), eTaskGetState(GpsTask)); #endif -#ifdef HAS_BME +#if (HAS_BME) ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask)); #endif @@ -58,11 +58,17 @@ void doHousekeeping() { #endif // display BME sensor data -#ifdef HAS_BME +#ifdef HAS_BME680 ESP_LOGI(TAG, "BME680 Temp: %.2f°C | IAQ: %.2f | IAQacc: %d", bme_status.temperature, bme_status.iaq, bme_status.iaq_accuracy); #endif +// display BME280 sensor data +#ifdef HAS_BME280 + ESP_LOGI(TAG, "BME680 Temp: %.2f°C | Humidity: %.2f | Pressure: %.0f", + bme_status.temperature, bme_status.humidity, bme_status.pressure); +#endif + // check free heap memory if (ESP.getMinFreeHeap() <= MEM_LOW) { ESP_LOGI(TAG, diff --git a/src/display.cpp b/src/display.cpp index 80d206e7..88bea2cc 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -105,7 +105,7 @@ void init_display(const char *Productname, const char *Version) { u8x8.print(" v"); u8x8.println(PROGVERSION); -#ifdef HAS_LORA +#if(HAS_LORA) u8x8.println("DEVEUI:"); os_getDevEui((u1_t *)buf); DisplayKey(buf, 8, true); @@ -163,7 +163,7 @@ void refreshtheDisplay() { #endif // update GPS status (line 2) -#ifdef HAS_GPS +#if(HAS_GPS) // have we ever got valid gps data? if (gps.passedChecksum() > 0) { u8x8.setCursor(9, 2); @@ -186,7 +186,7 @@ void refreshtheDisplay() { u8x8.printf("%s", "BLTH:off"); #endif -#ifdef HAS_LORA +#if(HAS_LORA) u8x8.setCursor(11, 3); u8x8.printf("SF:"); if (cfg.adrmode) // if ADR=on then display SF value inverse @@ -209,7 +209,7 @@ void refreshtheDisplay() { u8x8.setCursor(10, 5); u8x8.printf("%4dKB", getFreeRAM() / 1024); -#ifdef HAS_LORA +#if(HAS_LORA) u8x8.setCursor(0, 6); #if (!defined HAS_DCF77) && (!defined HAS_IF482) // update LoRa status display (line 6) diff --git a/src/gpsread.cpp b/src/gpsread.cpp index 59739bf4..27cc0eec 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -1,4 +1,4 @@ -#ifdef HAS_GPS +#if(HAS_GPS) #include "globals.h" diff --git a/src/hal/generic.h b/src/hal/generic.h index 0ca56b04..8cd00c11 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -18,8 +18,12 @@ // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL -#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND +#define HAS_BME 1 // Enable BME sensors in general +#define HAS_BME680 GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND +// BME280 sensor on I2C bus +//#define HAS_BME 1 // Enable BME sensors in general +//#define HAS_BME280 GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL // user defined sensors //#define HAS_SENSORS 1 // comment out if device has user defined sensors diff --git a/src/hal/heltec.h b/src/hal/heltec.h index a4465500..7351d36a 100644 --- a/src/hal/heltec.h +++ b/src/hal/heltec.h @@ -1,30 +1,32 @@ -// clang-format off - -#ifndef _HELTEC_H -#define _HELTEC_H - -#include - -// Hardware related definitions for Heltec V1 LoRa-32 Board - -//#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL -//#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND - -#define HAS_LORA 1 // comment out if device shall not send data via LoRa -#define CFG_sx1276_radio 1 - -#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board -#define HAS_LED LED_BUILTIN // white LED on board -#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board - -// Pins for I2C interface of OLED Display -#define MY_OLED_SDA (4) -#define MY_OLED_SCL (15) -#define MY_OLED_RST (16) - -// Pins for LORA chip SPI interface come from board file, we need some -// additional definitions for LMIC -#define LORA_IO1 (33) -#define LORA_IO2 LMIC_UNUSED_PIN - -#endif + +// clang-format off + +#ifndef _HELTEC_H +#define _HELTEC_H + +#include + +// Hardware related definitions for Heltec V1 LoRa-32 Board + +//#define HAS_BME 1 // Enable BME sensors in general +//#define HAS_BME680 GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND + +#define HAS_LORA 1 // comment out if device shall not send data via LoRa +#define CFG_sx1276_radio 1 + +#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board +#define HAS_LED LED_BUILTIN // white LED on board +#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board + +// Pins for I2C interface of OLED Display +#define MY_OLED_SDA (4) +#define MY_OLED_SCL (15) +#define MY_OLED_RST (16) + +// Pins for LORA chip SPI interface come from board file, we need some +// additional definitions for LMIC +#define LORA_IO1 (33) +#define LORA_IO2 (32) + +#endif \ No newline at end of file diff --git a/src/hal/heltecv2.h b/src/hal/heltecv2.h index 7cb9145c..82ecebc1 100644 --- a/src/hal/heltecv2.h +++ b/src/hal/heltecv2.h @@ -7,8 +7,9 @@ // Hardware related definitions for Heltec V2 LoRa-32 Board -//#define HAS_BME GPIO_NUM_4, GPIO_NUM_15 // SDA, SCL -//#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND +//#define HAS_BME 1 // Enable BME sensors in general +//#define HAS_BME680 GPIO_NUM_4, GPIO_NUM_15 // SDA, SCL +//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND #define HAS_LORA 1 // comment out if device shall not send data via LoRa #define CFG_sx1276_radio 1 @@ -17,6 +18,10 @@ #define HAS_LED LED_BUILTIN // white LED on board #define HAS_BUTTON KEY_BUILTIN // button "PROG" on board +//#define HAS_BATTERY_PROBE ADC2_GPIO13_CHANNEL // battery probe GPIO pin +#define BATT_FACTOR 4 // voltage divider 220k/100k on board +#define HAS_LOWPOWER_SWITCH GPIO_NUM_21 // switches battery power + // Pins for I2C interface of OLED Display #define MY_OLED_SDA (4) #define MY_OLED_SCL (15) @@ -27,4 +32,4 @@ #define LORA_IO1 (35) #define LORA_IO2 (34) -#endif +#endif \ No newline at end of file diff --git a/src/hal/lolin32lite.h b/src/hal/lolin32lite.h index 3995fac7..70dfbcdf 100644 --- a/src/hal/lolin32lite.h +++ b/src/hal/lolin32lite.h @@ -7,8 +7,9 @@ // Hardware related definitions for lolin32lite (without LoRa shield) +#define HAS_LORA 0 // no LoRa module #define CFG_sx1272_radio 1 // dummy #define HAS_LED LED_BUILTIN // on board LED on GPIO5 #define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW -#endif \ No newline at end of file +#endif diff --git a/src/hal/octopus32.h b/src/hal/octopus32.h index 288c286b..ad4fbb07 100644 --- a/src/hal/octopus32.h +++ b/src/hal/octopus32.h @@ -12,8 +12,9 @@ #define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature // Octopus32 has a pre-populated BME680 on i2c addr 0x76 -#define HAS_BME GPIO_NUM_23, GPIO_NUM_22 // SDA, SCL -#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND +#define HAS_BME 1 // Enable BME sensors in general +#define HAS_BME680 GPIO_NUM_23, GPIO_NUM_22 // SDA, SCL +#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND // user defined sensors //#define HAS_SENSORS 1 // comment out if device has user defined sensors diff --git a/src/hal/ttgobeam.h b/src/hal/ttgobeam.h index d86acd18..dcce95ad 100644 --- a/src/hal/ttgobeam.h +++ b/src/hal/ttgobeam.h @@ -31,8 +31,9 @@ // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -#define HAS_BME SDA, SCL -#define BME_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! +#define HAS_BME 1 // Enable BME sensors in general +#define HAS_BME680 SDA, SCL +#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! // display (if connected) #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C diff --git a/src/hal/ttgov21new.h b/src/hal/ttgov21new.h index f2cf0571..8852d2f2 100644 --- a/src/hal/ttgov21new.h +++ b/src/hal/ttgov21new.h @@ -13,6 +13,11 @@ #define HAS_LORA 1 // comment out if device shall not send data via LoRa #define CFG_sx1276_radio 1 // HPD13A LoRa SoC +// enable only if device has these sensors, otherwise comment these lines +// BME280 sensor on I2C bus +//#define HAS_BME 1 // Enable BME sensors in general +//#define HAS_BME280 GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL + #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C #define HAS_LED (25) // green on board LED #define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7 diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index 0757430b..bbfdafca 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -33,10 +33,13 @@ void irqHandler(void *pvParameters) { if (InterruptStatus & CYCLIC_IRQ) doHousekeeping(); -#ifdef TIME_SYNC_INTERVAL +#if (TIME_SYNC_INTERVAL) // is time to be synced? - if (InterruptStatus & TIMESYNC_IRQ) - setTime(timeProvider()); + if (InterruptStatus & TIMESYNC_IRQ) { + time_t t = timeProvider(); + if (timeIsValid(t)) + setTime(t); + } #endif // is time to send the payload? diff --git a/src/led.cpp b/src/led.cpp index a52315c5..edaa135a 100644 --- a/src/led.cpp +++ b/src/led.cpp @@ -137,7 +137,7 @@ void ledLoop(void *parameter) { // No custom blink, check LoRaWAN state } else { -#ifdef HAS_LORA +#if(HAS_LORA) // LED indicators for viusalizing LoRaWAN state if (LMIC.opmode & (OP_JOINING | OP_REJOIN)) { LEDColor = COLOR_YELLOW; diff --git a/src/loraconf.sample.h b/src/loraconf.sample.h index 3ad10a0d..5e60c0a5 100644 --- a/src/loraconf.sample.h +++ b/src/loraconf.sample.h @@ -1,4 +1,4 @@ -#ifdef HAS_LORA +#if(HAS_LORA) /************************************************************ * LMIC LoRaWAN configuration diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 04fb0fb6..3d79cf3b 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -1,10 +1,12 @@ // Basic Config +#if (HAS_LORA) #include "lorawan.h" +#endif // Local logging Tag static const char TAG[] = "lora"; -#ifdef HAS_LORA +#if (HAS_LORA) #if CLOCK_ERROR_PROCENTAGE > 7 #warning CLOCK_ERROR_PROCENTAGE value in lmic_config.h is too high; values > 7 will cause side effects @@ -155,7 +157,7 @@ void get_hard_deveui(uint8_t *pdeveui) { #endif // MCP 24AA02E64 } -#if(VERBOSE) +#if (VERBOSE) // Display OTAA keys void showLoraKeys(void) { @@ -175,6 +177,8 @@ void showLoraKeys(void) { void onEvent(ev_t ev) { char buff[24] = ""; + uint32_t now_micros = 0; + switch (ev) { case EV_SCAN_TIMEOUT: @@ -225,24 +229,42 @@ void onEvent(ev_t ev) { case EV_TXCOMPLETE: -#if(DBTIMESYNC) - if (!(LMIC.txrxFlags & TXRX_ACK) && time_sync_seqNo) - time_sync_messages[time_sync_seqNo - 1] = LMIC.txend; +#if (TIME_SYNC_TIMESERVER) + // if last packet sent was a timesync request, store TX timestamp + if (LMIC.pendTxPort == TIMEPORT) { + // store_time_sync_req(now(now_micros), now_micros); + // adjust sampled OS time back in time to the nearest second boundary + //const ostime_t tAdjust = LMIC.netDeviceTimeFrac * ms2osticks(1000) / 256; + //store_time_sync_req(osticks2ms(LMIC.txend - tAdjust)); // milliseconds + store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds + } #endif strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED_ACK") : PSTR("TX_COMPLETE")); sprintf(display_line6, " "); // clear previous lmic status - if (LMIC.dataLen) { - ESP_LOGI(TAG, "Received %d bytes of payload, RSSI -%d SNR %d", + if (LMIC.dataLen) { // did we receive data -> display info + ESP_LOGI(TAG, "Received %d bytes of payload, RSSI %d SNR %d", LMIC.dataLen, LMIC.rssi, LMIC.snr / 4); - sprintf(display_line6, "RSSI -%d SNR %d", LMIC.rssi, LMIC.snr / 4); + sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4); - // check if command is received on command port, then call interpreter - if ((LMIC.txrxFlags & TXRX_PORT) && - (LMIC.frame[LMIC.dataBeg - 1] == RCMDPORT)) - rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); + if (LMIC.txrxFlags & TXRX_PORT) { // FPort -> use to switch + switch (LMIC.frame[LMIC.dataBeg - 1]) { +#if (TIME_SYNC_TIMESERVER) + case TIMEPORT: // timesync answer -> call timesync processor + recv_timesync_ans(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); + break; +#endif + case RCMDPORT: // opcode -> call rcommand interpreter + rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); + break; + default: // unknown port -> display info + ESP_LOGI(TAG, "Received data on unsupported port #%d", + LMIC.frame[LMIC.dataBeg - 1]); + break; + } + } } break; @@ -385,15 +407,15 @@ esp_err_t lora_stack_init() { // in src/lmic_config.h if you are limited on battery. LMIC_setClockError(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100); // Set the data rate to Spreading Factor 7. This is the fastest supported - // rate for 125 kHz channels, and it minimizes air time and battery power. Set - // the transmission power to 14 dBi (25 mW). + // rate for 125 kHz channels, and it minimizes air time and battery power. + // Set the transmission power to 14 dBi (25 mW). LMIC_setDrTxpow(DR_SF7, 14); #if defined(CFG_US915) || defined(CFG_au921) - // in the US, with TTN, it saves join time if we start on subband 1 (channels - // 8-15). This will get overridden after the join by parameters from the - // network. If working with other networks or in other regions, this will need - // to be changed. + // in the US, with TTN, it saves join time if we start on subband 1 + // (channels 8-15). This will get overridden after the join by parameters + // from the network. If working with other networks or in other regions, + // this will need to be changed. LMIC_selectSubBand(1); #endif @@ -417,11 +439,8 @@ void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0); break; } - if (ret == pdTRUE) { - ESP_LOGI(TAG, "%d bytes enqueued for LORA interface", message->MessageSize); - } else { + if (ret != pdTRUE) ESP_LOGW(TAG, "LORA sendqueue is full"); - } } void lora_queuereset(void) { xQueueReset(LoraSendQueue); } @@ -457,8 +476,7 @@ void user_request_network_time_callback(void *pVoidUserUTCTime, } // Update userUTCTime, considering the difference between the GPS and UTC - // time, and the leap seconds - // !!! DANGER !!! This code will expire in next year with leap second + // time, and the leap seconds until year 2019 *pUserUTCTime = lmicTimeReference.tNetwork + 315964800; // Current time, in ticks ostime_t ticksNow = os_getTime(); @@ -479,4 +497,4 @@ void user_request_network_time_callback(void *pVoidUserUTCTime, ESP_LOGI(TAG, "Invalid time received from LoRa"); } // user_request_network_time_callback -#endif // HAS_LORA \ No newline at end of file +#endif // HAS_LORA diff --git a/src/main.cpp b/src/main.cpp index a9a54669..ced48264 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,8 +47,10 @@ Tasks using i2c bus all must have same priority, because using mutex semaphore // ESP32 hardware timers ------------------------------------------------------------------------------- - 0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS in -paxcounter.conf) 1 ppsIRQ -> pps clock irq -> 1sec 2 unused 3 unused +0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS in paxcounter.conf) +1 ppsIRQ -> pps clock irq -> 1sec +2 unused +3 unused // Interrupt routines @@ -121,7 +123,7 @@ void setup() { #endif // setup debug output or silence device -#if(VERBOSE) +#if (VERBOSE) Serial.begin(115200); esp_log_level_set("*", ESP_LOG_VERBOSE); #else @@ -132,7 +134,7 @@ void setup() { ESP_LOGI(TAG, "Starting %s v%s", PRODUCTNAME, PROGVERSION); // print chip information on startup if in verbose mode -#if(VERBOSE) +#if (VERBOSE) esp_chip_info_t chip_info; esp_chip_info(&chip_info); ESP_LOGI(TAG, @@ -155,7 +157,7 @@ void setup() { ESP.getFlashChipSpeed()); ESP_LOGI(TAG, "Wifi/BT software coexist version %s", esp_coex_version_get()); -#ifdef HAS_LORA +#if(HAS_LORA) ESP_LOGI(TAG, "IBM LMIC version %d.%d.%d", LMIC_VERSION_MAJOR, LMIC_VERSION_MINOR, LMIC_VERSION_BUILD); ESP_LOGI(TAG, "Arduino LMIC version %d.%d.%d.%d", @@ -167,7 +169,7 @@ void setup() { showLoraKeys(); #endif // HAS_LORA -#ifdef HAS_GPS +#if(HAS_GPS) ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion()); #endif @@ -227,7 +229,7 @@ void setup() { batt_voltage = read_voltage(); #endif -#if(USE_OTA) +#if (USE_OTA) strcat_P(features, " OTA"); // reboot to firmware update mode if ota trigger switch is set if (cfg.runmode == 1) { @@ -239,7 +241,7 @@ void setup() { // start BLE scan callback if BLE function is enabled in NVRAM configuration // or switch off bluetooth, if not compiled -#if(BLECOUNTER) +#if (BLECOUNTER) strcat_P(features, " BLE"); if (cfg.blescan) { ESP_LOGI(TAG, "Starting Bluetooth..."); @@ -269,7 +271,7 @@ void setup() { #endif // HAS_BUTTON // initialize gps -#ifdef HAS_GPS +#if(HAS_GPS) strcat_P(features, " GPS"); if (gps_init()) { ESP_LOGI(TAG, "Starting GPS Feed..."); @@ -284,13 +286,13 @@ void setup() { #endif // initialize sensors -#ifdef HAS_SENSORS +#if(HAS_SENSORS) strcat_P(features, " SENS"); sensor_init(); #endif // initialize LoRa -#ifdef HAS_LORA +#if(HAS_LORA) strcat_P(features, " LORA"); assert(lora_stack_init() == ESP_OK); #endif @@ -301,7 +303,7 @@ void setup() { assert(spi_init() == ESP_OK); #endif -#if(VENDORFILTER) +#if (VENDORFILTER) strcat_P(features, " OUIFLT"); #endif @@ -358,9 +360,13 @@ void setup() { &irqHandlerTask, // task handle 1); // CPU core -// initialize bme -#ifdef HAS_BME - strcat_P(features, " BME"); +// initialize BME sensor (BME280/BME680) +#if (HAS_BME) +#ifdef HAS_BME680 + strcat_P(features, " BME680"); +#elif defined HAS_BME280 + strcat_P(features, " BME280"); +#endif if (bme_init()) { ESP_LOGI(TAG, "Starting BME sensor..."); xTaskCreatePinnedToCore(bme_loop, // task function @@ -400,7 +406,7 @@ void setup() { #endif #endif // HAS_BUTTON -#ifdef TIME_SYNC_INTERVAL +#if (TIME_SYNC_INTERVAL) // start pps timepulse ESP_LOGI(TAG, "Starting Timekeeper..."); assert(timepulse_init()); // setup timepulse @@ -410,7 +416,7 @@ void setup() { #endif #if defined HAS_IF482 || defined HAS_DCF77 -#ifndef TIME_SYNC_INTERVAL +#if (!TIME_SYNC_INTERVAL) #error for clock controller function TIME_SNYC_INTERVAL must be defined in paxcounter.conf #endif ESP_LOGI(TAG, "Starting Clock Controller..."); @@ -421,7 +427,7 @@ void setup() { void loop() { while (1) { -#ifdef HAS_LORA +#if(HAS_LORA) os_runloop_once(); // execute lmic scheduled jobs and events #endif delay(2); // yield to CPU diff --git a/src/paxcounter.conf b/src/paxcounter.conf index df834f6d..46d392ae 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -66,9 +66,9 @@ #define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds] // settings for syncing time of node with external time source -#define TIME_SYNC_INTERVAL 2 // sync time attempt each .. minutes from time source (GPS/LORA/RTC) [default = 60], comment out means off -#define TIME_SYNC_LORA 0 // set to 1 to use LORA network as time source, 0 means off [default = off] -#define DBTIMESYNC 0 // set to 1 to use DB LORA timeserver with patented sync algorithm [default = off] +#define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source (GPS/LORA/RTC) [default = 60], 0 means off +#define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 0] +#define TIME_SYNC_TIMESERVER 1 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0] // time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino #define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time diff --git a/src/payload.cpp b/src/payload.cpp index 45d7e6e3..20e34a05 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -18,6 +18,8 @@ uint8_t *PayloadConvert::getBuffer(void) { return buffer; } #if PAYLOAD_ENCODER == 1 +void PayloadConvert::addByte(uint8_t value) { buffer[cursor++] = (value); } + void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) { buffer[cursor++] = highByte(value); buffer[cursor++] = lowByte(value); @@ -78,7 +80,7 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float cputemp, } void PayloadConvert::addGPS(gpsStatus_t value) { -#ifdef HAS_GPS +#if(HAS_GPS) buffer[cursor++] = (byte)((value.latitude & 0xFF000000) >> 24); buffer[cursor++] = (byte)((value.latitude & 0x00FF0000) >> 16); buffer[cursor++] = (byte)((value.latitude & 0x0000FF00) >> 8); @@ -96,7 +98,7 @@ void PayloadConvert::addGPS(gpsStatus_t value) { } void PayloadConvert::addSensor(uint8_t buf[]) { -#ifdef HAS_SENSORS +#if(HAS_SENSORS) uint8_t length = buf[0]; memcpy(buffer, buf + 1, length); cursor += length; // length of buffer @@ -104,7 +106,7 @@ void PayloadConvert::addSensor(uint8_t buf[]) { } void PayloadConvert::addBME(bmeStatus_t value) { -#ifdef HAS_BME +#if(HAS_BME) int16_t temperature = (int16_t)(value.temperature); // float -> int uint16_t humidity = (uint16_t)(value.humidity); // float -> int uint16_t pressure = (uint16_t)(value.pressure); // float -> int @@ -141,6 +143,8 @@ void PayloadConvert::addTime(time_t value) { #elif PAYLOAD_ENCODER == 2 +void PayloadConvert::addByte(uint8_t value) { writeUint8(value); } + void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) { writeUint16(value); } @@ -187,7 +191,7 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float cputemp, } void PayloadConvert::addGPS(gpsStatus_t value) { -#ifdef HAS_GPS +#if(HAS_GPS) writeLatLng(value.latitude, value.longitude); writeUint8(value.satellites); writeUint16(value.hdop); @@ -196,7 +200,7 @@ void PayloadConvert::addGPS(gpsStatus_t value) { } void PayloadConvert::addSensor(uint8_t buf[]) { -#ifdef HAS_SENSORS +#if(HAS_SENSORS) uint8_t length = buf[0]; memcpy(buffer, buf + 1, length); cursor += length; // length of buffer @@ -204,7 +208,7 @@ void PayloadConvert::addSensor(uint8_t buf[]) { } void PayloadConvert::addBME(bmeStatus_t value) { -#ifdef HAS_BME +#if(HAS_BME) writeFloat(value.temperature); writePressure(value.pressure); writeUFloat(value.humidity); @@ -299,6 +303,11 @@ void PayloadConvert::writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f, #elif (PAYLOAD_ENCODER == 3 || PAYLOAD_ENCODER == 4) +void PayloadConvert::addByte(uint8_t value) { + /* + not implemented + */ } + void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) { switch (snifftype) { case MAC_SNIFF_WIFI: @@ -375,7 +384,7 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float celsius, } void PayloadConvert::addGPS(gpsStatus_t value) { -#ifdef HAS_GPS +#if(HAS_GPS) int32_t lat = value.latitude / 100; int32_t lon = value.longitude / 100; int32_t alt = value.altitude * 100; @@ -396,7 +405,7 @@ void PayloadConvert::addGPS(gpsStatus_t value) { } void PayloadConvert::addSensor(uint8_t buf[]) { -#ifdef HAS_SENSORS +#if(HAS_SENSORS) // to come /* uint8_t length = buf[0]; @@ -407,7 +416,7 @@ void PayloadConvert::addSensor(uint8_t buf[]) { } void PayloadConvert::addBME(bmeStatus_t value) { -#ifdef HAS_BME +#if(HAS_BME) // data value conversions to meet cayenne data type definition // 0.1°C per bit => -3276,7 .. +3276,7 °C diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 34054bc2..e0b8637f 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -8,7 +8,9 @@ static const char TAG[] = __FILE__; // helper function void do_reset() { ESP_LOGI(TAG, "Remote command: restart device"); +#if(HAS_LORA) LMIC_shutdown(); +#endif delay(3000); esp_restart(); } @@ -38,7 +40,7 @@ void set_reset(uint8_t val[]) { break; case 9: // reset and ask for software update via Wifi OTA ESP_LOGI(TAG, "Remote command: software update via Wifi"); -#if(USE_OTA) +#if (USE_OTA) sprintf(display_line6, "Software update"); cfg.runmode = 1; #else @@ -130,7 +132,7 @@ void set_gps(uint8_t val[]) { } void set_sensor(uint8_t val[]) { -#ifdef HAS_SENSORS +#if(HAS_SENSORS) switch (val[0]) { // check if valid sensor number 1...4 case 1: case 2: @@ -168,7 +170,7 @@ void set_monitor(uint8_t val[]) { } void set_lorasf(uint8_t val[]) { -#ifdef HAS_LORA +#if(HAS_LORA) ESP_LOGI(TAG, "Remote command: set LoRa SF to %d", val[0]); switch_lora(val[0], cfg.txpower); #else @@ -177,7 +179,7 @@ void set_lorasf(uint8_t val[]) { } void set_loraadr(uint8_t val[]) { -#ifdef HAS_LORA +#if(HAS_LORA) ESP_LOGI(TAG, "Remote command: set LoRa ADR mode to %s", val[0] ? "on" : "off"); cfg.adrmode = val[0] ? 1 : 0; @@ -220,7 +222,7 @@ void set_rgblum(uint8_t val[]) { }; void set_lorapower(uint8_t val[]) { -#ifdef HAS_LORA +#if(HAS_LORA) ESP_LOGI(TAG, "Remote command: set LoRa TXPOWER to %d", val[0]); switch_lora(cfg.lorasf, val[0]); #else @@ -250,7 +252,7 @@ void get_status(uint8_t val[]) { void get_gps(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get gps status"); -#ifdef HAS_GPS +#if(HAS_GPS) gps_read(); payload.reset(); payload.addGPS(gps_status); @@ -262,12 +264,12 @@ void get_gps(uint8_t val[]) { void get_bme(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get bme680 sensor data"); -#ifdef HAS_BME +#if (HAS_BME) payload.reset(); payload.addBME(bme_status); SendPayload(BMEPORT, prio_high); #else - ESP_LOGW(TAG, "BME680 sensor not supported"); + ESP_LOGW(TAG, "BME sensor not supported"); #endif }; @@ -278,40 +280,35 @@ void get_time(uint8_t val[]) { SendPayload(TIMEPORT, prio_high); }; +void set_time(uint8_t val[]) { + ESP_LOGI(TAG, "Timesync requested by timeserver"); + timeSync(); +}; + +void set_flush(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: flush"); + // does nothing + // used to open receive window on LoRaWAN class a nodes +}; + // assign previously defined functions to set of numeric remote commands // format: opcode, function, #bytes params, // flag (true = do make settings persistent / false = don't) // -cmd_t table[] = {{0x01, set_rssi, 1, true}, - {0x02, set_countmode, 1, true}, - {0x03, set_gps, 1, true}, - {0x04, set_display, 1, true}, - {0x05, set_lorasf, 1, true}, - {0x06, set_lorapower, 1, true}, - {0x07, set_loraadr, 1, true}, - {0x08, set_screensaver, 1, true}, - {0x09, set_reset, 1, true}, - {0x0a, set_sendcycle, 1, true}, - {0x0b, set_wifichancycle, 1, true}, - {0x0c, set_blescantime, 1, true}, - {0x0d, set_vendorfilter, 1, false}, - {0x0e, set_blescan, 1, true}, - {0x0f, set_wifiant, 1, true}, - {0x10, set_rgblum, 1, true}, - {0x11, set_monitor, 1, true}, - {0x12, set_beacon, 7, false}, - {0x13, set_sensor, 2, true}, - {0x80, get_config, 0, false}, - {0x81, get_status, 0, false}, - {0x84, get_gps, 0, false}, - {0x85, get_bme, 0, false}, - {0x86, get_time, 0, false} -#if(DBTIMESYNC) - , - {TIME_ANS_OPCODE, recv_DBtime_ans, 0, false}, - {TIME_SYNC_OPCODE, force_DBtime_sync, 0, false} -#endif -}; +cmd_t table[] = { + {0x01, set_rssi, 1, true}, {0x02, set_countmode, 1, true}, + {0x03, set_gps, 1, true}, {0x04, set_display, 1, true}, + {0x05, set_lorasf, 1, true}, {0x06, set_lorapower, 1, true}, + {0x07, set_loraadr, 1, true}, {0x08, set_screensaver, 1, true}, + {0x09, set_reset, 1, true}, {0x0a, set_sendcycle, 1, true}, + {0x0b, set_wifichancycle, 1, true}, {0x0c, set_blescantime, 1, true}, + {0x0d, set_vendorfilter, 1, false}, {0x0e, set_blescan, 1, true}, + {0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true}, + {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, + {0x13, set_sensor, 2, true}, {0x80, get_config, 0, false}, + {0x81, get_status, 0, false}, {0x84, get_gps, 0, false}, + {0x85, get_bme, 0, false}, {0x86, get_time, 0, false}, + {0x87, set_time, 0, false}, {0x99, set_flush, 0, false}}; const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table @@ -355,4 +352,4 @@ void rcommand(uint8_t cmd[], uint8_t cmdlength) { if (storeflag) saveConfig(); -} // rcommand() \ No newline at end of file +} // rcommand() diff --git a/src/senddata.cpp b/src/senddata.cpp index 36e8b278..816ee7c7 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -39,7 +39,7 @@ void SendPayload(uint8_t port, sendprio_t prio) { memcpy(SendBuffer.Message, payload.getBuffer(), payload.getSize()); // enqueue message in device's send queues -#ifdef HAS_LORA +#if(HAS_LORA) lora_enqueuedata(&SendBuffer, prio); #endif #ifdef HAS_SPI @@ -63,7 +63,7 @@ void sendCounter() { if (cfg.blescan) payload.addCount(macs_ble, MAC_SNIFF_BLE); -#ifdef HAS_GPS +#if(HAS_GPS) if (gps.location.isValid()) { // send GPS position only if we have a fix gps_read(); payload.addGPS(gps_status); @@ -83,7 +83,7 @@ void sendCounter() { } break; -#ifdef HAS_BME +#if(HAS_BME) case MEMS_DATA: payload.reset(); payload.addBME(bme_status); @@ -91,7 +91,7 @@ void sendCounter() { break; #endif -#ifdef HAS_GPS +#if(HAS_GPS) case GPS_DATA: // send GPS position only if we have a fix if (gps.location.isValid()) { @@ -104,7 +104,7 @@ void sendCounter() { break; #endif -#ifdef HAS_SENSORS +#if(HAS_SENSORS) case SENSOR1_DATA: payload.reset(); payload.addSensor(sensor_read(1)); @@ -138,7 +138,7 @@ void sendCounter() { } // sendCounter() void flushQueues() { -#ifdef HAS_LORA +#if(HAS_LORA) lora_queuereset(); #endif #ifdef HAS_SPI diff --git a/src/spislave.cpp b/src/spislave.cpp index ed6ab7b0..cd6455a0 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -160,12 +160,8 @@ void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0); break; } - if (ret == pdTRUE) { - ESP_LOGI(TAG, "%d byte(s) enqueued for SPI interface", - message->MessageSize); - } else { + if (ret != pdTRUE) ESP_LOGW(TAG, "SPI sendqueue is full"); - } } void spi_queuereset(void) { xQueueReset(SPISendQueue); } diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 21259b21..f27d7343 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -1,5 +1,13 @@ #include "timekeeper.h" +#ifndef HAS_LORA +#if (TIME_SYNC_TIMESERVER) +#error TIME_SYNC_TIMESERVER defined, but device has no LORA configured +#elif (TIME_SYNC_LORAWAN) +#error TIME_SYNC_LORAWAN defined, but device has no LORA configured +#endif +#endif + // Local logging tag static const char TAG[] = __FILE__; @@ -14,7 +22,7 @@ time_t timeProvider(void) { time_t t = 0; -#ifdef HAS_GPS +#if(HAS_GPS) t = get_gpstime(); // fetch recent time from last NEMA record if (t) { #ifdef HAS_RTC @@ -35,11 +43,11 @@ time_t timeProvider(void) { } #endif -// kick off asychronous DB timesync if we have -#if(DBTIMESYNC) - send_DBtime_req(); -// kick off asychronous lora sync if we have -#elif defined HAS_LORA && (TIME_SYNC_LORA) +// kick off asychronous Lora timeserver timesync if we have +#if (TIME_SYNC_TIMESERVER) + send_timesync_req(); +// kick off asychronous lora network sync if we have +#elif (TIME_SYNC_LORAWAN) LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime); #endif @@ -108,7 +116,7 @@ void timepulse_start(void) { void IRAM_ATTR CLOCKIRQ(void) { BaseType_t xHigherPriorityTaskWoken; - SyncToPPS(); // calibrates UTC systime, see Time.h + SyncToPPS(); // calibrates UTC systime, see microTime.h xHigherPriorityTaskWoken = pdFALSE; if (ClockTask != NULL) @@ -141,7 +149,7 @@ time_t compiledUTC(void) { time_t tmConvert(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh, uint8_t mm, uint8_t ss) { tmElements_t tm; - tm.Year = CalendarYrToTm(YYYY); // year offset from 1970 in time.h + tm.Year = CalendarYrToTm(YYYY); // year offset from 1970 in microTime.h tm.Month = MM; tm.Day = DD; tm.Hour = hh; @@ -237,4 +245,4 @@ void clock_loop(void *taskparameter) { // ClockTask } // for } // clock_loop() -#endif // HAS_IF482 || defined HAS_DCF77 \ No newline at end of file +#endif // HAS_IF482 || defined HAS_DCF77 diff --git a/src/timesync.cpp b/src/timesync.cpp new file mode 100644 index 00000000..c2b44b4d --- /dev/null +++ b/src/timesync.cpp @@ -0,0 +1,189 @@ +/* + +///--> IMPORTANT LICENSE NOTE for this file <--/// + +PLEASE NOTE: There is a patent filed for the time sync algorithm used in the +code of this file. The shown implementation example is covered by the +repository's licencse, but you may not be eligible to deploy the applied +algorithm in applications without granted license by the patent holder. + +*/ + +#ifdef TIME_SYNC_TIMESERVER + +#include "timesync.h" + +using namespace std::chrono; + +// Local logging tag +static const char TAG[] = __FILE__; + +TaskHandle_t timeSyncReqTask; + +static uint8_t time_sync_seqNo = 0; +static bool lora_time_sync_pending = false; + +typedef std::chrono::system_clock myClock; +typedef myClock::time_point myClock_timepoint; +typedef std::chrono::duration> + myClock_msecTick; + +myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES]; +myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES]; + +// send time request message +void send_timesync_req() { + + // if a timesync handshake is pending then exit + if (lora_time_sync_pending) { + ESP_LOGI(TAG, "Timeserver sync request already pending"); + return; + } else { + ESP_LOGI(TAG, "Timeserver sync request started"); + + lora_time_sync_pending = true; + + // clear timestamp array + for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { + time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint(); // set to epoch + } + + // kick off temporary task for timeserver handshake processing + if (!timeSyncReqTask) + xTaskCreatePinnedToCore(process_timesync_req, // task function + "timesync_req", // name of task + 2048, // stack size of task + (void *)1, // task parameter + 0, // priority of the task + &timeSyncReqTask, // task handle + 1); // CPU core + } +} + +// process timeserver timestamp answer, called from lorawan.cpp +int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) { + + // if no timesync handshake is pending or spurious buffer then exit + if ((!lora_time_sync_pending) || (buf_len != TIME_SYNC_FRAME_LENGTH)) + return 0; // failure + + uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES; + uint16_t timestamp_msec = 4 * buf[5]; // convert 1/250th sec fractions to ms + uint32_t timestamp_sec = 0, tmp_sec = 0; + + for (uint8_t i = 1; i <= 4; i++) { + timestamp_sec = (tmp_sec <<= 8) |= buf[i]; + } + + if (timestamp_sec + timestamp_msec) // field validation: timestamp not 0 ? + time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec); + else + return 0; // failure + + ESP_LOGD(TAG, "Timesync request #%d rcvd at %d", seq_no, + myClock::to_time_t(time_sync_rx[k])); + + // inform processing task + if (timeSyncReqTask) + xTaskNotify(timeSyncReqTask, seq_no, eSetBits); + + return 1; // success +} + +// task for sending time sync requests +void process_timesync_req(void *taskparameter) { + + uint8_t k = 0, i = 0; + uint32_t seq_no = 0; + // milliseconds time_offset(0); + auto time_offset = myClock_msecTick::zero(); + int time_offset_msec; + time_t time_to_set; + + // enqueue timestamp samples in lora sendqueue + for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { + + // wrap around seqNo 0 .. 254 + time_sync_seqNo = (time_sync_seqNo >= 255) ? 0 : time_sync_seqNo + 1; + + // send sync request to server + payload.reset(); + payload.addByte(time_sync_seqNo); + SendPayload(TIMEPORT, prio_high); + + // process answer + if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no, + pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) || + (seq_no != time_sync_seqNo)) { + + ESP_LOGW(TAG, "Timeserver handshake failed"); + goto finish; + } // no valid sequence received before timeout + + else { // calculate time diff from collected timestamps + k = seq_no % TIME_SYNC_SAMPLES; + + auto t_tx = time_point_cast( + time_sync_tx[k]); // timepoint when node TX_completed + auto t_rx = time_point_cast( + time_sync_rx[k]); // timepoint when message was seen on gateway + + time_offset += t_rx - t_tx; // cumulate timepoint diffs + + ESP_LOGD(TAG, "time_offset: %lldms", time_offset.count()); + + if (i < TIME_SYNC_SAMPLES - 1) // wait until next cycle + vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000)); + } + } // for + + // calculate time offset from collected diffs and set time if necessary + ESP_LOGD(TAG, "Avg time diff: %lldms", time_offset.count()); + time_offset /= TIME_SYNC_SAMPLES; + // 1 sec floor round + 1sec wait for top of second + time_to_set = now() + 2 + time_offset.count() / 1000; + ESP_LOGD(TAG, "Calculated UTC epoch time: %d", time_to_set); + + // adjust system time + if (timeIsValid(time_to_set)) { + + if (abs(time_offset.count()) >= + TIME_SYNC_TRIGGER) { // milliseconds threshold + + // wait until top of second + time_offset_msec = abs(time_offset.count()) % 1000; + ESP_LOGD(TAG, "waiting %dms", 1000 - time_offset_msec); + vTaskDelay(pdMS_TO_TICKS(1000 - time_offset_msec)); + + // sync timer pps to top of second + if (ppsIRQ) + timerRestart(ppsIRQ); + + setTime(time_to_set); + timeSource = _lora; + timesyncer.attach(TIME_SYNC_INTERVAL * 60, + timeSync); // set to regular repeat + ESP_LOGI(TAG, "Timesync finished, time was adjusted"); + } else + ESP_LOGI(TAG, "Timesync finished, time is up to date"); + } else + ESP_LOGW(TAG, "Invalid time received from timeserver"); + +finish: + + lora_time_sync_pending = false; + timeSyncReqTask = NULL; + vTaskDelete(NULL); // end task +} + +// called from lorawan.cpp after time_sync_req was sent +void store_time_sync_req(time_t t_millisec) { + + uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES; + time_sync_tx[k] += milliseconds(t_millisec); + + ESP_LOGD(TAG, "Timesync request #%d sent at %d", time_sync_seqNo, + myClock::to_time_t(time_sync_tx[k])); +} + +#endif \ No newline at end of file