diff --git a/README.md b/README.md index a71c92f9..a6d43a0a 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,26 @@ This can all be done with a single small and cheap ESP32 board for less than $20 # Hardware Supported ESP32 based LoRa IoT boards: -- **Heltec LoRa-32** *a)* -- **TTGOv1** *a)* -- **TTGOv2** *a,d)* -- **TTGOv2.1** *a),e)* -- **TTGO T-Beam** *d),e),f)* -- **Pycom LoPy** *b),f)* -- **Pycom LoPy4** *b),f)* -- **Pycom FiPy** *b),f)* -- **LoLin32** with [LoraNode32 shield](https://github.com/hallard/LoLin32-Lora) *b),c)* -- **LoLin32 Lite** with [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-Lora) *b),c)* -a) on board OLED Display supported; -b) on board RGB LED supported; -c) on board Hardware unique DEVEUI supported; -d) external wiring needed, see instructions in board.h file; -e) battery voltage monitoring supported; -f) on board GPS supported (for Pycom PyTrack expansion needed) +- **Heltec LoRa-32** +- **TTGOv1** +- **TTGOv2** +- **TTGOv2.1** +- **TTGO T-Beam** +- **Pycom LoPy** +- **Pycom LoPy4** +- **Pycom FiPy** +- **LoLin32** + [LoraNode32 shield](https://github.com/hallard/LoLin32-Lora) +- **LoLin32 Lite** + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-Lora) + +Depending on board hardware following features are supported: +- LED +- OLED Display +- RGB LED +- button +- silicon unique ID +- battery voltage monitoring +- GPS Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/platformio.ini).
Hardware dependent settings (pinout etc.) are stored in board files in /hal directory.
@@ -106,6 +109,20 @@ Legend for RGB LED (LoPy/LoPy4/FiPy/Lolin32 only): # Payload +You can select between different payload formats in [paxcounter.conf](src/paxcounter.conf#L40): + +- ***Plain*** uses big endian format and generates json fields, e.g. useful for TTN console + +- ***Packed*** uses little endian format and generates json fields + +- [***CayenneLPP***](https://mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload-reference-implementation) generates MyDevices Cayenne readable fields + +If you're using [TheThingsNetwork](https://www.thethingsnetwork.org/) (TTN) you may want to use a payload converter. Go to TTN Console - Application - Payload Formats and paste the code example below in tabs Decoder and Converter. Make sure that your application parses the fields `pax`, `ble` and `wifi`. + +To map a GPS capable paxcounter device and at the same time contribute to TTN coverage mapping, you simply activate the [TTNmapper integration](https://www.thethingsnetwork.org/docs/applications/ttnmapper/) in TTN Console. Paxcounter generates ttnmapper compatible data fields. + +Hereafter described is the default *Plain* format. + **LoRaWAN Port #1:** Paxcounter data @@ -125,11 +142,7 @@ Legend for RGB LED (LoPy/LoPy4/FiPy/Lolin32 only): - see remote control - -If you're using [TheThingsNetwork](https://www.thethingsnetwork.org/) (TTN) you may want to use a payload converter. Go to TTN Console - Application - Payload Formats and paste the code example below in tabs Decoder and Converter. Make sure that your application parses the fields `pax`, `ble` and `wifi`. - -To map a GPS capable paxcounter device and at the same time contribute to TTN coverage mapping, you simply activate the [TTNmapper integration](https://www.thethingsnetwork.org/docs/applications/ttnmapper/) in TTN Console. Paxcounter generates ttnmapper compatible data fields. - -**Decoder:** +[**plain_decoder.js**](src/TTN/plain_decoder.js) ```javascript function Decoder(bytes, port) { @@ -140,11 +153,11 @@ function Decoder(bytes, port) { decoded.wifi = (bytes[i++] << 8) | bytes[i++]; decoded.ble = (bytes[i++] << 8) | bytes[i++]; if (bytes.length > 4) { - decoded.latitude = ( (bytes[i++]) | (bytes[i++] << 8) | (bytes[i++] << 16) | bytes[i++] << 24 ); - decoded.longitude = ( (bytes[i++]) | (bytes[i++] << 8) | (bytes[i++] << 16) | bytes[i++] << 24 ); - decoded.sats = ( bytes[i++] | (bytes[i++] << 8) ); - decoded.hdop = ( bytes[i++] | (bytes[i++] << 8) ); - decoded.altitude = ( bytes[i++] | (bytes[i++] << 8) ); + decoded.latitude = ( (bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++] ); + decoded.longitude = ( (bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++] ); + decoded.sats = ( bytes[i++] ); + decoded.hdop = ( bytes[i++] << 8) | (bytes[i++] ); + decoded.altitude = ( bytes[i++] << 8) | (bytes[i++] ); } } @@ -152,7 +165,7 @@ function Decoder(bytes, port) { } ``` -**Converter:** +[**plain_converter.js**](src/TTN/plain_converter.js) ```javascript function Converter(decoded, port) { @@ -264,7 +277,7 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts. 0x80 get device configuration -device answers with it's current configuration. The configuration is a C structure declared in file [globals.h](src/globals.h#L32-L50) with the following definition: +device answers with it's current configuration. The configuration is a C structure declared in file [main.h](src/main.h#L13-L31) with the following definition: byte 1: Lora SF (7..12) [default 9] byte 2: Lora TXpower (2..15) [default 15] @@ -283,25 +296,19 @@ device answers with it's current configuration. The configuration is a C structu byte 16: GPS send data mode (1=on, 0=ff) [default 1] bytes 17-27: Software version (ASCII format, terminating with zero) -0x81 get device uptime +0x81 get device status - bytes 1-8: Uptime in seconds (little endian format) - -0x82 get device cpu temperature - - bytes 1-4: Chip temperature in degrees celsius (little endian format) - -0x83 get device battery voltage - - bytes 1-2: Battery voltage in millivolt, 0 if unreadable (little endian format) + bytes 1-2: Battery voltage in millivolt, 0 if unreadable + bytes 3-10: Uptime in seconds + bytes 11-14: Chip temperature in degrees celsius 0x84 get device GPS status bytes 1-4: Latitude bytes 5-8: Longitude - byte 9-10: Number of satellites - byte 11-12: HDOP - bytes 13-14: altidute [meter] + byte 9: Number of satellites + byte 10-11: HDOP + bytes 12-13: altidute [meter] # License diff --git a/platformio.ini b/platformio.ini index b4c12689..75d57140 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,6 +27,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [common_env_data] platform_espressif32 = espressif32@>=1.0.2 board_build.partitions = no_ota.csv +lib_deps_all = lib_deps_display = U8g2@>=2.22.14 lib_deps_rgbled = @@ -55,6 +56,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 115200 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_display} build_flags = ${common_env_data.build_flags} @@ -68,6 +70,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 115200 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_display} build_flags = ${common_env_data.build_flags} @@ -81,6 +84,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 921600 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_display} build_flags = ${common_env_data.build_flags} @@ -94,6 +98,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 921600 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_display} build_flags = ${common_env_data.build_flags} @@ -107,6 +112,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 921600 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_gps} build_flags = ${common_env_data.build_flags} @@ -120,6 +126,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 921600 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_rgbled} build_flags = ${common_env_data.build_flags} @@ -133,7 +140,9 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 921600 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_rgbled} + ${common_env_data.lib_deps_gps} build_flags = ${common_env_data.build_flags} -include "src/hal/lopy.h" @@ -146,7 +155,9 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 921600 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_rgbled} + ${common_env_data.lib_deps_gps} build_flags = ${common_env_data.build_flags} -include "src/hal/lopy4.h" @@ -159,6 +170,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 256000 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_rgbled} build_flags = ${common_env_data.build_flags} @@ -172,6 +184,7 @@ board_build.partitions = ${common_env_data.board_build.partitions} monitor_speed = 115200 upload_speed = 921600 lib_deps = + ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_rgbled} build_flags = ${common_env_data.build_flags} diff --git a/src/TTN/packed_converter.js b/src/TTN/packed_converter.js new file mode 100644 index 00000000..7c35ad41 --- /dev/null +++ b/src/TTN/packed_converter.js @@ -0,0 +1,13 @@ +// Converter for device payload encoder "PACKED" +// copy&paste to TTN Console -> Applications -> PayloadFormat -> Converter + +function Converter(decoded, port) { + + var converted = decoded; + + if (port === 1) { + converted.pax = converted.ble + converted.wifi; + } + + return converted; +} \ No newline at end of file diff --git a/src/TTN/packed_decoder.js b/src/TTN/packed_decoder.js new file mode 100644 index 00000000..d6f43ed0 --- /dev/null +++ b/src/TTN/packed_decoder.js @@ -0,0 +1,167 @@ +// Decoder for device payload encoder "PACKED" +// copy&paste to TTN Console -> Applications -> PayloadFormat -> Decoder + +function Decoder(bytes, port) { + + var decoded = {}; + + if (port === 1) { + // only counter data, no gps + if (bytes.length === 4) { + return decode(bytes, [uint16, uint16], ['wifi', 'ble']); + } + // combined counter and gps data + if (bytes.length === 17) { + return decode(bytes, [uint16, uint16, latLng, latLng, uint8, hdop, uint16], ['wifi', 'ble', 'latitude', 'longitude', 'sats', 'hdop', 'altitude']); + } + } + + if (port === 2) { + // device status data + if (bytes.length === 12) { + return decode(bytes, [uint16, uptime, temperature], ['voltage', 'uptime', 'cputemp']); + } + // device config data + if (bytes.length === 8) { + return decode(bytes, [uint8, uint16, uint8, uint8, uint8, uint8, bitmap], ['lorasf', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags']); + } + } + +} + + +// ----- contents of /src/decoder.js -------------------------------------------- +// https://github.com/thesolarnomad/lora-serialization/blob/master/src/decoder.js + +var bytesToInt = function (bytes) { + var i = 0; + for (var x = 0; x < bytes.length; x++) { + i |= +(bytes[x] << (x * 8)); + } + return i; +}; + +var uptime = function (bytes) { + if (bytes.length !== uptime.BYTES) { + throw new Error('uptime must have exactly 8 bytes'); + } + return bytesToInt(bytes); +}; +uptime.BYTES = 4; + +var uint8 = function (bytes) { + if (bytes.length !== uint8.BYTES) { + throw new Error('int must have exactly 1 byte'); + } + return bytesToInt(bytes); +}; +uint8.BYTES = 1; + +var uint16 = function (bytes) { + if (bytes.length !== uint16.BYTES) { + throw new Error('int must have exactly 2 bytes'); + } + return bytesToInt(bytes); +}; +uint16.BYTES = 2; + +var latLng = function (bytes) { + if (bytes.length !== latLng.BYTES) { + throw new Error('Lat/Long must have exactly 4 bytes'); + } + return bytesToInt(bytes) / 1e6; +}; +latLng.BYTES = 4; + +var hdop = function (bytes) { + if (bytes.length !== hdop.BYTES) { + throw new Error('hdop must have exactly 2 bytes'); + } + return bytesToInt(bytes) / 100; +}; +hdop.BYTES = 2; + +var temperature = function (bytes) { + if (bytes.length !== temperature.BYTES) { + throw new Error('Temperature must have exactly 2 bytes'); + } + var isNegative = bytes[0] & 0x80; + var b = ('00000000' + Number(bytes[0]).toString(2)).slice(-8) + + ('00000000' + Number(bytes[1]).toString(2)).slice(-8); + if (isNegative) { + var arr = b.split('').map(function (x) { return !Number(x); }); + for (var i = arr.length - 1; i > 0; i--) { + arr[i] = !arr[i]; + if (arr[i]) { + break; + } + } + b = arr.map(Number).join(''); + } + var t = parseInt(b, 2); + if (isNegative) { + t = -t; + } + return t / 1e2; +}; +temperature.BYTES = 2; + +var humidity = function (bytes) { + if (bytes.length !== humidity.BYTES) { + throw new Error('Humidity must have exactly 2 bytes'); + } + + var h = bytesToInt(bytes); + return h / 1e2; +}; +humidity.BYTES = 2; + +var bitmap = function (byte) { + if (byte.length !== bitmap.BYTES) { + throw new Error('Bitmap must have exactly 1 byte'); + } + var i = bytesToInt(byte); + var bm = ('00000000' + Number(i).toString(2)).substr(-8).split('').map(Number).map(Boolean); + return ['adr', 'screensaver', 'screen', 'countermode', 'blescan', 'antenna', 'filter', 'gpsmode'] + .reduce(function (obj, pos, index) { + obj[pos] = bm[index]; + return obj; + }, {}); +}; +bitmap.BYTES = 1; + +var decode = function (bytes, mask, names) { + + var maskLength = mask.reduce(function (prev, cur) { + return prev + cur.BYTES; + }, 0); + if (bytes.length < maskLength) { + throw new Error('Mask length is ' + maskLength + ' whereas input is ' + bytes.length); + } + + names = names || []; + var offset = 0; + return mask + .map(function (decodeFn) { + var current = bytes.slice(offset, offset += decodeFn.BYTES); + return decodeFn(current); + }) + .reduce(function (prev, cur, idx) { + prev[names[idx] || idx] = cur; + return prev; + }, {}); +}; + +if (typeof module === 'object' && typeof module.exports !== 'undefined') { + module.exports = { + uptime: uptime, + uint8: uint8, + uint16: uint16, + temperature: temperature, + humidity: humidity, + latLng: latLng, + hdop: hdop, + bitmap: bitmap, + decode: decode + }; +} \ No newline at end of file diff --git a/src/TTN/plain_converter.js b/src/TTN/plain_converter.js new file mode 100644 index 00000000..1302d56c --- /dev/null +++ b/src/TTN/plain_converter.js @@ -0,0 +1,18 @@ +// Converter for device payload encoder "PLAIN" +// copy&paste to TTN Console -> Applications -> PayloadFormat -> Converter + +function Converter(decoded, port) { + + var converted = decoded; + + if (port === 1) { + converted.pax = converted.ble + converted.wifi; + if (converted.hdop) { + converted.hdop /= 100; + converted.latitude /= 1000000; + converted.longitude /= 1000000; + } + } + + return converted; +} \ No newline at end of file diff --git a/src/TTN/plain_decoder.js b/src/TTN/plain_decoder.js new file mode 100644 index 00000000..9f2768c6 --- /dev/null +++ b/src/TTN/plain_decoder.js @@ -0,0 +1,23 @@ +// Decoder for device payload encoder "PLAIN" +// copy&paste to TTN Console -> Applications -> PayloadFormat -> Decoder + +function Decoder(bytes, port) { + var decoded = {}; + + if (port === 1) { + var i = 0; + + decoded.wifi = (bytes[i++] << 8) | bytes[i++]; + decoded.ble = (bytes[i++] << 8) | bytes[i++]; + + if (bytes.length > 4) { + decoded.latitude = ((bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++]); + decoded.longitude = ((bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++]); + decoded.sats = (bytes[i++]); + decoded.hdop = (bytes[i++] << 8) | (bytes[i++]); + decoded.altitude = (bytes[i++] << 8) | (bytes[i++]); + } + } + + return decoded; +} \ No newline at end of file diff --git a/src/globals.h b/src/globals.h index d9adb1b9..21c7b1a8 100644 --- a/src/globals.h +++ b/src/globals.h @@ -28,38 +28,7 @@ #include "rgb_led.h" #include "macsniff.h" #include "main.h" - -// Struct holding devices's runtime configuration -typedef struct { - uint8_t lorasf; // 7-12, lora spreadfactor - uint8_t txpower; // 2-15, lora tx power - uint8_t adrmode; // 0=disabled, 1=enabled - uint8_t screensaver; // 0=disabled, 1=enabled - uint8_t screenon; // 0=disabled, 1=enabled - uint8_t countermode; // 0=cyclic unconfirmed, 1=cumulative, 2=cyclic confirmed - int16_t rssilimit; // threshold for rssilimiter, negative value! - uint8_t sendcycle; // payload send cycle [seconds/2] - uint8_t wifichancycle; // wifi channel switch cycle [seconds/100] - uint8_t blescantime; // BLE scan cycle duration [seconds] - uint8_t blescan; // 0=disabled, 1=enabled - uint8_t wifiant; // 0=internal, 1=external (for LoPy/LoPy4) - uint8_t vendorfilter; // 0=disabled, 1=enabled - uint8_t rgblum; // RGB Led luminosity (0..100%) - uint8_t gpsmode; // 0=disabled, 1=enabled - char version[10]; // Firmware version -} configData_t; - -#ifdef HAS_GPS -typedef struct { - uint32_t latitude; - uint32_t longitude; - uint8_t satellites; - uint16_t hdop; - uint16_t altitude; -} gpsStatus_t; -extern gpsStatus_t gps_status; // struct for storing gps data -extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe -#endif +#include "payload.h" extern configData_t cfg; extern uint64_t uptimecounter; @@ -69,6 +38,20 @@ extern int countermode, screensaver, adrmode, lorasf, txpower, rlim; extern uint16_t macs_total, macs_wifi, macs_ble; // MAC counters extern std::set macs; extern hw_timer_t - *channelSwitch; // hardware timer used for wifi channel switching -extern xref2u1_t rcmd_data; // buffer for rcommand results size -extern u1_t rcmd_data_size; // buffer for rcommand results size + *channelSwitch; // hardware timer used for wifi channel switching + +#ifdef HAS_GPS +extern gpsStatus_t gps_status; // struct for storing gps data +extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe +#endif + +// payload encoder +#if PAYLOAD_ENCODER == 1 +extern TTNplain payload; +#elif PAYLOAD_ENCODER == 2 +extern TTNpacked payload; +#elif PAYLOAD_ENCODER == 3 +extern CayenneLPP payload; +#else +#error "No valid payload converter defined" +#endif \ No newline at end of file diff --git a/src/gpsread.cpp b/src/gpsread.cpp index ebe16999..11a8cf92 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -1,14 +1,15 @@ #ifdef HAS_GPS #include "globals.h" +#include // Local logging tag static const char TAG[] = "main"; // read GPS data and cast to global struct void gps_read() { - gps_status.latitude = (uint32_t)(gps.location.lat() * 1000000); - gps_status.longitude = (uint32_t)(gps.location.lng() * 1000000); + gps_status.latitude = (uint32_t)(gps.location.lat() * 1e6); + gps_status.longitude = (uint32_t)(gps.location.lng() * 1e6); gps_status.satellites = (uint8_t)gps.satellites.value(); gps_status.hdop = (uint16_t)gps.hdop.value(); gps_status.altitude = (uint16_t)gps.altitude.meters(); @@ -22,8 +23,8 @@ void gps_loop(void *pvParameters) { // initialize and, if needed, configure, GPS #if defined GPS_SERIAL HardwareSerial GPS_Serial(1); -#elif defined GPS_I2C - // to be done +#elif defined GPS_QUECTEL_L76 + Wire.begin(GPS_QUECTEL_L76, 400000); // I2C connect to GPS device with 400 KHz #endif while (1) { @@ -44,30 +45,29 @@ void gps_loop(void *pvParameters) { // after GPS function was disabled, close connect to GPS device GPS_Serial.end(); -#elif defined GPS_I2C +#elif defined GPS_QUECTEL_L76 - // I2C connect to GPS device with 100 kHz - Wire.begin(GPS_I2C_PINS, 100000); - Wire.beginTransmission(GPS_I2C_ADDRESS_WRITE); - Wire.write(0x00); + Wire.beginTransmission(GPS_ADDR); + Wire.write(0x00); // dummy write to start read + Wire.endTransmission(); - i2c_ret == Wire.beginTransmission(GPS_I2C_ADDRESS_READ); - if (i2c_ret == 0) { // check if device seen on i2c bus - while (cfg.gpsmode) { - // feed GPS decoder with serial NMEA data from GPS device - while (Wire.available()) { - Wire.requestFrom(GPS_I2C_ADDRESS_READ, 255); - gps.encode(Wire.read()); - vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog - } + Wire.beginTransmission(GPS_ADDR); + while (cfg.gpsmode) { + Wire.requestFrom(GPS_ADDR | 0x01, 32); + while (Wire.available()) { + gps.encode(Wire.read()); + vTaskDelay(1 / portTICK_PERIOD_MS); // polling mode: 500ms sleep } - // after GPS function was disabled, close connect to GPS device - Wire.endTransmission(); - Wire.setClock(400000); // Set back to 400KHz to speed up OLED + ESP_LOGI(TAG, "GPS NMEA data: passed %d / failed: %d / with fix: %d", + gps.passedChecksum(), gps.failedChecksum(), + gps.sentencesWithFix()); } + // after GPS function was disabled, close connect to GPS device -#endif + Wire.endTransmission(); + +#endif // GPS Type } vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog diff --git a/src/hal/lopy.h b/src/hal/lopy.h index d54004f6..d9d4068d 100644 --- a/src/hal/lopy.h +++ b/src/hal/lopy.h @@ -5,13 +5,13 @@ #define HAS_RGB_LED GPIO_NUM_0 // WS2812B RGB LED on GPIO0 // !!EXPERIMENTAL - not tested yet!! -// uncomment this only if your LoPy lives on a Pytrack expansion board with GPS +// uncomment this only if your LoPy runs on a Pytrack expansion board with GPS // see http://www.quectel.com/UploadImage/Downlad/Quectel_L76-L_I2C_Application_Note_V1.0.pdf //#define HAS_GPS 1 -//#define GPS_I2C_PINS GPIO_NUM_9, GPIO_NUM_8 // SDA, SCL -//#define GPS_I2C_ADDRESS_READ 0x21 -//#define GPS_I2C_ADDRESS_WRITE 0x20 -//#define HAS_BUTTON GPIO_NUM_4 +//#define GPS_QUECTEL_L76 GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21) +//#define GPS_ADDR 0x10 +//#define HAS_BUTTON GPIO_NUM_37 // (P14) +//#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown // Hardware pin definitions for Pycom LoPy board #define PIN_SPI_SS GPIO_NUM_17 diff --git a/src/hal/lopy4.h b/src/hal/lopy4.h index 708a861d..e5481f12 100644 --- a/src/hal/lopy4.h +++ b/src/hal/lopy4.h @@ -4,14 +4,14 @@ #define HAS_LED NOT_A_PIN // LoPy4 has no on board LED, so we use RGB LED on LoPy4 #define HAS_RGB_LED GPIO_NUM_0 // WS2812B RGB LED on GPIO0 -// !!EXPERIMENTAL - not tested yet!!f -// uncomment this only if your LoPy lives on a Pytrack expansion board with GPS +// !!EXPERIMENTAL - not tested yet!! +// uncomment this only if your LoPy runs on a Pytrack expansion board with GPS // see http://www.quectel.com/UploadImage/Downlad/Quectel_L76-L_I2C_Application_Note_V1.0.pdf //#define HAS_GPS 1 -//#define GPS_I2C_PINS GPIO_NUM_9, GPIO_NUM_8 // SDA, SCL -//#define GPS_I2C_ADDRESS_READ 0x21 -//#define GPS_I2C_ADDRESS_WRITE 0x20 -//#define HAS_BUTTON GPIO_NUM_4 +//#define GPS_QUECTEL_L76 GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21) +//#define GPS_ADDR 0x10 +//#define HAS_BUTTON GPIO_NUM_37 // (P14) +//#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown // Hardware pin definitions for Pycom LoPy4 board #define PIN_SPI_SS GPIO_NUM_18 diff --git a/src/hal/ttgov2.h b/src/hal/ttgov2.h index e4bb49b4..5d116241 100644 --- a/src/hal/ttgov2.h +++ b/src/hal/ttgov2.h @@ -23,8 +23,8 @@ // Hardware pin definitions for TTGO V2 Board with OLED SSD1306 0,96" I2C Display #define OLED_RST U8X8_PIN_NONE // connected to CPU RST/EN -#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO4 (Pin4) -- SD1306 D1+D2 -#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO15 (Pin15) -- SD1306 D0 +#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO21 -- SD1306 D1+D2 +#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO22 -- SD1306 D0 /* diff --git a/src/hal/ttgov21.h b/src/hal/ttgov21.h index 1e3b38ec..96617e1f 100644 --- a/src/hal/ttgov21.h +++ b/src/hal/ttgov21.h @@ -22,5 +22,5 @@ // Hardware pin definitions for TTGO V2 Board with OLED SSD1306 0,96" I2C Display #define OLED_RST U8X8_PIN_NONE // connected to CPU RST/EN -#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO4 (Pin4) -- SD1306 D1+D2 -#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO15 (Pin15) -- SD1306 D0 \ No newline at end of file +#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO21 -- SD1306 D1+D2 +#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO22 -- SD1306 D0 \ No newline at end of file diff --git a/src/lorawan.cpp b/src/lorawan.cpp index ed07f19e..0000c618 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -117,42 +117,21 @@ void do_send(osjob_t *j) { return; } - // prepare payload with sum of unique WIFI MACs seen - static uint8_t mydata[4]; - - mydata[0] = (macs_wifi & 0xff00) >> 8; - mydata[1] = macs_wifi & 0xff; - - if (cfg.blescan) { - // append sum of unique BLE MACs seen to payload - mydata[2] = (macs_ble & 0xff00) >> 8; - mydata[3] = macs_ble & 0xff; - } else { - mydata[2] = 0; - mydata[3] = 0; - } + // Prepare payload with counter and, if applicable, gps data + payload.reset(); + payload.addCount(macs_wifi, cfg.blescan ? macs_ble : 0); #ifdef HAS_GPS - static uint8_t gpsdata[18]; - if (cfg.gpsmode && gps.location.isValid()) { + if ((cfg.gpsmode) && (gps.location.isValid())) { gps_read(); - memcpy(gpsdata, mydata, 4); - memcpy(gpsdata + 4, &gps_status, sizeof(gps_status)); - ESP_LOGI(TAG, "lat=%.6f / lon=%.6f | %u Sats | HDOP=%.1f | Altitude=%u m", - gps_status.latitude / (float)1000000, - gps_status.longitude / (float)1000000, gps_status.satellites, - gps_status.hdop / (float)100, gps_status.altitude); - LMIC_setTxData2(COUNTERPORT, gpsdata, sizeof(gpsdata), - (cfg.countermode & 0x02)); - ESP_LOGI(TAG, "%d bytes queued to send", sizeof(gpsdata)); - } else { -#endif - LMIC_setTxData2(COUNTERPORT, mydata, sizeof(mydata), - (cfg.countermode & 0x02)); - ESP_LOGI(TAG, "%d bytes queued to send", sizeof(mydata)); -#ifdef HAS_GPS + payload.addGPS(gps_status); } #endif + + // send payload + LMIC_setTxData2(COUNTERPORT, payload.getBuffer(), payload.getSize(), + (cfg.countermode & 0x02)); + ESP_LOGI(TAG, "%d bytes queued to send", payload.getSize()); sprintf(display_lmic, "PACKET QUEUED"); // clear counter if not in cumulative counter mode diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 40290731..fbe0469d 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -76,7 +76,7 @@ bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) { } // Log scan result - ESP_LOGI(TAG, + ESP_LOGD(TAG, "%s %s RSSI %ddBi -> MAC %s -> Hash %04X -> WiFi:%d BLTH:%d -> " "%d Bytes left", added ? "new " : "known", diff --git a/src/main.cpp b/src/main.cpp index 59251284..4808e7f7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,9 +56,7 @@ uint16_t LEDColor = COLOR_NONE; // state machine variable to set RGB LED color hw_timer_t *displaytimer = NULL; // configure hardware timer used for cyclic display refresh hw_timer_t *channelSwitch = - NULL; // configure hardware timer used for wifi channel switching -xref2u1_t rcmd_data; // buffer for rcommand results size -u1_t rcmd_data_size; // buffer for rcommand results size + NULL; // configure hardware timer used for wifi channel switching #ifdef HAS_GPS gpsStatus_t gps_status; // struct for storing gps data @@ -72,6 +70,17 @@ portMUX_TYPE timerMux = std::set macs; // associative container holds total of unique MAC // adress hashes (Wifi + BLE) +// initialize payload encoder +#if PAYLOAD_ENCODER == 1 +TTNplain payload(PAYLOAD_BUFFER_SIZE); +#elif PAYLOAD_ENCODER == 2 +TTNpacked payload(PAYLOAD_BUFFER_SIZE); +#elif PAYLOAD_ENCODER == 3 +CayenneLPP payload(PAYLOAD_BUFFER_SIZE); +#else +#error "No valid payload converter defined" +#endif + // this variables will be changed in the ISR, and read in main loop static volatile int ButtonPressedIRQ = 0, DisplayTimerIRQ = 0, ChannelTimerIRQ = 0; @@ -459,6 +468,7 @@ void led_loop() { * ------------------------------------------------------------ */ void setup() { + char features[64] = ""; // disable brownout detection @@ -508,24 +518,24 @@ void setup() { // initialize led if needed #if (HAS_LED != NOT_A_PIN) pinMode(HAS_LED, OUTPUT); - strcat(features, " LED"); + strcat_P(features, " LED"); #endif #ifdef HAS_RGB_LED rgb_set_color(COLOR_PINK); - strcat(features, " RGB"); + strcat_P(features, " RGB"); #endif // initialize button handling if needed #ifdef HAS_BUTTON - strcat(features, " BTN_"); + strcat_P(features, " BTN_"); #ifdef BUTTON_PULLUP - strcat(features, "PU"); + strcat_P(features, "PU"); // install button interrupt (pullup mode) pinMode(HAS_BUTTON, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING); #else - strcat(features, "PD"); + strcat_P(features, "PD"); // install button interrupt (pulldown mode) pinMode(HAS_BUTTON, INPUT_PULLDOWN); attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, FALLING); @@ -534,17 +544,17 @@ void setup() { // initialize wifi antenna if needed #ifdef HAS_ANTENNA_SWITCH - strcat(features, " ANT"); + strcat_P(features, " ANT"); antenna_init(); #endif // initialize gps if present #ifdef HAS_GPS - strcat(features, " GPS"); + strcat_P(features, " GPS"); #endif #ifdef HAS_DISPLAY - strcat(features, " OLED"); + strcat_P(features, " OLED"); // initialize display init_display(PROGNAME, PROGVERSION); DisplayState = cfg.screenon; @@ -580,8 +590,17 @@ void setup() { timerAlarmWrite(channelSwitch, cfg.wifichancycle * 10000, true); timerAlarmEnable(channelSwitch); +// show payload encoder +#if PAYLOAD_ENCODER == 1 + strcat_P(features, " PAYLOAD_PLAIN"); +#elif PAYLOAD_ENCODER == 2 + strcat_P(features, " PAYLOAD_PACKED"); +#elif PAYLOAD_ENCODER == 3 + strcat_P(features, " PAYLOAD_CAYENNE"); +#endif + // show compiled features - ESP_LOGI(TAG, "Features %s", features); + ESP_LOGI(TAG, "Features: %s", features); // output LoRaWAN keys to console #ifdef VERBOSE @@ -621,8 +640,7 @@ void setup() { } #endif -// if device has GPS and GPS function is enabled, start GPS reader task on core -// 0 +// if device has GPS and it is enabled, start GPS reader task on core 0 #ifdef HAS_GPS if (cfg.gpsmode) { ESP_LOGI(TAG, "Starting GPS task on core 0"); @@ -630,8 +648,7 @@ void setup() { } #endif - // kickoff sendjob -> joins network and rescedules sendjob for cyclic - // transmitting payload + // joins network and rescedules sendjob for cyclic transmitting payload do_send(&sendjob); } @@ -674,11 +691,22 @@ void loop() { } #ifdef HAS_GPS - // log NMEA status every 30 seconds, useful for debugging GPS connection - if ((uptime() % 30000) == 0) + // log NMEA status every 60 seconds, useful for debugging GPS connection + if ((uptime() % 60000) == 0) { ESP_LOGI(TAG, "GPS NMEA data: passed %d / failed: %d / with fix: %d", gps.passedChecksum(), gps.failedChecksum(), gps.sentencesWithFix()); + if ((cfg.gpsmode) && (gps.location.isValid())) { + gps_read(); + ESP_LOGI(TAG, + "lat=%.6f | lon=%.6f | %u Sats | HDOP=%.1f | Altitude=%um", + gps_status.latitude / (float)1e6, + gps_status.longitude / (float)1e6, gps_status.satellites, + gps_status.hdop / (float)100, gps_status.altitude); + } else { + ESP_LOGI(TAG, "No valid GPS position or GPS disabled"); + } + } #endif vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog diff --git a/src/main.h b/src/main.h index 52f7a44f..e807d7a1 100644 --- a/src/main.h +++ b/src/main.h @@ -5,11 +5,43 @@ // program version - note: increment version after modifications to configData_t // struct!! -#define PROGVERSION "1.3.81" // use max 10 chars here! +#define PROGVERSION "1.3.82" // use max 10 chars here! #define PROGNAME "PAXCNT" //--- Declarations --- +// Struct holding devices's runtime configuration +typedef struct { + uint8_t lorasf; // 7-12, lora spreadfactor + uint8_t txpower; // 2-15, lora tx power + uint8_t adrmode; // 0=disabled, 1=enabled + uint8_t screensaver; // 0=disabled, 1=enabled + uint8_t screenon; // 0=disabled, 1=enabled + uint8_t countermode; // 0=cyclic unconfirmed, 1=cumulative, 2=cyclic confirmed + int16_t rssilimit; // threshold for rssilimiter, negative value! + uint8_t sendcycle; // payload send cycle [seconds/2] + uint8_t wifichancycle; // wifi channel switch cycle [seconds/100] + uint8_t blescantime; // BLE scan cycle duration [seconds] + uint8_t blescan; // 0=disabled, 1=enabled + uint8_t wifiant; // 0=internal, 1=external (for LoPy/LoPy4) + uint8_t vendorfilter; // 0=disabled, 1=enabled + uint8_t rgblum; // RGB Led luminosity (0..100%) + uint8_t gpsmode; // 0=disabled, 1=enabled + char version[10]; // Firmware version +} configData_t; + +#ifdef HAS_GPS +typedef struct { + uint32_t latitude; + uint32_t longitude; + uint8_t satellites; + uint16_t hdop; + uint16_t altitude; +} gpsStatus_t; +extern gpsStatus_t gps_status; // struct for storing gps data +extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe +#endif + enum led_states { LED_OFF, LED_ON }; #if defined(CFG_eu868) @@ -35,4 +67,4 @@ void stop_BLEscan(void); #ifdef HAS_GPS void gps_read(void); void gps_loop(void *pvParameters); -#endif +#endif \ No newline at end of file diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 47432573..357ed009 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -1,6 +1,8 @@ // ----- Paxcounter user config file ------ // // --> adapt to your needs and use case <-- +// +// Note: After editing, before "build", use "clean" button in PlatformIO! // Verbose enables serial output #define VERBOSE 1 // comment out to silence the device, for mute use build option @@ -34,16 +36,17 @@ #define WIFI_MY_COUNTRY "EU" // select locale for Wifi RF settings #define WIFI_CHANNEL_SWITCH_INTERVAL 50 // [seconds/100] -> 0,5 sec. -// LoRa payload send cycle --> take care of duty cycle of LoRaWAN network! <-- -#define SEND_SECS 120 // [seconds/2] -> 240 sec. +// LoRa payload parameters +#define PAYLOAD_ENCODER 1 // select payload encoder: 1=Plain [default], 2=Packed, 3=CayenneLPP +#define SEND_SECS 120 // payload send cycle [seconds/2] -> 240 sec. #define MEM_LOW 2048 // [Bytes] low memory threshold triggering a send cycle #define RETRANSMIT_RCMD 5 // [seconds] wait time before retransmitting rcommand results +#define PAYLOAD_BUFFER_SIZE 51 // maximum size of payload block per transmit // Default LoRa Spreadfactor #define LORASFDEFAULT 9 // 7 ... 12 SF, according to LoRaWAN specs #define MAXLORARETRY 500 // maximum count of TX retries if LoRa busy #define RCMDPORT 2 // LoRaWAN Port on which device listenes for remote commands -#define GPSPORT 3 // LoRaWAN Port on which device sends gps data #define COUNTERPORT 1 // LoRaWAN Port on which device sends counts // Default RGB LED luminosity (in %) diff --git a/src/payload.cpp b/src/payload.cpp new file mode 100644 index 00000000..dce09224 --- /dev/null +++ b/src/payload.cpp @@ -0,0 +1,246 @@ + +#include "globals.h" +#include "payload.h" + +/* ---------------- plain format without special encoding ---------- */ + +TTNplain::TTNplain(uint8_t size) { + buffer = (uint8_t *)malloc(size); + cursor = 0; +} + +TTNplain::~TTNplain(void) { free(buffer); } + +void TTNplain::reset(void) { cursor = 0; } + +uint8_t TTNplain::getSize(void) { return cursor; } + +uint8_t *TTNplain::getBuffer(void) { return buffer; } + +void TTNplain::addCount(uint16_t value1, uint16_t value2) { + buffer[cursor++] = value1 >> 8; + buffer[cursor++] = value1; + buffer[cursor++] = value2 >> 8; + buffer[cursor++] = value2; +} + +#ifdef HAS_GPS +void TTNplain::addGPS(gpsStatus_t value) { + buffer[cursor++] = value.latitude >> 24; + buffer[cursor++] = value.latitude >> 16; + buffer[cursor++] = value.latitude >> 8; + buffer[cursor++] = value.latitude; + buffer[cursor++] = value.longitude >> 24; + buffer[cursor++] = value.longitude >> 16; + buffer[cursor++] = value.longitude >> 8; + buffer[cursor++] = value.longitude; + buffer[cursor++] = value.satellites; + buffer[cursor++] = value.hdop >> 8; + buffer[cursor++] = value.hdop; + buffer[cursor++] = value.altitude >> 8; + buffer[cursor++] = value.altitude; +} +#endif + +void TTNplain::addConfig(configData_t value) { + buffer[cursor++] = value.lorasf; + buffer[cursor++] = value.adrmode; + buffer[cursor++] = value.screensaver; + buffer[cursor++] = value.screenon; + buffer[cursor++] = value.countermode; + buffer[cursor++] = value.rssilimit >> 8; + buffer[cursor++] = value.rssilimit; + buffer[cursor++] = value.sendcycle; + buffer[cursor++] = value.wifichancycle; + buffer[cursor++] = value.blescantime; + buffer[cursor++] = value.blescan; + buffer[cursor++] = value.wifiant; + buffer[cursor++] = value.vendorfilter; + buffer[cursor++] = value.rgblum; + buffer[cursor++] = value.gpsmode; + memcpy(buffer + cursor, value.version, 10); + cursor += 10; +} + +void TTNplain::addStatus(uint16_t voltage, uint64_t uptime, float cputemp) { + buffer[cursor++] = voltage >> 8; + buffer[cursor++] = voltage; + buffer[cursor++] = uptime >> 56; + buffer[cursor++] = uptime >> 48; + buffer[cursor++] = uptime >> 40; + buffer[cursor++] = uptime >> 32; + buffer[cursor++] = uptime >> 24; + buffer[cursor++] = uptime >> 16; + buffer[cursor++] = uptime >> 8; + buffer[cursor++] = uptime; + buffer[cursor++] = (uint32_t)cputemp >> 24; + buffer[cursor++] = (uint32_t)cputemp >> 16; + buffer[cursor++] = (uint32_t)cputemp >> 8; + buffer[cursor++] = (uint32_t)cputemp; +} + +/* ---------------- packed format with LoRa serialization Encoder ---------- */ +// derived from +// https://github.com/thesolarnomad/lora-serialization/blob/master/src/LoraEncoder.cpp + +TTNpacked::TTNpacked(uint8_t size) { + buffer = (uint8_t *)malloc(size); + cursor = 0; +} + +TTNpacked::~TTNpacked(void) { free(buffer); } + +void TTNpacked::reset(void) { cursor = 0; } + +uint8_t TTNpacked::getSize(void) { return cursor; } + +uint8_t *TTNpacked::getBuffer(void) { return buffer; } + +void TTNpacked::addCount(uint16_t value1, uint16_t value2) { + writeUint16(value1); + writeUint16(value2); +} + +#ifdef HAS_GPS +void TTNpacked::addGPS(gpsStatus_t value) { + writeLatLng(value.latitude, value.longitude); + writeUint8(value.satellites); + writeUint16(value.hdop); + writeUint16(value.altitude); +} +#endif + +void TTNpacked::addConfig(configData_t value) { + writeUint8(value.lorasf); + writeUint16(value.rssilimit); + writeUint8(value.sendcycle); + writeUint8(value.wifichancycle); + writeUint8(value.blescantime); + writeUint8(value.rgblum); + writeBitmap(value.adrmode ? true : false, value.screensaver ? true : false, + value.screenon ? true : false, value.countermode ? true : false, + value.blescan ? true : false, value.wifiant ? true : false, + value.vendorfilter ? true : false, value.gpsmode ? true : false); +} + +void TTNpacked::addStatus(uint16_t voltage, uint64_t uptime, float cputemp) { + writeUint16(voltage); + writeUptime(uptime); + writeTemperature(cputemp); +} + +void TTNpacked::intToBytes(uint8_t pos, int32_t i, uint8_t byteSize) { + for (uint8_t x = 0; x < byteSize; x++) { + buffer[x + pos] = (byte)(i >> (x * 8)); + } + cursor += byteSize; +} + +void TTNpacked::writeUptime(uint64_t uptime) { + intToBytes(cursor, uptime, 8); +} + +void TTNpacked::writeLatLng(double latitude, double longitude) { + intToBytes(cursor, latitude, 4); + intToBytes(cursor, longitude, 4); +} + +void TTNpacked::writeUint16(uint16_t i) { intToBytes(cursor, i, 2); } + +void TTNpacked::writeUint8(uint8_t i) { intToBytes(cursor, i, 1); } + +void TTNpacked::writeHumidity(float humidity) { + int16_t h = (int16_t)(humidity * 100); + intToBytes(cursor, h, 2); +} + +/** + * Uses a 16bit two's complement with two decimals, so the range is + * -327.68 to +327.67 degrees + */ +void TTNpacked::writeTemperature(float temperature) { + int16_t t = (int16_t)(temperature * 100); + if (temperature < 0) { + t = ~-t; + t = t + 1; + } + buffer[cursor++] = (byte)((t >> 8) & 0xFF); + buffer[cursor++] = (byte)t & 0xFF; +} + +void TTNpacked::writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f, + bool g, bool h) { + uint8_t bitmap = 0; + // LSB first + bitmap |= (a & 1) << 7; + bitmap |= (b & 1) << 6; + bitmap |= (c & 1) << 5; + bitmap |= (d & 1) << 4; + bitmap |= (e & 1) << 3; + bitmap |= (f & 1) << 2; + bitmap |= (g & 1) << 1; + bitmap |= (h & 1) << 0; + writeUint8(bitmap); +} + +/* ---------------- Cayenne LPP format ---------- */ + +CayenneLPP::CayenneLPP(uint8_t size) { + buffer = (uint8_t *)malloc(size); + cursor = 0; +} + +CayenneLPP::~CayenneLPP(void) { free(buffer); } + +void CayenneLPP::reset(void) { cursor = 0; } + +uint8_t CayenneLPP::getSize(void) { return cursor; } + +uint8_t *CayenneLPP::getBuffer(void) { return buffer; } + +void CayenneLPP::addCount(uint16_t value1, uint16_t value2) { + buffer[cursor++] = LPP_COUNT_WIFI_CHANNEL; + buffer[cursor++] = LPP_ANALOG_INPUT; // workaround, type meter not found? + buffer[cursor++] = value1 >> 8; + buffer[cursor++] = value1; + buffer[cursor++] = LPP_COUNT_BLE_CHANNEL; + buffer[cursor++] = LPP_ANALOG_INPUT; // workaround, type meter not found? + buffer[cursor++] = value1 >> 8; + buffer[cursor++] = value2; +} + +#ifdef HAS_GPS +void CayenneLPP::addGPS(gpsStatus_t value) { + int32_t lat = value.latitude / 100; + int32_t lon = value.longitude / 100; + int32_t alt = value.altitude; + buffer[cursor++] = LPP_GPS_CHANNEL; + buffer[cursor++] = LPP_GPS; + buffer[cursor++] = lat >> 16; + buffer[cursor++] = lat >> 8; + buffer[cursor++] = lat; + buffer[cursor++] = lon >> 16; + buffer[cursor++] = lon >> 8; + buffer[cursor++] = lon; + buffer[cursor++] = alt >> 16; + buffer[cursor++] = alt >> 8; + buffer[cursor++] = alt; +} +#endif + +void CayenneLPP::addConfig(configData_t value) { + buffer[cursor++] = LPP_ADR_CHANNEL; + buffer[cursor++] = LPP_DIGITAL_INPUT; + buffer[cursor++] = value.adrmode; +} + +void CayenneLPP::addStatus(uint16_t voltage, uint64_t uptime, float cputemp) { + buffer[cursor++] = LPP_BATT_CHANNEL; + buffer[cursor++] = LPP_ANALOG_INPUT; + buffer[cursor++] = voltage >> 8; + buffer[cursor++] = voltage; + buffer[cursor++] = LPP_TEMP_CHANNEL; + buffer[cursor++] = LPP_TEMPERATURE; + buffer[cursor++] = (uint16_t)cputemp >> 8; + buffer[cursor++] = (uint16_t)cputemp; +} \ No newline at end of file diff --git a/src/payload.h b/src/payload.h new file mode 100644 index 00000000..53cda054 --- /dev/null +++ b/src/payload.h @@ -0,0 +1,95 @@ + +#ifndef _PAYLOAD_H_ +#define _PAYLOAD_H_ + +#include + +// MyDevices CayenneLPP channels +#define LPP_GPS_CHANNEL 20 +#define LPP_COUNT_WIFI_CHANNEL 21 +#define LPP_COUNT_BLE_CHANNEL 22 +#define LPP_BATT_CHANNEL 23 +#define LPP_ADR_CHANNEL 25 +#define LPP_TEMP_CHANNEL 26 +// MyDevices CayenneLPP types +#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 +#define LPP_DIGITAL_INPUT 0 // 1 byte +#define LPP_DIGITAL_OUTPUT 1 // 1 byte +#define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed +#define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned + +class TTNplain { +public: + TTNplain(uint8_t size); + ~TTNplain(); + + void reset(void); + uint8_t getSize(void); + uint8_t *getBuffer(void); + + void addCount(uint16_t value1, uint16_t value2); + void addConfig(configData_t value); + void addStatus(uint16_t voltage, uint64_t uptime, float cputemp); +#ifdef HAS_GPS + void addGPS(gpsStatus_t value); +#endif + +private: + uint8_t *buffer; + uint8_t cursor; +}; + +class TTNpacked { +public: + TTNpacked(uint8_t size); + ~TTNpacked(); + + void reset(void); + uint8_t getSize(void); + uint8_t *getBuffer(void); + + void addCount(uint16_t value1, uint16_t value2); + void addConfig(configData_t value); + void addStatus(uint16_t voltage, uint64_t uptime, float cputemp); +#ifdef HAS_GPS + void addGPS(gpsStatus_t value); +#endif + +private: + uint8_t *buffer; + uint8_t cursor; + void intToBytes(uint8_t pos, int32_t i, uint8_t byteSize); + void writeUptime(uint64_t unixtime); + void writeLatLng(double latitude, double longitude); + void writeUint16(uint16_t i); + void writeUint8(uint8_t i); + void writeHumidity(float humidity); + void writeTemperature(float temperature); + void writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f, bool g, + bool h); +}; + +class CayenneLPP { +public: + CayenneLPP(uint8_t size); + ~CayenneLPP(); + + void reset(void); + uint8_t getSize(void); + uint8_t *getBuffer(void); + + void addCount(uint16_t value1, uint16_t value2); + void addConfig(configData_t value); + void addStatus(uint16_t voltage, uint64_t uptime, float cputemp); +#ifdef HAS_GPS + void addGPS(gpsStatus_t value); +#endif + +private: + uint8_t *buffer; + uint8_t maxsize; + uint8_t cursor; +}; + +#endif // _PAYLOAD_H_ diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 6b446f78..1178892a 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -27,7 +27,7 @@ void antenna_select(const uint8_t _ant); // function defined in adcread.cpp #ifdef HAS_BATTERY_PROBE -uint32_t read_voltage(void); +uint16_t read_voltage(void); #endif // function sends result of get commands to LoRaWAN network @@ -40,20 +40,13 @@ void do_transmit(osjob_t *j) { os_setTimedCallback(&rcmdjob, os_getTime() + sec2osticks(RETRANSMIT_RCMD), do_transmit); } - LMIC_setTxData2(RCMDPORT, rcmd_data, rcmd_data_size, - 0); // send data unconfirmed on RCMD Port - ESP_LOGI(TAG, "%d bytes queued to send", rcmd_data_size); + // send payload + LMIC_setTxData2(RCMDPORT, payload.getBuffer(), payload.getSize(), + (cfg.countermode & 0x02)); + ESP_LOGI(TAG, "%d bytes queued to send", payload.getSize()); sprintf(display_lmic, "PACKET QUEUED"); } -// help function to transmit result of get commands, since callback function -// do_transmit() cannot have params -void transmit(xref2u1_t mydata, u1_t mydata_size) { - rcmd_data = mydata; - rcmd_data_size = mydata_size; - do_transmit(&rcmdjob); -} - // help function to assign LoRa datarates to numeric spreadfactor values void switch_lora(uint8_t sf, uint8_t tx) { if (tx > 20) @@ -292,41 +285,33 @@ void set_lorapower(uint8_t val) { }; void get_config(uint8_t val) { - ESP_LOGI(TAG, "Remote command: get configuration"); - transmit((byte *)&cfg, sizeof(cfg)); + ESP_LOGI(TAG, "Remote command: get device configuration"); + payload.reset(); + payload.addConfig(cfg); + do_transmit(&rcmdjob); }; -void get_uptime(uint8_t val) { - ESP_LOGI(TAG, "Remote command: get uptime"); - transmit((byte *)&uptimecounter, sizeof(uptimecounter)); -}; - -void get_cputemp(uint8_t val) { - ESP_LOGI(TAG, "Remote command: get cpu temperature"); - float temp = temperatureRead(); - transmit((byte *)&temp, sizeof(temp)); -}; - -void get_voltage(uint8_t val) { - ESP_LOGI(TAG, "Remote command: get battery voltage"); +void get_status(uint8_t val) { + ESP_LOGI(TAG, "Remote command: get device status"); #ifdef HAS_BATTERY_PROBE uint16_t voltage = read_voltage(); #else uint16_t voltage = 0; #endif - transmit((byte *)&voltage, sizeof(voltage)); + payload.reset(); + payload.addStatus(voltage, uptimecounter, temperatureRead()); + do_transmit(&rcmdjob); }; void get_gps(uint8_t val) { ESP_LOGI(TAG, "Remote command: get gps status"); #ifdef HAS_GPS gps_read(); - transmit((byte *)&gps_status, sizeof(gps_status)); - ESP_LOGI(TAG, "lat=%f / lon=%f | Sats=%u | HDOP=%u | Alti=%u", - gps_status.latitude / 1000000, gps_status.longitude / 1000000, - gps_status.satellites, gps_status.hdop, gps_status.altitude); + payload.reset(); + payload.addGPS(gps_status); + do_transmit(&rcmdjob); #else - ESP_LOGE(TAG, "GPS not present"); + ESP_LOGW(TAG, "GPS function not supported"); #endif }; @@ -341,8 +326,7 @@ cmd_t table[] = {{0x01, set_rssi, true}, {0x02, set_countmode, true}, {0x0b, set_wifichancycle, true}, {0x0c, set_blescantime, true}, {0x0d, set_vendorfilter, false}, {0x0e, set_blescan, true}, {0x0f, set_wifiant, true}, {0x10, set_rgblum, true}, - {0x80, get_config, false}, {0x81, get_uptime, false}, - {0x82, get_cputemp, false}, {0x83, get_voltage, false}, + {0x80, get_config, false}, {0x81, get_status, false}, {0x84, get_gps, false}}; // check and execute remote command