Commit de1e0902 authored by schneider's avatar schneider
Browse files

Merge branch 'schneider/covid-tracing' into 'master'

Show COVID-19 exposure notification statistics

See merge request card10/firmware!392
parents c0a3ed9e ae08d2f9
......@@ -45,10 +45,15 @@
#include "api/interrupt-sender.h"
#include "modules/log.h"
#define SCAN_REPORTS_NUM 16
static bool active;
static uint8_t advertising_mode = APP_MODE_NONE;
static uint8_t advertising_mode_target = APP_MODE_NONE;
static enum ble_event_type ble_event;
static struct epic_scan_report scan_reports[SCAN_REPORTS_NUM];
static int scan_reports_head;
static int scan_reports_tail;
/**************************************************************************************************
Macros
......@@ -188,6 +193,15 @@ static const uint8_t bleAdvDataConn[] =
DM_FLAG_LE_BREDR_NOT_SUP,
};
static const appMasterCfg_t scannerMasterCfg =
{
420, /*! The scan interval, in 0.625 ms units */
420, /*! The scan window, in 0.625 ms units */
0, /*! The scan duration in ms */
DM_DISC_MODE_NONE, /*! The GAP discovery mode */
DM_SCAN_TYPE_PASSIVE
/*!< The scan type (active or passive) */
};
/**************************************************************************************************
Client Characteristic Configuration Descriptors
......@@ -472,15 +486,45 @@ static void bleSetup(bleMsg_t *pMsg)
active = true;
/* TODO: Sadly, not advertising leads to a higher current consumption... */
epic_ble_set_bondable(false);
epic_ble_set_mode(false, false);
}
void epic_ble_set_bondable(bool bondable)
void epic_ble_set_mode(bool bondable, bool scanner)
{
if(!active) {
return;
}
if(scanner && bondable) {
/* TODO: return error */
return;
}
if(scanner) {
if(advertising_mode != APP_MODE_NONE) {
advertising_mode_target = APP_MODE_NONE;
advertising_mode = APP_MODE_NONE;
AppAdvStop();
}
dmConnId_t connId;
if ((connId = AppConnIsOpen()) != DM_CONN_ID_NONE) {
AppConnClose(connId);
}
/* Normal scanning filters out duplicates. We don't
* want that for now... */
//AppScanStart(scannerMasterCfg.discMode, scannerMasterCfg.scanType, scannerMasterCfg.scanDuration);
DmScanSetInterval(HCI_SCAN_PHY_LE_1M_BIT, &pAppMasterCfg->scanInterval,
&pAppMasterCfg->scanWindow);
DmScanStart(HCI_SCAN_PHY_LE_1M_BIT, scannerMasterCfg.discMode,
&scannerMasterCfg.scanType, FALSE, scannerMasterCfg.scanDuration, 0);
return;
} else {
AppScanStop();
}
if(bondable) {
/* We need to stop advertising in between or the
* adv set will not be changed.
......@@ -549,7 +593,10 @@ static void trigger_event(enum ble_event_type event)
{
bool enabled;
epic_interrupt_is_enabled(EPIC_INT_BLE, &enabled);
if(ble_event && enabled) {
/* Print a warning if the app is missing events. Missing scan results
* is considered OK though, as they are queued and periodic. */
if(ble_event && enabled && ble_event != BLE_EVENT_SCAN_REPORT) {
LOG_WARN("ble", "Application missed event %u", ble_event);
}
......@@ -576,6 +623,48 @@ static void bleHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
trigger_event(BLE_EVENT_HANDLE_NUMERIC_COMPARISON);
}
int epic_ble_get_scan_report(struct epic_scan_report *rpt)
{
if(scan_reports_head == scan_reports_tail) {
return -ENOENT;
}
int new_tail = (scan_reports_tail + 1) % SCAN_REPORTS_NUM;
*rpt = scan_reports[new_tail];
scan_reports_tail = new_tail;
return 0;
}
static void scannerScanReport(dmEvt_t *pMsg)
{
struct epic_scan_report *scan_report;
int next_head = (scan_reports_head + 1) % SCAN_REPORTS_NUM;
if(next_head == scan_reports_tail) {
trigger_event(BLE_EVENT_SCAN_REPORT);
return;
}
scan_reports_head = next_head;
scan_report = &scan_reports[scan_reports_head];
memset(scan_report->data, 0, sizeof(scan_report->data));
memccpy(scan_report->data, pMsg->scanReport.pData, pMsg->scanReport.len, sizeof(scan_report->data));
scan_report->len = pMsg->scanReport.len;
scan_report->rssi = pMsg->scanReport.rssi;
scan_report->eventType = pMsg->scanReport.eventType;
scan_report->addrType = pMsg->scanReport.addrType;
memcpy(scan_report->addr, pMsg->scanReport.addr, BDA_ADDR_LEN);
scan_report->directAddrType = pMsg->scanReport.directAddrType;
memcpy(scan_report->directAddr, pMsg->scanReport.directAddr, BDA_ADDR_LEN);
trigger_event(BLE_EVENT_SCAN_REPORT);
if((scan_reports_head + 1) % SCAN_REPORTS_NUM == scan_reports_tail) {
LOG_WARN("ble", "Application missing scan results");
}
}
/*************************************************************************************************/
/*!
* \brief Process messages from the event handler.
......@@ -610,10 +699,9 @@ static void bleProcMsg(bleMsg_t *pMsg)
case DM_ADV_START_IND:
LOG_INFO("ble", "Advertisement started %u %u", advertising_mode, advertising_mode_target);
if(advertising_mode != advertising_mode_target) {
if(advertising_mode != advertising_mode_target || advertising_mode_target == APP_MODE_NONE) {
AppAdvStop();
}
break;
case DM_ADV_STOP_IND:
......@@ -719,6 +807,10 @@ static void bleProcMsg(bleMsg_t *pMsg)
bleHandleNumericComparison(&pMsg->dm.cnfInd);
break;
case DM_SCAN_REPORT_IND:
scannerScanReport((dmEvt_t *)pMsg);
break;
case DM_HW_ERROR_IND:
LOG_ERR("ble", "HW Error");
break;
......@@ -749,6 +841,7 @@ static void BleHandlerInit(void)
pAppSlaveCfg = (appSlaveCfg_t *) &bleSlaveCfg;
pAppSecCfg = (appSecCfg_t *) &bleSecCfg;
pAppUpdateCfg = (appUpdateCfg_t *) &bleUpdateCfg;
pAppMasterCfg = (appMasterCfg_t *) &scannerMasterCfg;
/* Initialize application framework */
AppSlaveInit();
......@@ -779,7 +872,7 @@ static void BleHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
if (pMsg->event >= DM_CBACK_START && pMsg->event <= DM_CBACK_END)
{
LOG_INFO("ble", "Ble got evt %d: %s", pMsg->event, dm_events[pMsg->event - DM_CBACK_START]);
if(pMsg->event != DM_SCAN_REPORT_IND) LOG_INFO("ble", "Ble got evt %d: %s", pMsg->event, dm_events[pMsg->event - DM_CBACK_START]);
/* process advertising and connection-related messages */
AppSlaveProcDmMsg((dmEvt_t *) pMsg);
......
......@@ -130,11 +130,7 @@ void StackInit(void)
.freeMemAvail = LL_MEMORY_FOOTPRINT
};
#ifdef DATS_APP_USE_LEGACY_API
memUsed = LlInitControllerExtInit(&ll_init_cfg);
#else /* DATS_APP_USE_LEGACY_API */
memUsed = LlInitControllerExtInit(&ll_init_cfg);
#endif /* DATS_APP_USE_LEGACY_API */
if(memUsed != LL_MEMORY_FOOTPRINT)
{
printf("Controller memory mismatch 0x%x != 0x%x\n", (unsigned int)memUsed,
......@@ -142,6 +138,11 @@ void StackInit(void)
}
#endif
SecInit();
SecRandInit();
SecAesInit();
SecCmacInit();
SecEccInit();
/* card10:
* These calls register a queue for callbacks in the OS abstraction
......@@ -153,14 +154,10 @@ void StackInit(void)
handlerId = WsfOsSetNextHandler(HciHandler);
HciHandlerInit(handlerId);
SecInit();
SecAesInit();
SecCmacInit();
SecEccInit();
handlerId = WsfOsSetNextHandler(DmHandler);
DmDevVsInit(0);
DmAdvInit();
DmScanInit();
DmConnInit();
DmConnSlaveInit();
DmSecInit();
......
......@@ -149,10 +149,11 @@ typedef _Bool bool;
#define API_CONFIG_GET_BOOLEAN 0x132
#define API_CONFIG_SET_STRING 0x133
#define API_BLE_GET_COMPARE_VALUE 0x140
#define API_BLE_COMPARE_RESPONSE 0x141
#define API_BLE_SET_BONDABLE 0x142
#define API_BLE_GET_EVENT 0x143
#define API_BLE_GET_COMPARE_VALUE 0x140
#define API_BLE_COMPARE_RESPONSE 0x141
#define API_BLE_SET_MODE 0x142
#define API_BLE_GET_EVENT 0x143
#define API_BLE_GET_SCAN_REPORT 0x144
/* clang-format on */
......@@ -2036,7 +2037,7 @@ API(API_USB_CDCACM, int epic_usb_cdcacm(void));
/**
* Takes a gpio pin specified with the gpio module and transmits
* the led data. The format `GG:RR:BB` is expected.
* the led data. The format ``GG:RR:BB`` is expected.
*
* :param uint8_t pin: The gpio pin to be used for data.
* :param uint8_t * pixels: The buffer, in which the pixel data is stored.
......@@ -2135,9 +2136,29 @@ enum ble_event_type {
BLE_EVENT_PAIRING_FAILED = 2,
/** A pairing procedure has successfully completed */
BLE_EVENT_PAIRING_COMPLETE = 3,
/** New scan data is available */
BLE_EVENT_SCAN_REPORT = 4,
};
/**
* Scan report data. Bases on ``hciLeAdvReportEvt_t`` from BLE stack.
*
* TODO: 64 bytes for data is an arbitrary number ATM */
struct epic_scan_report
{
uint8_t data[64]; /*!< \brief advertising or scan response data. */
uint8_t len; /*!< \brief length of advertising or scan response data. */
int8_t rssi; /*!< \brief RSSI. */
uint8_t eventType; /*!< \brief Advertising event type. */
uint8_t addrType; /*!< \brief Address type. */
uint8_t addr[6]; /*!< \brief Device address. */
/* \brief direct fields */
uint8_t directAddrType; /*!< \brief Direct advertising address type. */
uint8_t directAddr[6]; /*!< \brief Direct advertising address. */
};
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_BLE`
*
......@@ -2202,22 +2223,44 @@ API(API_BLE_GET_COMPARE_VALUE, uint32_t epic_ble_get_compare_value(void));
API(API_BLE_COMPARE_RESPONSE, void epic_ble_compare_response(bool confirmed));
/**
* Allow or disallow new bondings to happen
* Set the desired mode of the BLE stack.
*
* There are three allowed modes:
*
* - Peripheral which is not bondable (bondable = ``false``, scanner = ``false``).
* - Peripheral which is bondable (bondable = ``true``, scanner = ``false``).
* - Observer which scans for advertisements (bondable = ``false``, scanner = ``true``).
*
* By default the card10 will not allow new bondings to be made. New
* bondings have to explicitly allowed by calling this function.
*
* While bonadable the card10 will change its advertisements to
* While bondable the card10 will change its advertisements to
* indicate to scanning hosts that it is available for discovery.
*
* When scanning is active, :c:data:`BLE_EVENT_SCAN_REPORT` events will be sent
* and the scan reports can be fetched using :c:func:`epic_ble_get_scan_report`.
*
* When switching applications new bondings are automatically
* disallowed.
* disallowed and scanning is stopped.
*
* :param bool bondable: `true` if new bondings should be allowed.
* :param bool scanner: `true` if scanning should be turned on.
*
* .. versionadded:: 1.16
*/
API(API_BLE_SET_BONDABLE, void epic_ble_set_bondable(bool bondable));
API(API_BLE_SET_MODE, void epic_ble_set_mode(bool bondable, bool scanner));
/**
* Retrieve a scan report from the queue of scan reports.
*
* :param struct epic_scan_report* rpt: Pointer where the report will be stored.
*
* :return: `0` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-ENOENT``: No scan report available
*
*/
API(API_BLE_GET_SCAN_REPORT, int epic_ble_get_scan_report(struct epic_scan_report *rpt));
#endif /* _EPICARDIUM_H */
......@@ -292,7 +292,7 @@ int hardware_reset(void)
/*
* BLE
*/
epic_ble_set_bondable(false);
epic_ble_set_mode(false, false);
return 0;
}
......@@ -421,6 +421,7 @@ ble_compileargs = [
'-DINIT_BROADCASTER',
'-DINIT_PERIPHERAL',
'-DINIT_ENCRYPTED',
'-DINIT_OBSERVER',
]
if get_option('ble_trace')
......
import interrupt
import sys_ble
import time
import vibra
import display
import color
import buttons
import leds
import config
DM_ADV_TYPE_FLAGS = 0x01
DM_ADV_TYPE_16_UUID = 0x03
DM_ADV_TYPE_SERVICE_DATA = 0x16
UUID = b"\x6f\xfd"
TIMEOUT = 100
MODE_OFF = 0
MODE_ON_NEW_MAC = 1
MODE_ON_RX = 2
MODE_BOTH = 3
seen = {}
vib_mode = MODE_BOTH
led_mode = MODE_BOTH
def parse_advertisement_data(data):
ads = {}
l = len(data)
p = 0
while p < l:
ad_len = data[p]
p += 1
if ad_len > 0:
ad_type = data[p]
ad_data = b""
p += 1
if ad_len > 1:
ad_data = data[p : p + ad_len - 1]
p += ad_len - 1
ads[ad_type] = ad_data
return ads
def bytes2hex(bin, sep=""):
return sep.join(["%02x" % x for x in bin])
def process_covid_data(mac, service_data, rssi, flags):
global vib_mode
if vib_mode in [MODE_ON_RX, MODE_BOTH]:
vibra.vibrate(10)
if vib_mode in [MODE_ON_NEW_MAC, MODE_BOTH] and mac not in seen:
vibra.vibrate(100)
if led_mode in [MODE_ON_RX, MODE_BOTH]:
leds.flash_rocket(0, 31, 20)
if led_mode in [MODE_ON_NEW_MAC, MODE_BOTH] and mac not in seen:
leds.flash_rocket(1, 31, 200)
print(bytes2hex(mac, ":"), rssi, bytes2hex(service_data), flags)
# try to produce a small int
last_rx_time = time.time() - t0
seen[mac] = [int(last_rx_time), flags]
def prune():
global seen
seen_pruned = {}
now = time.time() - t0
for mac in seen:
if seen[mac][0] + TIMEOUT > now:
seen_pruned[mac] = seen[mac]
seen = seen_pruned
def process_scan_report(scan_report):
ads = parse_advertisement_data(scan_report[0])
mac = scan_report[4]
mac = bytes([mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]])
rssi = scan_report[1]
# print(bytes2hex(mac, ':'), rssi, bytes2hex(scan_report[0]), ads)
# According to spec there is no other service announced and the
# service is always listed in the complete list of 16 bit services
if DM_ADV_TYPE_16_UUID in ads:
if ads[DM_ADV_TYPE_16_UUID] == UUID:
if DM_ADV_TYPE_SERVICE_DATA in ads:
flags = None
if DM_ADV_TYPE_FLAGS in ads:
flags = ads[DM_ADV_TYPE_FLAGS]
# service data contains another copy of the service UUID
process_covid_data(mac, ads[DM_ADV_TYPE_SERVICE_DATA][2:], rssi, flags)
def ble_callback(_):
event = sys_ble.get_event()
if event == sys_ble.EVENT_SCAN_REPORT:
while True:
scan_report = sys_ble.get_scan_report()
if scan_report == None:
break
process_scan_report(scan_report)
prune()
def show_stats():
seen_google = 0
seen_apple = 0
t = time.time() - t0
t_min = t
# Make a copy as `seen` could change in between
# and we're not locking it
seen_copy = seen.copy()
for mac in seen_copy:
info = seen_copy[mac]
if info[1]:
seen_apple += 1
else:
seen_google += 1
if info[0] < t_min:
t_min = info[0]
seen_total = seen_google + seen_apple
window = t - t_min if len(seen_copy) > 0 else min(TIMEOUT, t - last_rx_time)
disp.clear()
disp.print("Last %u s:" % window, posy=0, fg=color.WHITE)
disp.print("Google: %d" % seen_google, posy=20, fg=color.GREEN)
disp.print("Apple: %d" % seen_apple, posy=40, fg=color.BLUE)
disp.print("Total: %d" % seen_total, posy=60, fg=color.WHITE)
disp.update()
last_rx_time = 0
disp = display.open()
v_old = 0
pause = 1
interrupt.set_callback(interrupt.BLE, ble_callback)
interrupt.enable_callback(interrupt.BLE)
try:
vib_mode = int(config.get_string("exno_vib_mode"))
except:
pass
try:
led_mode = int(config.get_string("exno_led_mode"))
except:
pass
disp.clear()
disp.print(" Exp Notif", posy=0, fg=color.WHITE)
disp.print(" Counter", posy=20, fg=color.WHITE)
disp.print("Vib Mode ->", posy=40, fg=color.GREEN)
disp.print("<- LED Mode", posy=60, fg=color.BLUE)
disp.update()
time.sleep(3)
t0 = time.time()
sys_ble.scan_start()
while True:
v_new = buttons.read()
v = ~v_old & v_new
v_old = v_new
if v & buttons.TOP_RIGHT:
disp.clear()
disp.print("Vib Mode:", posy=0, fg=color.WHITE)
if vib_mode == MODE_OFF:
vib_mode = MODE_ON_NEW_MAC
disp.print("New MAC", posy=40, fg=color.YELLOW)
elif vib_mode == MODE_ON_NEW_MAC:
vib_mode = MODE_ON_RX
disp.print("Each RX", posy=40, fg=color.BLUE)
elif vib_mode == MODE_ON_RX:
vib_mode = MODE_BOTH
disp.print("MAC", posy=40, fg=color.YELLOW)
disp.print("&", posy=40, posx=14 * 4, fg=color.WHITE)
disp.print("RX", posy=40, posx=14 * 6, fg=color.BLUE)
elif vib_mode == MODE_BOTH:
vib_mode = MODE_OFF
disp.print("Vib off", posy=40, fg=color.WHITE)
disp.update()
config.set_string("exno_vib_mode", str(vib_mode))
pause = 20
if v & buttons.BOTTOM_LEFT:
disp.clear()
disp.print("LED Mode:", posy=0, fg=color.WHITE)
if led_mode == MODE_OFF:
led_mode = MODE_ON_NEW_MAC
disp.print("New MAC", posy=40, fg=color.YELLOW)
elif led_mode == MODE_ON_NEW_MAC:
led_mode = MODE_ON_RX
disp.print("Each RX", posy=40, fg=color.BLUE)
elif led_mode == MODE_ON_RX:
led_mode = MODE_BOTH
disp.print("MAC", posy=40, fg=color.YELLOW)
disp.print("&", posy=40, posx=14 * 4, fg=color.WHITE)
disp.print("RX", posy=40, posx=14 * 6, fg=color.BLUE)
elif led_mode == MODE_BOTH:
led_mode = MODE_OFF
disp.print("LED off", posy=40, fg=color.WHITE)
disp.update()
config.set_string("exno_led_mode", str(led_mode))
pause = 20
pause -= 1
if pause == 0:
show_stats()
pause = 10
time.sleep(0.1)
{"author": "schneider", "name": "Exposure Notification Stats", "description": "Count Exposure Notifications received via BLE", "category": "Hardware", "revision": 1, "source":"preload"}
......@@ -199,11 +199,14 @@ Q(get_string)
Q(BLE)
Q(ble)
Q(get_compare_value)
Q(get_scan_report)
Q(confirm_compare_value)
Q(set_bondable)
Q(scan_start)
Q(get_event)
Q(EVENT_NONE)
Q(EVENT_HANDLE_NUMERIC_COMPARISON)
Q(EVENT_PAIRING_COMPLETE)
Q(EVENT_PAIRING_FAILED)
Q(EVENT_SCAN_REPORT)
......@@ -24,6 +24,29 @@ static MP_DEFINE_CONST_FUN_OBJ_0(
ble_get_compare_value_obj, mp_ble_get_compare_value
);
static mp_obj_t mp_ble_get_scan_report(void)
{
struct epic_scan_report scan_report;
int ret = epic_ble_get_scan_report(&scan_report);
if (ret < 0) {
return mp_const_none;
}
mp_obj_t data = mp_obj_new_bytes(scan_report.data, scan_report.len);
mp_obj_t rssi = MP_OBJ_NEW_SMALL_INT(scan_report.rssi);
mp_obj_t eventType = MP_OBJ_NEW_SMALL_INT(scan_report.eventType);
mp_obj_t addrType = MP_OBJ_NEW_SMALL_INT(scan_report.addrType);
mp_obj_t addr = mp_obj_new_bytes(scan_report.addr, 6);
mp_obj_t values_list[] = { data, rssi, eventType, addrType, addr };
return mp_obj_new_tuple(5, values_list);
}
static MP_DEFINE_CONST_FUN_OBJ_0(
ble_get_scan_report_obj, mp_ble_get_scan_report
);
static mp_obj_t mp_ble_get_event(void)