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