Merge pull request #855 from cyberman54/development

v3.2.0
This commit is contained in:
Verkehrsrot 2022-02-16 21:20:12 +01:00 committed by GitHub
commit 19ade61e66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 580 additions and 638 deletions

View File

@ -202,7 +202,7 @@ Output of sensor and peripheral data is internally switched by a bitmask registe
# Power saving mode
Paxcounter supports a battery friendly power saving mode. In this mode the device enters deep sleep, after all data is polled from all sensors and the dataset is completeley sent through all user configured channels (LORAWAN / SPI / MQTT). Set *#define SLEEPCYCLE* in paxcounter.conf to enable power saving mode and to specify the duration of a sleep cycle. Power consumption in deep sleep mode depends on your hardware, i.e. if on board peripherals can be switched off or set to a chip specific sleep mode either by MCU or by power management unit (PMU) as found on TTGO T-BEAM v1.0/V1.1. See *power.cpp* for power management, and *reset.cpp* for sleep and wakeup logic.
Paxcounter supports a battery friendly power saving mode. In this mode the device enters deep sleep, after all data is polled from all sensors and the dataset is completeley sent through all user configured channels (LORAWAN / SPI / MQTT / SD-Card). Set *#define SLEEPCYCLE* in paxcounter.conf to enable power saving mode and to specify the duration of a sleep cycle. Power consumption in deep sleep mode depends on your hardware, i.e. if on board peripherals can be switched off or set to a chip specific sleep mode either by MCU or by power management unit (PMU) as found on TTGO T-BEAM v1.0/V1.1. See *power.cpp* for power management, and *reset.cpp* for sleep and wakeup logic.
# Time sync
@ -216,22 +216,13 @@ Paxcounter can be used to sync a wall clock which has a DCF77 or IF482 time tele
This describes how to set up a mobile PaxCounter:<br> Follow all steps so far for preparing the device, selecting the packed payload format. In `paxcounter.conf` set PAYLOAD_OPENSENSEBOX to 1. Register a new sensebox on https://opensensemap.org/. In the sensor configuration select "TheThingsNetwork" and set decoding profile to "LoRa serialization". Enter your TTN Application and Device ID. Setup decoding option using `[{"decoder":"latLng"},{"decoder":"uint16",sensor_id":"yoursensorid"}]`
# Covid-19 Exposure Notification System beacon detection (currently NOT working with v3.0.x, use v2.4.x for this feature)
Bluetooth low energy service UUID 0xFD6F, used by Google/Apple COVID-19 Exposure Notification System, can be monitored and counted. By comparing with the total number of observed devices this <A HREF="https://linux-fuer-wi.blogspot.com/2020/10/suche-die-zahl-64879.html">gives an indication</A> how many people staying in proximity are using Apps for tracing COVID-19 exposures, e.g. in Germany the "Corona Warn App". To achive best results with this funcion, use following settings in `paxcounter.conf`:
#define COUNT_ENS 1 // enable ENS monitoring function
#define BLECOUNTER 1 // enable bluetooth sniffing
#define WIFICOUNTER 0 // disable wifi sniffing (improves BLE scan speed)
#define HAS_SENSOR_1 1 // optional, in board's hal file: transmit ENS counter data to server
# SD-card
Data can be stored on an SD-card if one is availabe. Simply choose the file in src/hal and add the following lines to your hal-file:
Data can be stored on SD-card if the board provides an SD card interface, either with SPI or MMC mode. To enable this feature, specify interface mode and hardware pins in board's hal file (src/hal/<board.h>):
#define HAS_SDCARD 1 // SD-card-reader/writer, using SPI interface
#define HAS_SDCARD 1 // SD-card interface, using SPI mode
OR
#define HAS_SDCARD 2 // SD-card-reader/writer, using SDMMC interface
#define HAS_SDCARD 2 // SD-card interface, using MMC mode
// Pins for SPI interface
#define SDCARD_CS (13) // fill in the correct numbers for your board
@ -239,31 +230,24 @@ Data can be stored on an SD-card if one is availabe. Simply choose the file in s
#define SDCARD_MISO (2)
#define SDCARD_SCLK (14)
Please choose the correct number for the connection of the reader/writer.
This is an example of a board with SD-card: https://www.aliexpress.com/item/32990008126.html
In this case you take the file src/hal/ttgov21new.h and add the lines given above (numbers given are for this board).
This is an example of a board with MMC SD-card interface: https://www.aliexpress.com/item/32915894264.html. For this board use file src/hal/ttgov21new.h and add the lines given above.
Another approach would be this tiny board: https://www.aliexpress.com/item/32424558182.html (needs 5V).
In this case you choose the correct file for your ESP32-board in the src/hal-directory and add the lines given above to the correct h-file. Please correct the numbers given in the example to the numbers used corresponding to your wiring.
In this case you choose the correct file for your ESP32-board in the src/hal-directory and add the lines given above. Edit the pin numbers given in the example, according to your wiring.
Some hints:
These cheap devices often handle SD-cards up to 32GB, not bigger ones. They can handle files in the old DOS-way, to say the filenames are in the 8.3-format. And they often cannot handle subdirectories.
Data is written on SD-card to a single file. After 3 write operations the data is flushed to the disk to minimize flash write cycles. Thus, up to the last 3 records of data will get lost when the PAXCOUNTER looses power during operation.
The software included here writes data in a file named PAXCOUNT.xx, where xx can range from 00 to 99. The software starts with 00, checks to see if such a file already exists and if yes it will continue with the next number (up to 99 - in this case it will return no sd-card). So an existing file will not be overwritten.
Format of the resulting file is CSV, thus easy import in LibreOffice, Excel, Influx, etc. Each record contains timestamp (in ISO8601 format), paxcount (wifi and ble) and battery voltage (optional). Voltage is logged if the device has a battery voltage sensor (to be configured in board hal file).
The data is written to the card and after 3 write-operations the data is flushed to the disk. So maybe the last 3 minutes of data get lost when you disconnect the PAXCOUNTER from power.
File contents example:
And finally: this is the data written to the disk:
timestamp,wifi,ble[,voltage]
2022-01-30T21:12:41Z,11,25[,4100]
2022-01-30T21:14:24Z,10,21[,4070]
2022-01-30T21:16:08Z,12,26[,4102]
2022-01-30T21:17:52Z,11,26[,4076]
date, time, wifi, bluet
00.00.1970,00:01:09,2,0
00.00.1970,00:02:09,1,0
00.00.1970,00:03:09,2,0
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.
If you want to change this, modify src/sdcard.cpp and include/sdcard.h.
# Integration into "The Things Stack Community Edition" aka "The Things Stack V3"
@ -383,7 +367,7 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering.
**Ports #10, #11, #12:** User sensor data
Format is specified by user in function `sensor_read(uint8_t sensor)`, see `src/sensor.cpp`. Port #10 is also used for ENS counter (2 bytes = 16 bit), if ENS is compiled AND ENS data transfer is enabled
Format is specified by user in function `sensor_read(uint8_t sensor)`, see `src/sensor.cpp`.
# Remote control
@ -503,7 +487,7 @@ Send for example `83` `86` as Downlink on Port 2 to get battery status and time/
0x02 = RESERVED_DATA
0x04 = MEMS_DATA
0x08 = GPS_DATA
0x10 = SENSOR_1_DATA (also ENS counter)
0x10 = SENSOR_1_DATA
0x20 = SENSOR_2_DATA
0x40 = SENSOR_3_DATA
0x80 = BATT_DATA
@ -524,10 +508,9 @@ Send for example `83` `86` as Downlink on Port 2 to get battery status and time/
0 = disabled
1 = enabled [default]
0x18 set ENS counter on/off
0x18 reserved
0 = disabled [default]
1 = enabled
unused, does nothing
0x19 set sleep cycle
@ -621,4 +604,4 @@ Thanks to
- [terrillmoore](https://github.com/mcci-catena) for maintaining the LMIC for arduino LoRaWAN stack
- [sbamueller](https://github.com/sbamueller) for writing the tutorial in Make Magazine
- [Stefan](https://github.com/nerdyscout) for paxcounter opensensebox integration
- [August Quint](https://github.com/AugustQu) for adding SD card data logger, SDS011 and ENS support
- [August Quint](https://github.com/AugustQu) for adding SD card data logger and SDS011 support

View File

@ -1,16 +0,0 @@
#ifndef _CORONA_h
#define _CORONA_H
// inspired by https://github.com/kmetz/BLEExposureNotificationBeeper
// (c) by Kaspar Metz
// modified for use in the Paxcounter by AQ
#include "globals.h"
#include <map>
bool cwa_init(void);
void cwa_mac_add(uint16_t hashedmac);
void cwa_clear(void);
uint16_t cwa_report(void);
#endif

View File

@ -5,10 +5,7 @@
#include "cyclic.h"
#include "qrcode.h"
#include "power.h"
#if (COUNT_ENS)
#include "corona.h"
#endif
#include "timekeeper.h"
#if (HAS_DISPLAY) == 1
#include <OneBitDisplay.h>

View File

@ -71,7 +71,6 @@ typedef struct __attribute__((packed)) {
uint8_t wifiant; // 0=internal, 1=external (for LoPy/LoPy4)
uint8_t rgblum; // RGB Led luminosity (0..100%)
uint8_t payloadmask; // bitswitches for payload data
uint8_t enscount; // 0=disabled 1= enabled
#ifdef HAS_BME680
uint8_t
@ -111,6 +110,5 @@ typedef struct {
} sdsStatus_t;
extern char clientId[20]; // unique clientID
extern time_t _COMPILETIME; // epoch build time
#endif

View File

@ -5,13 +5,10 @@
#include <RtcDateTime.h>
#include "timekeeper.h"
#ifdef GPS_I2C // Needed for reading from I2C Bus
#include <Wire.h>
#ifndef GPS_BAUDRATE
#define GPS_BAUDRATE 115200UL
#endif
#define NMEA_FRAME_SIZE 82 // NEMA has a maxium of 82 bytes per record
#define NMEA_COMPENSATION_FACTOR 480 // empiric for Ublox Neo 6M
extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe
extern TaskHandle_t GpsTask;

View File

@ -24,7 +24,7 @@
void irqHandler(void *pvParameters);
void mask_user_IRQ();
void unmask_user_IRQ();
void doIRQ(int irq);
void IRAM_ATTR doIRQ(int irq);
extern TaskHandle_t irqHandlerTask;

View File

@ -8,6 +8,6 @@
void init_libpax(void);
extern uint16_t volatile libpax_macs_ble, libpax_macs_wifi; // libpax values
extern struct count_payload_t count_from_libpax; // libpax count storage
#endif

View File

@ -7,6 +7,8 @@
#include <esp_coexist.h> // needed for coex version display
#include <esp_wifi.h> // needed for wifi init / deinit
#include <libpax_helpers.h>
#include "globals.h"
#include "reset.h"
#include "i2c.h"
@ -18,9 +20,7 @@
#include "sensor.h"
#include "lorawan.h"
#include "timekeeper.h"
#include "corona.h"
#include "boot.h"
#include "libpax_helpers.h"
#include "power.h"
#include "antenna.h"

View File

@ -3,6 +3,7 @@
#include <rom/rtc.h>
#include "libpax_helpers.h"
#include "senddata.h"
#include "cyclic.h"
#include "configmanager.h"

View File

@ -8,6 +8,8 @@
#include "lorawan.h"
#include "display.h"
#include "power.h"
#include "sdcard.h"
#include "sds011read.h"
void reset_rtc_vars(void);
void do_reset(bool warmstart);

View File

@ -12,7 +12,7 @@ extern RtcDS3231<TwoWire> Rtc; // make RTC instance globally available
uint8_t rtc_init(void);
uint8_t set_rtctime(time_t t);
void sync_rtctime(void);
time_t get_rtctime(void);
time_t get_rtctime(uint16_t *msec);
float get_rtctemp(void);
#endif // _RTCTIME_H

View File

@ -1,13 +1,13 @@
#ifndef _SDCARD_H
#define _SDCARD_H
#include <globals.h>
#include "globals.h"
#include <stdio.h>
#include <SPI.h>
#if (HAS_SDCARD)
#if HAS_SDCARD == 1
#include <mySD.h>
#include <SD.h>
#elif HAS_SDCARD == 2
#include <SD_MMC.h>
#else
@ -54,14 +54,15 @@
#define SDCARD_DATA3 13
#endif
#define SDCARD_FILE_NAME "/paxcount.%02d"
#define SDCARD_FILE_HEADER "date, time, wifi, bluet"
#define SDCARD_FILE_NAME clientId
#define SDCARD_FILE_HEADER "timestamp,wifi,ble"
#if (COUNT_ENS)
#define SDCARD_FILE_HEADER_CWA ",cwa"
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
#define SDCARD_FILE_HEADER_VOLTAGE ",voltage"
#endif
bool sdcard_init(void);
bool sdcard_init(bool create = true);
void sdcard_close(void);
void sdcardWriteData(uint16_t, uint16_t, uint16_t = 0);
#endif // _SDCARD_H

View File

@ -1,15 +1,24 @@
#ifndef _SDS011READ_H
#define _SDS011READ_H
#include <SDS011.h>
#include <SdsDustSensor.h>
#include "globals.h"
#define SDCARD_FILE_HEADER_SDS011 ", PM10,PM25"
// use original pins from HardwareSerial if none defined
#ifndef SDS_TX
#define SDS_TX -1
#endif
#ifndef SDS_RX
#define SDS_RX -1
#endif
extern bool isSDS011Active;
bool sds011_init();
void sds011_loop();
void sds011_sleep(void);
void sds011_wakeup(void);
void sds011_store(sdsStatus_t *sds_store);
#endif // _SDS011READ_H

View File

@ -1,7 +1,6 @@
#ifndef _SENDDATA_H
#define _SENDDATA_H
#include "libpax_helpers.h"
#include "spislave.h"
#include "mqttclient.h"
#include "cyclic.h"
@ -10,18 +9,13 @@
#include "display.h"
#include "sdcard.h"
#if (COUNT_ENS)
#include "corona.h"
#endif
extern struct count_payload_t count_from_libpax;
void SendPayload(uint8_t port);
void sendData(void);
void checkSendQueues(void);
void flushQueues(void);
bool allQueuesEmtpy(void);
void setSendIRQ(TimerHandle_t xTimer = NULL);
void setSendIRQ(TimerHandle_t xTimer);
void setSendIRQ(void);
void initSendDataTimer(uint8_t sendcycle);
#endif // _SENDDATA_H_

View File

@ -10,8 +10,8 @@
#include "dcf77.h"
#include "esp_sntp.h"
#define SECS_YR_2000 (946684800UL) // the time at the start of y2k
#define GPS_UTC_DIFF 315964800UL // seconds diff between gps and utc epoch
#define SECS_YR_2000 (946684800UL) // the time at the start of y2k
#define GPS_UTC_DIFF 315964800UL // seconds diff between gps and utc epoch
#define LEAP_SECS_SINCE_GPSEPOCH 18UL // state of 2021
enum timesource_t { _gps, _rtc, _lora, _unsynced, _set };
@ -20,22 +20,22 @@ extern const char timeSetSymbols[];
extern Ticker timesyncer;
extern timesource_t timeSource;
extern TaskHandle_t ClockTask;
extern bool volatile TimePulseTick; // 1sec pps flag set by GPS or RTC
extern DRAM_ATTR bool TimePulseTick; // 1sec pps flag set by GPS or RTC
extern DRAM_ATTR unsigned long lastPPS;
extern hw_timer_t *ppsIRQ;
void IRAM_ATTR CLOCKIRQ(void);
void IRAM_ATTR GPSIRQ(void);
void clock_init(void);
void clock_loop(void *pvParameters);
void timepulse_start(void);
void setTimeSyncIRQ(void);
uint8_t timepulse_init(void);
bool timeIsValid(time_t const t);
void calibrateTime(void);
void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
timesource_t mytimesource);
time_t compileTime(const String compile_date);
time_t compileTime(void);
time_t mkgmtime(const struct tm *ptm);
TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
int8_t rxPin, int8_t txPins);
#endif // _timekeeper_H

View File

@ -48,7 +48,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I
[common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
release_version = 3.1.0
release_version = 3.2.0
; 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,7 +56,7 @@ extra_scripts = pre:build.py
otakeyfile = ota.conf
lorakeyfile = loraconf.h
lmicconfigfile = lmic_config.h
platform_espressif32 = espressif32@3.4.0
platform_espressif32 = espressif32@3.5.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
@ -77,7 +77,7 @@ lib_deps_sensors =
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
https://github.com/cyberman54/sds-dust-sensors-arduino-library.git
lib_deps_basic =
https://github.com/dbSuS/libpax.git @ ^1.0.0
https://github.com/SukkoPera/Arduino-Rokkit-Hash.git
@ -85,7 +85,6 @@ lib_deps_basic =
makuna/RTC @ ^2.3.5
spacehuhn/SimpleButton
lewisxhe/AXP202X_Library @ ^1.1.3
geeksville/esp32-micro-sdcard @ ^0.1.1
256dpi/MQTT @ ^2.4.8
lib_deps_all =
${common.lib_deps_basic}

View File

@ -84,11 +84,6 @@ function Decoder(bytes, port) {
}
}
if (port === 10) {
// ENS count
return decode(bytes, [uint16], ['ens']);
}
}

View File

@ -91,11 +91,6 @@ function decodeUplink(input) {
}
}
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

View File

@ -84,13 +84,6 @@ function decodeUplink(input) {
}
}
if (input.fPort === 10) {
var i = 0;
if (input.bytes.length >= 2) {
data.ens = (input.bytes[i++] << 8) | input.bytes[i++];
}
}
if (data.hdop) {
data.hdop /= 100;
data.latitude /= 1000000;

View File

@ -6,12 +6,6 @@
// Local logging tag
static const char TAG[] = __FILE__;
// default settings for device data to be sent
#define PAYLOADMASK \
((GPS_DATA | MEMS_DATA | COUNT_DATA | SENSOR1_DATA | SENSOR2_DATA | \
SENSOR3_DATA) & \
(~BATT_DATA) & (~RESERVED_DATA))
// namespace for device runtime preferences
#define DEVCONFIG "paxcntcfg"
@ -57,7 +51,6 @@ static void defaultConfig(configData_t *myconfig) {
myconfig->wifiant = 0; // 0=internal, 1=external (for LoPy/LoPy4)
myconfig->rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%)
myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default
myconfig->enscount = COUNT_ENS; // 0=disabled, 1=enabled
#ifdef HAS_BME680
// initial BSEC state for BME680 sensor

View File

@ -1,54 +0,0 @@
// routines for counting the number of devices which advertise Exposure
// Notification Service e.g. "Corona Warn App" in Germany
// copied from https://github.com/kmetz/BLEExposureNotificationBeeper
// (c) by Kaspar Metz
// modified for use in the Paxcounter by AQ
#if (COUNT_ENS) && !(BLECOUNTER)
#warning ENS-Counter needs Bluetooth, but Bluetooth compile option is disabled
#endif
#if (COUNT_ENS)
// Local logging tag
static const char TAG[] = __FILE__;
#define BT_BD_ADDR_HEX(addr) \
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]
#include "corona.h"
// When to forget old senders ** currently not used **
#define FORGET_AFTER_MINUTES 2
// array of timestamps for seen notifiers: hash -> timestamp[ms]
static std::map<uint16_t, unsigned long> cwaSeenNotifiers;
// Remove notifiers last seen over FORGET_AFTER_MINUTES ago.
void cwa_clear() {
#ifdef VERBOSE
ESP_LOGV(TAG, "CWA: forget old notifier: %d", cwaSeenNotifiers.size());
for (auto const &notifier : cwaSeenNotifiers) {
ESP_LOGD(TAG, "CWA forget <%04X>", notifier.first);
// }
}
#endif
// clear everything, otherwise we would count the same device again, as in the
// next cycle it likely will advertise with a different hash-value
cwaSeenNotifiers.clear();
}
// return the total number of devices seen advertising ENS
uint16_t cwa_report(void) { return cwaSeenNotifiers.size(); }
bool cwa_init(void) {
ESP_LOGD(TAG, "init BLE-scanner for ENS");
return true;
}
void cwa_mac_add(uint16_t hashedmac) {
cwaSeenNotifiers[hashedmac] = millis(); // hash last seen at ....
}
#endif

View File

@ -9,10 +9,6 @@ static const char TAG[] = __FILE__;
Ticker cyclicTimer;
#if (HAS_SDS011)
extern boolean isSDS011Active;
#endif
void setCyclicIRQ() { xTaskNotify(irqHandlerTask, CYCLIC_IRQ, eSetBits); }
// do all housekeeping
@ -22,44 +18,62 @@ void doHousekeeping() {
if ((RTC_runmode == RUNMODE_UPDATE) || (RTC_runmode == RUNMODE_MAINTENANCE))
do_reset(true); // warmstart
// heap and task storage debugging
// try to get time if we don't yet have a recent timesource
if (timeSource == _unsynced || timeSource == _set)
calibrateTime();
// print heap and task storage information
ESP_LOGD(TAG, "Heap: Free:%d, Min:%d, Size:%d, Alloc:%d, StackHWM:%d",
ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getHeapSize(),
ESP.getMaxAllocHeap(), uxTaskGetStackHighWaterMark(NULL));
ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(irqHandlerTask),
eTaskGetState(irqHandlerTask));
ESP_LOGD(TAG, "Rcommand interpreter %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(rcmdTask), eTaskGetState(rcmdTask));
if (irqHandlerTask != NULL)
ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(irqHandlerTask),
eTaskGetState(irqHandlerTask));
if (rcmdTask != NULL)
ESP_LOGD(TAG, "Rcommand interpreter %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(rcmdTask), eTaskGetState(rcmdTask));
#if (HAS_LORA)
ESP_LOGD(TAG, "LMiCtask %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(lmicTask), eTaskGetState(lmicTask));
ESP_LOGD(TAG, "Lorasendtask %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(lorasendTask),
eTaskGetState(lorasendTask));
if (lmicTask != NULL)
ESP_LOGD(TAG, "LMiCtask %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(lmicTask), eTaskGetState(lmicTask));
if (lorasendTask != NULL)
ESP_LOGD(TAG, "Lorasendtask %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(lorasendTask),
eTaskGetState(lorasendTask));
#endif
#if (HAS_GPS)
ESP_LOGD(TAG, "Gpsloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(GpsTask), eTaskGetState(GpsTask));
if (GpsTask != NULL)
ESP_LOGD(TAG, "Gpsloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(GpsTask), eTaskGetState(GpsTask));
#endif
#ifdef HAS_SPI
ESP_LOGD(TAG, "spiloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(spiTask), eTaskGetState(spiTask));
if (spiTask != NULL)
ESP_LOGD(TAG, "spiloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(spiTask), eTaskGetState(spiTask));
#endif
#ifdef HAS_MQTT
ESP_LOGD(TAG, "MQTTloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(mqttTask), eTaskGetState(mqttTask));
if (mqttTask != NULL)
ESP_LOGD(TAG, "MQTTloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(mqttTask), eTaskGetState(mqttTask));
#endif
#if (defined HAS_DCF77 || defined HAS_IF482)
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
if (ClockTask != NULL)
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
#endif
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
ESP_LOGD(TAG, "LEDloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(ledLoopTask),
eTaskGetState(ledLoopTask));
if (ledLoopTask != NULL)
ESP_LOGD(TAG, "LEDloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(ledLoopTask),
eTaskGetState(ledLoopTask));
#endif
// read battery voltage into global variable
@ -106,10 +120,8 @@ void doHousekeeping() {
#if (HAS_SDS011)
if (isSDS011Active) {
ESP_LOGD(TAG, "SDS011: go to sleep");
sds011_loop();
} else {
ESP_LOGD(TAG, "SDS011: wakeup");
sds011_wakeup();
}
#endif

View File

@ -51,18 +51,17 @@ void DCF77_Pulse(uint8_t bit) {
// helper function to convert decimal to bcd digit
uint64_t dec2bcd(uint8_t const dec, uint8_t const startpos,
uint8_t const endpos, uint8_t *odd_parity) {
uint8_t const endpos, uint8_t *parity) {
uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10);
uint8_t data = dec < 10 ? dec : ((dec / 10) << 4) + dec % 10;
uint64_t bcd = 0;
*odd_parity = 0;
*parity = 0;
for (uint8_t i = startpos; i <= endpos; i++) {
bcd += (data & 1) ? set_dcfbit(i) : 0;
*odd_parity += (data & 1);
bcd += data & 1 ? set_dcfbit(i) : 0;
*parity ^= data & 1;
data >>= 1;
}
*odd_parity %= 2;
return bcd;
}
@ -96,14 +95,14 @@ uint64_t DCF77_Frame(const struct tm t) {
// DATE (36..58)
frame += dec2bcd(t.tm_mday, 36, 41, &parity);
parity_sum += parity;
parity_sum ^= parity;
frame += dec2bcd((t.tm_wday == 0) ? 7 : t.tm_wday, 42, 44, &parity);
parity_sum += parity;
parity_sum ^= parity;
frame += dec2bcd(t.tm_mon + 1, 45, 49, &parity);
parity_sum += parity;
parity_sum ^= parity;
frame += dec2bcd(t.tm_year + 1900 - 2000, 50, 57, &parity);
parity_sum += parity;
frame += parity_sum % 2 ? set_dcfbit(58) : 0;
parity_sum ^= parity;
frame += parity_sum ? set_dcfbit(58) : 0;
return frame;

View File

@ -174,16 +174,10 @@ void dp_init(bool verbose) {
void dp_refresh(bool nextPage) {
// update counter values from libpax
libpax_counter_count(&count_from_libpax);
#ifndef HAS_BUTTON
static uint32_t framecounter = 0;
#endif
// update histogram
dp_plotCurve(count_from_libpax.pax, false);
// if display is switched off we don't refresh it to relax cpu
if (!DisplayIsOn && (DisplayIsOn == cfg.screenon))
return;
@ -207,7 +201,6 @@ void dp_refresh(bool nextPage) {
#endif
dp_drawPage(nextPage);
dp_dump(displaybuf);
I2C_MUTEX_UNLOCK(); // release i2c bus access
@ -219,6 +212,7 @@ void dp_drawPage(bool nextpage) {
// write display content to display buffer
// nextpage = true -> flip 1 page
struct count_payload_t count; // libpax count storage
static uint8_t DisplayPage = 0;
char timeState, strftime_buf[64];
time_t now;
@ -233,6 +227,9 @@ void dp_drawPage(bool nextpage) {
dp_clear();
}
// update counter values from libpax
libpax_counter_count(&count);
// cursor home
dp_setTextCursor(0, 0);
@ -240,7 +237,7 @@ void dp_drawPage(bool nextpage) {
// display number of unique macs total Wifi + BLE
if (DisplayPage < 5) {
dp_setFont(MY_FONT_STRETCHED);
dp_printf("%-5d", count_from_libpax.pax);
dp_printf("%-5d", count.pax);
}
switch (DisplayPage) {
@ -264,32 +261,22 @@ void dp_drawPage(bool nextpage) {
#if ((WIFICOUNTER) && (BLECOUNTER))
if (cfg.wifiscan)
dp_printf("WIFI:%-5d", count_from_libpax.wifi_count);
dp_printf("WIFI:%-5d", count.wifi_count);
else
dp_printf("WIFI:off");
if (cfg.blescan)
#if (COUNT_ENS)
if (cfg.enscount)
dp_printf(" CWA:%-5d", cwa_report());
else
#endif
dp_printf("BLTH:%-5d", count_from_libpax.ble_count);
dp_printf("BLTH:%-5d", count.ble_count);
else
dp_printf(" BLTH:off");
#elif ((WIFICOUNTER) && (!BLECOUNTER))
if (cfg.wifiscan)
dp_printf("WIFI:%-5d", count_from_libpax.wifi_count);
dp_printf("WIFI:%-5d", count.wifi_count);
else
dp_printf("WIFI:off");
#elif ((!WIFICOUNTER) && (BLECOUNTER))
if (cfg.blescan)
dp_printf("BLTH:%-5d", count_from_libpax.ble_count);
#if (COUNT_ENS)
if (cfg.enscount)
dp_printf("(CWA:%d)", cwa_report());
else
#endif
dp_printf("BLTH:off");
dp_printf("BLTH:%-5d", count.ble_count);
dp_printf("BLTH:off");
#else
dp_printf("Sniffer disabled");
#endif
@ -327,7 +314,6 @@ void dp_drawPage(bool nextpage) {
#if (TIME_SYNC_INTERVAL)
timeState = TimePulseTick ? ' ' : timeSetSymbols[timeSource];
TimePulseTick = false;
time(&now);
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
@ -355,6 +341,8 @@ void dp_drawPage(bool nextpage) {
dp_printf("%-4s", getSfName(updr2rps(LMIC.datarate)));
dp_setFont(MY_FONT_SMALL, 0);
#endif // HAS_LORA
dp_dump(displaybuf);
break;
// ---------- page 1: lorawan parameters ----------
@ -380,6 +368,8 @@ void dp_drawPage(bool nextpage) {
LMIC.seqnoDn ? LMIC.seqnoDn - 1 : 0);
dp_println();
dp_printf("SNR:%-5d RSSI:%-5d", (LMIC.snr + 2) / 4, LMIC.rssi);
dp_dump(displaybuf);
break;
#else // flip page if we are unattended
DisplayPage++;
@ -412,6 +402,8 @@ void dp_drawPage(bool nextpage) {
dp_printf("No fix");
wasnofix = true;
}
dp_dump(displaybuf);
break;
#else // flip page if we are unattended
DisplayPage++;
@ -435,10 +427,12 @@ void dp_drawPage(bool nextpage) {
#ifdef HAS_BME680
// line 6-7: IAQ
dp_printf("IAQ:%-3.0f", bme_status.iaq);
#else // is BME280 or BMP180
#else // is BME280 or BMP180
// line 6-7: Pre
dp_printf("PRE:%-2.1f", bme_status.pressure);
#endif // HAS_BME680
#endif // HAS_BME680
dp_dump(displaybuf);
break; // page 3
#else // flip page if we are unattended
DisplayPage++;
@ -453,14 +447,15 @@ void dp_drawPage(bool nextpage) {
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%T", &timeinfo);
dp_printf("%.8s", strftime_buf);
dp_dump(displaybuf);
break;
// ---------- page 5: pax graph ----------
case 5:
dp_setFont(MY_FONT_NORMAL);
dp_setTextCursor(0, 0);
dp_printf("Pax graph");
// update histogram
dp_plotCurve(count.pax, false);
dp_dump(plotbuf);
break;

View File

@ -6,68 +6,147 @@
// Local logging tag
static const char TAG[] = __FILE__;
// we use NMEA ZDA sentence field 1 for time synchronization
// ZDA gives time for preceding pps pulse
// downsight is that it does not have a constant offset
// thus precision is only +/- 1 second
TinyGPSPlus gps;
TinyGPSCustom gpstime(gps, "GPZDA", 1); // field 1 = UTC time (hhmmss.ss)
TinyGPSCustom gpsday(gps, "GPZDA", 2); // field 2 = day (01..31)
TinyGPSCustom gpsmonth(gps, "GPZDA", 3); // field 3 = month (01..12)
TinyGPSCustom gpsyear(gps, "GPZDA", 4); // field 4 = year (4-digit)
static const String ZDA_Request = "$EIGPQ,ZDA*39\r\n";
TaskHandle_t GpsTask;
#ifdef GPS_SERIAL
HardwareSerial GPS_Serial(1); // use UART #1
static uint16_t nmea_txDelay_ms =
(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL) / portTICK_PERIOD_MS);
#else
static uint16_t nmea_txDelay_ms = 0;
#endif
// Ublox UBX packet data
// UBX CFG-PRT packet
byte CFG_PRT[] = {
0xB5, // sync char 1
0x62, // sync char 2
0x06, // class
0x00, // id
0x14, // length
0x00, // .
0x01, // portID (UART 1)
0x00, // reserved
0x00, // txReady
0x00, // .
0b11010000, // UART mode: 8N1
0b00001000, // .
0x00, // .
0x00, // .
(byte)GPS_BAUDRATE, // baudrate
(byte)(GPS_BAUDRATE >> 8), // .
(byte)(GPS_BAUDRATE >> 16), // .
(byte)(GPS_BAUDRATE >> 24), // .
0b00000011, // input protocols: NMEA + UBX
0b00000000, // .
0b00000010, // output protocols: NMEA
0x00000000, // .
0x00, // reserved
0x00, // .
0x00, // .
0x00 // .
};
// Array of two bytes for CFG-MSG packets payload.
byte CFG_MSG_CID[][2] = {{0xF0, 0x01}, {0xF0, 0x02}, {0xF0, 0x03}, {0xF0, 0x05},
{0xF0, 0x06}, {0xF0, 0x07}, {0xF0, 0x08}, {0xF0, 0x09},
{0xF0, 0x0A}, {0xF0, 0x0E}, {0xF1, 0x00}, {0xF1, 0x03},
{0xF1, 0x04}, {0xF1, 0x05}, {0xF1, 0x06}};
// UBX CFG-MSG packet
byte CFG_MSG[] = {
0xB5, // sync char 1
0x62, // sync char 2
0x06, // class
0x01, // id
0x03, // length
0x00, // .
0x00, // payload (first byte from messages array element)
0x00, // payload (second byte from messages array element)
0x00 // payload (zero to disable message)
};
// UBX CFG-CFG packet
byte CFG_CFG[] = {
0xB5, // sync char 1
0x62, // sync char 2
0x06, // class
0x09, // id
0x0D, // length
0x00, // .
0b00011111, // clearmask
0b00000110, // .
0x00, // .
0x00, // .
0x00, // savemask
0x00, // .
0x00, // .
0x00, // .
0b00011111, // loadmask
0b00000110, // .
0x00, // .
0x00, // .
0b00010001 // devicemask
};
// helper functions to send UBX commands to ublox gps chip
void sendPacket(byte *packet, byte len) {
uint8_t CK_A = 0;
uint8_t CK_B = 0;
for (int i = 0; i < len; i++)
GPS_Serial.write(packet[i]);
// calculate and send Fletcher checksum
for (int i = 2; i < len; i++) {
CK_A += packet[i];
CK_B += CK_A;
}
GPS_Serial.write(CK_A);
GPS_Serial.write(CK_B);
}
void restoreDefaults() { sendPacket(CFG_CFG, sizeof(CFG_CFG)); }
void changeBaudrate() { sendPacket(CFG_PRT, sizeof(CFG_PRT)); }
void disableNmea() {
// tinygps++ processes only $GPGGA/$GNGGA and $GPRMC/$GNRMC
// thus, we disable all other NMEA messages
byte packetSize = sizeof(CFG_MSG);
// Offset to the place where payload starts.
byte payloadOffset = 6;
// Iterate over the messages array.
for (byte i = 0; i < sizeof(CFG_MSG_CID) / sizeof(*CFG_MSG_CID); i++) {
// Copy two bytes of payload to the packet buffer.
for (byte j = 0; j < sizeof(*CFG_MSG_CID); j++) {
CFG_MSG[payloadOffset + j] = CFG_MSG_CID[i][j];
}
sendPacket(CFG_MSG, packetSize);
}
}
// initialize and configure GPS
int gps_init(void) {
if (!gps_config()) {
ESP_LOGE(TAG, "GPS chip initializiation error");
return 0;
}
#ifdef GPS_SERIAL
ESP_LOGI(TAG, "Opening serial GPS");
GPS_Serial.begin(GPS_SERIAL);
#elif defined GPS_I2C
ESP_LOGI(TAG, "Opening I2C GPS");
Wire.begin(GPS_I2C, 400000); // I2C connect to GPS device with 400 KHz
Wire.beginTransmission(GPS_ADDR);
Wire.write(0x00); // dummy write
if (Wire.endTransmission()) {
ESP_LOGE(TAG, "Quectel L76 GPS chip not found");
return 0;
} else
ESP_LOGI(TAG, "Quectel L76 GPS chip found");
#endif
restoreDefaults();
delay(100);
changeBaudrate();
delay(100);
GPS_Serial.flush();
GPS_Serial.updateBaudRate(GPS_BAUDRATE);
disableNmea();
return 1;
} // gps_init()
// detect gps chipset type and configure it with device specific settings
int gps_config() {
int rslt = 1; // success
#if defined GPS_SERIAL
/* insert user configuration here, if needed */
#elif defined GPS_I2C
/* insert user configuration here, if needed */
#endif
return rslt;
}
// store current GPS location data in struct
void gps_storelocation(gpsStatus_t *gps_store) {
if (gps.location.isUpdated() && gps.location.isValid() &&
@ -90,46 +169,45 @@ bool gps_hasfix() {
}
// function to poll UTC time from GPS NMEA data; note: this is costly
time_t get_gpstime(uint16_t *msec) {
time_t get_gpstime(uint16_t *msec = 0) {
// poll NMEA ZDA sentence
#ifdef GPS_SERIAL
GPS_Serial.print(ZDA_Request);
// wait for gps NMEA answer
// vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL));
#elif defined GPS_I2C
Wire.print(ZDA_Request);
#endif
const uint16_t txDelay =
70U * 1000 / (GPS_BAUDRATE / 9); // serial tx of 70 NMEA chars
// did we get a current date & time?
if (gpstime.isValid()) {
if (gps.time.age() < 1000) {
uint32_t delay_ms =
gpstime.age() + nmea_txDelay_ms + NMEA_COMPENSATION_FACTOR;
uint32_t zdatime = atof(gpstime.value());
// convert UTC time from gps NMEA ZDA sentence to tm format
// convert tinygps time format to struct tm format
struct tm gps_tm = {0};
gps_tm.tm_sec = zdatime % 100; // second (UTC)
gps_tm.tm_min = (zdatime / 100) % 100; // minute (UTC)
gps_tm.tm_hour = zdatime / 10000; // hour (UTC)
gps_tm.tm_mday = atoi(gpsday.value()); // day, 01 to 31
gps_tm.tm_mon = atoi(gpsmonth.value()) - 1; // month, 01 to 12
gps_tm.tm_year = atoi(gpsyear.value()) - 1900; // year, YYYY
gps_tm.tm_sec = gps.time.second();
gps_tm.tm_min = gps.time.minute();
gps_tm.tm_hour = gps.time.hour();
gps_tm.tm_mday = gps.date.day();
gps_tm.tm_mon = gps.date.month() - 1; // 1-12 -> 0-11
gps_tm.tm_year = gps.date.year() - 1900; // 2000+ -> years since 1900
// convert UTC tm to time_t epoch
gps_tm.tm_isdst = 0; // UTC has no DST
time_t t = mkgmtime(&gps_tm);
// add protocol delay with millisecond precision
t += (time_t)(delay_ms / 1000);
*msec = delay_ms % 1000; // fractional seconds
#ifdef GPS_INT
// if we have a recent GPS PPS pulse, sync on top of next second
uint16_t ppsDiff = millis() - lastPPS;
if (ppsDiff < 1000)
*msec = ppsDiff;
else {
ESP_LOGD(TAG, "no PPS from GPS");
return 0;
}
#else
// best guess for sync on top of next second
*msec = gps.time.centisecond() * 10U + txDelay;
#endif
return t;
}
ESP_LOGD(TAG, "no valid GPS time");
return 0;
} // get_gpstime()
@ -139,42 +217,17 @@ void gps_loop(void *pvParameters) {
_ASSERT((uint32_t)pvParameters == 1); // FreeRTOS check
// feed GPS decoder with serial NMEA data from GPS device
while (1) {
while (cfg.payloadmask & GPS_DATA) {
if (cfg.payloadmask & GPS_DATA) {
#ifdef GPS_SERIAL
// feed GPS decoder with serial NMEA data from GPS device
while (GPS_Serial.available()) {
while (GPS_Serial.available())
gps.encode(GPS_Serial.read());
yield();
}
#elif defined GPS_I2C
Wire.requestFrom(GPS_ADDR, 32); // caution: this is a blocking call
while (Wire.available()) {
gps.encode(Wire.read());
delay(2); // 2ms delay according L76 datasheet
yield();
}
#endif
// (only) while device time is not set or unsynched, and we have a valid
// GPS time, we trigger a device time update to poll time from GPS
if ((timeSource == _unsynced || timeSource == _set) &&
(gpstime.isUpdated() && gpstime.isValid() && gpstime.age() < 1000)) {
calibrateTime();
}
} // if
// show NMEA data in verbose mode, useful only for debugging GPS, very
// noisy ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix:
// %u",
// gps.passedChecksum(), gps.failedChecksum(),
// gps.sentencesWithFix());
yield(); // yield to CPU
} // end of infinite loop
delay(5);
}
delay(1000);
} // infinite while loop
} // gps_loop()

View File

@ -18,7 +18,7 @@
#define CFG_sx1276_radio 1
#define HAS_DISPLAY 1 // OLED-Display on board
#define HAS_LED LED_BUILTIN // white LED on board
#define HAS_LED LED_BUILTIN // white LED on board (set to NOT_A_PIN to disable)
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board
// Pins for I2C interface of OLED Display

View File

@ -20,7 +20,7 @@
#define CFG_sx1276_radio 1
#define HAS_DISPLAY 1 // OLED-Display on board
#define HAS_LED LED_BUILTIN // white LED on board
#define HAS_LED LED_BUILTIN // white LED on board (set to NOT_A_PIN to disable)
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board
#define BAT_MEASURE_ADC ADC2_GPIO13_CHANNEL // battery probe GPIO pin

View File

@ -20,7 +20,7 @@
#define CFG_sx1276_radio 1
#define HAS_DISPLAY 1 // OLED-Display on board
#define HAS_LED LED_BUILTIN // white LED on board
#define HAS_LED LED_BUILTIN // white LED on board (set to NOT_A_PIN to disable)
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board
#define BAT_MEASURE_ADC ADC1_GPIO37_CHANNEL // battery probe GPIO pin

View File

@ -10,7 +10,7 @@
// Hardware related definitions for lolin32lite (without LoRa shield)
#define HAS_LED LED_BUILTIN // on board LED on GPIO5
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW (set to NOT_A_PIN to disable)
#define MY_SDA SDA
#define MY_SCL SCL

View File

@ -15,7 +15,7 @@
#define HAS_DISPLAY 1 // OLED-Display on board
//#define MY_DISPLAY_FLIP 1 // uncomment this for rotated display
#define HAS_LED 22 // ESP32 GPIO12 (pin22) On Board LED
#define HAS_LED 22 // ESP32 GPIO12 (pin22) On Board LED (set to NOT_A_PIN to disable)
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_13) // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED
#define HAS_BUTTON 15 // ESP32 GPIO15 (pin15) Button is on the LoraNode32 shield

View File

@ -20,11 +20,6 @@
#define HAS_ANTENNA_SWITCH (16) // pin for switching wifi antenna
#define WIFI_ANTENNA 0 // 0 = internal, 1 = external
// uncomment this only if your LoPy runs on a PYTRACK BOARD
#define HAS_GPS 1
#define GPS_I2C GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21)
#define GPS_ADDR 0x10
// uncomment this only if your LoPy runs on a EXPANSION BOARD
//#define HAS_LED (12) // use if LoPy is on Expansion Board, this has a user LED
//#define LED_ACTIVE_LOW 1 // use if LoPy is on Expansion Board, this has a user LED

View File

@ -30,11 +30,6 @@
#define HAS_ANTENNA_SWITCH (21) // pin for switching wifi antenna (P12)
#define WIFI_ANTENNA 0 // 0 = internal, 1 = external
// uncomment defines in this section ONLY if your LoPy lives on a PYTRACK BOARD
//#define HAS_GPS 1
//#define GPS_I2C GPIO_NUM_25, GPIO_NUM_26 // SDA (P22), SCL (P21)
//#define GPS_ADDR 0x10
// uncomment defines in this section ONLY if your LoPy lives on a EXPANSION BOARD
//#define HAS_LED (12) // use if LoPy is on Expansion Board, this has a user LED
//#define LED_ACTIVE_LOW 1 // use if LoPy is on Expansion Board, this has a user LED

View File

@ -14,7 +14,7 @@
#define HAS_DISPLAY 1 // OLED-Display on board
//#define MY_DISPLAY_FLIP 1 // uncomment this for rotated display
#define HAS_LED LED_BUILTIN
#define HAS_LED LED_BUILTIN // set to NOT_A_PIN to disable
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
#define HAS_BUTTON KEY_BUILTIN

View File

@ -16,10 +16,12 @@
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
// enable only if you want to store a local paxcount table on the device
#define HAS_SDCARD 2 // // this board has a SDMMC card-reader/writer
#define HAS_SDCARD 2 // this board has a SDMMC card-reader/writer
#define HAS_DISPLAY 1
#define HAS_LED (25) // green on board LED
//#define HAS_LED NOT_A_PIN
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board

View File

@ -86,18 +86,19 @@ static const char TAG[] = __FILE__;
String IF482_Frame(time_t t) {
char mon, out[IF482_FRAME_SIZE + 1];
char mon, out[IF482_FRAME_SIZE + 1], buf[IF482_FRAME_SIZE - 3];
if (sntp_get_sync_status() == SNTP_SYNC_STATUS_IN_PROGRESS)
mon = 'M'; // time had been set but sync not completed
else
mon = 'A'; // time has been set and was recently synced
// generate IF482 telegram
// snprintf(out, sizeof(out), "O%cL%s\r", mon, myTZ.dateTime(t, UTC_TIME,
// "ymdwHis").c_str());
// ESP_LOGD(TAG, "[%s] IF482 date/time: %s", ctime(time(NULL), out);
// generate IF482 telegram for local time
struct tm tt;
localtime_r(&t, &tt);
mktime(&tt);
strftime(buf, sizeof(buf), "%y%m%d%u%H%M%S", &tt);
snprintf(out, sizeof(out), "O%cL%s\r", mon, buf);
return out;
}

View File

@ -212,7 +212,7 @@ void ledLoop(void *parameter) {
previousLEDState = LEDState;
}
// give yield to CPU
delay(2);
delay(5);
} // while(1)
}; // ledloop()

View File

@ -13,6 +13,7 @@ uint8_t MatrixDisplayIsOn = 0;
static uint8_t displaybuf[LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT / 8] = {0};
static unsigned long ulLastNumMacs = 0;
static time_t ulLastTime = time(NULL);
static struct count_payload_t count; // libpax count storage
hw_timer_t *matrixDisplayIRQ = NULL;
@ -76,12 +77,12 @@ void refreshTheMatrixDisplay(bool nextPage) {
case 0:
// update counter values from libpax
libpax_counter_count(&count_from_libpax);
libpax_counter_count(&count);
if (cfg.countermode == 1) {
// cumulative counter mode -> display total number of pax
if (ulLastNumMacs != count_from_libpax.pax) {
ulLastNumMacs = count_from_libpax.pax;
if (ulLastNumMacs != count.pax) {
ulLastNumMacs = count.pax;
matrix.clear();
DrawNumber(String(ulLastNumMacs));
}
@ -89,10 +90,10 @@ void refreshTheMatrixDisplay(bool nextPage) {
else { // cyclic counter mode -> plot a line diagram
if (ulLastNumMacs != count_from_libpax.pax) {
if (ulLastNumMacs != count.pax) {
// next count cycle?
if (count_from_libpax.pax == 0) {
if (count.pax == 0) {
// matrix full? then scroll left 1 dot, else increment column
if (col < (LED_MATRIX_WIDTH - 1))
@ -104,7 +105,7 @@ void refreshTheMatrixDisplay(bool nextPage) {
matrix.drawPoint(col, row, 0); // clear current dot
// scale and set new dot
ulLastNumMacs = count_from_libpax.pax;
ulLastNumMacs = count.pax;
level = ulLastNumMacs / LINE_DIAGRAM_DIVIDER;
row = level <= LED_MATRIX_HEIGHT
? LED_MATRIX_HEIGHT - 1 - level % LED_MATRIX_HEIGHT
@ -120,7 +121,7 @@ void refreshTheMatrixDisplay(bool nextPage) {
if (ulLastTime != t) {
ulLastTime = t;
matrix.clear();
//DrawNumber(myTZ.dateTime("H:i:s").c_str());
// DrawNumber(myTZ.dateTime("H:i:s").c_str());
}
break;

View File

@ -5,18 +5,9 @@ static const char TAG[] = __FILE__;
// libpax payload
struct count_payload_t count_from_libpax;
uint16_t volatile libpax_macs_ble, libpax_macs_wifi;
void process_count(void) {
ESP_LOGD(TAG, "pax: %d / %d / %d", count_from_libpax.pax,
count_from_libpax.wifi_count, count_from_libpax.ble_count);
libpax_macs_ble = count_from_libpax.ble_count;
libpax_macs_wifi = count_from_libpax.wifi_count;
setSendIRQ();
}
void init_libpax(void) {
libpax_counter_init(process_count, &count_from_libpax, cfg.sendcycle * 2,
libpax_counter_init(setSendIRQ, &count_from_libpax, cfg.sendcycle * 2,
cfg.countermode);
libpax_counter_start();
}

View File

@ -91,11 +91,6 @@ function decodeUplink(input) {
}
}
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

View File

@ -179,10 +179,10 @@ void showLoraKeys(void) {
// all EUI buffer so we do it here to a temp
// buffer to be able to display them
uint8_t buf[32];
os_getDevEui((u1_t *)buf);
printKey("DevEUI", buf, 8, true);
os_getArtEui((u1_t *)buf);
printKey("AppEUI", buf, 8, true);
os_getDevEui((u1_t *)buf);
printKey("DevEUI", buf, 8, true);
os_getDevKey((u1_t *)buf);
printKey("AppKey", buf, 16, false);
}
@ -314,7 +314,7 @@ esp_err_t lmic_init(void) {
"lorasendtask", // name of task
3072, // stack size of task
(void *)1, // parameter of the task
1, // priority of the task
2, // priority of the task
&lorasendTask, // task handle
1); // CPU core

View File

@ -25,62 +25,54 @@ licenses. Refer to LICENSE.txt file in repository for more details.
// Tasks and timers:
Task Core Prio Purpose
Task Core Prio Purpose
-------------------------------------------------------------------------------
ledloop 0 3 blinks LEDs
spiloop 0 2 reads/writes data on spi interface
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
ledloop* 1 1 blinks LEDs
spiloop# 0 2 reads/writes data on spi interface
lmictask* 1 2 MCCI LMiC LORAWAN stack
clockloop# 1 6 generates realtime telegrams for external clock
mqttloop# 1 5 reads/writes data on ETH interface
timesync_proc# 1 7 processes realtime time sync requests
irqhandler# 1 4 application IRQ (i.e. displayrefresh)
gpsloop* 1 1 reads data from GPS via serial or i2c
lorasendtask# 1 2 feeds data from lora sendqueue to lmcic
rmcd_process# 1 1 Remote command interpreter loop
lmictask 1 2 MCCI LMiC LORAWAN stack
clockloop 1 4 generates realtime telegrams for external clock
mqttloop 1 2 reads/writes data on ETH interface
timesync_proc 1 3 processes realtime time sync requests
irqhandler 1 2 cyclic tasks (i.e. displayrefresh) triggered by timers
gpsloop 1 1 reads data from GPS via serial or i2c
lorasendtask 1 1 feeds data from lora sendqueue to lmcic
rmcd_process 1 1 Remote command interpreter loop
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
* spinning task, always ready
# blocked/waiting task
Low priority numbers denote low priority tasks.
NOTE: Changing any timings will have impact on time accuracy of whole code.
So don't do it if you do not own a digital oscilloscope.
-------------------------------------------------------------------------------
// ESP32 hardware timers
-------------------------------------------------------------------------------
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS)
1 ppsIRQ -> pps clock irq -> 1sec
2 (unused)
3 MatrixDisplayIRQ -> matrix mux cycle -> 0,5ms (MATRIX_DISPLAY_SCAN_US)
// Interrupt routines
-------------------------------------------------------------------------------
irqHandlerTask (Core 1), see irqhandler.cpp
fired by hardware
DisplayIRQ -> esp32 timer 0
CLOCKIRQ -> esp32 timer 1 or external GPIO (RTC_INT or GPS_INT)
MatrixDisplayIRQ-> esp32 timer 3
ButtonIRQ -> external GPIO
PMUIRQ -> PMU chip GPIO
fired by software
TIMESYNC_IRQ -> setTimeSyncIRQ() -> Ticker.h
CYCLIC_IRQ -> setCyclicIRQ() -> Ticker.h
SENDCYCLE_IRQ -> setSendIRQ() -> xTimer
BME_IRQ -> setBMEIRQ() -> Ticker.h
ClockTask (Core 1), see timekeeper.cpp
fired by hardware
CLOCKIRQ -> esp32 timer 1
// External RTC timer (if present)
-------------------------------------------------------------------------------
triggers pps 1 sec impulse
// Interrupt routines
-------------------------------------------------------------------------------
ISRs fired by CPU or GPIO:
DisplayIRQ <- esp32 timer 0
CLOCKIRQ <- esp32 timer 1 or GPIO (RTC_INT)
MatrixDisplayIRQ<- esp32 timer 3
ButtonIRQ <- GPIO <- Button
PMUIRQ <- GPIO <- PMU chip
Application IRQs fired by software:
TIMESYNC_IRQ <- setTimeSyncIRQ() <- Ticker.h
CYCLIC_IRQ <- setCyclicIRQ() <- Ticker.h
SENDCYCLE_IRQ <- setSendIRQ() <- xTimer or libpax callback
BME_IRQ <- setBMEIRQ() <- Ticker.h
*/
// Basic Config
@ -126,7 +118,7 @@ void setup() {
snprintf(clientId, 20, "paxcounter_%08x", hashedmac);
ESP_LOGI(TAG, "Starting %s v%s (runmode=%d / restarts=%d)", clientId,
PROGVERSION, RTC_runmode, RTC_restarts);
ESP_LOGI(TAG, "code build date: %d", _COMPILETIME);
ESP_LOGI(TAG, "code build date: %d", compileTime());
// print chip information on startup if in verbose mode after coldstart
#if (VERBOSE)
@ -244,9 +236,9 @@ void setup() {
"ledloop", // name of task
1024, // stack size of task
(void *)1, // parameter of the task
3, // priority of the task
1, // priority of the task
&ledLoopTask, // task handle
0); // CPU core
1); // CPU core
#endif
// initialize wifi antenna
@ -328,7 +320,7 @@ void setup() {
ESP_LOGI(TAG, "Starting GPS Feed...");
xTaskCreatePinnedToCore(gps_loop, // task function
"gpsloop", // name of task
4096, // stack size of task
8192, // stack size of task
(void *)1, // parameter of the task
1, // priority of the task
&GpsTask, // task handle
@ -339,15 +331,9 @@ void setup() {
// initialize sensors
#if (HAS_SENSORS)
#if (HAS_SENSOR_1)
#if (COUNT_ENS)
ESP_LOGI(TAG, "init CWA-counter");
if (cwa_init())
strcat_P(features, " CWA");
#else
strcat_P(features, " SENS(1)");
sensor_init();
#endif
#endif
#if (HAS_SENSOR_2)
strcat_P(features, " SENS(2)");
sensor_init();
@ -432,7 +418,7 @@ void setup() {
"irqhandler", // name of task
4096, // stack size of task
(void *)1, // parameter of the task
2, // priority of the task
4, // priority of the task
&irqHandlerTask, // task handle
1); // CPU core
@ -494,8 +480,7 @@ void setup() {
cyclicTimer.attach(HOMECYCLE, setCyclicIRQ);
// only if we have a timesource we do timesync
#if ((TIME_SYNC_LORAWAN) || (TIME_SYNC_LORASERVER) || (HAS_GPS) || \
defined HAS_RTC)
#if ((TIME_SYNC_LORAWAN) || (TIME_SYNC_LORASERVER) || (HAS_GPS) || (HAS_RTC))
#if (defined HAS_IF482 || defined HAS_DCF77)
ESP_LOGI(TAG, "Starting Clock Controller...");
@ -507,8 +492,7 @@ void setup() {
#endif
ESP_LOGI(TAG, "Starting Timekeeper...");
_ASSERT(timepulse_init()); // setup pps timepulse
timepulse_start(); // starts pps and cyclic time sync
_ASSERT(timepulse_init()); // starts pps and cyclic time sync
strcat_P(features, " TIME");
#endif // timesync

View File

@ -37,7 +37,7 @@ esp_err_t mqtt_init(void) {
SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE);
ESP_LOGI(TAG, "Starting MQTTloop...");
xTaskCreatePinnedToCore(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 1,
xTaskCreatePinnedToCore(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 5,
&mqttTask, 1);
return ESP_OK;
}

View File

@ -18,6 +18,11 @@
#define PAYLOAD_ENCODER 2 // payload encoder: 1=Plain, 2=Packed, 3=Cayenne LPP dynamic, 4=Cayenne LPP packed
#define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed
// default settings for transmission of sensor data (first list = data on / second line = data off)
#define PAYLOADMASK \
((GPS_DATA | MEMS_DATA | COUNT_DATA | SENSOR1_DATA | SENSOR2_DATA | SENSOR3_DATA) & \
(~BATT_DATA) & (~RESERVED_DATA))
// MAC sniffing parameters
#define BLECOUNTER 1 // set to 0 if you do not want to install the BLE sniffer
#define WIFICOUNTER 1 // set to 0 if you do not want to install the WIFI sniffer
@ -27,10 +32,6 @@
#define BLESCANWINDOW 80 // [milliseconds] scan window, see below, 3 .. 10240, default 80ms
#define BLESCANINTERVAL 80 // [illiseconds] scan interval, see below, 3 .. 10240, default 80ms = 100% duty cycle
// Corona Exposure Notification Service(ENS) counter
#define COUNT_ENS 0 // count found number of devices which advertise Exposure Notification Service
// set to 1 if you want to enable this function [default=0]
/* Note: guide for setting bluetooth parameters
*
* |< Scan Window > |< Scan Window > | ... |< Scan Window > |

View File

@ -1,7 +1,6 @@
// Basic Config
#include "globals.h"
#include "rcommand.h"
#include "libpax_helpers.h"
// Local logging tag
static const char TAG[] = __FILE__;
@ -397,15 +396,6 @@ void set_flush(uint8_t val[]) {
// used to open receive window on LoRaWAN class a nodes
};
void set_enscount(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: set ENS_COUNT to %s", val[0] ? "on" : "off");
cfg.enscount = val[0] ? 1 : 0;
if (val[0])
cfg.payloadmask |= (uint8_t)SENSOR1_DATA;
else
cfg.payloadmask &= (uint8_t)~SENSOR1_DATA;
}
void set_loadconfig(uint8_t val[]) {
ESP_LOGI(TAG, "Remote command: load config from NVRAM");
loadConfig();
@ -430,7 +420,7 @@ static const cmd_t table[] = {
{0x10, set_rgblum, 1}, {0x13, set_sensor, 2},
{0x14, set_payloadmask, 1}, {0x15, set_bme, 1},
{0x16, set_batt, 1}, {0x17, set_wifiscan, 1},
{0x18, set_enscount, 1}, {0x19, set_sleepcycle, 2},
{0x18, set_flush, 0}, {0x19, set_sleepcycle, 2},
{0x20, set_loadconfig, 0}, {0x21, set_saveconfig, 0},
{0x80, get_config, 0}, {0x81, get_status, 0},
{0x83, get_batt, 0}, {0x84, get_gps, 0},
@ -493,7 +483,7 @@ void rcmd_process(void *pvParameters) {
rcmd_execute(RcmdBuffer.cmd, RcmdBuffer.cmdLen);
}
delay(2); // yield to CPU
delay(5); // yield to CPU
} // rcmd_process()
// enqueue remote command

View File

@ -126,6 +126,11 @@ void enter_deepsleep(const uint64_t wakeup_sec, gpio_num_t wakeup_gpio) {
sds011_sleep();
#endif
// flush & close sd card, if we have
#if (HAS_SDCARD)
sdcard_close();
#endif
// wait a while (max 100 sec) to clear send queues
ESP_LOGI(TAG, "Waiting until send queues are empty...");
for (i = 100; i > 0; i--) {

View File

@ -25,14 +25,14 @@ uint8_t rtc_init(void) {
}
#if (TIME_SYNC_COMPILEDATE)
// initialize a blank RTC without battery backup with compiled time
// initialize a blank RTC without battery backup with build time
RtcDateTime tt = Rtc.GetDateTime();
time_t t = tt.Epoch32Time(); // sec2000 -> epoch
if (!Rtc.IsDateTimeValid() || !timeIsValid(t)) {
ESP_LOGW(TAG, "RTC has no recent time, setting to compiled time");
Rtc.SetDateTime(
RtcDateTime(_COMPILETIME - SECS_YR_2000)); // epoch -> sec2000
ESP_LOGW(TAG, "RTC has no recent time, setting to compiletime");
Rtc.SetDateTime(RtcDateTime(mkgmtime(compileTime()) -
SECS_YR_2000)); // epoch -> sec2000
}
#endif
@ -62,15 +62,23 @@ uint8_t set_rtctime(time_t t) { // t is sec epoch time
}
} // set_rtctime()
time_t get_rtctime(void) {
time_t get_rtctime(uint16_t *msec) {
time_t t = 0;
*msec = 0;
if (I2C_MUTEX_LOCK()) {
if (Rtc.IsDateTimeValid() && Rtc.GetIsRunning()) {
RtcDateTime tt = Rtc.GetDateTime();
t = tt.Epoch32Time(); // sec2000 -> epoch
}
I2C_MUTEX_UNLOCK();
return timeIsValid(t);
#ifdef RTC_INT
// adjust time to top of next second by waiting TimePulseTick to flip
bool lastTick = TimePulseTick;
while (TimePulseTick == lastTick) {
};
t++;
#endif
return t;
} else {
ESP_LOGE(TAG, "RTC get time failure");
return 0; // failure

View File

@ -1,4 +1,7 @@
// routines for writing data to an SD-card, if present
// use FAT32 formatted card
// check whether your card reader supports SPI oder SDMMC and select appropriate
// SD low level driver in board hal file
// Local logging tag
static const char TAG[] = __FILE__;
@ -8,20 +11,26 @@ static const char TAG[] = __FILE__;
#ifdef HAS_SDCARD
static bool useSDCard;
static void createFile(void);
static void openFile(void);
File fileSDCard;
bool sdcard_init() {
ESP_LOGI(TAG, "looking for SD-card...");
#if HAS_SDCARD == 1
SPIClass sd_spi;
#endif
bool sdcard_init(bool create) {
// for usage of SD drivers on ESP32 platform see
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sdspi_host.html
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sdmmc_host.html
ESP_LOGI(TAG, "looking for SD-card...");
#if HAS_SDCARD == 1 // use SD SPI host driver
useSDCard = SD.begin(SDCARD_CS, SDCARD_MOSI, SDCARD_MISO, SDCARD_SCLK);
digitalWrite(SDCARD_CS, HIGH);
sd_spi.begin(SDCARD_SCLK, SDCARD_MISO, SDCARD_MOSI, SDCARD_CS);
digitalWrite(SDCARD_CS, LOW);
useSDCard = SD.begin(SDCARD_CS, sd_spi);
#elif HAS_SDCARD == 2 // use SD MMC host driver
// enable internal pullups of sd-data lines
gpio_set_pull_mode(gpio_num_t(SDCARD_DATA0), GPIO_PULLUP_ONLY);
@ -33,20 +42,27 @@ bool sdcard_init() {
if (useSDCard) {
ESP_LOGI(TAG, "SD-card found");
createFile();
} else
openFile();
return true;
} else {
ESP_LOGI(TAG, "SD-card not found");
return useSDCard;
return false;
}
}
void sdcard_close(void) {
ESP_LOGI(TAG, "closing SD-card");
fileSDCard.flush();
fileSDCard.close();
}
void sdcardWriteData(uint16_t noWifi, uint16_t noBle,
__attribute__((unused)) uint16_t noBleCWA) {
__attribute__((unused)) uint16_t voltage) {
static int counterWrites = 0;
char tempBuffer[12 + 1];
char tempBuffer[20 + 1];
time_t t = time(NULL);
struct tm tt;
localtime_r(&t, &tt);
mktime(&tt);
gmtime_r(&t, &tt); // make UTC timestamp
#if (HAS_SDS011)
sdsStatus_t sds;
@ -55,71 +71,85 @@ void sdcardWriteData(uint16_t noWifi, uint16_t noBle,
if (!useSDCard)
return;
ESP_LOGD(TAG, "writing to SD-card");
strftime(tempBuffer, sizeof(tempBuffer), "%d.%m.%Y", &tt);
ESP_LOGI(TAG, "SD: writing data");
strftime(tempBuffer, sizeof(tempBuffer), "%FT%TZ", &tt);
fileSDCard.print(tempBuffer);
strftime(tempBuffer, sizeof(tempBuffer), "%H.%M.%S", &tt);
snprintf(tempBuffer, sizeof(tempBuffer), ",%d,%d", noWifi, noBle);
fileSDCard.print(tempBuffer);
sprintf(tempBuffer, "%d,%d", noWifi, noBle);
fileSDCard.print(tempBuffer);
#if (COUNT_ENS)
sprintf(tempBuffer, ",%d", noBleCWA);
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
snprintf(tempBuffer, sizeof(tempBuffer), ",%d", voltage);
fileSDCard.print(tempBuffer);
#endif
#if (HAS_SDS011)
sds011_store(&sds);
sprintf(tempBuffer, ",%5.1f,%4.1f", sds.pm10, sds.pm25);
snprintf(tempBuffer, sizeof(tempBuffer), ",%5.1f,%4.1f", sds.pm10 / 10,
sds.pm25 / 10);
fileSDCard.print(tempBuffer);
#endif
fileSDCard.println();
if (++counterWrites > 2) {
// force writing to SD-card
ESP_LOGD(TAG, "flushing data to card");
ESP_LOGI(TAG, "SD: flushing data");
fileSDCard.flush();
counterWrites = 0;
}
}
void createFile(void) {
char bufferFilename[8 + 1 + 3 + 1];
void openFile(void) {
char bufferFilename[30];
useSDCard = false;
for (int i = 0; i < 100; i++) {
sprintf(bufferFilename, SDCARD_FILE_NAME, i);
// ESP_LOGD(TAG, "SD: looking for file <%s>", bufferFilename);
snprintf(bufferFilename, sizeof(bufferFilename), "/%s.csv", SDCARD_FILE_NAME);
ESP_LOGI(TAG, "SD: looking for file <%s>", bufferFilename);
#if HAS_SDCARD == 1
bool fileExists = SD.exists(bufferFilename);
bool fileExists = SD.exists(bufferFilename);
#elif HAS_SDCARD == 2
bool fileExists = SD_MMC.exists(bufferFilename);
bool fileExists = SD_MMC.exists(bufferFilename);
#endif
if (!fileExists) {
// ESP_LOGD(TAG, "SD: file does not exist: opening");
// file not exists, create it
if (!fileExists) {
ESP_LOGD(TAG, "SD: file not found, creating...");
#if HAS_SDCARD == 1
fileSDCard = SD.open(bufferFilename, FILE_WRITE);
fileSDCard = SD.open(bufferFilename, FILE_WRITE);
#elif HAS_SDCARD == 2
fileSDCard = SD_MMC.open(bufferFilename, FILE_WRITE);
fileSDCard = SD_MMC.open(bufferFilename, FILE_WRITE);
#endif
if (fileSDCard) {
ESP_LOGD(TAG, "SD: name opened: <%s>", bufferFilename);
fileSDCard.print(SDCARD_FILE_HEADER);
#if (COUNT_ENS)
fileSDCard.print(SDCARD_FILE_HEADER_CWA); // for Corona-data (CWA)
if (fileSDCard) {
ESP_LOGD(TAG, "SD: name opened: <%s>", bufferFilename);
fileSDCard.print(SDCARD_FILE_HEADER);
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
fileSDCard.print(SDCARD_FILE_HEADER_VOLTAGE); // for battery level data
#endif
#if (HAS_SDS011)
fileSDCard.print(SDCARD_FILE_HEADER_SDS011);
fileSDCard.print(SDCARD_FILE_HEADER_SDS011);
#endif
fileSDCard.println();
useSDCard = true;
break;
}
fileSDCard.println();
useSDCard = true;
}
}
// file exists, append data
else {
ESP_LOGD(TAG, "SD: file found, opening...");
#if HAS_SDCARD == 1
fileSDCard = SD.open(bufferFilename, FILE_APPEND);
#elif HAS_SDCARD == 2
fileSDCard = SD_MMC.open(bufferFilename, FILE_APPEND);
#endif
if (fileSDCard) {
ESP_LOGD(TAG, "SD: name opened: <%s>", bufferFilename);
useSDCard = true;
}
}
return;
}

View File

@ -10,35 +10,37 @@ static const char TAG[] = __FILE__;
#if (HAS_IF482)
#error cannot use IF482 together with SDS011 (both use UART#2)
#endif
// UART(2) is unused in this project
static HardwareSerial sdsSerial(2); // so we use it here
static SDS011 sdsSensor; // fine dust sensor
// the results of the sensor:
static float pm10, pm25;
boolean isSDS011Active;
SdsDustSensor sds(Serial2);
bool isSDS011Active = false;
static float pm10 = 0.0, pm25 = 0.0;
// init
bool sds011_init() {
pm25 = pm10 = 0.0;
sdsSensor.begin(&sdsSerial, SDS_RX, SDS_TX);
sds011_sleep(); // we do sleep/wakup by ourselves
sds.begin(9600, SERIAL_8N1, SDS_RX, SDS_TX);
sds011_wakeup();
ESP_LOGI(TAG, "SDS011: %s", sds.queryFirmwareVersion().toString().c_str());
sds.setQueryReportingMode();
return true;
}
// reading data:
void sds011_loop() {
if (isSDS011Active) {
int sdsErrorCode = sdsSensor.read(&pm25, &pm10);
if (sdsErrorCode) {
pm25 = pm10 = 0.0;
ESP_LOGI(TAG, "SDS011 error: %d", sdsErrorCode);
PmResult pm = sds.queryPm();
if (!pm.isOk()) {
ESP_LOGE(TAG, "SDS011: query error %s", pm.statusToString().c_str());
pm10 = pm25 = 0.0;
} else {
ESP_LOGI(TAG, "fine-dust-values: %5.1f,%4.1f", pm10, pm25);
ESP_LOGI(TAG, "SDS011: %s", pm.toString().c_str());
pm10 = pm.pm10;
pm25 = pm.pm25;
}
ESP_LOGD(TAG, "SDS011: go to sleep");
sds011_sleep();
}
return;
}
// retrieving stored data:
@ -49,17 +51,16 @@ void sds011_store(sdsStatus_t *sds_store) {
// putting the SDS-sensor to sleep
void sds011_sleep(void) {
sdsSensor.sleep();
isSDS011Active = false;
WorkingStateResult state = sds.sleep();
isSDS011Active = state.isWorking();
}
// start the SDS-sensor
// needs 30 seconds for warming up
void sds011_wakeup() {
if (!isSDS011Active) {
sdsSensor.wakeup();
isSDS011Active = true;
}
WorkingStateResult state = sds.wakeup();
isSDS011Active = state.isWorking();
ESP_LOGD(TAG, "SDS011: %s", state.toString().c_str());
}
#endif // HAS_SDS011

View File

@ -5,6 +5,8 @@ void setSendIRQ(TimerHandle_t xTimer) {
xTaskNotify(irqHandlerTask, SENDCYCLE_IRQ, eSetBits);
}
void setSendIRQ(void) { setSendIRQ(NULL); }
void initSendDataTimer(uint8_t sendcycle) {
static TimerHandle_t SendDataTimer = NULL;
@ -65,31 +67,26 @@ void SendPayload(uint8_t port) {
mqtt_enqueuedata(&SendBuffer);
#endif
// write data to sdcard, if present
#if (HAS_SDCARD)
if (port == COUNTERPORT) {
sdcardWriteData(libpax_macs_wifi, libpax_macs_ble
#if (COUNT_ENS)
,
cwa_report()
#endif
);
}
#endif
} // SendPayload
// interrupt triggered function to prepare payload to send
// timer triggered function to prepare payload to send
void sendData() {
uint8_t bitmask = cfg.payloadmask;
uint8_t mask = 1;
#if (HAS_GPS)
gpsStatus_t gps_status;
#endif
#if (HAS_SDS011)
sdsStatus_t sds_status;
#endif
#if ((WIFICOUNTER) || (BLECOUNTER))
struct count_payload_t count =
count_from_libpax; // copy values from global libpax var
ESP_LOGD(TAG, "Sending count results: pax=%d / wifi=%d / ble=%d", count.pax,
count.wifi_count, count.ble_count);
#endif
while (bitmask) {
switch (bitmask & mask) {
@ -97,14 +94,14 @@ void sendData() {
#if ((WIFICOUNTER) || (BLECOUNTER))
case COUNT_DATA:
payload.reset();
#if !(PAYLOAD_OPENSENSEBOX)
ESP_LOGI(TAG, "Sending libpax wifi count: %d", libpax_macs_wifi);
payload.addCount(libpax_macs_wifi, MAC_SNIFF_WIFI);
payload.addCount(count.wifi_count, MAC_SNIFF_WIFI);
if (cfg.blescan) {
ESP_LOGI(TAG, "Sending libpax ble count: %d", libpax_macs_ble);
payload.addCount(libpax_macs_ble, MAC_SNIFF_BLE);
payload.addCount(count.ble_count, MAC_SNIFF_BLE);
}
#endif
#if (HAS_GPS)
if (GPSPORT == COUNTERPORT) {
// send GPS position only if we have a fix
@ -115,23 +112,35 @@ void sendData() {
ESP_LOGD(TAG, "No valid GPS position");
}
#endif
#if (PAYLOAD_OPENSENSEBOX)
ESP_LOGI(TAG, "Sending libpax wifi count: %d", libpax_macs_wifi);
payload.addCount(libpax_macs_wifi, MAC_SNIFF_WIFI);
payload.addCount(count.wifi_count, MAC_SNIFF_WIFI);
if (cfg.blescan) {
ESP_LOGI(TAG, "Sending libpax ble count: %d", libpax_macs_ble);
payload.addCount(libpax_macs_ble, MAC_SNIFF_BLE);
payload.addCount(count.ble_count, MAC_SNIFF_BLE);
#endif
#if (HAS_SDS011)
sds011_store(&sds_status);
payload.addSDS(sds_status);
#endif
SendPayload(COUNTERPORT);
#ifdef HAS_DISPLAY
dp_plotCurve(libpax_macs_ble + libpax_macs_wifi, true);
dp_plotCurve(count.pax, true);
#endif
break;
#if (HAS_SDCARD)
sdcardWriteData(count.wifi_count, count.ble_count
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
,
read_voltage()
#endif
);
#endif // HAS_SDCARD
SendPayload(COUNTERPORT);
break; // case COUNTDATA
#endif // ((WIFICOUNTER) || (BLECOUNTER))
#if (HAS_BME)
case MEMS_DATA:
@ -162,10 +171,6 @@ void sendData() {
payload.reset();
payload.addSensor(sensor_read(1));
SendPayload(SENSOR1PORT);
#if (COUNT_ENS)
if (cfg.countermode != 1)
cwa_clear();
#endif
break;
#endif
#if (HAS_SENSOR_2)

View File

@ -2,12 +2,6 @@
#include "globals.h"
#include "sensor.h"
#if (COUNT_ENS)
#include "payload.h"
#include "corona.h"
extern PayloadConvert payload;
#endif
// Local logging tag
static const char TAG[] = __FILE__;
@ -53,16 +47,10 @@ uint8_t *sensor_read(uint8_t sensor) {
case 1:
// insert user specific sensor data frames here
// note: Sensor1 fields are used for ENS count, if ENS detection enabled
#if (COUNT_ENS)
if (cfg.enscount)
payload.addCount(cwa_report(), MAC_SNIFF_BLE_ENS);
#else
buf[0] = length;
buf[1] = 0x01;
buf[2] = 0x02;
buf[3] = 0x03;
#endif
break;
case 2:

View File

@ -19,9 +19,9 @@ static const char TAG[] = __FILE__;
// G = GPS / R = RTC / L = LORA / * = no sync / ? = never synced
const char timeSetSymbols[] = {'G', 'R', 'L', '*', '?'};
bool volatile TimePulseTick = false;
DRAM_ATTR bool TimePulseTick = false;
DRAM_ATTR unsigned long lastPPS = millis();
timesource_t timeSource = _unsynced;
time_t _COMPILETIME = compileTime(__DATE__);
TaskHandle_t ClockTask = NULL;
hw_timer_t *ppsIRQ = NULL;
@ -40,8 +40,7 @@ Ticker timesyncer;
void setTimeSyncIRQ() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); }
void calibrateTime(void) {
ESP_LOGD(TAG, "[%0.3f] calibrateTime, timeSource == %d", _seconds(),
timeSource);
time_t t = 0;
uint16_t t_msec = 0;
@ -57,7 +56,7 @@ void calibrateTime(void) {
// has RTC -> fallback to RTC time
#ifdef HAS_RTC
t = get_rtctime();
t = get_rtctime(&t_msec);
// set time from RTC - method will check if time is valid
setMyTime((uint32_t)t, t_msec, _rtc);
#endif
@ -101,26 +100,27 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000));
}
// from here on we are on top of next second
tv.tv_sec = time_to_set;
tv.tv_usec = 0;
sntp_sync_time(&tv);
ESP_LOGI(TAG, "[%0.3f] UTC time: %d.000 sec", _seconds(), time_to_set);
// if we have a software pps timer, shift it to top of second
if (ppsIRQ != NULL) {
timerWrite(ppsIRQ, 0); // reset pps timer
CLOCKIRQ(); // fire clock pps, this advances time 1 sec
}
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
// to top of second
// if we have a precise time timesource, set RTC time and shift RTC_INT
// pulse to top of second
#ifdef HAS_RTC
if ((mytimesource == _gps) || (mytimesource == _lora))
set_rtctime(time_to_set);
#endif
// if we have a software pps timer, shift it to top of second
if (ppsIRQ != NULL) {
timerWrite(ppsIRQ, 0); // reset pps timer
CLOCKIRQ(); // fire clock pps to advance wall clock by 1 sec
}
timeSource = mytimesource; // set global variable
timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ);
@ -132,7 +132,7 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
"[%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,
_COMPILETIME);
compileTime());
}
}
@ -140,29 +140,26 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
uint8_t timepulse_init() {
// set esp-idf API sntp sync mode
//sntp_init();
// sntp_init();
sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED);
// use time pulse from GPS as time base with fixed 1Hz frequency
// if we have, use PPS time pulse from GPS for syncing time on top of second
#ifdef GPS_INT
// setup external interupt pin for rising edge GPS INT
// setup external interupt pin for rising edge of GPS PPS
pinMode(GPS_INT, INPUT_PULLDOWN);
// setup external rtc 1Hz clock as pulse per second clock
ESP_LOGI(TAG, "Timepulse: external (GPS)");
return 1; // success
attachInterrupt(digitalPinToInterrupt(GPS_INT), GPSIRQ, RISING);
#endif
// use pulse from on board RTC chip as time base with fixed frequency
#elif defined RTC_INT
// if we have, use pulse from on board RTC chip as time base for calendar time
#if defined RTC_INT
// setup external interupt pin for falling edge RTC INT
pinMode(RTC_INT, INPUT_PULLUP);
// setup external rtc 1Hz clock as pulse per second clock
// setup external rtc 1Hz clock pulse
if (I2C_MUTEX_LOCK()) {
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
I2C_MUTEX_UNLOCK();
pinMode(RTC_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
ESP_LOGI(TAG, "Timepulse: external (RTC)");
return 1; // success
} else {
@ -172,33 +169,39 @@ uint8_t timepulse_init() {
return 1; // success
#else
// use ESP32 hardware timer as time base with adjustable frequency
// use ESP32 hardware timer as time base for calendar time
ppsIRQ = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms
timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true);
timerAlarmEnable(ppsIRQ);
ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)");
return 1; // success
#endif
} // timepulse_init
void timepulse_start(void) {
#ifdef GPS_INT // start external clock gps pps line
attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING);
#elif defined RTC_INT // start external clock rtc
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
#else // start internal clock esp32 hardware timer
timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true);
timerAlarmEnable(ppsIRQ);
#endif
// start cyclic time sync
timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ);
// 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);
} // timepulse_init
// interrupt service routine triggered by GPS PPS
void IRAM_ATTR GPSIRQ(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// take timestamp
lastPPS = millis(); // last time of pps
// yield only if we should
if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR();
}
// interrupt service routine triggered by either pps or esp32 hardware timer
// interrupt service routine triggered by esp32 hardware timer
void IRAM_ATTR CLOCKIRQ(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
@ -211,9 +214,7 @@ void IRAM_ATTR CLOCKIRQ(void) {
// flip time pulse ticker, if needed
#ifdef HAS_DISPLAY
#if (defined GPS_INT || defined RTC_INT)
TimePulseTick = !TimePulseTick; // flip pulse ticker
#endif
TimePulseTick = !TimePulseTick; // flip global variable pulse ticker
#endif
// yield only if we should
@ -224,7 +225,9 @@ void IRAM_ATTR CLOCKIRQ(void) {
// helper function to check plausibility of a given epoch time
bool timeIsValid(time_t const t) {
// is t a time in the past? we use compile time to guess
return (t > _COMPILETIME);
// compile time is some local time, but we do not know it's time zone
// thus, we go 1 full day back to be sure to catch a time in the past
return (t > (compileTime() - 86400));
}
// helper function to calculate serial transmit time
@ -252,7 +255,7 @@ void clock_init(void) {
"clockloop", // name of task
3072, // stack size of task
(void *)1, // task parameter
4, // priority of the task
6, // priority of the task
&ClockTask, // task handle
1); // CPU core
@ -283,7 +286,7 @@ void clock_loop(void *taskparameter) { // ClockTask
// set calendar time for next second of clock output
tt = (time_t)(current_time + 1);
localtime_r(&tt, &t);
mktime(&t);
tt = mktime(&t);
#if defined HAS_IF482
@ -293,13 +296,13 @@ void clock_loop(void *taskparameter) { // ClockTask
if (xTaskNotifyWait(0x00, ULONG_MAX, &current_time, txDelay) == pdTRUE) {
tt = (time_t)(current_time + 1);
localtime_r(&tt, &t);
mktime(&t);
tt = mktime(&t);
}
// send IF482 telegram
IF482.print(IF482_Frame(t)); // note: telegram is for *next* second
IF482.print(IF482_Frame(tt)); // note: telegram is for *next* second
ESP_LOGD(TAG, "[%0.3f] IF482: %s", _seconds(), IF482_Frame(t));
ESP_LOGD(TAG, "[%0.3f] IF482: %s", _seconds(), IF482_Frame(tt).c_str());
#elif defined HAS_DCF77
@ -340,7 +343,7 @@ void clock_loop(void *taskparameter) { // ClockTask
} // clock_loop()
// we use compile date to create a time_t reference "in the past"
time_t compileTime(const String compile_date) {
time_t compileTime(void) {
char s_month[5];
int year;
@ -353,10 +356,11 @@ time_t compileTime(const String compile_date) {
if (secs == -1) {
// determine date
// we go one day back to bypass unknown timezone of local time
sscanf(compile_date.c_str(), "%s %d %d", s_month, &t.tm_mday - 1, &year);
sscanf(__DATE__, "%s %d %d", s_month, &t.tm_mday, &year);
t.tm_mon = (strstr(month_names, s_month) - month_names) / 3;
t.tm_year = year - 1900;
// determine time
sscanf(__TIME__, "%d:%d:%d", &t.tm_hour, &t.tm_min, &t.tm_sec);
// convert to secs local time
secs = mktime(&t);

View File

@ -34,7 +34,7 @@ void timesync_init(void) {
"timesync_proc", // name of task
4096, // stack size of task
(void *)1, // task parameter
3, // priority of the task
7, // priority of the task
&timeSyncProcTask, // task handle
1); // CPU core
}