diff --git a/README.md b/README.md
index a71c92f9..243d4ea8 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.
@@ -87,16 +90,16 @@ Note: If you use this software you do this at your own risk. That means that you
Paxcounter generates identifiers for sniffed MAC adresses and collects them temporary in the device's RAM for a configurable scan cycle time (default 240 seconds). After each scan cycle the collected identifiers are cleared. Identifiers are generated by salting and hashing MAC adresses. The random salt value changes after each scan cycle. Identifiers and MAC adresses are never transferred to the LoRaWAN network. No persistent storing of MAC adresses, identifiers or timestamps and no other kind of analytics than counting are implemented in this code. Wireless networks are not touched by this code, but MAC adresses from wireless devices as well within as not within wireless networks, regardless if encrypted or unencrypted, are sniffed and processed by this code. If the bluetooth option in the code is enabled, bluetooth MACs are scanned and processed by the included BLE stack, then hashed and counted by this code.
-# LED
+# LED blink pattern
-Legend for mono color on board LED:
+**Mono color LED:**
- Single Flash (50ms): seen a new Wifi or BLE device
- Quick blink (20ms on each 1/5 second): joining LoRaWAN network in progress or pending
- Small blink (10ms on each 1/2 second): LoRaWAN data transmit in progress or pending
- Long blink (200ms on each 2 seconds): LoRaWAN stack error
-Legend for RGB LED (LoPy/LoPy4/FiPy/Lolin32 only):
+**RGB LED:**
- Green each blink: seen a new Wifi device
- Magenta each blink: seen a new BLE device
@@ -104,7 +107,21 @@ Legend for RGB LED (LoPy/LoPy4/FiPy/Lolin32 only):
- Blue blink: LoRaWAN data transmit in progress or pending
- Red long blink: LoRaWAN stack error
-# Payload
+# Payload format
+
+You can select 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. This way your MQTT application can parse the fields `pax`, `ble` and `wifi`.
+
+To track a paxcounter device with on board GPS 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. The formats *plain* and *packed* generate the fields `latitude`, `longitude` and `hdop` required by ttnmapper.
+
+Hereafter described is the default *plain* format.
**LoRaWAN Port #1:**
@@ -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..99415985 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,19 +112,21 @@ 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}
-include "src/hal/ttgobeam.h"
[env:fipy]
-platform = espressif32@1.0.1
+platform = ${common_env_data.platform_espressif32}
framework = arduino
board = esp32dev
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..6f4d1255 100644
--- a/src/hal/ttgov21.h
+++ b/src/hal/ttgov21.h
@@ -1,12 +1,19 @@
-// Hardware related definitions for TTGO V2.1 Board
+/* Hardware related definitions for TTGO V2.1 Board
+/ ATTENTION: check your board version!
+/ Different versions are on the market which need different settings in this file:
+/ - without label -> "old"
+/ - labelled v1.5 on pcb -> "old"
+/ - labelled v1.6 on pcb -> "new"
+*/
+
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define DISPLAY_FLIP 1 // rotated display
-#define HAS_LED GPIO_NUM_23 // green on board LED_G3 (not in initial board version)
-#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
-#define BATT_FACTOR 2 // voltage divider 100k/100k on board
+#define HAS_LED GPIO_NUM_23 // green on board LED (new board ONLY)
+#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // uses GPIO7 (new board ONLY)
+#define BATT_FACTOR 2 // voltage divider 100k/100k on board (new board ONLY)
// re-define pin definitions of pins_arduino.h
#define PIN_SPI_SS GPIO_NUM_18 // ESP32 GPIO18 (Pin18) -- HPD13A NSS/SEL (Pin4) SPI Chip Select Input
@@ -15,12 +22,14 @@
#define PIN_SPI_SCK GPIO_NUM_5 // ESP32 GPIO5 (Pin5) -- HPD13A SCK (Pin5) SPI Clock Input
// non arduino pin definitions
-#define RST LMIC_UNUSED_PIN // connected to ESP32 RST/EN
+#define RST LMIC_UNUSED_PIN // connected to ESP32 RST/EN (old board)
+//#define RST GPIO_NUM_12 // (old board v1.5)
+//#define RST GPIO_NUM_19 //(new board)
#define DIO0 GPIO_NUM_26 // ESP32 GPIO26 <-> HPD13A IO0
#define DIO1 GPIO_NUM_33 // ESP32 GPIO33 <-> HPDIO1 <-> HPD13A IO1
#define DIO2 GPIO_NUM_32 // ESP32 GPIO32 <-> HPDIO2 <-> HPD13A IO2
// 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