Merge branch 'master' of https://github.com/cyberman54/ESP32-Paxcounter
This commit is contained in:
commit
08a0e2cf05
97
README.md
97
README.md
@ -20,23 +20,26 @@ This can all be done with a single small and cheap ESP32 board for less than $20
|
|||||||
# Hardware
|
# Hardware
|
||||||
|
|
||||||
Supported ESP32 based LoRa IoT boards:
|
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;
|
- **Heltec LoRa-32**
|
||||||
b) on board RGB LED supported;
|
- **TTGOv1**
|
||||||
c) on board Hardware unique DEVEUI supported;
|
- **TTGOv2**
|
||||||
d) external wiring needed, see instructions in board.h file;
|
- **TTGOv2.1**
|
||||||
e) battery voltage monitoring supported;
|
- **TTGO T-Beam**
|
||||||
f) on board GPS supported (for Pycom PyTrack expansion needed)
|
- **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>
|
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>
|
Hardware dependent settings (pinout etc.) are stored in board files in /hal directory.<br>
|
||||||
@ -87,16 +90,16 @@ Note: If you use this software you do this at your own risk. That means that you
|
|||||||
|
|
||||||
Paxcounter generates identifiers for sniffed MAC adresses and collects them temporary in the device's RAM for a configurable scan cycle time (default 240 seconds). After each scan cycle the collected identifiers are cleared. Identifiers are generated by salting and hashing MAC adresses. The random salt value changes after each scan cycle. Identifiers and MAC adresses are never transferred to the LoRaWAN network. No persistent storing of MAC adresses, identifiers or timestamps and no other kind of analytics than counting are implemented in this code. Wireless networks are not touched by this code, but MAC adresses from wireless devices as well within as not within wireless networks, regardless if encrypted or unencrypted, are sniffed and processed by this code. If the bluetooth option in the code is enabled, bluetooth MACs are scanned and processed by the included BLE stack, then hashed and counted by this code.
|
Paxcounter generates identifiers for sniffed MAC adresses and collects them temporary in the device's RAM for a configurable scan cycle time (default 240 seconds). After each scan cycle the collected identifiers are cleared. Identifiers are generated by salting and hashing MAC adresses. The random salt value changes after each scan cycle. Identifiers and MAC adresses are never transferred to the LoRaWAN network. No persistent storing of MAC adresses, identifiers or timestamps and no other kind of analytics than counting are implemented in this code. Wireless networks are not touched by this code, but MAC adresses from wireless devices as well within as not within wireless networks, regardless if encrypted or unencrypted, are sniffed and processed by this code. If the bluetooth option in the code is enabled, bluetooth MACs are scanned and processed by the included BLE stack, then hashed and counted by this code.
|
||||||
|
|
||||||
# LED
|
# LED blink pattern
|
||||||
|
|
||||||
Legend for mono color on board LED:
|
**Mono color LED:**
|
||||||
|
|
||||||
- Single Flash (50ms): seen a new Wifi or BLE device
|
- Single Flash (50ms): seen a new Wifi or BLE device
|
||||||
- Quick blink (20ms on each 1/5 second): joining LoRaWAN network in progress or pending
|
- Quick blink (20ms on each 1/5 second): joining LoRaWAN network in progress or pending
|
||||||
- Small blink (10ms on each 1/2 second): LoRaWAN data transmit in progress or pending
|
- Small blink (10ms on each 1/2 second): LoRaWAN data transmit in progress or pending
|
||||||
- Long blink (200ms on each 2 seconds): LoRaWAN stack error
|
- Long blink (200ms on each 2 seconds): LoRaWAN stack error
|
||||||
|
|
||||||
Legend for RGB LED (LoPy/LoPy4/FiPy/Lolin32 only):
|
**RGB LED:**
|
||||||
|
|
||||||
- Green each blink: seen a new Wifi device
|
- Green each blink: seen a new Wifi device
|
||||||
- Magenta each blink: seen a new BLE device
|
- Magenta each blink: seen a new BLE device
|
||||||
@ -104,7 +107,21 @@ Legend for RGB LED (LoPy/LoPy4/FiPy/Lolin32 only):
|
|||||||
- Blue blink: LoRaWAN data transmit in progress or pending
|
- Blue blink: LoRaWAN data transmit in progress or pending
|
||||||
- Red long blink: LoRaWAN stack error
|
- Red long blink: LoRaWAN stack error
|
||||||
|
|
||||||
# Payload
|
# Payload format
|
||||||
|
|
||||||
|
You can select different payload formats in [paxcounter.conf](src/paxcounter.conf#L40):
|
||||||
|
|
||||||
|
- ***Plain*** uses big endian format and generates json fields, e.g. useful for TTN console
|
||||||
|
|
||||||
|
- ***Packed*** uses little endian format and generates json fields
|
||||||
|
|
||||||
|
- [***CayenneLPP***](https://mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload-reference-implementation) generates MyDevices Cayenne readable fields
|
||||||
|
|
||||||
|
If you're using [TheThingsNetwork](https://www.thethingsnetwork.org/) (TTN) you may want to use a payload converter. Go to TTN Console - Application - Payload Formats and paste the code example below in tabs Decoder and Converter. This way your MQTT application can parse the fields `pax`, `ble` and `wifi`.
|
||||||
|
|
||||||
|
To track a paxcounter device with on board GPS and at the same time contribute to TTN coverage mapping, you simply activate the [TTNmapper integration](https://www.thethingsnetwork.org/docs/applications/ttnmapper/) in TTN Console. The formats *plain* and *packed* generate the fields `latitude`, `longitude` and `hdop` required by ttnmapper.
|
||||||
|
|
||||||
|
Hereafter described is the default *plain* format.
|
||||||
|
|
||||||
**LoRaWAN Port #1:**
|
**LoRaWAN Port #1:**
|
||||||
|
|
||||||
@ -125,11 +142,7 @@ Legend for RGB LED (LoPy/LoPy4/FiPy/Lolin32 only):
|
|||||||
|
|
||||||
- see remote control -
|
- 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`.
|
[**plain_decoder.js**](src/TTN/plain_decoder.js)
|
||||||
|
|
||||||
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:**
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function Decoder(bytes, port) {
|
function Decoder(bytes, port) {
|
||||||
@ -140,11 +153,11 @@ function Decoder(bytes, port) {
|
|||||||
decoded.wifi = (bytes[i++] << 8) | bytes[i++];
|
decoded.wifi = (bytes[i++] << 8) | bytes[i++];
|
||||||
decoded.ble = (bytes[i++] << 8) | bytes[i++];
|
decoded.ble = (bytes[i++] << 8) | bytes[i++];
|
||||||
if (bytes.length > 4) {
|
if (bytes.length > 4) {
|
||||||
decoded.latitude = ( (bytes[i++]) | (bytes[i++] << 8) | (bytes[i++] << 16) | bytes[i++] << 24 );
|
decoded.latitude = ( (bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++] );
|
||||||
decoded.longitude = ( (bytes[i++]) | (bytes[i++] << 8) | (bytes[i++] << 16) | bytes[i++] << 24 );
|
decoded.longitude = ( (bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++] );
|
||||||
decoded.sats = ( bytes[i++] | (bytes[i++] << 8) );
|
decoded.sats = ( bytes[i++] );
|
||||||
decoded.hdop = ( bytes[i++] | (bytes[i++] << 8) );
|
decoded.hdop = ( bytes[i++] << 8) | (bytes[i++] );
|
||||||
decoded.altitude = ( bytes[i++] | (bytes[i++] << 8) );
|
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
|
```javascript
|
||||||
function Converter(decoded, port) {
|
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
|
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 1: Lora SF (7..12) [default 9]
|
||||||
byte 2: Lora TXpower (2..15) [default 15]
|
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]
|
byte 16: GPS send data mode (1=on, 0=ff) [default 1]
|
||||||
bytes 17-27: Software version (ASCII format, terminating with zero)
|
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)
|
bytes 1-2: Battery voltage in millivolt, 0 if unreadable
|
||||||
|
bytes 3-10: Uptime in seconds
|
||||||
0x82 get device cpu temperature
|
bytes 11-14: Chip temperature in degrees celsius
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
0x84 get device GPS status
|
0x84 get device GPS status
|
||||||
|
|
||||||
bytes 1-4: Latitude
|
bytes 1-4: Latitude
|
||||||
bytes 5-8: Longitude
|
bytes 5-8: Longitude
|
||||||
byte 9-10: Number of satellites
|
byte 9: Number of satellites
|
||||||
byte 11-12: HDOP
|
byte 10-11: HDOP
|
||||||
bytes 13-14: altidute [meter]
|
bytes 12-13: altidute [meter]
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
|
|||||||
[common_env_data]
|
[common_env_data]
|
||||||
platform_espressif32 = espressif32@>=1.0.2
|
platform_espressif32 = espressif32@>=1.0.2
|
||||||
board_build.partitions = no_ota.csv
|
board_build.partitions = no_ota.csv
|
||||||
|
lib_deps_all =
|
||||||
lib_deps_display =
|
lib_deps_display =
|
||||||
U8g2@>=2.22.14
|
U8g2@>=2.22.14
|
||||||
lib_deps_rgbled =
|
lib_deps_rgbled =
|
||||||
@ -55,6 +56,7 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_display}
|
${common_env_data.lib_deps_display}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
@ -68,6 +70,7 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_display}
|
${common_env_data.lib_deps_display}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
@ -81,6 +84,7 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_display}
|
${common_env_data.lib_deps_display}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
@ -94,6 +98,7 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_display}
|
${common_env_data.lib_deps_display}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
@ -107,19 +112,21 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_gps}
|
${common_env_data.lib_deps_gps}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
-include "src/hal/ttgobeam.h"
|
-include "src/hal/ttgobeam.h"
|
||||||
|
|
||||||
[env:fipy]
|
[env:fipy]
|
||||||
platform = espressif32@1.0.1
|
platform = ${common_env_data.platform_espressif32}
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
board_build.partitions = ${common_env_data.board_build.partitions}
|
board_build.partitions = ${common_env_data.board_build.partitions}
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_rgbled}
|
${common_env_data.lib_deps_rgbled}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
@ -133,7 +140,9 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_rgbled}
|
${common_env_data.lib_deps_rgbled}
|
||||||
|
${common_env_data.lib_deps_gps}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
-include "src/hal/lopy.h"
|
-include "src/hal/lopy.h"
|
||||||
@ -146,7 +155,9 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_rgbled}
|
${common_env_data.lib_deps_rgbled}
|
||||||
|
${common_env_data.lib_deps_gps}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
-include "src/hal/lopy4.h"
|
-include "src/hal/lopy4.h"
|
||||||
@ -159,6 +170,7 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 256000
|
upload_speed = 256000
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_rgbled}
|
${common_env_data.lib_deps_rgbled}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.build_flags}
|
${common_env_data.build_flags}
|
||||||
@ -172,6 +184,7 @@ board_build.partitions = ${common_env_data.board_build.partitions}
|
|||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
upload_speed = 921600
|
upload_speed = 921600
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_all}
|
||||||
${common_env_data.lib_deps_rgbled}
|
${common_env_data.lib_deps_rgbled}
|
||||||
build_flags =
|
build_flags =
|
||||||
${common_env_data.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 "rgb_led.h"
|
||||||
#include "macsniff.h"
|
#include "macsniff.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "payload.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
|
|
||||||
|
|
||||||
extern configData_t cfg;
|
extern configData_t cfg;
|
||||||
extern uint64_t uptimecounter;
|
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 uint16_t macs_total, macs_wifi, macs_ble; // MAC counters
|
||||||
extern std::set<uint16_t> macs;
|
extern std::set<uint16_t> macs;
|
||||||
extern hw_timer_t
|
extern hw_timer_t
|
||||||
*channelSwitch; // hardware timer used for wifi channel switching
|
*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
|
#ifdef HAS_GPS
|
||||||
|
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
// Local logging tag
|
// Local logging tag
|
||||||
static const char TAG[] = "main";
|
static const char TAG[] = "main";
|
||||||
|
|
||||||
// read GPS data and cast to global struct
|
// read GPS data and cast to global struct
|
||||||
void gps_read() {
|
void gps_read() {
|
||||||
gps_status.latitude = (uint32_t)(gps.location.lat() * 1000000);
|
gps_status.latitude = (uint32_t)(gps.location.lat() * 1e6);
|
||||||
gps_status.longitude = (uint32_t)(gps.location.lng() * 1000000);
|
gps_status.longitude = (uint32_t)(gps.location.lng() * 1e6);
|
||||||
gps_status.satellites = (uint8_t)gps.satellites.value();
|
gps_status.satellites = (uint8_t)gps.satellites.value();
|
||||||
gps_status.hdop = (uint16_t)gps.hdop.value();
|
gps_status.hdop = (uint16_t)gps.hdop.value();
|
||||||
gps_status.altitude = (uint16_t)gps.altitude.meters();
|
gps_status.altitude = (uint16_t)gps.altitude.meters();
|
||||||
@ -22,8 +23,8 @@ void gps_loop(void *pvParameters) {
|
|||||||
// initialize and, if needed, configure, GPS
|
// initialize and, if needed, configure, GPS
|
||||||
#if defined GPS_SERIAL
|
#if defined GPS_SERIAL
|
||||||
HardwareSerial GPS_Serial(1);
|
HardwareSerial GPS_Serial(1);
|
||||||
#elif defined GPS_I2C
|
#elif defined GPS_QUECTEL_L76
|
||||||
// to be done
|
Wire.begin(GPS_QUECTEL_L76, 400000); // I2C connect to GPS device with 400 KHz
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
@ -44,30 +45,29 @@ void gps_loop(void *pvParameters) {
|
|||||||
// after GPS function was disabled, close connect to GPS device
|
// after GPS function was disabled, close connect to GPS device
|
||||||
GPS_Serial.end();
|
GPS_Serial.end();
|
||||||
|
|
||||||
#elif defined GPS_I2C
|
#elif defined GPS_QUECTEL_L76
|
||||||
|
|
||||||
// I2C connect to GPS device with 100 kHz
|
Wire.beginTransmission(GPS_ADDR);
|
||||||
Wire.begin(GPS_I2C_PINS, 100000);
|
Wire.write(0x00); // dummy write to start read
|
||||||
Wire.beginTransmission(GPS_I2C_ADDRESS_WRITE);
|
Wire.endTransmission();
|
||||||
Wire.write(0x00);
|
|
||||||
|
|
||||||
i2c_ret == Wire.beginTransmission(GPS_I2C_ADDRESS_READ);
|
Wire.beginTransmission(GPS_ADDR);
|
||||||
if (i2c_ret == 0) { // check if device seen on i2c bus
|
while (cfg.gpsmode) {
|
||||||
while (cfg.gpsmode) {
|
Wire.requestFrom(GPS_ADDR | 0x01, 32);
|
||||||
// feed GPS decoder with serial NMEA data from GPS device
|
while (Wire.available()) {
|
||||||
while (Wire.available()) {
|
gps.encode(Wire.read());
|
||||||
Wire.requestFrom(GPS_I2C_ADDRESS_READ, 255);
|
vTaskDelay(1 / portTICK_PERIOD_MS); // polling mode: 500ms sleep
|
||||||
gps.encode(Wire.read());
|
|
||||||
vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// after GPS function was disabled, close connect to GPS device
|
|
||||||
|
|
||||||
Wire.endTransmission();
|
ESP_LOGI(TAG, "GPS NMEA data: passed %d / failed: %d / with fix: %d",
|
||||||
Wire.setClock(400000); // Set back to 400KHz to speed up OLED
|
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
|
vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog
|
||||||
|
@ -5,13 +5,13 @@
|
|||||||
#define HAS_RGB_LED GPIO_NUM_0 // WS2812B RGB LED on GPIO0
|
#define HAS_RGB_LED GPIO_NUM_0 // WS2812B RGB LED on GPIO0
|
||||||
|
|
||||||
// !!EXPERIMENTAL - not tested yet!!
|
// !!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
|
// see http://www.quectel.com/UploadImage/Downlad/Quectel_L76-L_I2C_Application_Note_V1.0.pdf
|
||||||
//#define HAS_GPS 1
|
//#define HAS_GPS 1
|
||||||
//#define GPS_I2C_PINS GPIO_NUM_9, GPIO_NUM_8 // SDA, SCL
|
//#define GPS_QUECTEL_L76 GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21)
|
||||||
//#define GPS_I2C_ADDRESS_READ 0x21
|
//#define GPS_ADDR 0x10
|
||||||
//#define GPS_I2C_ADDRESS_WRITE 0x20
|
//#define HAS_BUTTON GPIO_NUM_37 // (P14)
|
||||||
//#define HAS_BUTTON GPIO_NUM_4
|
//#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown
|
||||||
|
|
||||||
// Hardware pin definitions for Pycom LoPy board
|
// Hardware pin definitions for Pycom LoPy board
|
||||||
#define PIN_SPI_SS GPIO_NUM_17
|
#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_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
|
#define HAS_RGB_LED GPIO_NUM_0 // WS2812B RGB LED on GPIO0
|
||||||
|
|
||||||
// !!EXPERIMENTAL - not tested yet!!f
|
// !!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
|
// see http://www.quectel.com/UploadImage/Downlad/Quectel_L76-L_I2C_Application_Note_V1.0.pdf
|
||||||
//#define HAS_GPS 1
|
//#define HAS_GPS 1
|
||||||
//#define GPS_I2C_PINS GPIO_NUM_9, GPIO_NUM_8 // SDA, SCL
|
//#define GPS_QUECTEL_L76 GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21)
|
||||||
//#define GPS_I2C_ADDRESS_READ 0x21
|
//#define GPS_ADDR 0x10
|
||||||
//#define GPS_I2C_ADDRESS_WRITE 0x20
|
//#define HAS_BUTTON GPIO_NUM_37 // (P14)
|
||||||
//#define HAS_BUTTON GPIO_NUM_4
|
//#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown
|
||||||
|
|
||||||
// Hardware pin definitions for Pycom LoPy4 board
|
// Hardware pin definitions for Pycom LoPy4 board
|
||||||
#define PIN_SPI_SS GPIO_NUM_18
|
#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
|
// 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_RST U8X8_PIN_NONE // connected to CPU RST/EN
|
||||||
#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO4 (Pin4) -- SD1306 D1+D2
|
#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO21 -- SD1306 D1+D2
|
||||||
#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO15 (Pin15) -- SD1306 D0
|
#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO22 -- SD1306 D0
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,12 +1,19 @@
|
|||||||
// Hardware related definitions for TTGO V2.1 Board
|
/* Hardware related definitions for TTGO V2.1 Board
|
||||||
|
/ ATTENTION: check your board version!
|
||||||
|
/ Different versions are on the market which need different settings in this file:
|
||||||
|
/ - without label -> "old"
|
||||||
|
/ - labelled v1.5 on pcb -> "old"
|
||||||
|
/ - labelled v1.6 on pcb -> "new"
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
|
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
|
||||||
|
|
||||||
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
|
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
|
||||||
#define DISPLAY_FLIP 1 // rotated display
|
#define DISPLAY_FLIP 1 // rotated display
|
||||||
#define HAS_LED GPIO_NUM_23 // green on board LED_G3 (not in initial board version)
|
#define HAS_LED GPIO_NUM_23 // green on board LED (new board ONLY)
|
||||||
#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // uses GPIO7 (new board ONLY)
|
||||||
#define BATT_FACTOR 2 // voltage divider 100k/100k on board
|
#define BATT_FACTOR 2 // voltage divider 100k/100k on board (new board ONLY)
|
||||||
|
|
||||||
// re-define pin definitions of pins_arduino.h
|
// re-define pin definitions of pins_arduino.h
|
||||||
#define PIN_SPI_SS GPIO_NUM_18 // ESP32 GPIO18 (Pin18) -- HPD13A NSS/SEL (Pin4) SPI Chip Select Input
|
#define PIN_SPI_SS GPIO_NUM_18 // ESP32 GPIO18 (Pin18) -- HPD13A NSS/SEL (Pin4) SPI Chip Select Input
|
||||||
@ -15,12 +22,14 @@
|
|||||||
#define PIN_SPI_SCK GPIO_NUM_5 // ESP32 GPIO5 (Pin5) -- HPD13A SCK (Pin5) SPI Clock Input
|
#define PIN_SPI_SCK GPIO_NUM_5 // ESP32 GPIO5 (Pin5) -- HPD13A SCK (Pin5) SPI Clock Input
|
||||||
|
|
||||||
// non arduino pin definitions
|
// non arduino pin definitions
|
||||||
#define RST LMIC_UNUSED_PIN // connected to ESP32 RST/EN
|
#define RST LMIC_UNUSED_PIN // connected to ESP32 RST/EN (old board)
|
||||||
|
//#define RST GPIO_NUM_12 // (old board v1.5)
|
||||||
|
//#define RST GPIO_NUM_19 //(new board)
|
||||||
#define DIO0 GPIO_NUM_26 // ESP32 GPIO26 <-> HPD13A IO0
|
#define DIO0 GPIO_NUM_26 // ESP32 GPIO26 <-> HPD13A IO0
|
||||||
#define DIO1 GPIO_NUM_33 // ESP32 GPIO33 <-> HPDIO1 <-> HPD13A IO1
|
#define DIO1 GPIO_NUM_33 // ESP32 GPIO33 <-> HPDIO1 <-> HPD13A IO1
|
||||||
#define DIO2 GPIO_NUM_32 // ESP32 GPIO32 <-> HPDIO2 <-> HPD13A IO2
|
#define DIO2 GPIO_NUM_32 // ESP32 GPIO32 <-> HPDIO2 <-> HPD13A IO2
|
||||||
|
|
||||||
// Hardware pin definitions for TTGO V2 Board with OLED SSD1306 0,96" I2C Display
|
// 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_RST U8X8_PIN_NONE // connected to CPU RST/EN
|
||||||
#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO4 (Pin4) -- SD1306 D1+D2
|
#define OLED_SDA GPIO_NUM_21 // ESP32 GPIO21 -- SD1306 D1+D2
|
||||||
#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO15 (Pin15) -- SD1306 D0
|
#define OLED_SCL GPIO_NUM_22 // ESP32 GPIO22 -- SD1306 D0
|
@ -117,42 +117,21 @@ void do_send(osjob_t *j) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare payload with sum of unique WIFI MACs seen
|
// Prepare payload with counter and, if applicable, gps data
|
||||||
static uint8_t mydata[4];
|
payload.reset();
|
||||||
|
payload.addCount(macs_wifi, cfg.blescan ? macs_ble : 0);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAS_GPS
|
#ifdef HAS_GPS
|
||||||
static uint8_t gpsdata[18];
|
if ((cfg.gpsmode) && (gps.location.isValid())) {
|
||||||
if (cfg.gpsmode && gps.location.isValid()) {
|
|
||||||
gps_read();
|
gps_read();
|
||||||
memcpy(gpsdata, mydata, 4);
|
payload.addGPS(gps_status);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
#endif
|
#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");
|
sprintf(display_lmic, "PACKET QUEUED");
|
||||||
|
|
||||||
// clear counter if not in cumulative counter mode
|
// 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
|
// Log scan result
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGD(TAG,
|
||||||
"%s %s RSSI %ddBi -> MAC %s -> Hash %04X -> WiFi:%d BLTH:%d -> "
|
"%s %s RSSI %ddBi -> MAC %s -> Hash %04X -> WiFi:%d BLTH:%d -> "
|
||||||
"%d Bytes left",
|
"%d Bytes left",
|
||||||
added ? "new " : "known",
|
added ? "new " : "known",
|
||||||
|
64
src/main.cpp
64
src/main.cpp
@ -56,9 +56,7 @@ uint16_t LEDColor = COLOR_NONE; // state machine variable to set RGB LED color
|
|||||||
hw_timer_t *displaytimer =
|
hw_timer_t *displaytimer =
|
||||||
NULL; // configure hardware timer used for cyclic display refresh
|
NULL; // configure hardware timer used for cyclic display refresh
|
||||||
hw_timer_t *channelSwitch =
|
hw_timer_t *channelSwitch =
|
||||||
NULL; // configure hardware timer used for wifi channel switching
|
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
|
#ifdef HAS_GPS
|
||||||
gpsStatus_t gps_status; // struct for storing gps data
|
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
|
std::set<uint16_t> macs; // associative container holds total of unique MAC
|
||||||
// adress hashes (Wifi + BLE)
|
// 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
|
// this variables will be changed in the ISR, and read in main loop
|
||||||
static volatile int ButtonPressedIRQ = 0, DisplayTimerIRQ = 0,
|
static volatile int ButtonPressedIRQ = 0, DisplayTimerIRQ = 0,
|
||||||
ChannelTimerIRQ = 0;
|
ChannelTimerIRQ = 0;
|
||||||
@ -459,6 +468,7 @@ void led_loop() {
|
|||||||
* ------------------------------------------------------------ */
|
* ------------------------------------------------------------ */
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
|
||||||
char features[64] = "";
|
char features[64] = "";
|
||||||
|
|
||||||
// disable brownout detection
|
// disable brownout detection
|
||||||
@ -508,24 +518,24 @@ void setup() {
|
|||||||
// initialize led if needed
|
// initialize led if needed
|
||||||
#if (HAS_LED != NOT_A_PIN)
|
#if (HAS_LED != NOT_A_PIN)
|
||||||
pinMode(HAS_LED, OUTPUT);
|
pinMode(HAS_LED, OUTPUT);
|
||||||
strcat(features, " LED");
|
strcat_P(features, " LED");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_RGB_LED
|
#ifdef HAS_RGB_LED
|
||||||
rgb_set_color(COLOR_PINK);
|
rgb_set_color(COLOR_PINK);
|
||||||
strcat(features, " RGB");
|
strcat_P(features, " RGB");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// initialize button handling if needed
|
// initialize button handling if needed
|
||||||
#ifdef HAS_BUTTON
|
#ifdef HAS_BUTTON
|
||||||
strcat(features, " BTN_");
|
strcat_P(features, " BTN_");
|
||||||
#ifdef BUTTON_PULLUP
|
#ifdef BUTTON_PULLUP
|
||||||
strcat(features, "PU");
|
strcat_P(features, "PU");
|
||||||
// install button interrupt (pullup mode)
|
// install button interrupt (pullup mode)
|
||||||
pinMode(HAS_BUTTON, INPUT_PULLUP);
|
pinMode(HAS_BUTTON, INPUT_PULLUP);
|
||||||
attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING);
|
attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING);
|
||||||
#else
|
#else
|
||||||
strcat(features, "PD");
|
strcat_P(features, "PD");
|
||||||
// install button interrupt (pulldown mode)
|
// install button interrupt (pulldown mode)
|
||||||
pinMode(HAS_BUTTON, INPUT_PULLDOWN);
|
pinMode(HAS_BUTTON, INPUT_PULLDOWN);
|
||||||
attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, FALLING);
|
attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, FALLING);
|
||||||
@ -534,17 +544,17 @@ void setup() {
|
|||||||
|
|
||||||
// initialize wifi antenna if needed
|
// initialize wifi antenna if needed
|
||||||
#ifdef HAS_ANTENNA_SWITCH
|
#ifdef HAS_ANTENNA_SWITCH
|
||||||
strcat(features, " ANT");
|
strcat_P(features, " ANT");
|
||||||
antenna_init();
|
antenna_init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// initialize gps if present
|
// initialize gps if present
|
||||||
#ifdef HAS_GPS
|
#ifdef HAS_GPS
|
||||||
strcat(features, " GPS");
|
strcat_P(features, " GPS");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_DISPLAY
|
#ifdef HAS_DISPLAY
|
||||||
strcat(features, " OLED");
|
strcat_P(features, " OLED");
|
||||||
// initialize display
|
// initialize display
|
||||||
init_display(PROGNAME, PROGVERSION);
|
init_display(PROGNAME, PROGVERSION);
|
||||||
DisplayState = cfg.screenon;
|
DisplayState = cfg.screenon;
|
||||||
@ -580,8 +590,17 @@ void setup() {
|
|||||||
timerAlarmWrite(channelSwitch, cfg.wifichancycle * 10000, true);
|
timerAlarmWrite(channelSwitch, cfg.wifichancycle * 10000, true);
|
||||||
timerAlarmEnable(channelSwitch);
|
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
|
// show compiled features
|
||||||
ESP_LOGI(TAG, "Features %s", features);
|
ESP_LOGI(TAG, "Features: %s", features);
|
||||||
|
|
||||||
// output LoRaWAN keys to console
|
// output LoRaWAN keys to console
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
@ -621,8 +640,7 @@ void setup() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// if device has GPS and GPS function is enabled, start GPS reader task on core
|
// if device has GPS and it is enabled, start GPS reader task on core 0
|
||||||
// 0
|
|
||||||
#ifdef HAS_GPS
|
#ifdef HAS_GPS
|
||||||
if (cfg.gpsmode) {
|
if (cfg.gpsmode) {
|
||||||
ESP_LOGI(TAG, "Starting GPS task on core 0");
|
ESP_LOGI(TAG, "Starting GPS task on core 0");
|
||||||
@ -630,8 +648,7 @@ void setup() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// kickoff sendjob -> joins network and rescedules sendjob for cyclic
|
// joins network and rescedules sendjob for cyclic transmitting payload
|
||||||
// transmitting payload
|
|
||||||
do_send(&sendjob);
|
do_send(&sendjob);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -674,11 +691,22 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_GPS
|
#ifdef HAS_GPS
|
||||||
// log NMEA status every 30 seconds, useful for debugging GPS connection
|
// log NMEA status every 60 seconds, useful for debugging GPS connection
|
||||||
if ((uptime() % 30000) == 0)
|
if ((uptime() % 60000) == 0) {
|
||||||
ESP_LOGI(TAG, "GPS NMEA data: passed %d / failed: %d / with fix: %d",
|
ESP_LOGI(TAG, "GPS NMEA data: passed %d / failed: %d / with fix: %d",
|
||||||
gps.passedChecksum(), gps.failedChecksum(),
|
gps.passedChecksum(), gps.failedChecksum(),
|
||||||
gps.sentencesWithFix());
|
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
|
#endif
|
||||||
|
|
||||||
vTaskDelay(1 / portTICK_PERIOD_MS); // reset watchdog
|
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
|
// program version - note: increment version after modifications to configData_t
|
||||||
// struct!!
|
// struct!!
|
||||||
#define PROGVERSION "1.3.81" // use max 10 chars here!
|
#define PROGVERSION "1.3.82" // use max 10 chars here!
|
||||||
#define PROGNAME "PAXCNT"
|
#define PROGNAME "PAXCNT"
|
||||||
|
|
||||||
//--- Declarations ---
|
//--- 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 };
|
enum led_states { LED_OFF, LED_ON };
|
||||||
|
|
||||||
#if defined(CFG_eu868)
|
#if defined(CFG_eu868)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// ----- Paxcounter user config file ------
|
// ----- Paxcounter user config file ------
|
||||||
//
|
//
|
||||||
// --> adapt to your needs and use case <--
|
// --> adapt to your needs and use case <--
|
||||||
|
//
|
||||||
|
// Note: After editing, before "build", use "clean" button in PlatformIO!
|
||||||
|
|
||||||
// Verbose enables serial output
|
// Verbose enables serial output
|
||||||
#define VERBOSE 1 // comment out to silence the device, for mute use build option
|
#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_MY_COUNTRY "EU" // select locale for Wifi RF settings
|
||||||
#define WIFI_CHANNEL_SWITCH_INTERVAL 50 // [seconds/100] -> 0,5 sec.
|
#define WIFI_CHANNEL_SWITCH_INTERVAL 50 // [seconds/100] -> 0,5 sec.
|
||||||
|
|
||||||
// LoRa payload send cycle --> take care of duty cycle of LoRaWAN network! <--
|
// LoRa payload parameters
|
||||||
#define SEND_SECS 120 // [seconds/2] -> 240 sec.
|
#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 MEM_LOW 2048 // [Bytes] low memory threshold triggering a send cycle
|
||||||
#define RETRANSMIT_RCMD 5 // [seconds] wait time before retransmitting rcommand results
|
#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
|
// Default LoRa Spreadfactor
|
||||||
#define LORASFDEFAULT 9 // 7 ... 12 SF, according to LoRaWAN specs
|
#define LORASFDEFAULT 9 // 7 ... 12 SF, according to LoRaWAN specs
|
||||||
#define MAXLORARETRY 500 // maximum count of TX retries if LoRa busy
|
#define MAXLORARETRY 500 // maximum count of TX retries if LoRa busy
|
||||||
#define RCMDPORT 2 // LoRaWAN Port on which device listenes for remote commands
|
#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
|
#define COUNTERPORT 1 // LoRaWAN Port on which device sends counts
|
||||||
|
|
||||||
// Default RGB LED luminosity (in %)
|
// 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
|
// function defined in adcread.cpp
|
||||||
#ifdef HAS_BATTERY_PROBE
|
#ifdef HAS_BATTERY_PROBE
|
||||||
uint32_t read_voltage(void);
|
uint16_t read_voltage(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// function sends result of get commands to LoRaWAN network
|
// 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),
|
os_setTimedCallback(&rcmdjob, os_getTime() + sec2osticks(RETRANSMIT_RCMD),
|
||||||
do_transmit);
|
do_transmit);
|
||||||
}
|
}
|
||||||
LMIC_setTxData2(RCMDPORT, rcmd_data, rcmd_data_size,
|
// send payload
|
||||||
0); // send data unconfirmed on RCMD Port
|
LMIC_setTxData2(RCMDPORT, payload.getBuffer(), payload.getSize(),
|
||||||
ESP_LOGI(TAG, "%d bytes queued to send", rcmd_data_size);
|
(cfg.countermode & 0x02));
|
||||||
|
ESP_LOGI(TAG, "%d bytes queued to send", payload.getSize());
|
||||||
sprintf(display_lmic, "PACKET QUEUED");
|
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
|
// help function to assign LoRa datarates to numeric spreadfactor values
|
||||||
void switch_lora(uint8_t sf, uint8_t tx) {
|
void switch_lora(uint8_t sf, uint8_t tx) {
|
||||||
if (tx > 20)
|
if (tx > 20)
|
||||||
@ -292,41 +285,33 @@ void set_lorapower(uint8_t val) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void get_config(uint8_t val) {
|
void get_config(uint8_t val) {
|
||||||
ESP_LOGI(TAG, "Remote command: get configuration");
|
ESP_LOGI(TAG, "Remote command: get device configuration");
|
||||||
transmit((byte *)&cfg, sizeof(cfg));
|
payload.reset();
|
||||||
|
payload.addConfig(cfg);
|
||||||
|
do_transmit(&rcmdjob);
|
||||||
};
|
};
|
||||||
|
|
||||||
void get_uptime(uint8_t val) {
|
void get_status(uint8_t val) {
|
||||||
ESP_LOGI(TAG, "Remote command: get uptime");
|
ESP_LOGI(TAG, "Remote command: get device status");
|
||||||
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");
|
|
||||||
#ifdef HAS_BATTERY_PROBE
|
#ifdef HAS_BATTERY_PROBE
|
||||||
uint16_t voltage = read_voltage();
|
uint16_t voltage = read_voltage();
|
||||||
#else
|
#else
|
||||||
uint16_t voltage = 0;
|
uint16_t voltage = 0;
|
||||||
#endif
|
#endif
|
||||||
transmit((byte *)&voltage, sizeof(voltage));
|
payload.reset();
|
||||||
|
payload.addStatus(voltage, uptimecounter, temperatureRead());
|
||||||
|
do_transmit(&rcmdjob);
|
||||||
};
|
};
|
||||||
|
|
||||||
void get_gps(uint8_t val) {
|
void get_gps(uint8_t val) {
|
||||||
ESP_LOGI(TAG, "Remote command: get gps status");
|
ESP_LOGI(TAG, "Remote command: get gps status");
|
||||||
#ifdef HAS_GPS
|
#ifdef HAS_GPS
|
||||||
gps_read();
|
gps_read();
|
||||||
transmit((byte *)&gps_status, sizeof(gps_status));
|
payload.reset();
|
||||||
ESP_LOGI(TAG, "lat=%f / lon=%f | Sats=%u | HDOP=%u | Alti=%u",
|
payload.addGPS(gps_status);
|
||||||
gps_status.latitude / 1000000, gps_status.longitude / 1000000,
|
do_transmit(&rcmdjob);
|
||||||
gps_status.satellites, gps_status.hdop, gps_status.altitude);
|
|
||||||
#else
|
#else
|
||||||
ESP_LOGE(TAG, "GPS not present");
|
ESP_LOGW(TAG, "GPS function not supported");
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -341,8 +326,7 @@ cmd_t table[] = {{0x01, set_rssi, true}, {0x02, set_countmode, true},
|
|||||||
{0x0b, set_wifichancycle, true}, {0x0c, set_blescantime, true},
|
{0x0b, set_wifichancycle, true}, {0x0c, set_blescantime, true},
|
||||||
{0x0d, set_vendorfilter, false}, {0x0e, set_blescan, true},
|
{0x0d, set_vendorfilter, false}, {0x0e, set_blescan, true},
|
||||||
{0x0f, set_wifiant, true}, {0x10, set_rgblum, true},
|
{0x0f, set_wifiant, true}, {0x10, set_rgblum, true},
|
||||||
{0x80, get_config, false}, {0x81, get_uptime, false},
|
{0x80, get_config, false}, {0x81, get_status, false},
|
||||||
{0x82, get_cputemp, false}, {0x83, get_voltage, false},
|
|
||||||
{0x84, get_gps, false}};
|
{0x84, get_gps, false}};
|
||||||
|
|
||||||
// check and execute remote command
|
// check and execute remote command
|
||||||
|
Loading…
Reference in New Issue
Block a user