Merge pull request #631 from cyberman54/CWA

Covid-19 exposure notifications system scanning function
This commit is contained in:
Verkehrsrot 2020-09-05 14:30:05 +02:00 committed by GitHub
commit 81879c23f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 238 additions and 65 deletions

View File

@ -200,12 +200,24 @@ Paxcounter can keep it's time-of-day synced with an external time source. Set *#
Paxcounter can be used to sync a wall clock which has a DCF77 or IF482 time telegram input. Set *#define HAS_IF482* or *#define HAS_DCF77* in board's hal file to setup clock controller. Use case of this function is to integrate paxcounter and clock. Accurary of the synthetic DCF77 signal depends on accuracy of on board's time base, see above.
# mobile PaxCounter via https://opensensemap.org/
This describes how to set up a mobile PaxCounter:
Follow all steps so far for preparing the device, use the packed payload format. In paxcounter.conf set PAYLOAD_OPENSENSEBOX to 1. Register a new sensbox on https://opensensemap.org/.
Follow all steps so far for preparing the device, use the packed payload format. In [paxcounter.conf](src/paxcounter.conf) set PAYLOAD_OPENSENSEBOX to 1. Register a new sensbox on https://opensensemap.org/.
There in the sensor configuration select "TheThingsNetwork" and set Decoding Profil to "LoRa serialization", enter your TTN Application and Device Id. Decoding option has to be
[{"decoder":"latLng"},{"decoder":"uint16","sensor_id":"yoursensorid"}]
# Covid-19 Exposure Notification System beacon detection (Germany: "Corona Warn App counter")
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 gives an indication 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 resulta withs this funcion, use following settings in [paxcounter.conf](src/paxcounter.conf):
#define COUNT_ENS 1 // enable ENS monitoring function
#define VENDORFILTER 0 // disable OUI filter (scans ALL device MACs)
#define BLECOUNTER 1 // enable bluetooth sniffing
#define WIFICOUNTER 0 // disable wifi sniffing (improves BLE scan speed)
#define HAS_SENSOR_1 1 // optional: 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:
#define HAS_SDCARD 1 // SD-card-reader/writer, using SPI interface
@ -559,4 +571,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 and SDS011 support
- [August Quint](https://github.com/AugustQu) for adding SD card data logger, SDS011 and ENS support

16
include/corona.h Normal file
View File

@ -0,0 +1,16 @@
#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

@ -12,10 +12,11 @@
#define MAC_SNIFF_WIFI 0
#define MAC_SNIFF_BLE 1
#define MAC_SNIFF_BLE_CWA 2
uint16_t get_salt(void);
uint64_t macConvert(uint8_t *paddr);
bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type);
uint16_t mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type);
void printKey(const char *name, const uint8_t *key, uint8_t len, bool lsb);
#endif

View File

@ -20,5 +20,6 @@
#include "sensor.h"
#include "lorawan.h"
#include "timekeeper.h"
#include "corona.h"
#endif

View File

@ -5,7 +5,7 @@
#include <stdio.h>
#include <SPI.h>
#ifdef HAS_SDCARD
#if (HAS_SDCARD)
#if HAS_SDCARD == 1
#include <mySD.h>
//#include <SD.h>
@ -38,9 +38,13 @@
#define SDCARD_FILE_NAME "/paxcount.%02d"
#define SDCARD_FILE_HEADER "date, time, wifi, bluet"
bool sdcard_init(void);
void sdcardWriteData(uint16_t, uint16_t);
static void createFile(void);
#define SDCARD_FILE_NAME "paxcount.%02d"
#define SDCARD_FILE_HEADER "date, time, wifi, bluet"
#if (COUNT_ENS)
#define SDCARD_FILE_HEADER_CWA ",cwa"
#endif
bool sdcard_init( void );
void sdcardWriteData( uint16_t, uint16_t, uint16_t = 0);
#endif // _SDCARD_H

View File

@ -8,6 +8,7 @@
#include "lorawan.h"
#include "display.h"
#include "sdcard.h"
#include "corona.h"
extern Ticker sendcycler;

View File

