commit
e3f7394cd4
89
README.md
89
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).<br>
|
||||
Hardware dependent settings (pinout etc.) are stored in board files in /hal directory.<br>
|
||||
@ -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
|
||||
|
||||
|
@ -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}
|
||||
|
13
src/TTN/packed_converter.js
Normal file
13
src/TTN/packed_converter.js
Normal file
@ -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;
|
||||
}
|
167
src/TTN/packed_decoder.js
Normal file
167
src/TTN/packed_decoder.js
Normal file
@ -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
|
||||
};
|
||||
}
|
18
src/TTN/plain_converter.js
Normal file
18
src/TTN/plain_converter.js
Normal file
@ -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;
|
||||
}
|
23
src/TTN/plain_decoder.js
Normal file
23
src/TTN/plain_decoder.js
Normal file
@ -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;
|
||||
}
|
@ -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;
|
||||
@ -70,5 +39,19 @@ extern uint16_t macs_total, macs_wifi, macs_ble; // MAC counters
|
||||
extern std::set<uint16_t> 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
|
||||
|
||||
#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
|
@ -1,14 +1,15 @@
|
||||
#ifdef HAS_GPS
|
||||
|
||||
#include "globals.h"
|
||||
#include <Wire.h>
|
||||
|
||||
// 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
|
||||
Wire.beginTransmission(GPS_ADDR);
|
||||
while (cfg.gpsmode) {
|
||||
// feed GPS decoder with serial NMEA data from GPS device
|
||||
Wire.requestFrom(GPS_ADDR | 0x01, 32);
|
||||
while (Wire.available()) {
|
||||
Wire.requestFrom(GPS_I2C_ADDRESS_READ, 255);
|
||||
gps.encode(Wire.read());
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS); // polling mode: 500ms sleep
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Wire.endTransmission();
|
||||
Wire.setClock(400000); // Set back to 400KHz to speed up OLED
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // GPS Type
|
||||
}
|
||||
|
||||
vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
/*
|
||||
|
@ -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
|
||||
#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO21 -- SD1306 D1+D2
|
||||
#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO22 -- SD1306 D0
|
@ -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
|
||||
|
@ -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",
|
||||
|
62
src/main.cpp
62
src/main.cpp
@ -57,8 +57,6 @@ 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
|
||||
|
||||
#ifdef HAS_GPS
|
||||
gpsStatus_t gps_status; // struct for storing gps data
|
||||
@ -72,6 +70,17 @@ portMUX_TYPE timerMux =
|
||||
std::set<uint16_t> 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
|
||||
|
34
src/main.h
34
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)
|
||||
|
@ -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 %)
|
||||
|
246
src/payload.cpp
Normal file
246
src/payload.cpp
Normal file
@ -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;
|
||||
}
|
95
src/payload.h
Normal file
95
src/payload.h
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
#ifndef _PAYLOAD_H_
|
||||
#define _PAYLOAD_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// 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_
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user