Merge pull request #842 from cyberman54/master

sync dev to master
This commit is contained in:
Verkehrsrot 2022-01-10 13:27:58 +01:00 committed by GitHub
commit bbc9cc735b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 941 additions and 154 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: cyberman54

View File

@ -12,7 +12,7 @@ Tutorial (in german language): https://www.heise.de/select/make/2019/1/155109923
<img src="img/TTGO-case.jpg">
<img src="img/TTGO-curves.jpg">
<img src="img/Paxcounter-LEDmatrix.jpg">
<img src="img/Paxcounter-Clock.png">
<img src="img/Paxcounter-Clock2.png">
<img src="img/Paxcounter-ttgo-twristband.jpg">
@ -33,7 +33,7 @@ You can build this project battery powered using ESP32 deep sleep mode and reach
*LoRa & SPI*:
- Heltec: LoRa-32 v1 and v2
- TTGO: T1*, T2*, T3*, T-Beam, T-Fox
- TTGO: [Paxcounter-Board*](https://www.aliexpress.com/item/32915894264.html?spm=a2g0o.productlist.0.0.3d656325QrcfQc&algo_pvid=4a150199-63e7-4d21-bdb1-b48164537744&algo_exp_id=4a150199-63e7-4d21-bdb1-b48164537744-2&pdp_ext_f=%7B%22sku_id%22%3A%2212000023374441919%22%7D), T1*, T2*, T3*, T-Beam, T-Fox
- Pycom: LoPy, LoPy4, FiPy
- Radioshuttle.de: [ECO Power Board](https://www.radioshuttle.de/esp32-eco-power/esp32-eco-power-board/)
- WeMos: LoLin32 + [LoraNode32 shield](https://github.com/hallard/LoLin32-Lora),
@ -140,6 +140,8 @@ If option *BOOTMENU* is defined in `paxcounter.conf`, the ESP32 board will try t
(e.g. Citizens in the the Netherlands and EU may want to read [this article](https://www.ivir.nl/publicaties/download/PrivacyInformatie_2016_6.pdf) and [this article](https://autoriteitpersoonsgegevens.nl/nl/nieuws/europese-privacytoezichthouders-publiceren-opinie-eprivacyverordening)) and [this decision](https://edpb.europa.eu/news/national-news/2021/dutch-dpa-fines-municipality-wi-fi-tracking_en)
(e.g. Citizens in Germany may want to read [this article of Wissenschaftliche Dienste des Deutschen Bundestages](https://www.bundestag.de/resource/blob/538890/3dfae197d2c930693aa16d1619204f58/WD-3-206-17-pdf-data.pdf)
Note: If you use this software you do this at your own risk. That means that you alone - not the authors of this software - are responsible for the legal compliance of an application using this or build from this software and/or usage of a device created using this software. You should take special care and get prior legal advice if you plan metering passengers in public areas and/or publish data drawn from doing so.
# Privacy disclosure
@ -263,6 +265,22 @@ Format of the data is CSV, which can easily imported into LibreOffice, Excel, ..
If you want to change this please look into src/sdcard.cpp and include/sdcard.h.
# Integration into "The Things Stack Community Edition" aka "The Things Stack V3"
To use the ESP32-Paxcounter in The Things Stack Community Edition you need an account to reach the console. Go to:
- [The Things Stack Community Edition Console](https://console.cloud.thethings.network/)
- choose your region and go to applications
- create an application by clicking "**+ Add application**" and give it a id, name, etc.
- create a device by clicking "**+ Add end device**"
- Select the end device: choose the Brand "**Open Source Community Projects**" and the Model "**ESP32-Paxcounter**", leave Hardware Version to "**Unknown**" and select your **Firmware Version** and **Profile (Region)**
- Enter registration data: choose the **frequency plan** (for EU choose the recommended), set the **AppEUI** (Fill with zeros), set the **DeviceEUI** (generate), set the **AppKey** (generate), choose a **device ID** and hit "Register end device"
- got to Applications -> "your App ID" -> Payload formatters -> Uplink, choose "**Repository**" and hit "Save changes"
The "Repository" payload decoder uses the packed format, explained below. If you want to use MyDevices from Cayenne you should use the Cayenne payload decoder instead.
# TTN Mapper
If you want your devices to be feeding the [TTN Mapper](https://ttnmapper.org/), just follow this manual: https://docs.ttnmapper.org/integration/tts-integration-v3.html - different than indicated in the manual you can leave the payload decoder to "Repository" for the ESP32-Paxcounter and you are fine.
# Payload format
@ -274,12 +292,16 @@ You can select different payload formats in `paxcounter.conf`:
- [***CayenneLPP***](https://mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload-reference-implementation) generates MyDevices Cayenne readable fields
**Decrepated information from the things network v2 >>**
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 add your device to myDevices Cayenne platform select "Cayenne-LPP" from Lora device list and use the CayenneLPP payload encoder.
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. Both formats *plain* and *packed* generate the fields `latitude`, `longitude` and `hdop` required by ttnmapper. Important: set TTN mapper port filter to '4' (paxcounter GPS Port).
**<< Decrepated information from the things network v2**
Hereafter described is the default *plain* format, which uses MSB bit numbering. Under /TTN in this repository you find some ready-to-go decoders which you may copy to your TTN console:
[**plain_decoder.js**](src/TTN/plain_decoder.js) |

View File

@ -20,7 +20,7 @@ config = configparser.ConfigParser()
config.read("platformio.ini")
# get platformio source path
srcdir = env.get("PROJECTSRC_DIR")
srcdir = env.get("PROJECT_SRC_DIR")
# get hal path
haldir = os.path.join (srcdir, "hal")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

BIN
img/Paxcounter-Clock2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -8,7 +8,7 @@
extern configData_t cfg;
void saveConfig(bool erase);
bool loadConfig(void);
void loadConfig(void);
void eraseConfig(void);
int version_compare(const String v1, const String v2);

View File

@ -55,7 +55,7 @@ enum snifftype_t { MAC_SNIFF_WIFI, MAC_SNIFF_BLE, MAC_SNIFF_BLE_ENS };
// using packed to avoid compiler padding, because struct will be memcpy'd to
// byte array
typedef struct __attribute__((packed)) {
char version[10]; // Firmware version
char version[10] = ""; // Firmware version
uint8_t loradr; // 0-15, lora datarate
uint8_t txpower; // 2-15, lora tx power
uint8_t adrmode; // 0=disabled, 1=enabled

View File

@ -36,7 +36,6 @@ void lora_send(void *pvParameters);
void lora_enqueuedata(MessageBuffer_t *message);
void lora_queuereset(void);
uint32_t lora_queuewaiting(void);
uint8_t myBattLevelCb(void *pUserData);
void IRAM_ATTR myEventCallback(void *pUserData, ev_t ev);
void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
size_t nMsg);

View File

@ -2,7 +2,7 @@
#define _MAIN_H
#include <esp_spi_flash.h> // needed for reading ESP32 chip attributes
#include <esp_event_loop.h> // needed for Wifi event handler
#include <esp_event.h> // needed for Wifi event handler
#include <esp32-hal-timer.h> // needed for timers
#include <esp_coexist.h> // needed for coex version display
#include <esp_wifi.h> // needed for wifi init / deinit

View File

@ -3,7 +3,8 @@
#include <Arduino.h>
#include <esp_adc_cal.h>
//#include <esp32-hal-adc.h>
//include <esp32-hal-adc.h>
#include <soc/adc_channel.h>
#include "i2c.h"
#include "reset.h"

View File

@ -6,10 +6,11 @@
#include "timekeeper.h"
#define TIME_SYNC_FRAME_LENGTH 6 // timeserver answer frame length [bytes]
#define TIME_SYNC_FIXUP 16 // compensation for processing time [milliseconds]
#define TIME_SYNC_FIXUP 25 // compensation for processing time [milliseconds]
#define TIME_SYNC_MAX_SEQNO 0xfe // threshold for wrap around time_sync_seqNo
#define TIME_SYNC_END_FLAG (TIME_SYNC_MAX_SEQNO + 1) // end of handshake marker
#define GPS_UTC_DIFF 315964800 // seconds diff between gps and utc epoch
#define GPS_UTC_DIFF 315964800UL // seconds diff between gps and utc epoch
#define LEAP_SECS_SINCE_GPSEPOCH 18UL // state of 2021
enum timesync_t {
timesync_tx,

View File

@ -42,13 +42,13 @@ halfile = ttgobeam10.h
default_envs = usb
; upload firmware to a paxexpress repository
;default_envs = ota
; use latest versions of libraries
; use upstream version of arduino-espressif32 framework
;default_envs = dev
description = Paxcounter is a device for metering passenger flows in realtime. It counts how many mobile devices are around.
[common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
release_version = 3.0.0
release_version = 3.0.3
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
debug_level = 3
@ -56,31 +56,30 @@ extra_scripts = pre:build.py
otakeyfile = ota.conf
lorakeyfile = loraconf.h
lmicconfigfile = lmic_config.h
platform_espressif32 = espressif32@3.2.0
platform_espressif32 = espressif32@3.4.0
monitor_speed = 115200
upload_speed = 115200 ; set by build.py and taken from hal file
display_library = ; set by build.py and taken from hal file
lib_deps_lora =
;mcci-catena/MCCI LoRaWAN LMIC library @ ^3.3.0
https://github.com/mcci-catena/arduino-lmic.git
mcci-catena/MCCI LoRaWAN LMIC library @ ^4.1.1
lib_deps_display =
bitbank2/OneBitDisplay @ ^1.10.0
bitbank2/BitBang_I2C @ ^2.1.3
bitbank2/OneBitDisplay @ ^1.11.0
ricmoo/QRCode @ ^0.0.1
bodmer/TFT_eSPI @ ^2.3.58
bodmer/TFT_eSPI @ ^2.3.84
lib_deps_ledmatrix =
seeed-studio/Ultrathin_LED_Matrix @ ^1.0.0
lib_deps_rgbled =
roboticsbrno/SmartLeds @ ^1.2.1
https://github.com/RoboticsBrno/SmartLeds.git
lib_deps_gps =
mikalhart/TinyGPSPlus @ ^1.0.2
lib_deps_sensors =
adafruit/Adafruit Unified Sensor @ ^1.1.4
adafruit/Adafruit BME280 Library @ ^2.1.1
adafruit/Adafruit BME280 Library @ ^2.2.1
adafruit/Adafruit BMP085 Library @ ^1.2.0
boschsensortec/BSEC Software Library @ 1.6.1480
https://github.com/ricki-z/SDS011.git
lib_deps_basic =
https://github.com/dbSuS/libpax.git @ ^1.0.0
https://github.com/SukkoPera/Arduino-Rokkit-Hash.git
bblanchon/ArduinoJson @ ^6
ropg/ezTime @ ^0.8.3
@ -89,7 +88,6 @@ lib_deps_basic =
lewisxhe/AXP202X_Library @ ^1.1.3
geeksville/esp32-micro-sdcard @ ^0.1.1
256dpi/MQTT @ ^2.4.8
https://github.com/cyberman54/libpax.git
lib_deps_all =
${common.lib_deps_basic}
${common.lib_deps_lora}
@ -114,7 +112,6 @@ build_flags_all =
-mfix-esp32-psram-cache-issue
[env]
lib_ldf_mode = deep ; #632 Fixes compiler error with OneBitDisplay library
framework = arduino
board = esp32dev
board_build.partitions = min_spiffs.csv
@ -137,7 +134,4 @@ upload_protocol = esptool
[env:dev]
upload_protocol = esptool
build_type = debug
platform = https://github.com/platformio/platform-espressif32.git#develop
platform_packages =
; use upstream Git version
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git
platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream

View File

@ -32,7 +32,9 @@ static uint8_t buffer[cfgLen + cfgLen2];
// 3. magicByte [cfgLen2 bytes, containing a fixed identifier]
static void defaultConfig(configData_t *myconfig) {
memcpy(myconfig->version, &PROGVERSION, 10); // Firmware version
strncpy(myconfig->version, PROGVERSION,
sizeof(myconfig->version) - 1); // Firmware version
// device factory settings
myconfig->loradr = LORADRDEFAULT; // 0-15, lora datarate, see paxcounter.conf
@ -96,30 +98,33 @@ void saveConfig(bool erase) {
}
// load configuration from NVRAM into RAM and make it current
bool loadConfig() {
void loadConfig(void) {
int readBytes = 0;
ESP_LOGI(TAG, "Loading device configuration from NVRAM...");
if (!nvram.begin(DEVCONFIG, true)) {
if (nvram.begin(DEVCONFIG, true)) {
// load device runtime config from nvram and copy it to byte array
readBytes = nvram.getBytes(DEVCONFIG, buffer, cfgLen + cfgLen2);
nvram.end();
// check that runtime config data length matches
if (readBytes != cfgLen + cfgLen2) {
ESP_LOGE(TAG, "No valid configuration found");
migrateConfig();
}
} else {
ESP_LOGI(TAG, "NVRAM initialized, device starts with factory settings");
eraseConfig();
}
// simple check that runtime config data matches
// if (nvram.getBytesLength(DEVCONFIG) != (cfgLen + cfgLen2)) {
// ESP_LOGE(TAG, "Configuration invalid");
// return false;
//}
// load device runtime config from nvram and copy it to byte array
nvram.getBytes(DEVCONFIG, buffer, cfgLen + cfgLen2);
nvram.end();
// validate loaded configuration by checking magic bytes at end of array
// if (memcmp(buffer + cfgLen, &cfgMagicBytes, cfgLen2) != 0) {
// ESP_LOGW(TAG, "No configuration found");
// return false;
//}
if (memcmp(buffer + cfgLen, &cfgMagicBytes, cfgLen2) != 0) {
ESP_LOGE(TAG, "Configuration data corrupt");
eraseConfig();
}
// copy loaded configuration into runtime cfg struct
memcpy(&cfg, buffer, cfgLen);
@ -130,13 +135,13 @@ bool loadConfig() {
case -1: // device configuration belongs to newer than current firmware
ESP_LOGE(TAG, "Incompatible device configuration");
eraseConfig();
return true;
break;
case 1: // device configuration belongs to older than current firmware
ESP_LOGW(TAG, "Device was updated, attempt to migrate configuration");
migrateConfig();
return true;
break;
default: // device configuration version matches current firmware version
return true;
break; // nothing to do here
}
}

View File

@ -120,7 +120,7 @@ time_t get_gpstime(uint16_t *msec) {
t = makeTime(tm);
ESP_LOGD(TAG, "GPS date/time: %s",
UTC.dateTime(t, "d.M Y H:i:s T").c_str());
UTC.dateTime(t, "d.M Y H:i:s.v T").c_str());
// add protocol delay with millisecond precision
t += delay_ms / 1000 - 1; // whole seconds

View File

@ -19,7 +19,7 @@
#define HAS_BUTTON KEY_BUILTIN
// enable only if you want to store a local paxcount table on the device
#define HAS_SDCARD 1 // this board has an SD-card-reader/writer
// #define HAS_SDCARD 1 // this board has an SD-card-reader/writer
// Pins for SD-card
#define SDCARD_CS (13)
#define SDCARD_MOSI (15)

View File

@ -10,20 +10,20 @@ void irqHandler(void *pvParameters) {
_ASSERT((uint32_t)pvParameters == 1); // FreeRTOS check
uint32_t InterruptStatus;
uint32_t irqSource;
// task remains in blocked state until it is notified by an irq
for (;;) {
xTaskNotifyWait(0x00, // Don't clear any bits on entry
ULONG_MAX, // Clear all bits on exit
&InterruptStatus, // Receives the notification value
&irqSource, // Receives the notification value
portMAX_DELAY); // wait forever
if (InterruptStatus & UNMASK_IRQ) // interrupt handler to be enabled?
InterruptStatus &= ~MASK_IRQ; // then clear irq mask flag
if (irqSource & UNMASK_IRQ) // interrupt handler to be enabled?
irqSource &= ~MASK_IRQ; // then clear irq mask flag
// else suppress processing if interrupt handler is disabled
// or time critical lmic jobs are pending in next 100ms
else if ((InterruptStatus & MASK_IRQ)
else if ((irqSource & MASK_IRQ)
#if (HAS_LORA)
|| os_queryTimeCriticalJobs(ms2osticks(100))
#endif
@ -32,63 +32,49 @@ void irqHandler(void *pvParameters) {
// button pressed?
#ifdef HAS_BUTTON
if (InterruptStatus & BUTTON_IRQ) {
if (irqSource & BUTTON_IRQ)
readButton();
InterruptStatus &= ~BUTTON_IRQ;
}
#endif
// display needs refresh?
#ifdef HAS_DISPLAY
if (InterruptStatus & DISPLAY_IRQ) {
if (irqSource & DISPLAY_IRQ)
dp_refresh();
InterruptStatus &= ~DISPLAY_IRQ;
}
#endif
// LED Matrix display needs refresh?
#ifdef HAS_MATRIX_DISPLAY
if (InterruptStatus & MATRIX_DISPLAY_IRQ) {
if (irqSource & MATRIX_DISPLAY_IRQ)
refreshTheMatrixDisplay();
InterruptStatus &= ~MATRIX_DISPLAY_IRQ;
}
#endif
#if (TIME_SYNC_INTERVAL)
// is time to be synced?
if (InterruptStatus & TIMESYNC_IRQ) {
if (irqSource & TIMESYNC_IRQ) {
now(); // ensure sysTime is recent
calibrateTime();
InterruptStatus &= ~TIMESYNC_IRQ;
}
#endif
// BME sensor data to be read?
#if (HAS_BME)
if (InterruptStatus & BME_IRQ) {
if (irqSource & BME_IRQ)
bme_storedata(&bme_status);
InterruptStatus &= ~BME_IRQ;
}
#endif
// are cyclic tasks due?
if (InterruptStatus & CYCLIC_IRQ) {
if (irqSource & CYCLIC_IRQ)
doHousekeeping();
InterruptStatus &= ~CYCLIC_IRQ;
}
// do we have a power event?
#ifdef HAS_PMU
if (InterruptStatus & PMU_IRQ) {
if (irqSource & PMU_IRQ)
AXP192_powerevent_IRQ();
InterruptStatus &= ~PMU_IRQ;
}
#endif
// is time to send the payload?
if (InterruptStatus & SENDCYCLE_IRQ) {
if (irqSource & SENDCYCLE_IRQ) {
sendData();
InterruptStatus &= ~SENDCYCLE_IRQ;
// goto sleep if we have a sleep cycle
if (cfg.sleepcycle)
#ifdef HAS_BUTTON

View File

@ -16,7 +16,7 @@ void process_count(void) {
}
void init_libpax(void) {
libpax_counter_init(process_count, &count_from_libpax,
cfg.sendcycle * 2 * 1000, cfg.countermode);
libpax_counter_init(process_count, &count_from_libpax, cfg.sendcycle * 2,
cfg.countermode);
libpax_counter_start();
}

View File

@ -43,7 +43,7 @@
// enable more verbose output. Make sure that printf is actually
// configured (e.g. on AVR it is not by default), otherwise using it can
// cause crashing.
//#define LMIC_DEBUG_LEVEL 2
//#define LMIC_DEBUG_LEVEL 1
// Enable this to allow using printf() to print to the given serial port
// (or any other Print object). This can be easy for debugging. The

View File

@ -0,0 +1,12 @@
vendors:
- id: example
name: Example
vendorID: 0
draft: true
#SNIP - a lot of other vendors, just attach the lower lines to the index.yaml
#SNIP - could add a logo with "logo: filename"
- id: opensource
name: Open Source Community Projects
website: https://en.wikipedia.org/wiki/Open_source

View File

@ -0,0 +1,21 @@
# lorawan-devices repo for The Things Network & The Things Stack V3
To add bigger payload decoders than 4k (via web ui) we provide the metadata to the lorawan-devices repo on github. For this we create a vendor "opensource" and specify a device "esp32-paxcounter" via this files:
> /
> - index.yaml (include the marked lines at the bottom of the original file)
>
> /vendor (existing folder in the repo)
>
> - /opensource (new folder with all the good stuff inside)
>
> /vendor/opensource
>
> - index.yaml (name of the device inside)
> - esp32-paxcounter.yaml (metadata for the software)
> - esp32-paxcounter-profile-eu868.yaml (profile for europe)
> - esp32-paxcounter-profile-us915.yaml (profile for north america)
> - esp32-paxcounter-codec.yaml (examples for the payload decoder)
> - esp32-paxcounter-packed_decodeUplink.js (payload decoder as provided before for the web ui, now for the lorawan devices repo)
With these files, we can make a pull request at the lorawan-devices repo: https://github.com/TheThingsNetwork/lorawan-devices and hope to be included in the future. Cool thing about it would be that then the users need only to choose "Open Source Community Projects" from the vendor list and "EXP32-Paxcounter" as device and most of the compex things is taking care of (like choosing the right LoRaWAN version and installing the payload decoder).

View File

@ -0,0 +1,143 @@
# Uplink decoder decodes binary data uplink into a JSON object (optional)
# For documentation on writing encoders and decoders, see: https://thethingsstack.io/integrations/payload-formatters/javascript/
uplinkDecoder:
fileName: esp32-paxcounter-packed.js
examples:
- description: Paxcount data
input:
fPort: 1
bytes: [0x07, 0x00, 0x03, 0x00]
output:
data:
bytes: [0x07, 0x00, 0x03, 0x00]
port: 1
wifi: 7
ble: 3
pax: 10
errors: []
warnings: []
- description: Device status query result
input:
fPort: 2
bytes: [0x2F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x4B, 0x03, 0x00, 0x2D, 0xC0, 0x4B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
output:
data:
bytes: [0x2F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x4B, 0x03, 0x00, 0x2D, 0xC0, 0x4B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
port: 2
voltage: 303
uptime: 216000
cputemp: 45
memory: 216000
reset0: 0
restarts: 0
errors: []
warnings: []
- description: Device config data
input:
fPort: 3
bytes: [0x09, 0x0F, 0x00, 0x00, 0x78, 0x32, 0x0A, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
output:
data:
bytes: [0x09, 0x0F, 0x00, 0x00, 0x78, 0x32, 0x0A, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
port: 3
loradr: 9
txpower: 15
rssilimit: 0
sendcycle: 120
wifichancycle: 50
blescantime: 10
rgblum: 30
flags:
adr: 0
antenna: 0
blescan: 0
countermode: 0
reserved: 0
screen: 0
screensaver: 0
payloadmask:
battery: 0
bme: 0
counter: 0
gps: 0
reserved: 0
sensor1: 0
sensor2: 0
sensor3: 0
version: ''
errors: []
warnings: []
- description: GPS data
input:
fPort: 4
bytes: [0x65, 0xCA, 0x06, 0x03, 0x05, 0x19, 0x6F, 0x00, 0x05, 0xC6, 0x00, 0x42, 0x00]
output:
data:
bytes: [0x65, 0xCA, 0x06, 0x03, 0x05, 0x19, 0x6F, 0x00, 0x05, 0xC6, 0x00, 0x42, 0x00]
port: 4
latitude: 50.776677
longitude: 7.280901
sats: 5
hdop: 1.98
altitude: 66
errors: []
warnings: []
- description: Button data
input:
fPort: 5
bytes: [0x01]
output:
data:
bytes: [0x01]
port: 5
button: 1
errors: []
warnings: []
- description: Environmental sensor data
input:
fPort: 7
bytes: [0x08, 0x34, 0x10, 0x27, 0x40, 0x1F, 0x10, 0x27]
output:
data:
bytes: [0x08, 0x34, 0x10, 0x27, 0x40, 0x1F, 0x10, 0x27]
port: 7
temperature: 21.00
pressure: 1000.0
humidity: 80.00
air: 100.00
errors: []
warnings: []
- description: Battery voltage data
input:
fPort: 8
bytes: [0x2F, 0x01]
output:
data:
bytes: [0x2F, 0x01]
port: 8
voltage: 303
errors: []
warnings: []
- description: Time/Date
input:
fPort: 9
bytes: [0x90, 0x86, 0xC8, 0x60, 0x00]
output:
data:
bytes: [0x90, 0x86, 0xC8, 0x60, 0x00]
port: 9
time: 1623754384
timestatus: 0
errors: []
warnings: []
- description: User sensor data
input:
fPort: 10
bytes: [0x00, 0x00]
output:
data:
bytes: [0x00, 0x00]
port: 10
ens: 0
errors: []
warnings: []

View File

@ -0,0 +1,344 @@
// Decoder for device payload encoder "PACKED"
// copy&paste to TTN Console V3 -> Applications -> Payload formatters -> Uplink -> Javascript
// modified for The Things Stack V3 by Caspar Armster, dasdigidings e.V.
function decodeUplink(input) {
var data = {};
if (input.fPort === 1) {
// only wifi counter data, no gps
if (input.bytes.length === 2) {
data = decode(input.bytes, [uint16], ['wifi']);
}
// wifi + ble counter data, no gps
if (input.bytes.length === 4) {
data = decode(input.bytes, [uint16, uint16], ['wifi', 'ble']);
}
// combined wifi + ble + SDS011
if (input.bytes.length === 8) {
data = decode(input.bytes, [uint16, uint16, uint16, uint16], ['wifi', 'ble', 'PM10', 'PM25']);
}
// combined wifi counter and gps data, used by https://opensensemap.org
if (input.bytes.length === 10) {
data = decode(input.bytes, [latLng, latLng, uint16], ['latitude', 'longitude', 'wifi']);
}
// combined wifi + ble counter and gps data, used by https://opensensemap.org
if (input.bytes.length === 12) {
data = decode(input.bytes, [latLng, latLng, uint16, uint16], ['latitude', 'longitude', 'wifi', 'ble']);
}
// combined wifi counter and gps data
if (input.bytes.length === 15) {
data = decode(input.bytes, [uint16, latLng, latLng, uint8, hdop, altitude], ['wifi', 'latitude', 'longitude', 'sats', 'hdop', 'altitude']);
}
// combined wifi + ble counter and gps data
if (input.bytes.length === 17) {
data = decode(input.bytes, [uint16, uint16, latLng, latLng, uint8, hdop, altitude], ['wifi', 'ble', 'latitude', 'longitude', 'sats', 'hdop', 'altitude']);
}
data.pax = 0;
if ('wifi' in data) {
data.pax += data.wifi;
}
if ('ble' in data) {
data.pax += data.ble;
}
}
if (input.fPort === 2) {
// device status data
if (input.bytes.length === 20) {
data = decode(input.bytes, [uint16, uptime, uint8, uint32, uint8, uint32], ['voltage', 'uptime', 'cputemp', 'memory', 'reset0', 'restarts']);
}
}
if (input.fPort === 3) {
// device config data
data = decode(input.bytes, [uint8, uint8, int16, uint8, uint8, uint8, uint8, bitmap1, bitmap2, version], ['loradr', 'txpower', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags', 'payloadmask', 'version']);
}
if (input.fPort === 4) {
// gps data
if (input.bytes.length === 8) {
data = decode(input.bytes, [latLng, latLng], ['latitude', 'longitude']);
} else {
data = decode(input.bytes, [latLng, latLng, uint8, hdop, altitude], ['latitude', 'longitude', 'sats', 'hdop', 'altitude']);
}
}
if (input.fPort === 5) {
// button pressed
data = decode(input.bytes, [uint8], ['button']);
}
if (input.fPort === 7) {
// BME680 sensor data
data = decode(input.bytes, [float, pressure, ufloat, ufloat], ['temperature', 'pressure', 'humidity', 'air']);
}
if (input.fPort === 8) {
// battery voltage
data = decode(input.bytes, [uint16], ['voltage']);
}
if (input.fPort === 9) {
// timesync request
if (input.bytes.length === 1) {
data.timesync_seqno = input.bytes[0];
}
// epoch time answer
if (input.bytes.length === 5) {
data = decode(input.bytes, [uint32, uint8], ['time', 'timestatus']);
}
}
if (input.fPort === 10) {
// ENS count
data = decode(input.bytes, [uint16], ['ens']);
}
data.bytes = input.bytes; // comment out if you do not want to include the original payload
data.port = input.fPort; // comment out if you do not want to inlude the port
return {
data: data,
warnings: [],
errors: []
};
}
function encodeDownlink(input) {
return {
data: {
bytes: input.bytes
},
warnings: ["Encoding of downlink is not supported by the JS decoder."],
errors: []
}
}
function decodeDownlink(input) {
return {
data: {
bytes: input.bytes
},
warnings: ["Decoding of downlink is not supported by the JS decoder."],
errors: []
}
}
// ----- 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 version = function (bytes) {
if (bytes.length !== version.BYTES) {
throw new Error('version must have exactly 10 bytes');
}
return String.fromCharCode.apply(null, bytes).split('\u0000')[0];
};
version.BYTES = 10;
var uint8 = function (bytes) {
if (bytes.length !== uint8.BYTES) {
throw new Error('uint8 must have exactly 1 byte');
}
return bytesToInt(bytes);
};
uint8.BYTES = 1;
var uint16 = function (bytes) {
if (bytes.length !== uint16.BYTES) {
throw new Error('uint16 must have exactly 2 bytes');
}
return bytesToInt(bytes);
};
uint16.BYTES = 2;
var uint32 = function (bytes) {
if (bytes.length !== uint32.BYTES) {
throw new Error('uint32 must have exactly 4 bytes');
}
return bytesToInt(bytes);
};
uint32.BYTES = 4;
var uint64 = function (bytes) {
if (bytes.length !== uint64.BYTES) {
throw new Error('uint64 must have exactly 8 bytes');
}
return bytesToInt(bytes);
};
uint64.BYTES = 8;
var int8 = function (bytes) {
if (bytes.length !== int8.BYTES) {
throw new Error('int8 must have exactly 1 byte');
}
var value = +(bytesToInt(bytes));
if (value > 127) {
value -= 256;
}
return value;
};
int8.BYTES = 1;
var int16 = function (bytes) {
if (bytes.length !== int16.BYTES) {
throw new Error('int16 must have exactly 2 bytes');
}
var value = +(bytesToInt(bytes));
if (value > 32767) {
value -= 65536;
}
return value;
};
int16.BYTES = 2;
var int32 = function (bytes) {
if (bytes.length !== int32.BYTES) {
throw new Error('int32 must have exactly 4 bytes');
}
var value = +(bytesToInt(bytes));
if (value > 2147483647) {
value -= 4294967296;
}
return value;
};
int32.BYTES = 4;
var latLng = function (bytes) {
return +(int32(bytes) / 1e6).toFixed(6);
};
latLng.BYTES = int32.BYTES;
var uptime = function (bytes) {
return uint64(bytes);
};
uptime.BYTES = uint64.BYTES;
var hdop = function (bytes) {
return +(uint16(bytes) / 100).toFixed(2);
};
hdop.BYTES = uint16.BYTES;
var altitude = function (bytes) {
// Option to increase altitude resolution (also on encoder side)
// return +(int16(bytes) / 4 - 1000).toFixed(1);
return +(int16(bytes));
};
altitude.BYTES = int16.BYTES;
var float = function (bytes) {
if (bytes.length !== float.BYTES) {
throw new Error('Float 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 / 100).toFixed(2);
};
float.BYTES = 2;
var ufloat = function (bytes) {
return +(uint16(bytes) / 100).toFixed(2);
};
ufloat.BYTES = uint16.BYTES;
var pressure = function (bytes) {
return +(uint16(bytes) / 10).toFixed(1);
};
pressure.BYTES = uint16.BYTES;
var bitmap1 = function (byte) {
if (byte.length !== bitmap1.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', 'reserved', 'reserved']
.reduce(function (obj, pos, index) {
obj[pos] = +bm[index];
return obj;
}, {});
};
bitmap1.BYTES = 1;
var bitmap2 = function (byte) {
if (byte.length !== bitmap2.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 ['battery', 'sensor3', 'sensor2', 'sensor1', 'gps', 'bme', 'reserved', 'counter']
.reduce(function (obj, pos, index) {
obj[pos] = +bm[index];
return obj;
}, {});
};
bitmap2.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 = {
uint8: uint8,
uint16: uint16,
uint32: uint32,
int8: int8,
int16: int16,
int32: int32,
uptime: uptime,
float: float,
ufloat: ufloat,
pressure: pressure,
latLng: latLng,
hdop: hdop,
altitude: altitude,
bitmap1: bitmap1,
bitmap2: bitmap2,
version: version,
decode: decode
};
}

View File

@ -0,0 +1,24 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
#vendorProfileID: 0
# Whether the end device supports class B
supportsClassB: false
# Whether the end device supports class C
supportsClassC: false
# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.3
# LoRaWAN Regional Parameters version. Values depend on the LoRaWAN version:
# 1.0: TS001-1.0
# 1.0.1: TS001-1.0.1
# 1.0.2: RP001-1.0.2 or RP001-1.0.2-RevB
# 1.0.3: RP001-1.0.3-RevA
# 1.0.4: RP002-1.0.0 or RP002-1.0.1
# 1.1: RP001-1.1-RevA or RP001-1.1-RevB
regionalParametersVersion: RP001-1.0.3-RevA
# Whether the end device supports join (OTAA) or not (ABP)
supportsJoin: true
# Maximum EIRP
maxEIRP: 16
# Whether the end device supports 32-bit frame counters
supports32bitFCnt: true

View File

@ -0,0 +1,24 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
#vendorProfileID: 0
# Whether the end device supports class B
supportsClassB: false
# Whether the end device supports class C
supportsClassC: false
# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.3
# LoRaWAN Regional Parameters version. Values depend on the LoRaWAN version:
# 1.0: TS001-1.0
# 1.0.1: TS001-1.0.1
# 1.0.2: RP001-1.0.2 or RP001-1.0.2-RevB
# 1.0.3: RP001-1.0.3-RevA
# 1.0.4: RP002-1.0.0 or RP002-1.0.1
# 1.1: RP001-1.1-RevA or RP001-1.1-RevB
regionalParametersVersion: RP001-1.0.3-RevA
# Whether the end device supports join (OTAA) or not (ABP)
supportsJoin: true
# Maximum EIRP
maxEIRP: 30
# Whether the end device supports 32-bit frame counters
supports32bitFCnt: true

View File

@ -0,0 +1,137 @@
name: ESP32-Paxcounter
description: Software providing a basis on the esp32 platform for multiple sensors, including pax (ble/wifi), gps, temperature, humidity, pressure, pm2.5, pm10 and more.
# Hardware versions (optional, use when you have revisions)
#hardwareVersions:
# - version: '1.0'
# numeric: 1
# Firmware versions (at least one is mandatory)
# LoRaWAN Device Profiles per region
# Supported regions are EU863-870, US902-928, AU915-928, AS923, CN779-787, EU433, CN470-510, KR920-923, IN865-867, RU864-870
firmwareVersions:
- version: '2.4.0'
numeric: 240
profiles:
EU863-870:
id: esp32-paxcounter-profile-eu868
#lorawanCertified: true
codec: esp32-paxcounter-codec
US902-928:
id: esp32-paxcounter-profile-us915
#lorawanCertified: true
codec: esp32-paxcounter-codec
- version: '3.0.0'
numeric: 300
profiles:
EU863-870:
id: esp32-paxcounter-profile-eu868
#lorawanCertified: true
codec: esp32-paxcounter-codec
US902-928:
id: esp32-paxcounter-profile-us915
#lorawanCertified: true
codec: esp32-paxcounter-codec
# Sensors that this device features (optional)
# 4-20 ma, accelerometer, altitude, analog input, auxiliary, barometer, battery, button, bvoc, co, co2, conductivity,
# current, digital input, dissolved oxygen, distance, dust, energy, gps, gyroscope, h2s, humidity, iaq, level, light,
# lightning, link, magnetometer, moisture, motion, no, no2, o3, particulate matter, ph, pir, pm2.5, pm10, potentiometer,
# power, precipitation, pressure, proximity, pulse count, pulse frequency, radar, rainfall, rssi, smart valve, snr, so2,
# solar radiation, sound, strain, surface temperature, temperature, tilt, time, tvoc, uv, vapor pressure, velocity,
# vibration, voltage, water potential, water, weight, wifi ssid, wind direction, wind speed.
sensors:
- battery
# - ble
# - wifi
# - pax
- gps
- altitude
# - latitude
# - longitude
# - hdop
# - sats
- temperature
- humidity
- barometer
- pm2.5
- pm10
# Additional radios that this device has (optional)
# Valid values are: ble, nfc, wifi, cellular.
additionalRadios:
- ble
- wifi
# Dimensions in mm (optional)
# Use width, height, length and/or diameter
#dimensions:
# width: 22.5
# length: 119
# height: 101
# Weight in grams (optional)
#weight: 160
## Operating conditions (optional)
#operatingConditions:
# Temperature (Celsius)
# temperature:
# min: -30
# max: 60
# Relative humidity (fraction of 1)
# relativeHumidity:
# min: 0
# max: 0.9
# IP rating (optional)
#ipCode: IP20
# Key provisioning (optional)
# Valid values are: custom (user can configure keys), join server and manifest.
keyProvisioning:
- custom
- join server
# Key security (optional)
# Valid values are: none, read protected and secure element.
keySecurity: none
# Product and data sheet URLs (optional)
productURL: https://github.com/cyberman54/ESP32-Paxcounter
dataSheetURL: https://github.com/cyberman54/ESP32-Paxcounter
#resellerURLs:
# - name: 'Reseller 1'
# region:
# - European Union
# url: https://example.org/reseller1
# - name: 'Reseller 2'
# region:
# - United States
# - Canada
# url: https://example.org/reseller2
# Photos
#photos:
# main: Paxcounter-title.jpg
# other:
# - Paxcounter-title.jpg
# Youtube or Vimeo Video (optional)
###video: https://www.youtube.com/watch?v=JHzxcD2oEn8
# Regulatory compliances (optional)
#compliances:
# safety:
# - body: IEC
# norm: EN
# standard: 62368-1
# radioEquipment:
# - body: ETSI
# norm: EN
# standard: 301 489-1
# version: 2.2.0
# - body: ETSI
# norm: EN
# standard: 301 489-3
# version: 2.1.0

View File

@ -0,0 +1,3 @@
endDevices:
# Unique identifier of the end device (lowercase, alphanumeric with dashes, max 36 characters)
- esp32-paxcounter # look in esp32-paxcounter.yaml for the end device definition

View File

@ -6,9 +6,6 @@
// Local logging Tag
static const char TAG[] = "lora";
// Saves the LMIC structure during deep sleep
RTC_DATA_ATTR lmic_t RTC_LMIC;
#if CLOCK_ERROR_PROCENTAGE > 7
#warning CLOCK_ERROR_PROCENTAGE value in lmic_config.h is too high; values > 7 will cause side effects
#endif
@ -265,7 +262,6 @@ esp_err_t lmic_init(void) {
LMIC_registerRxMessageCb(myRxCallback, NULL);
LMIC_registerEventCb(myEventCallback, NULL);
// to come with future LMIC version
// LMIC_registerBattLevelCb(myBattLevelCb, NULL);
// Reset the MAC state. Session and pending data transfers will be
// discarded.
@ -412,34 +408,6 @@ void myEventCallback(void *pUserData, ev_t ev) {
ESP_LOGD(TAG, "%s", lmic_event_msg);
}
uint8_t myBattLevelCb(void *pUserData) {
// set the battery value to send by LMIC in MAC Command
// DevStatusAns. Available defines in lorabase.h:
// MCMD_DEVS_EXT_POWER = 0x00, // external power supply
// MCMD_DEVS_BATT_MIN = 0x01, // min battery value
// MCMD_DEVS_BATT_MAX = 0xFE, // max battery value
// MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level
// we calculate the applicable value from MCMD_DEVS_BATT_MIN to
// MCMD_DEVS_BATT_MAX from bat_percent value
uint8_t const batt_percent = read_battlevel();
if (batt_percent == 0)
return MCMD_DEVS_BATT_NOINFO;
else
#ifdef HAS_PMU
if (pmu.isVBUSPlug())
return MCMD_DEVS_EXT_POWER;
#elif defined HAS_IP5306
if (IP5306_GetPowerSource())
return MCMD_DEVS_EXT_POWER;
#endif // HAS_PMU
return (batt_percent / 100.0 * (MCMD_DEVS_BATT_MAX - MCMD_DEVS_BATT_MIN + 1));
}
// event EV_RXCOMPLETE message handler
void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
size_t nMsg) {
@ -484,14 +452,70 @@ const char *getCrName(rps_t rps) {
return t[getCr(rps)];
}
// following code snippet was taken from
/*******************************************************************************
*
* ttn-esp32 - The Things Network device library for ESP-IDF / SX127x
*
* Copyright (c) 2018-2021 Manuel Bleichenbacher
*
* Licensed under MIT License
* https://opensource.org/licenses/MIT
*
* Functions for storing and retrieving TTN communication state from RTC memory.
*******************************************************************************/
#define LMIC_OFFSET(field) __builtin_offsetof(struct lmic_t, field)
#define LMIC_DIST(field1, field2) (LMIC_OFFSET(field2) - LMIC_OFFSET(field1))
#define TTN_RTC_MEM_SIZE \
(sizeof(struct lmic_t) - LMIC_OFFSET(radio) - MAX_LEN_PAYLOAD - MAX_LEN_FRAME)
#define TTN_RTC_FLAG_VALUE 0xf8025b8a
RTC_DATA_ATTR uint8_t ttn_rtc_mem_buf[TTN_RTC_MEM_SIZE];
RTC_DATA_ATTR uint32_t ttn_rtc_flag;
void ttn_rtc_save() {
// Copy LMIC struct except client, osjob, pendTxData and frame
size_t len1 = LMIC_DIST(radio, pendTxData);
memcpy(ttn_rtc_mem_buf, &LMIC.radio, len1);
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
memcpy(ttn_rtc_mem_buf + len1, (u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD,
len2);
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
memcpy(ttn_rtc_mem_buf + len1 + len2, (u1_t *)&LMIC.frame + MAX_LEN_FRAME,
len3);
ttn_rtc_flag = TTN_RTC_FLAG_VALUE;
}
bool ttn_rtc_restore() {
if (ttn_rtc_flag != TTN_RTC_FLAG_VALUE)
return false;
// Restore data
size_t len1 = LMIC_DIST(radio, pendTxData);
memcpy(&LMIC.radio, ttn_rtc_mem_buf, len1);
memset(LMIC.pendTxData, 0, MAX_LEN_PAYLOAD);
size_t len2 = LMIC_DIST(pendTxData, frame) - MAX_LEN_PAYLOAD;
memcpy((u1_t *)&LMIC.pendTxData + MAX_LEN_PAYLOAD, ttn_rtc_mem_buf + len1,
len2);
memset(LMIC.frame, 0, MAX_LEN_FRAME);
size_t len3 = sizeof(struct lmic_t) - LMIC_OFFSET(frame) - MAX_LEN_FRAME;
memcpy((u1_t *)&LMIC.frame + MAX_LEN_FRAME, ttn_rtc_mem_buf + len1 + len2,
len3);
ttn_rtc_flag = 0xffffffff; // invalidate RTC data
return true;
}
// following code includes snippets taken from
// https://github.com/JackGruber/ESP32-LMIC-DeepSleep-example/blob/master/src/main.cpp
void SaveLMICToRTC(int deepsleep_sec) {
RTC_LMIC = LMIC;
// ESP32 can't track millis during DeepSleep and no option to advance
// millis after DeepSleep. Therefore reset DutyCyles
// millis after DeepSleep. Therefore reset DutyCyles before saving LMIC struct
unsigned long now = millis();
@ -499,29 +523,34 @@ void SaveLMICToRTC(int deepsleep_sec) {
#if CFG_LMIC_EU_like
for (int i = 0; i < MAX_BANDS; i++) {
ostime_t correctedAvail =
RTC_LMIC.bands[i].avail -
LMIC.bands[i].avail -
((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC);
if (correctedAvail < 0) {
correctedAvail = 0;
}
RTC_LMIC.bands[i].avail = correctedAvail;
LMIC.bands[i].avail = correctedAvail;
}
RTC_LMIC.globalDutyAvail = RTC_LMIC.globalDutyAvail -
((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC);
if (RTC_LMIC.globalDutyAvail < 0) {
RTC_LMIC.globalDutyAvail = 0;
LMIC.globalDutyAvail =
LMIC.globalDutyAvail - ((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC);
if (LMIC.globalDutyAvail < 0) {
LMIC.globalDutyAvail = 0;
}
#else
ESP_LOGW(TAG, "No DutyCycle recalculation function!");
#endif
ttn_rtc_save();
ESP_LOGI(TAG, "LMIC state saved");
}
void LoadLMICFromRTC() {
LMIC = RTC_LMIC;
if (ttn_rtc_restore())
ESP_LOGI(TAG, "LMIC state loaded");
else {
ESP_LOGE(TAG, "LMIC state not found - resetting device");
do_reset(false); // coldstart
}
}
#endif // HAS_LORA

View File

@ -118,14 +118,10 @@ void setup() {
// load device configuration from NVRAM and set runmode
do_after_reset();
// set time zone to user value from paxcounter.conf
#ifdef TIME_SYNC_TIMEZONE
myTZ.setPosix(TIME_SYNC_TIMEZONE);
#endif
// hash 6 byte device MAC to 4 byte clientID
uint8_t mac[6];
esp_eth_get_mac(mac);
esp_read_mac(mac, ESP_MAC_WIFI_STA);
const uint32_t hashedmac = myhash((const char *)mac, 6);
snprintf(clientId, 20, "paxcounter_%08x", hashedmac);
ESP_LOGI(TAG, "Starting %s v%s (runmode=%d / restarts=%d)", clientId,
@ -302,6 +298,7 @@ void setup() {
// configure BLE sniffing
configuration.blecounter = cfg.blescan;
configuration.blescantime = cfg.blescantime;
configuration.ble_rssi_threshold = cfg.rssilimit;
ESP_LOGI(TAG, "BLESCAN: %s", cfg.blescan ? "on" : "off");
int config_update = libpax_update_config(&configuration);

View File

@ -80,7 +80,7 @@
#define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds]
// settings for syncing time of node with a time source (network / gps / rtc / timeserver)
#define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 1]
#define TIME_SYNC_LORAWAN 1 // set to 1 to use LORA network as time source, 0 means off [default = 1]
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
#define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source [default = 60], 0 means off
#define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off

View File

@ -258,17 +258,48 @@ uint16_t read_voltage(void) {
uint8_t read_battlevel(mapFn_t mapFunction) {
// returns the estimated battery level in values 0 ... 100 [percent]
uint8_t batt_percent = 0;
#ifdef HAS_IP5306
return IP5306_GetBatteryLevel();
batt_percent = IP5306_GetBatteryLevel();
#else
const uint16_t batt_voltage = read_voltage();
if (batt_voltage <= BAT_MIN_VOLTAGE)
return 0;
batt_percent = 0;
else if (batt_voltage >= BAT_MAX_VOLTAGE)
return 100;
batt_percent = 100;
else
return (*mapFunction)(batt_voltage, BAT_MIN_VOLTAGE, BAT_MAX_VOLTAGE);
batt_percent =
(*mapFunction)(batt_voltage, BAT_MIN_VOLTAGE, BAT_MAX_VOLTAGE);
#endif
#if (HAS_LORA)
// set the battery status value to send by LMIC in MAC Command
// DevStatusAns. Available defines in lorabase.h:
// MCMD_DEVS_EXT_POWER = 0x00, // external power supply
// MCMD_DEVS_BATT_MIN = 0x01, // min battery value
// MCMD_DEVS_BATT_MAX = 0xFE, // max battery value
// MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level
// we calculate the applicable value from MCMD_DEVS_BATT_MIN to
// MCMD_DEVS_BATT_MAX from batt_percent value
if (batt_percent == 0)
LMIC_setBatteryLevel(MCMD_DEVS_BATT_NOINFO);
else
LMIC_setBatteryLevel(batt_percent / 100.0 *
(MCMD_DEVS_BATT_MAX - MCMD_DEVS_BATT_MIN + 1));
// overwrite calculated value if we have external power
#ifdef HAS_PMU
if (pmu.isVBUSPlug())
LMIC_setBatteryLevel(MCMD_DEVS_EXT_POWER);
#elif defined HAS_IP5306
if (IP5306_GetPowerSource())
LMIC_setBatteryLevel(MCMD_DEVS_EXT_POWER);
#endif // HAS_PMU
#endif // HAS_LORA
return batt_percent;
}
bool batt_sufficient() {

View File

@ -60,6 +60,7 @@ void set_rssi(uint8_t val[]) {
libpax_config_t current_config;
libpax_get_current_config(&current_config);
current_config.wifi_rssi_threshold = cfg.rssilimit;
current_config.ble_rssi_threshold = cfg.rssilimit;
libpax_update_config(&current_config);
init_libpax();
#endif

View File

@ -47,6 +47,12 @@ void do_after_reset(void) {
// read (and initialize on first run) runtime settings from NVRAM
loadConfig();
// set time zone to user value from paxcounter.conf
#ifdef TIME_SYNC_TIMEZONE
myTZ.setPosix(TIME_SYNC_TIMEZONE);
ESP_LOGD(TAG, "Timezone set to %s", myTZ.getPosix().c_str());
#endif
switch (rtc_get_reset_reason(0)) {
case POWERON_RESET: // 0x01 Vbat power on reset
@ -69,8 +75,8 @@ void do_after_reset(void) {
RTC_millis += sleep_time_ms; // increment system monotonic time
ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms);
// set time
setMyTime(RTC_time + sleep_time_ms / 1000, sleep_time_ms % 1000, _set);
// restore time
setMyTime(RTC_time, sleep_time_ms, _set);
// set wakeup state, not if we have pending OTA update
if (RTC_runmode == RUNMODE_SLEEP)
@ -132,7 +138,7 @@ void enter_deepsleep(const uint64_t wakeup_sec, gpio_num_t wakeup_gpio) {
#if (HAS_LORA)
ESP_LOGI(TAG, "Waiting until LMIC is idle...");
for (i = 100; i > 0; i--) {
if ((LMIC.opmode & OP_TXRXPEND) ||
if ((LMIC.opmode & (OP_JOINING | OP_TXDATA | OP_POLL | OP_TXRXPEND)) ||
os_queryTimeCriticalJobs(sec2osticks(wakeup_sec)))
vTaskDelay(pdMS_TO_TICKS(1000));
else

View File

@ -121,7 +121,7 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
_seconds(), mytimesource);
} else {
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, setTimeSyncIRQ);
ESP_LOGD(TAG,
ESP_LOGV(TAG,
"[%0.3f] Failed to synchronise time from source %c | unix sec "
"obtained from source: %d | unix sec at program compilation: %d",
_seconds(), timeSetSymbols[mytimesource], time_to_set,
@ -180,8 +180,10 @@ void timepulse_start(void) {
timerAlarmEnable(ppsIRQ);
#endif
// start cyclic time sync
// get time if we don't have one
if (timeSource != _set)
setTimeSyncIRQ(); // init systime by RTC or GPS or LORA
// start cyclic time sync
timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ);
}

View File

@ -131,19 +131,19 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) {
// calculate average time offset over the summed up difference
time_offset_ms /= TIME_SYNC_SAMPLES;
// add milliseconds from latest gateway time, and apply a compensation
// constant for processing times on node and gateway, strip full seconds
time_offset_ms += timesync_timestamp[sample_idx - 1][gwtime_msec];
time_offset_ms -= TIME_SYNC_FIXUP;
time_offset_ms %= 1000;
// take latest timestamp received from gateway
// and add time difference rounded to whole seconds
time_offset_sec = timesync_timestamp[sample_idx - 1][gwtime_sec];
time_offset_sec += time_offset_ms / 1000;
// add milliseconds from latest gateway time, and apply a compensation
// constant for processing times on node and gateway, strip full seconds
time_offset_ms += timesync_timestamp[sample_idx - 1][gwtime_msec];
time_offset_ms += TIME_SYNC_FIXUP;
time_offset_ms %= 1000;
ESP_LOGD(TAG, "LORA date/time: %s",
myTZ.dateTime(time_offset_sec, "d.M Y H:i:s T").c_str());
myTZ.dateTime(time_offset_sec, "d.M Y H:i:s.v T").c_str());
setMyTime(time_offset_sec, time_offset_ms, _lora);
@ -168,7 +168,7 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) {
// store incoming timestamps
void timesync_store(uint32_t timestamp, timesync_t timestamp_type) {
ESP_LOGD(TAG, "[%0.3f] seq#%d[%d]: t%d=%d", _seconds(), time_sync_seqNo,
ESP_LOGD(TAG, "[%0.3f] seq#%d[%d]: t%d=%d", _seconds(), time_sync_seqNo - 1,
sample_idx, timestamp_type, timestamp);
timesync_timestamp[sample_idx][timestamp_type] = timestamp;
}
@ -230,7 +230,7 @@ void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag) {
// pUserData: contains pointer to SeqNo (not needed here)
// flag: indicates if we got a recent time from the network
uint32_t delay_msec;
int32_t delay_msec;
lmic_time_reference_t lmicTime;
if (flag != 1) {
@ -239,14 +239,16 @@ void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag) {
}
// Populate lmic_time_reference
if ((LMIC_getNetworkTimeReference(&lmicTime)) != 1) {
flag = LMIC_getNetworkTimeReference(&lmicTime);
if (flag != 1) {
ESP_LOGW(TAG, "[%0.3f] Network time request failed", _seconds());
goto Exit;
}
// Calculate UTCTime, considering the difference between GPS and UTC time
// epoch, and the leap seconds
timestamp_sec = lmicTime.tNetwork + GPS_UTC_DIFF;
timestamp_sec = lmicTime.tNetwork + GPS_UTC_DIFF - LEAP_SECS_SINCE_GPSEPOCH;
ESP_LOGD(TAG, "lmicTime.tNetwork = %d", timestamp_sec);
// Add the delay between the instant the time was transmitted and
// the current time