@ -37,19 +37,19 @@ halfile = generic.h
[platformio]
; upload firmware to board with usb cable
;default_envs = usb
default_envs = usb
; upload firmware to a jfrog bintray repository
;default_envs = ota
; use latest versions of libraries
default_envs = dev
;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 = 1.9.996
release_version = 2.0.1
; 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 = 4
debug_level = 3
extra_scripts = pre:build.py
otakeyfile = ota.conf
lorakeyfile = loraconf.h
@ -64,16 +64,16 @@ lib_deps_display =
OneBitDisplay@1.5.0
QRCode@0.0.1
BitBang_I2C@2.1.1
TFT_eSPI@>=2.2.8
TFT_eSPI@>=2.2.18
lib_deps_ledmatrix =
Ultrathin_LED_Matrix@>=1.0.0
lib_deps_rgbled =
SmartLeds@>=1.2.0
SmartLeds@>=1.2.1
lib_deps_gps =
1655@>=1.0.2 ; #1655 TinyGPSPlus by Mikal Hart
lib_deps_sensors =
Adafruit Unified Sensor@>=1.1.4
Adafruit BME280 Library@>=2.0.2
Adafruit BME280 Library@>=2.1.0
Adafruit BMP085 Library@>=1.1.0
BSEC Software Library@1.5.1474
https://github.com/ricki-z/SDS011.git

View File

@ -6,6 +6,11 @@
#define BT_BD_ADDR_HEX(addr) \
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]
// UUID of Exposure Notification Service (ENS)
// see
// https://blog.google/documents/70/Exposure_Notification_-_Bluetooth_Specification_v1.2.2.pdf
static const char ensMagicBytes[] = "\x03\x03\x6F\xfd";
// local Tag for logging
static const char TAG[] = "bluetooth";
@ -109,7 +114,9 @@ const char *btsig_gap_type(uint32_t gap_type) {
// using IRAM_:ATTR here to speed up callback function
IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t *param) {
esp_ble_gap_cb_param_t *p = (esp_ble_gap_cb_param_t *)param;
uint16_t hashedmac = 0;
ESP_LOGV(TAG, "BT payload rcvd -> type: 0x%.2x -> %s", *p->scan_rst.ble_adv,
btsig_gap_type(*p->scan_rst.ble_adv));
@ -146,22 +153,25 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event,
}
#if (VENDORFILTER)
if ((p->scan_rst.ble_addr_type == BLE_ADDR_TYPE_RANDOM) ||
(p->scan_rst.ble_addr_type == BLE_ADDR_TYPE_RPA_RANDOM)) {
ESP_LOGV(TAG, "BT device filtered");
break;
}
#endif
// add this device and show new count total if it was not previously added
mac_add((uint8_t *)p->scan_rst.bda, p->scan_rst.rssi, MAC_SNIFF_BLE);
// hash and add this device and show new count total if it was not
// previously added
hashedmac =
mac_add((uint8_t *)p->scan_rst.bda, p->scan_rst.rssi, MAC_SNIFF_BLE);
#if (COUNT_ENS)
// check for ens signature
if (0 == strncmp((const char *)p->scan_rst.ble_adv, ensMagicBytes, 4))
cwa_mac_add(hashedmac);
#endif
/* to be improved in vendorfilter if:
// you can search for elements in the payload using the
// function esp_ble_resolve_adv_data()
//

55
src/corona.cpp Normal file
View File

@ -0,0 +1,55 @@
// 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)
// 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 SOME_FORM_OF_DEBUG
ESP_LOGD(TAG, "CWA: forget old notifier: %d", cwaSeenNotifiers.size());
for (auto const &notifier : cwaSeenNotifiers) {
ESP_LOGD(TAG, "CWA forget <%X>", 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

@ -265,7 +265,11 @@ void dp_drawPage(time_t t, bool nextpage) {
else
dp_printf("WIFI:off");
if (cfg.blescan)
dp_printf(" BLTH:%-5d", macs_ble);
#if (!COUNT_ENS)
dp_printf("BLTH:%-5d", macs_ble);
#else
dp_printf(" CWA:%-5d", cwa_report());
#endif
else
dp_printf(" BLTH:off");
#elif ((WIFICOUNTER) && (!BLECOUNTER))
@ -274,9 +278,12 @@ void dp_drawPage(time_t t, bool nextpage) {
else
dp_printf("WIFI:off");
#elif ((!WIFICOUNTER) && (BLECOUNTER))
if (cfg.blescan)
if (cfg.blescan) {
dp_printf("BLTH:%-5d", macs_ble);
else
#if (COUNT_ENS)
dp_printf("(CWA:%d)", cwa_report());
#endif
} else
dp_printf("BLTH:off");
#else
dp_printf("Sniffer disabled");
@ -349,18 +356,17 @@ void dp_drawPage(time_t t, bool nextpage) {
#if (HAS_LORA)
// 3|NtwkID:000000 TXpw:aa
// 4|DevAdd:00000000 DR:0
// 3|Net:000000 Pwr:aa
// 4|Dev:00000000 DR:0
// 5|CHMsk:0000 Nonce:0000
// 6|CUp:000000 CDn:000000
// 6|fUp:000000 fDn:000000
// 7|SNR:-0000 RSSI:-0000
dp_setFont(MY_FONT_SMALL);
dp_setTextCursor(0, 3);
dp_printf("NetwID:%06X TXpw:%-2d", LMIC.netid & 0x001FFFFF,
LMIC.radio_txpow);
dp_printf("Net:%06X Pwr:%-2d", LMIC.netid & 0x001FFFFF, LMIC.radio_txpow);
dp_println();
dp_printf("DevAdd:%08X DR:%1d", LMIC.devaddr, LMIC.datarate);
dp_printf("Dev:%08X DR:%1d", LMIC.devaddr, LMIC.datarate);
dp_println();
dp_printf("ChMsk:%04X Nonce:%04X", LMIC.channelMap, LMIC.devNonce);
dp_println();

View File

@ -12,11 +12,11 @@
// This settings are for boards labeled v1.6 on pcb, NOT for v1.5 or older
*/
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#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 an SD-card-reader/writer
#define HAS_SDCARD 1 // this board has an SD-card-reader/writer
#define HAS_DISPLAY 1
#define HAS_LED (25) // green on board LED

View File

@ -10,7 +10,7 @@
// Local logging tag
static const char TAG[] = __FILE__;
uint16_t salt;
uint16_t salt = 0;
uint16_t get_salt(void) {
salt = (uint16_t)random(65536); // get new 16bit random for salting hashes
@ -43,16 +43,16 @@ uint64_t macConvert(uint8_t *paddr) {
return (__builtin_bswap64(*mac) >> 16);
}
bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) {
uint16_t mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) {
if (!salt) // ensure we have salt (appears after radio is turned on)
return false;
if (salt == 0) // ensure we have salt (appears after radio is turned on)
return 0;
char buff[10]; // temporary buffer for printf
uint16_t hashedmac = 0; // temporary buffer for generated hash value
char buff[10]; // temporary buffer for printf
bool added = false;
int8_t beaconID; // beacon number in test monitor mode
uint16_t hashedmac; // temporary buffer for generated hash value
uint32_t *mac; // temporary buffer for shortened MAC
int8_t beaconID; // beacon number in test monitor mode
uint32_t *mac; // temporary buffer for shortened MAC
// only last 3 MAC Address bytes are used for MAC address anonymization
// but since it's uint32 we take 4 bytes to avoid 1st value to be 0.
@ -116,13 +116,20 @@ bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) {
} // added
// Log scan result
ESP_LOGV(TAG,
ESP_LOGD(TAG,
"%s %s RSSI %ddBi -> salted MAC %s -> Hash %04X -> WiFi:%d "
"BLTH:%d -> "
"%d Bytes left",
"BLTH:%d "
#if (COUNT_ENS)
"(CWA:%d)"
#endif
"-> %d Bytes left",
added ? "new " : "known",
sniff_type == MAC_SNIFF_WIFI ? "WiFi" : "BLTH", rssi, buff,
hashedmac, macs_wifi, macs_ble, getFreeRAM());
hashedmac, macs_wifi, macs_ble,
#if (COUNT_ENS)
cwa_report(),
#endif
getFreeRAM());
#if (VENDORFILTER)
} else {
@ -132,7 +139,7 @@ bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) {
}
#endif
// True if MAC WiFi/BLE was new
return added; // function returns bool if a new and unique Wifi or BLE mac was
// counted (true) or not (false)
// if a new and unique Wifi or BLE mac was counted, returs hash of this mac,
// else 0
return hashedmac;
}

View File

@ -312,9 +312,25 @@ void setup() {
// initialize sensors
#if (HAS_SENSORS)
strcat_P(features, " SENS");
#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();
#endif
#if (HAS_SENSOR_3)
strcat_P(features, " SENS(3)");
sensor_init();
#endif
#endif
// initialize LoRa
#if (HAS_LORA)
@ -336,7 +352,7 @@ void setup() {
assert(mqtt_init() == ESP_OK);
#endif
#ifdef HAS_SDCARD
#if (HAS_SDCARD)
if (sdcard_init())
strcat_P(features, " SD");
#endif

View File

@ -15,15 +15,25 @@
#define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed
// Set this to include BLE counting and vendor filter functions, or to switch off WIFI counting
#define VENDORFILTER 1 // set to 0 if you want to count things, not people
#define BLECOUNTER 0 // set it to 1 if you want to use BLE count, at expense of power & memory
#define WIFICOUNTER 1 // set it to 0 if you want to switch off WIFI count
#define VENDORFILTER 0 // set to 0 if you want to all devices, not filtering smartphone OUIs
#define BLECOUNTER 1 // set it to 1 if you want to use BLE count, at expense of power & memory
#define WIFICOUNTER 0 // set it to 0 if you want to switch off WIFI count
// BLE scan parameters
#define BLESCANTIME 0 // [seconds] scan duration, 0 means infinite [default], see note below
#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 1 // count found number of devices which advertise Exposure Notification Service
// set to 0 if you do not want to enable this function
// for additional sensors (added by some user)
#define HAS_SENSOR_1 1 // set to 1 if you want to transmit CWA counter
#define HAS_SENSOR_2 0 // not used
#define HAS_SENSOR_3 0 // not used
#define HAS_SENSORS (HAS_SENSOR_1 || HAS_SENSOR_2 || HAS_SENSOR_3) // to simplify things
/* Note: guide for setting bluetooth parameters
*
* |< Scan Window > |< Scan Window > | ... |< Scan Window > |
@ -73,8 +83,8 @@
#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 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_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 1]
#define TIME_SYNC_LORASERVER 1 // 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
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging, max. 255

View File

@ -9,6 +9,8 @@ static const char TAG[] = __FILE__;
static bool useSDCard;
static void createFile(void);
File fileSDCard;
bool sdcard_init() {
@ -19,9 +21,7 @@ bool sdcard_init() {
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sdmmc_host.html
#if HAS_SDCARD == 1 // use SD SPI host driver
useSDCard = SD.begin(SDCARD_CS, SDCARD_MOSI, SDCARD_MISO, SDCARD_SCLK);
//SPI.begin(SDCARD_SCLK, SDCARD_MSO, SDCARD_MOSI, SDCARD_CS);
//delay(10);
//useSDCard = SD.begin(SDCARD_CS, SPI, 40000000, "/sd");
@ -38,7 +38,7 @@ bool sdcard_init() {
return useSDCard;
}
void sdcardWriteData(uint16_t noWifi, uint16_t noBle) {
void sdcardWriteData(uint16_t noWifi, uint16_t noBle, __attribute__((unused)) uint16_t noBleCWA) {
static int counterWrites = 0;
char tempBuffer[12 + 1];
time_t t = now();
@ -56,6 +56,10 @@ void sdcardWriteData(uint16_t noWifi, uint16_t noBle) {
fileSDCard.print(tempBuffer);
sprintf(tempBuffer, "%d,%d", noWifi, noBle);
fileSDCard.print(tempBuffer);
#if (COUNT_ENS)
sprintf(tempBuffer, ",%d", noBleCWA);
fileSDCard.print(tempBuffer);
#endif
#if (HAS_SDS011)
sds011_store(&sds);
sprintf(tempBuffer, ",%5.1f,%4.1f", sds.pm10, sds.pm25);
@ -98,6 +102,9 @@ void createFile(void) {
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)
#endif
#if (HAS_SDS011)
fileSDCard.print(SDCARD_FILE_HEADER_SDS011);
#endif

View File

@ -55,8 +55,14 @@ void SendPayload(uint8_t port, sendprio_t prio) {
#endif
// write data to sdcard, if present
#ifdef HAS_SDCARD
sdcardWriteData(macs_wifi, macs_ble);
#if (HAS_SDCARD)
if ( port == COUNTERPORT ) {
sdcardWriteData(macs_wifi, macs_ble
#if (COUNT_ENS)
, cwa_report()
#endif
);
}
#endif
} // SendPayload
@ -143,22 +149,31 @@ void sendData() {
#endif
#if (HAS_SENSORS)
#if (HAS_SENSOR_1)
case SENSOR1_DATA:
payload.reset();
payload.addSensor(sensor_read(1));
SendPayload(SENSOR1PORT, prio_normal);
#if (COUNT_ENS)
cwa_clear();
#endif
break;
#endif
#if (HAS_SENSOR_2)
case SENSOR2_DATA:
payload.reset();
payload.addSensor(sensor_read(2));
SendPayload(SENSOR2PORT, prio_normal);
break;
#endif
#if (HAS_SENSOR_3)
case SENSOR3_DATA:
payload.reset();
payload.addSensor(sensor_read(3));
SendPayload(SENSOR3PORT, prio_normal);
break;
#endif
#endif
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
case BATT_DATA:

View File

@ -2,6 +2,14 @@
#include "globals.h"
#include "sensor.h"
#if (COUNT_ENS)
#include "payload.h"
#include "corona.h"
#include "macsniff.h"
extern PayloadConvert payload;
#endif
// Local logging tag
static const char TAG[] = __FILE__;
@ -47,10 +55,14 @@ uint8_t *sensor_read(uint8_t sensor) {
case 1:
// insert user specific sensor data frames here */
#if (COUNT_ENS)
payload.addCount( cwa_report(), MAC_SNIFF_BLE_CWA);
#else
buf[0] = length;
buf[1] = 0x01;
buf[2] = 0x02;
buf[3] = 0x03;
#endif
break;
case 2: