read and parse Key Export, started WiFi and CWA connection

This commit is contained in:
Lurkars 2020-07-25 13:33:03 +02:00
parent 4ba1352a05
commit 20818020d8
32 changed files with 9907 additions and 258 deletions

View File

@ -3,6 +3,8 @@
Implementation of contact tracing with the Covid-19 Exposure Notification API by Apple and Google on an ESP32 (with [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html)). Implementation of contact tracing with the Covid-19 Exposure Notification API by Apple and Google on an ESP32 (with [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/index.html)).
More information about the Covid-19 Exposure Notification at [Apple](https://www.apple.com/covid19/contacttracing/) and [Google](https://www.google.com/covid19/exposurenotifications/). This is meant for people without smartphone or without smartphones with Apples/Googles implementation. More information about the Covid-19 Exposure Notification at [Apple](https://www.apple.com/covid19/contacttracing/) and [Google](https://www.google.com/covid19/exposurenotifications/). This is meant for people without smartphone or without smartphones with Apples/Googles implementation.
The main source (the Exposure Notification API) is a separate module in [**components/ena**](components/ena).
[Demo Video](https://twitter.com/Lurkars/status/1282223547579019264) [Demo Video](https://twitter.com/Lurkars/status/1282223547579019264)
This implementation fully covers for the BLE part including the cryptography specifications needed (see Bluetooth Specifications and Cryptography Specifications documents in the links above): This implementation fully covers for the BLE part including the cryptography specifications needed (see Bluetooth Specifications and Cryptography Specifications documents in the links above):
@ -10,26 +12,24 @@ This implementation fully covers for the BLE part including the cryptography spe
* store TEKs on flash (last 14) * store TEKs on flash (last 14)
* receive beacons * receive beacons
* received beacons are stored after 5 minutes threshold (storage is limited, ~100k beacons can be stored) * received beacons are stored after 5 minutes threshold (storage is limited, ~100k beacons can be stored)
* parse key export binaries as defined in [Exposure Key export file format and verification](https://developers.google.com/android/exposure-notifications/exposure-key-file-format) (big thanks to [nanopb](https://github.com/nanopb/nanopb) for making this easier than I thought!)
* calculating risks scores (after adding reported keys and storing exposure information)
Additional features for full ENA device Additional features for full ENA device
* calculating risks scores (after adding reported keys and storing exposure information)
* RTC support with DS3231 * RTC support with DS3231
* display support with SSD1306 * display support with SSD1306
* interface to * interface to
* set time * set time
* show exposure status
Features missing for now are:
* retrieve infected list and parse from binary (started with binary parsing)
Extensions planned: Extensions planned:
* interface to * automatically receive key export from web (will test [Corona Warn App](https://github.com/corona-warn-app))
* delete data
* show status
* report infection?
* receive infected beacons list (will test [Corona Warn App](https://github.com/corona-warn-app))
* send infected status (will test [Corona Warn App](https://github.com/corona-warn-app)) * send infected status (will test [Corona Warn App](https://github.com/corona-warn-app))
* battery support * battery support
* 3d print case * 3d print case
* interface to
* delete data
* report infection
Limitations/Problems Limitations/Problems
* storage only ~2.8mb available * storage only ~2.8mb available
@ -44,8 +44,9 @@ The following acronyms will be used in code and comments:
* *RPI* Rolling Proximity Identifier - send and received identifer changed every 10 minutes * *RPI* Rolling Proximity Identifier - send and received identifer changed every 10 minutes
* *AEM* Associated Encrypted Metadata - send and received metadata * *AEM* Associated Encrypted Metadata - send and received metadata
Open questions Open questions/problems
* service UUID is send reversed, must RPI and AEM also beeing send in reverse? Don't know BLE specification enough * memory is really low with BLE and WiFi enabled, unzipping a Key Export not possible for now, maybe disable BLE service for download.
* service UUID is send reversed, RPI and AEM also send in reverse? Don't know BLE specification enough
## How to use ## How to use
@ -100,18 +101,23 @@ E (909164) BT_HCI: btu_hcif_hdl_command_complete opcode 0x2005 status 0xc
## Structure ## Structure
The project is divided in different components. The main.c just wrap up all components. The project is divided in different components. The main.c just wrap up all components. The Exposure Notification API is in **ena** module
### ena ### ena
The ena module contains the main functions of eps-ena with bluetooth scanning and adverting, storing data and handle beacons. The ena module contains the main functions of eps-ena with bluetooth scanning and adverting, storing data, handle beacons and check exposure.
* *ena-beacons* handles scanned data by storing temporary beacons, check for threshold and store beacons permanently * *ena-beacons* handles scanned data by storing temporary beacons, check for threshold and store beacons permanently
* *ena-crypto* covers cryptography part (key creation, encryption etc.) * *ena-crypto* covers cryptography part (key creation, encryption etc.)
* *ena-storage* storage part to store own TEKs and beacons * *ena-storage* storage part to store own TEKs and beacons
* *ena-bluetooth-scan* BLE scans for detecting other beacons * *ena-bluetooth-scan* BLE scans for detecting other beacons
* *ena-bluetooth-advertise* BLE advertising to send own beacons * *ena-bluetooth-advertise* BLE advertising to send own beacons
* *ena-exposure* decode Key Export, compare with stored beacons, calculate score and risk
* *ena* run all together and timing for scanning and advertising * *ena* run all together and timing for scanning and advertising
### ena-cwa
Connection to german Exposure App ([Corona Warn App](https://github.com/corona-warn-app)) for download Key Export (and maybe later report infection).
### ena-interface ### ena-interface
Adds interface functionality via touch pads for control and setup. Adds interface functionality via touch pads for control and setup.
@ -120,6 +126,18 @@ Adds interface functionality via touch pads for control and setup.
Just start I2C driver for display and RTC. Just start I2C driver for display and RTC.
### ds3231
I2C driver for a DS3231 RTC
### ssd1306 ### ssd1306
I2C driver for a SSD1306 display. I2C driver for a SSD1306 display.
### nanopb
[Nanopb](https://github.com/nanopb/nanopb) for reading Protocol Buffers of Key Export. Including already generated Headers from *.proto files.
### miniz
[Miniz](https://github.com/richgel999/miniz) for unzipping Key Export (not successful for now due to memory limit)

View File

@ -0,0 +1,11 @@
idf_component_register(
SRCS
"ena-cwa.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES
esp_http_client
miniz
ena
EMBED_FILES
"certs/telekom.pem"
)

View File

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFwDCCBKigAwIBAgIIfjnHrR3Z8EMwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV
BAYTAkRFMSswKQYDVQQKDCJULVN5c3RlbXMgRW50ZXJwcmlzZSBTZXJ2aWNlcyBH
bWJIMR8wHQYDVQQLDBZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMSUwIwYDVQQDDBxU
LVRlbGVTZWMgR2xvYmFsUm9vdCBDbGFzcyAyMB4XDTE0MDIxMTE0MzkxMFoXDTI0
MDIxMTIzNTk1OVowgd8xCzAJBgNVBAYTAkRFMSUwIwYDVQQKDBxULVN5c3RlbXMg
SW50ZXJuYXRpb25hbCBHbWJIMR8wHQYDVQQLDBZULVN5c3RlbXMgVHJ1c3QgQ2Vu
dGVyMRwwGgYDVQQIDBNOb3JkcmhlaW4gV2VzdGZhbGVuMQ4wDAYDVQQRDAU1NzI1
MDEQMA4GA1UEBwwHTmV0cGhlbjEgMB4GA1UECQwXVW50ZXJlIEluZHVzdHJpZXN0
ci4gMjAxJjAkBgNVBAMMHVRlbGVTZWMgU2VydmVyUGFzcyBDbGFzcyAyIENBMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3oxwJVY3bSb6ejJ42f9VEt1N
vW2swwllcs5ifPsHAulpSoFc2Y9gMOKQqkuyjN1foCegDDeEr6FBLD5YuROldcX8
2aDNBKDh9GpSJYZMLrYwlfR4EJUGwLidHDn93H95j1M67sNlCyCfcbso0zFBQzXK
KO06sbC1QH9M1Xdrltz8bQS+LbGRTM5JcPYhhxXcnsFstQVaGmfqFQitPXhT3g9+
8Fbob6taSylFVk1E89G2N0NrrtIVTnaD0PcWF8AdMyX34zIoQAXMezyGV2kqst/Q
Ghvzd09jjMT6f8Q8pAlyGFTGuxsEjeU/rrS/yKU8bFEEvuR5WT/I4Kme+8OlzQID
AQABo4IB2TCCAdUwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHSAEPDA6MDgGBFUd
IAAwMDAuBggrBgEFBQcCARYiaHR0cDovL3BraS50ZWxlc2VjLmRlL2Nwcy9jcHMu
aHRtbDAOBgNVHQ8BAf8EBAMCAQYwge8GA1UdHwSB5zCB5DA1oDOgMYYvaHR0cDov
L3BraS50ZWxlc2VjLmRlL3JsL0dsb2JhbFJvb3RfQ2xhc3NfMi5jcmwwgaqggaeg
gaSGgaFsZGFwOi8vcGtpLnRlbGVzZWMuZGUvQ049VC1UZWxlU2VjJTIwR2xvYmFs
Um9vdCUyMENsYXNzJTIwMixPVT1ULVN5c3RlbXMlMjBUcnVzdCUyMENlbnRlcixP
PVQtU3lzdGVtcyUyMEVudGVycHJpc2UlMjBTZXJ2aWNlcyUyMEdtYkgsQz1ERT9B
dXRob3JpdHlSZXZvY2F0aW9uTGlzdDA4BggrBgEFBQcBAQQsMCowKAYIKwYBBQUH
MAGGHGh0dHA6Ly9vY3NwLnRlbGVzZWMuZGUvb2NzcHIwHQYDVR0OBBYEFJTIdEb1
OrRGSCb4K8o0HlYmBBIAMB8GA1UdIwQYMBaAFL9ZIDYAeaCgImuM1fJh0rgsy4JK
MA0GCSqGSIb3DQEBCwUAA4IBAQB55S9CfCkclWVtUIxl2c4aM5wqlLZRZ7zVhynK
KOhWKyTw+D2BOjc+TXIPkgRMqF3Sn8ZD4UTOARboJxswYnLZDkvBuvTbYa+N52Jy
oBP2TXIpEWEyJl7Oq8NFbERwg4X6MabLgjGvJETicPpKGfAINKDwPScQCsWHiCaX
X50cZzmWw17S0rWECOvPEt/4tXJ4Me9aAxx6WRm708n/K8O4mB3AzvA/M7VUDaP9
8LtreoTnWInjyg/8+Ahtce3foMXiIP4+9IX7fbm6yqh4u33tqMESDcRP6eGdzq4D
qnHyIvj9XNpuGgMvDgq357kZQS9e5XVH5icSvW1kr2kX2t1f
-----END CERTIFICATE-----

View File

@ -0,0 +1,142 @@
// Copyright 2020 Lukas Haubaum
//
// Licensed under the GNU Affero General Public License, Version 3;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.gnu.org/licenses/agpl-3.0.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "esp_log.h"
#include "esp_http_client.h"
#include "miniz.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ena-cwa.h"
extern const uint8_t telekom_pem_start[] asm("_binary_telekom_pem_start");
extern const uint8_t telekom__pem_end[] asm("_binary_telekom_pem_end");
esp_err_t ena_cwa_http_event_handler(esp_http_client_event_t *evt)
{
static char *output_buffer;
static int output_len;
switch (evt->event_id)
{
case HTTP_EVENT_ON_DATA:
if (!esp_http_client_is_chunked_response(evt->client))
{
if (output_buffer == NULL)
{
output_buffer = (char *)malloc(esp_http_client_get_content_length(evt->client));
output_len = 0;
if (output_buffer == NULL)
{
ESP_LOGE(ENA_CWA_LOG, "Failed to allocate memory for output buffer");
return ESP_FAIL;
}
}
memcpy(output_buffer + output_len, evt->data, evt->data_len);
output_len += evt->data_len;
}
break;
case HTTP_EVENT_ON_FINISH:
if (output_buffer != NULL)
{
ESP_LOGD(ENA_CWA_LOG, "memory: %d kB", (xPortGetFreeHeapSize() / 1024));
size_t zip_image_size = esp_http_client_get_content_length(evt->client);
// const char *file_name = "export.sig";
/*
mz_zip_archive zip_archive;
mz_zip_archive_file_stat file_stat;
mz_uint32 file_index;
memset(&zip_archive, 0, sizeof(zip_archive));
ESP_LOGD(ENA_CWA_LOG, "memory: %d kB", (xPortGetFreeHeapSize() / 1024));
vTaskDelay(1000 / portTICK_PERIOD_MS);
mz_zip_reader_init_mem(&zip_archive, output_buffer, sizeof(output_buffer), 0);
ESP_LOGD(ENA_CWA_LOG, "memory: %d kB", (xPortGetFreeHeapSize() / 1024));
mz_zip_reader_locate_file_v2(&zip_archive, file_name, NULL, 0, &file_index);
ESP_LOGD(ENA_CWA_LOG, "memory: %d kB", (xPortGetFreeHeapSize() / 1024));
mz_zip_reader_file_stat(&zip_archive, file_index, &file_stat);
ESP_LOGD(ENA_CWA_LOG, "memory: %d kB", (xPortGetFreeHeapSize() / 1024));
size_t extracted_size = file_stat.m_uncomp_size;
ESP_LOGD(ENA_CWA_LOG, "size of export.sig: %d", extracted_size);
char *file_buffer = malloc(extracted_size);
mz_zip_reader_extract_to_mem(&zip_archive, file_index, file_buffer, extracted_size, 0);
ESP_LOGD(ENA_CWA_LOG, "memory: %d kB", (xPortGetFreeHeapSize() / 1024));
mz_zip_reader_end(&zip_archive);
ESP_LOG_BUFFER_HEXDUMP(ENA_CWA_LOG, file_buffer, extracted_size, ESP_LOG_DEBUG);
free(file_buffer);
*/
mz_zip_archive zip_archive;
ESP_LOGD(ENA_CWA_LOG, "1 memory: %d kB (min %d kB)", (esp_get_free_heap_size() / 1024), (esp_get_minimum_free_heap_size() / 1024));
memset(&zip_archive, 0, sizeof(zip_archive));
ESP_LOGD(ENA_CWA_LOG, "2 memory: %d kB (min %d kB)", (esp_get_free_heap_size() / 1024), (esp_get_minimum_free_heap_size() / 1024));
mz_zip_reader_init_mem(&zip_archive, output_buffer, zip_image_size, 0);
ESP_LOGD(ENA_CWA_LOG, "3 memory: %d kB (min %d kB)", (esp_get_free_heap_size() / 1024), (esp_get_minimum_free_heap_size() / 1024));
mz_zip_reader_end(&zip_archive);
ESP_LOGD(ENA_CWA_LOG, "4 memory: %d kB (min %d kB)", (esp_get_free_heap_size() / 1024), (esp_get_minimum_free_heap_size() / 1024));
/*
p = mz_zip_reader_extract_file_to_heap(&zip_archive, file_name, &extracted_size, 0);
ESP_LOGD(ENA_CWA_LOG, "4 memory: %d kB", (esp_get_free_heap_size() / 1024));
mz_zip_reader_end(&zip_archive);
ESP_LOG_BUFFER_HEXDUMP(ENA_CWA_LOG, p, extracted_size, ESP_LOG_DEBUG);
free(p);
*/
free(output_buffer);
output_buffer = NULL;
output_len = 0;
ESP_LOGD(ENA_CWA_LOG, "memory freed: %d kB (min %d kB)", (esp_get_free_heap_size() / 1024), (esp_get_minimum_free_heap_size() / 1024));
}
break;
default:
break;
}
return ESP_OK;
}
void ena_cwa_receive_keys(char *date_string)
{
char *url = malloc(strlen(ENA_CWA_KEYFILES_URL) + strlen(date_string));
sprintf(url, ENA_CWA_KEYFILES_URL, date_string);
esp_http_client_config_t config = {
.url = url,
.cert_pem = (char *)telekom_pem_start,
.event_handler = ena_cwa_http_event_handler,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK)
{
int content_length = esp_http_client_get_content_length(client);
ESP_LOGD(ENA_CWA_LOG, "Url = %s, Status = %d, content_length = %d", url,
esp_http_client_get_status_code(client),
content_length);
}
// free(url);
esp_http_client_close(client);
esp_http_client_cleanup(client);
}

View File

@ -0,0 +1,28 @@
// Copyright 2020 Lukas Haubaum
//
// Licensed under the GNU Affero General Public License, Version 3;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.gnu.org/licenses/agpl-3.0.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _ena_CWA_H_
#define _ena_CWA_H_
#define ENA_CWA_LOG "ESP-ENA-corona-warn-app" // TAG for Logging
#define ENA_CWA_KEYFILES_URL "http://svc90.main.px.t-online.de/version/v1/diagnosis-keys/country/DE/date/%s"
/**
* @brief fetch key export for given date
*
* @param[in] date_string the date to fetch the data for
*/
void ena_cwa_receive_keys(char *date_string);
#endif

View File

@ -33,6 +33,9 @@ void ena_interface_menu_ok(void)
{ {
ena_interface_status_start(); ena_interface_status_start();
} }
else if (interface_menu_state == ENA_INTERFACE_MENU_STATE_SELECT_DEBUG)
{
}
else if (interface_menu_state == ENA_INTERFACE_MENU_STATE_IDLE) else if (interface_menu_state == ENA_INTERFACE_MENU_STATE_IDLE)
{ {
if (ena_interface_get_state() == ENA_INTERFACE_STATE_MENU) if (ena_interface_get_state() == ENA_INTERFACE_STATE_MENU)

View File

@ -18,6 +18,7 @@ typedef enum
{ {
ENA_INTERFACE_MENU_STATE_IDLE = 0, ENA_INTERFACE_MENU_STATE_IDLE = 0,
ENA_INTERFACE_MENU_STATE_SELECT_TIME, ENA_INTERFACE_MENU_STATE_SELECT_TIME,
ENA_INTERFACE_MENU_STATE_SELECT_DEBUG,
ENA_INTERFACE_MENU_STATE_SELECT_STATUS, ENA_INTERFACE_MENU_STATE_SELECT_STATUS,
} ena_interface_menu_state; } ena_interface_menu_state;

View File

@ -15,5 +15,4 @@ idf_component_register(
nanopb nanopb
EMBED_FILES EMBED_FILES
"test/export.bin" "test/export.bin"
"test/export.sig"
) )

View File

@ -87,12 +87,4 @@ menu "Exposure Notification API"
Defines the TEK rolling period in 10 minute steps. (Default 144 => 24 hours) Defines the TEK rolling period in 10 minute steps. (Default 144 => 24 hours)
endmenu endmenu
menu "Miscellaneous"
config ENA_RAM
int "ENA RAM"
default 100000
help
RAM required for main task. (Default 100 KB)
endmenu
endmenu endmenu

View File

@ -15,12 +15,16 @@
#include <time.h> #include <time.h>
#include <limits.h> #include <limits.h>
#include "esp_err.h"
#include "esp_log.h" #include "esp_log.h"
#include "ena-crypto.h" #include "ena-crypto.h"
#include "ena-storage.h" #include "ena-storage.h"
#include "ena-beacons.h" #include "ena-beacons.h"
#include "pb_decode.h"
#include "TemporaryExposureKeyExport.pb.h"
#include "ena-exposure.h" #include "ena-exposure.h"
static ena_exposure_config_t DEFAULT_ENA_EXPOSURE_CONFIG = { static ena_exposure_config_t DEFAULT_ENA_EXPOSURE_CONFIG = {
@ -73,52 +77,8 @@ static ena_exposure_config_t DEFAULT_ENA_EXPOSURE_CONFIG = {
static const char kFileHeader[] = "EK Export v1 "; static const char kFileHeader[] = "EK Export v1 ";
static size_t kFileHeaderSize = sizeof(kFileHeader) - 1; static size_t kFileHeaderSize = sizeof(kFileHeader) - 1;
extern const uint8_t export_bin_start[] asm("_binary_export_bin_start"); extern uint8_t export_bin_start[] asm("_binary_export_bin_start"); // test data from Google or https://svc90.main.px.t-online.de/version/v1/diagnosis-keys/country/DE/date/2020-07-22
extern const uint8_t export_bin_end[] asm("_binary_export_bin_end"); extern uint8_t export_bin_end[] asm("_binary_export_bin_end");
void ena_exposure_keyfiletest(void)
{
ESP_LOG_BUFFER_HEXDUMP(ENA_EXPOSURE_LOG, export_bin_start, (export_bin_end - export_bin_start), ESP_LOG_INFO);
}
void ena_exposure_check(ena_tek_reported_t tek_reported)
{
bool match = false;
ena_beacon_t beacon;
ena_exposure_information_t exposure_info;
exposure_info.duration_minutes = 0;
exposure_info.min_attenuation = INT_MAX;
exposure_info.typical_attenuation = 0;
exposure_info.report_type = tek_reported.report_type;
uint8_t rpi[ENA_KEY_LENGTH];
uint8_t rpik[ENA_KEY_LENGTH];
ena_crypto_rpik(rpik, tek_reported.key_data);
uint32_t beacons_count = ena_storage_beacons_count();
for (int i = 0; i < tek_reported.rolling_period; i++)
{
ena_crypto_rpi(rpi, rpik, tek_reported.rolling_start_interval_number + i);
for (int y = 0; y < beacons_count; y++)
{
ena_storage_get_beacon(y, &beacon);
if (memcmp(beacon.rpi, rpi, sizeof(ENA_KEY_LENGTH)) == 0)
{
match = true;
exposure_info.day = tek_reported.rolling_start_interval_number * ENA_TIME_WINDOW;
exposure_info.duration_minutes += (ENA_BEACON_TRESHOLD / 60);
exposure_info.typical_attenuation = (exposure_info.typical_attenuation + beacon.rssi) / 2;
if (beacon.rssi < exposure_info.min_attenuation)
{
exposure_info.min_attenuation = beacon.rssi;
}
}
}
}
if (match)
{
ena_storage_add_exposure_information(&exposure_info);
}
}
int ena_exposure_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params) int ena_exposure_risk_score(ena_exposure_config_t *config, ena_exposure_parameter_t params)
{ {
@ -238,6 +198,9 @@ int ena_exposure_risk_score(ena_exposure_config_t *config, ena_exposure_paramete
void ena_exposure_summary(ena_exposure_config_t *config, ena_exposure_summary_t *summary) void ena_exposure_summary(ena_exposure_config_t *config, ena_exposure_summary_t *summary)
{ {
// XXX TEST key export (should be called on other location though)
ESP_ERROR_CHECK_WITHOUT_ABORT(ena_exposure_check_export(export_bin_start, (export_bin_end - export_bin_start)));
uint32_t count = ena_storage_exposure_information_count(); uint32_t count = ena_storage_exposure_information_count();
uint32_t current_time = (uint32_t)time(NULL); uint32_t current_time = (uint32_t)time(NULL);
@ -270,11 +233,109 @@ void ena_exposure_summary(ena_exposure_config_t *config, ena_exposure_summary_t
} }
summary->risk_score_sum += score; summary->risk_score_sum += score;
} }
ena_exposure_keyfiletest();
} }
ena_exposure_config_t *ena_exposure_default_config(void) ena_exposure_config_t *ena_exposure_default_config(void)
{ {
return &DEFAULT_ENA_EXPOSURE_CONFIG; return &DEFAULT_ENA_EXPOSURE_CONFIG;
} }
bool ena_exposure_decode_key_data(pb_istream_t *stream, const pb_field_t *field, void **arg)
{
uint8_t *key_data = (uint8_t *)*arg;
if (!pb_read(stream, key_data, stream->bytes_left))
{
ESP_LOGW(ENA_EXPOSURE_LOG, "Decoding failed: %s\n", PB_GET_ERROR(stream));
return false;
}
return true;
}
bool ena_exposure_decode_key(pb_istream_t *stream, const pb_field_t *field, void **arg)
{
uint8_t key_data[ENA_KEY_LENGTH] = {0};
TemporaryExposureKey tek = TemporaryExposureKey_init_zero;
tek.key_data = (pb_callback_t){
.funcs.decode = ena_exposure_decode_key_data,
.arg = &key_data,
};
if (!pb_decode(stream, TemporaryExposureKey_fields, &tek))
{
ESP_LOGW(ENA_EXPOSURE_LOG, "Decoding failed: %s\n", PB_GET_ERROR(stream));
return false;
}
ESP_LOGD(ENA_EXPOSURE_LOG,
"check reported tek: rolling_start_interval_number %d, rolling_period %d, days_since_last_exposure %d, report_type %d, transmission_risk_values %d",
tek.rolling_start_interval_number, tek.rolling_period, tek.days_since_onset_of_symptoms, tek.report_type, tek.transmission_risk_level);
ESP_LOG_BUFFER_HEXDUMP(ENA_EXPOSURE_LOG, &key_data, ENA_KEY_LENGTH, ESP_LOG_DEBUG);
bool match = false;
ena_beacon_t beacon;
ena_exposure_information_t exposure_info;
exposure_info.duration_minutes = 0;
exposure_info.min_attenuation = INT_MAX;
exposure_info.typical_attenuation = 0;
exposure_info.report_type = tek.report_type;
uint8_t rpi[ENA_KEY_LENGTH];
uint8_t rpik[ENA_KEY_LENGTH];
ena_crypto_rpik(rpik, key_data);
uint32_t beacons_count = ena_storage_beacons_count();
for (int i = 0; i < tek.rolling_period; i++)
{
ena_crypto_rpi(rpi, rpik, tek.rolling_start_interval_number + i);
for (int y = 0; y < beacons_count; y++)
{
ena_storage_get_beacon(y, &beacon);
if (memcmp(beacon.rpi, rpi, sizeof(ENA_KEY_LENGTH)) == 0)
{
match = true;
exposure_info.day = tek.rolling_start_interval_number * ENA_TIME_WINDOW;
exposure_info.duration_minutes += (ENA_BEACON_TRESHOLD / 60);
exposure_info.typical_attenuation = (exposure_info.typical_attenuation + beacon.rssi) / 2;
if (beacon.rssi < exposure_info.min_attenuation)
{
exposure_info.min_attenuation = beacon.rssi;
}
}
}
}
if (match)
{
ena_storage_add_exposure_information(&exposure_info);
}
return true;
}
esp_err_t ena_exposure_check_export(uint8_t *buf, size_t size)
{
// validate header
if (memcmp(kFileHeader, buf, kFileHeaderSize) != 0)
{
ESP_LOGW(ENA_EXPOSURE_LOG, "Wrong or missing header!");
return ESP_FAIL;
}
TemporaryExposureKeyExport tek_export = TemporaryExposureKeyExport_init_zero;
tek_export.keys = (pb_callback_t){
.funcs.decode = ena_exposure_decode_key,
};
pb_istream_t stream = pb_istream_from_buffer(&buf[kFileHeaderSize], (size - kFileHeaderSize));
if (!pb_decode(&stream, TemporaryExposureKeyExport_fields, &tek_export))
{
ESP_LOGW(ENA_EXPOSURE_LOG, "Decoding failed: %s\n", PB_GET_ERROR(&stream));
return ESP_FAIL;
}
return ESP_OK;
}

View File

@ -14,10 +14,6 @@
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_bt.h" #include "esp_bt.h"
@ -47,48 +43,42 @@ void ena_next_rpi_timestamp(uint32_t timestamp)
ESP_LOGD(ENA_LOG, "next rpi at %u (%u from %u)", next_rpi_timestamp, (ENA_BT_ROTATION_TIMEOUT_INTERVAL + random_interval), timestamp); ESP_LOGD(ENA_LOG, "next rpi at %u (%u from %u)", next_rpi_timestamp, (ENA_BT_ROTATION_TIMEOUT_INTERVAL + random_interval), timestamp);
} }
void ena_run(void *pvParameter) void ena_run(void)
{ {
static uint32_t unix_timestamp = 0; static uint32_t unix_timestamp = 0;
static uint32_t current_enin = 0; static uint32_t current_enin = 0;
while (1) unix_timestamp = (uint32_t)time(NULL);
current_enin = ena_crypto_enin(unix_timestamp);
if (current_enin - last_tek.enin >= last_tek.rolling_period)
{ {
unix_timestamp = (uint32_t)time(NULL); ena_crypto_tek(last_tek.key_data);
current_enin = ena_crypto_enin(unix_timestamp); last_tek.enin = current_enin;
if (current_enin - last_tek.enin >= last_tek.rolling_period) // validity only to next day 00:00
{ last_tek.rolling_period = ENA_TEK_ROLLING_PERIOD - (last_tek.enin % ENA_TEK_ROLLING_PERIOD);
ena_crypto_tek(last_tek.key_data); ena_storage_write_tek(&last_tek);
last_tek.enin = current_enin; }
// validity only to next day 00:00
last_tek.rolling_period = ENA_TEK_ROLLING_PERIOD - (last_tek.enin % ENA_TEK_ROLLING_PERIOD);
ena_storage_write_tek(&last_tek);
}
// change RPI // change RPI
if (unix_timestamp >= next_rpi_timestamp) if (unix_timestamp >= next_rpi_timestamp)
{
if (ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_SCANNING)
{ {
if (ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_SCANNING) ena_bluetooth_scan_stop();
{
ena_bluetooth_scan_stop();
}
ena_bluetooth_advertise_stop();
ena_bluetooth_advertise_set_payload(current_enin, last_tek.key_data);
ena_bluetooth_advertise_start();
if (ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_WAITING)
{
ena_bluetooth_scan_start(ENA_SCANNING_TIME);
}
ena_next_rpi_timestamp(unix_timestamp);
} }
ena_bluetooth_advertise_stop();
// scan ena_bluetooth_advertise_set_payload(current_enin, last_tek.key_data);
if (unix_timestamp % ENA_SCANNING_INTERVAL == 0 && ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_NOT_SCANNING) ena_bluetooth_advertise_start();
if (ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_WAITING)
{ {
ena_bluetooth_scan_start(ENA_SCANNING_TIME); ena_bluetooth_scan_start(ENA_SCANNING_TIME);
} }
ena_next_rpi_timestamp(unix_timestamp);
}
// one second loop correct?! // scan
vTaskDelay(1000 / portTICK_PERIOD_MS); if (unix_timestamp % ENA_SCANNING_INTERVAL == 0 && ena_bluetooth_scan_get_status() == ENA_SCAN_STATUS_NOT_SCANNING)
{
ena_bluetooth_scan_start(ENA_SCANNING_TIME);
} }
} }
@ -173,5 +163,15 @@ void ena_start(void)
ena_bluetooth_scan_start(ENA_SCANNING_TIME); ena_bluetooth_scan_start(ENA_SCANNING_TIME);
// what is a good stack size here? // what is a good stack size here?
xTaskCreate(&ena_run, "ena_run", ENA_RAM, NULL, 5, NULL); // xTaskCreate(&ena_run, "ena_run", ENA_RAM, NULL, 5, NULL);
}
void ena_stop(void)
{
ena_bluetooth_advertise_stop();
ena_bluetooth_scan_stop();
esp_bluedroid_disable();
esp_bluedroid_deinit();
esp_bt_controller_disable();
esp_bt_controller_deinit();
} }

View File

@ -15,6 +15,7 @@
#define _ena_EXPOSURE_H_ #define _ena_EXPOSURE_H_
#include <stdio.h> #include <stdio.h>
#include "ena-crypto.h" #include "ena-crypto.h"
#define ENA_EXPOSURE_LOG "ESP-ENA-exposure" // TAG for Logging #define ENA_EXPOSURE_LOG "ESP-ENA-exposure" // TAG for Logging
@ -134,25 +135,6 @@ typedef struct __attribute__((__packed__))
int risk_score_sum; // sum of all risk_scores int risk_score_sum; // sum of all risk_scores
} ena_exposure_summary_t; } ena_exposure_summary_t;
/**
* @brief structure for a reported TEK
*/
typedef struct __attribute__((__packed__))
{
uint8_t key_data[ENA_KEY_LENGTH]; // Key of infected user
uint32_t rolling_start_interval_number; // The interval number since epoch for which a key starts
uint8_t rolling_period; // Increments of 10 minutes describing how long a key is valid
ena_report_type_t report_type; // Type of diagnosis associated with a key.
uint32_t days_since_onset_of_symptoms; // Number of days elapsed between symptom onset and the TEK being used. E.g. 2 means TEK is 2 days after onset of symptoms.
} ena_tek_reported_t;
/**
* @brief check for exposure for a reported tek and store exposure information on finding
*
* @param[in] tek_reported the reported tek to check
*/
void ena_exposure_check(ena_tek_reported_t tek_reported);
/** /**
* @brief calculate risk score * @brief calculate risk score
* *
@ -174,4 +156,15 @@ void ena_exposure_summary(ena_exposure_config_t *config, ena_exposure_summary_t
*/ */
ena_exposure_config_t *ena_exposure_default_config(void); ena_exposure_config_t *ena_exposure_default_config(void);
/**
* @brief reads a Temporary Exposue Key Export binary and check for exposures
*
* @param[in] buf the buffer containing the binary data
* @param[in] size the size of the buffer
*
* @return
* esp_err_t status of reading binary
*/
esp_err_t ena_exposure_check_export(uint8_t *buf, size_t size);
#endif #endif

View File

@ -15,10 +15,17 @@
#define _ena_H_ #define _ena_H_
#define ENA_LOG "ESP-ENA" // TAG for Logging #define ENA_LOG "ESP-ENA" // TAG for Logging
#define ENA_RAM (CONFIG_ENA_RAM) // change advertising payload and therefore the BT address
#define ENA_BT_ROTATION_TIMEOUT_INTERVAL (CONFIG_ENA_BT_ROTATION_TIMEOUT_INTERVAL) // change advertising payload and therefore the BT address #define ENA_BT_ROTATION_TIMEOUT_INTERVAL (CONFIG_ENA_BT_ROTATION_TIMEOUT_INTERVAL) // change advertising payload and therefore the BT address
#define ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL (CONFIG_ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL) // random intervall change for BT address change #define ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL (CONFIG_ENA_BT_RANDOMIZE_ROTATION_TIMEOUT_INTERVAL) // random intervall change for BT address change
/**
* @brief Run Exposure Notification API
*
* This runs the complete BLE logic
*
*/
void ena_run(void);
/** /**
* @brief Start Exposure Notification API * @brief Start Exposure Notification API
* *
@ -28,4 +35,9 @@
*/ */
void ena_start(void); void ena_start(void);
/**
* @brief stop ena
*/
void ena_stop(void);
#endif #endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
idf_component_register(
SRCS
"miniz.c"
INCLUDE_DIRS "."
)

22
components/miniz/LICENSE Normal file
View File

@ -0,0 +1,22 @@
Copyright 2013-2014 RAD Game Tools and Valve Software
Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

7657
components/miniz/miniz.c Normal file

File diff suppressed because it is too large Load Diff

1338
components/miniz/miniz.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
Copyright (c) 2011 Petteri Aimonen <jpa at nanopb.mail.kapsi.fi>
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held liable
for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

View File

@ -91,7 +91,6 @@ void ssd1306_clear_line(uint8_t i2address, uint8_t line, bool invert);
*/ */
void ssd1306_clear(uint8_t i2address); void ssd1306_clear(uint8_t i2address);
/** /**
* @brief set display on or offf * @brief set display on or offf
* *
@ -100,6 +99,17 @@ void ssd1306_clear(uint8_t i2address);
*/ */
void ssd1306_on(uint8_t i2address, bool on); void ssd1306_on(uint8_t i2address, bool on);
/**
* @brief write raw bytes to display line at starting column
*
* @param[in] i2address I2C address of SSD1306
* @param[in] data bytes to display
* @param[in] length length of data
* @param[in] line the line to write to
* @param[in] offset number of offset chars to start
*/
void ssd1306_data(uint8_t i2address, uint8_t *data, size_t length, uint8_t line, uint8_t offset, bool invert);
/** /**
* @brief write text to display line at starting column * @brief write text to display line at starting column
* *

View File

@ -1,5 +1,7 @@
idf_component_register( idf_component_register(
SRCS SRCS
"main.c" "main.c"
"display-interface.c"
"wifi.c"
INCLUDE_DIRS "" INCLUDE_DIRS ""
) )

20
main/Kconfig.projbuild Normal file
View File

@ -0,0 +1,20 @@
menu "Wifi Setup"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config WIFI_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
endmenu

153
main/display-interface.c Normal file
View File

@ -0,0 +1,153 @@
// Copyright 2020 Lukas Haubaum
//
// Licensed under the GNU Affero General Public License, Version 3;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.gnu.org/licenses/agpl-3.0.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "ds3231.h"
#include "ena-exposure.h"
#include "ena-interface.h"
#include "ena-interface-menu.h"
#include "ena-interface-datetime.h"
#include "ssd1306.h"
void interface_display_time(void *pvParameter)
{
static time_t curtime;
static char *curtime_text;
static struct tm rtc_time;
static bool edit_invert = false;
while (1)
{
curtime = time(NULL);
localtime_r(&curtime, &rtc_time);
curtime_text = asctime(&rtc_time);
ssd1306_text_line(SSD1306_ADDRESS, curtime_text, 0, false);
gmtime_r(&curtime, &rtc_time);
curtime_text = asctime(&rtc_time);
ssd1306_text_line(SSD1306_ADDRESS, curtime_text, 1, false);
if (ena_interface_get_state() == ENA_INTERFACE_STATE_SET_DATETIME)
{
edit_invert = !edit_invert;
ds3231_set_time(&rtc_time);
char edit_year[4] = "";
char edit_month[3] = "";
char edit_day[2] = "";
char edit_hour[2] = "";
char edit_minute[2] = "";
char edit_second[2] = "";
switch (ena_interface_datetime_state())
{
case ENA_INTERFACE_DATETIME_STATE_YEAR:
memcpy(&edit_year, &curtime_text[20], 4);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_year, 0, 20, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_MONTH:
memcpy(&edit_month, &curtime_text[4], 3);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_month, 0, 4, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_DAY:
memcpy(&edit_day, &curtime_text[8], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_day, 0, 8, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_HOUR:
memcpy(&edit_hour, &curtime_text[11], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_hour, 0, 11, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_MINUTE:
memcpy(&edit_minute, &curtime_text[14], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_minute, 0, 14, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_SECONDS:
memcpy(&edit_second[0], &curtime_text[17], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_second, 0, 17, edit_invert);
break;
}
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void interface_display_status(void *pvParameter)
{
static bool get_status = true;
while (1)
{
if (ena_interface_get_state() == ENA_INTERFACE_STATE_STATUS)
{
if (get_status)
{
ena_exposure_summary_t summary;
ena_exposure_summary(ena_exposure_default_config(), &summary);
char buffer[23];
sprintf(buffer, "Days: %d", summary.days_since_last_exposure);
ssd1306_text_line(SSD1306_ADDRESS, buffer, 3, false);
sprintf(buffer, "Exposures: %d", summary.num_exposures);
ssd1306_text_line(SSD1306_ADDRESS, buffer, 4, false);
sprintf(buffer, "Score: %d, Max: %d", summary.risk_score_sum, summary.max_risk_score);
ssd1306_text_line(SSD1306_ADDRESS, buffer, 5, false);
get_status = false;
}
}
else if (!get_status)
{
ssd1306_clear_line(SSD1306_ADDRESS, 3, false);
ssd1306_clear_line(SSD1306_ADDRESS, 4, false);
ssd1306_clear_line(SSD1306_ADDRESS, 5, false);
get_status = true;
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void interface_display_idle(void *pvParameter)
{
static bool set_status = true;
while (1)
{
if (ena_interface_get_state() == ENA_INTERFACE_STATE_IDLE)
{
if (set_status)
{
ssd1306_on(SSD1306_ADDRESS, false);
set_status = false;
}
}
else if (!set_status)
{
ssd1306_on(SSD1306_ADDRESS, true);
set_status = true;
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void display_interface_start(void)
{
ssd1306_start(SSD1306_ADDRESS);
ssd1306_clear(SSD1306_ADDRESS);
ena_interface_start();
ena_interface_menu_start();
xTaskCreate(&interface_display_time, "interface_display_time", 4096, NULL, 5, NULL);
xTaskCreate(&interface_display_status, "interface_display_status", 4096, NULL, 5, NULL);
xTaskCreate(&interface_display_idle, "interface_display_idle", 4096, NULL, 5, NULL);
}

22
main/display-interface.h Normal file
View File

@ -0,0 +1,22 @@
// Copyright 2020 Lukas Haubaum
//
// Licensed under the GNU Affero General Public License, Version 3;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.gnu.org/licenses/agpl-3.0.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _display_interface_H_
#define _display_interface_H_
/**
* @brief start display + interface
*/
void display_interface_start(void);
#endif

View File

@ -11,7 +11,6 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <stdio.h>
#include <string.h> #include <string.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
@ -24,149 +23,47 @@
#include "ena-storage.h" #include "ena-storage.h"
#include "ena-beacons.h" #include "ena-beacons.h"
#include "ena-exposure.h" #include "ena-exposure.h"
#include "ena-bluetooth-advertise.h"
#include "ena-bluetooth-scan.h"
#include "ena-interface.h" #include "ena-interface.h"
#include "ena-interface-menu.h" #include "ena-cwa.h"
#include "ena-interface-datetime.h"
#include "ssd1306.h"
#include "ds3231.h" #include "ds3231.h"
#include "wifi.h"
#include "sdkconfig.h" #include "sdkconfig.h"
static time_t curtime;
void interface_display_time(void *pvParameter)
{
static char *curtime_text;
static struct tm rtc_time;
static bool edit_invert = false;
while (1)
{
curtime = time(NULL);
localtime_r(&curtime, &rtc_time);
curtime_text = asctime(&rtc_time);
ssd1306_text_line(SSD1306_ADDRESS, curtime_text, 0, false);
gmtime_r(&curtime, &rtc_time);
curtime_text = asctime(&rtc_time);
ssd1306_text_line(SSD1306_ADDRESS, curtime_text, 1, false);
if (ena_interface_get_state() == ENA_INTERFACE_STATE_SET_DATETIME)
{
edit_invert = !edit_invert;
ds3231_set_time(&rtc_time);
char edit_year[4] = "";
char edit_month[3] = "";
char edit_day[2] = "";
char edit_hour[2] = "";
char edit_minute[2] = "";
char edit_second[2] = "";
switch (ena_interface_datetime_state())
{
case ENA_INTERFACE_DATETIME_STATE_YEAR:
memcpy(&edit_year, &curtime_text[20], 4);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_year, 0, 20, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_MONTH:
memcpy(&edit_month, &curtime_text[4], 3);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_month, 0, 4, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_DAY:
memcpy(&edit_day, &curtime_text[8], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_day, 0, 8, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_HOUR:
memcpy(&edit_hour, &curtime_text[11], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_hour, 0, 11, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_MINUTE:
memcpy(&edit_minute, &curtime_text[14], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_minute, 0, 14, edit_invert);
break;
case ENA_INTERFACE_DATETIME_STATE_SECONDS:
memcpy(&edit_second[0], &curtime_text[17], 2);
ssd1306_text_line_column(SSD1306_ADDRESS, edit_second, 0, 17, edit_invert);
break;
}
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void interface_display_status(void *pvParameter)
{
static bool get_status = true;
while (1)
{
if (ena_interface_get_state() == ENA_INTERFACE_STATE_STATUS)
{
if (get_status)
{
ena_exposure_summary_t summary;
ena_exposure_summary(ena_exposure_default_config(), &summary);
char buffer[23];
sprintf(buffer, "Days: %d", summary.days_since_last_exposure);
ssd1306_text_line(SSD1306_ADDRESS, buffer, 3, false);
sprintf(buffer, "Exposures: %d", summary.num_exposures);
ssd1306_text_line(SSD1306_ADDRESS, buffer, 4, false);
sprintf(buffer, "Score: %d, Max: %d", summary.risk_score_sum, summary.max_risk_score);
ssd1306_text_line(SSD1306_ADDRESS, buffer, 5, false);
get_status = false;
}
}
else if (!get_status)
{
ssd1306_clear_line(SSD1306_ADDRESS, 2, false);
ssd1306_clear_line(SSD1306_ADDRESS, 3, false);
ssd1306_clear_line(SSD1306_ADDRESS, 4, false);
get_status = true;
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void interface_display_idle(void *pvParameter)
{
static bool set_status = true;
while (1)
{
if (ena_interface_get_state() == ENA_INTERFACE_STATE_IDLE)
{
if (set_status)
{
ssd1306_on(SSD1306_ADDRESS, false);
set_status = false;
}
}
else if (!set_status)
{
ssd1306_on(SSD1306_ADDRESS, true);
set_status = true;
}
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void app_main(void) void app_main(void)
{ {
// debug only own LOG TAGs
esp_log_level_set("*", ESP_LOG_WARN);
esp_log_level_set(ENA_LOG, ESP_LOG_DEBUG);
esp_log_level_set(ENA_BEACON_LOG, ESP_LOG_DEBUG);
esp_log_level_set(ENA_ADVERTISE_LOG, ESP_LOG_DEBUG);
esp_log_level_set(ENA_SCAN_LOG, ESP_LOG_DEBUG);
esp_log_level_set(ENA_EXPOSURE_LOG, ESP_LOG_DEBUG);
esp_log_level_set(ENA_STORAGE_LOG, ESP_LOG_INFO);
esp_log_level_set(ENA_CWA_LOG, ESP_LOG_DEBUG);
esp_log_level_set(ENA_INTERFACE_LOG, ESP_LOG_DEBUG);
esp_log_level_set(WIFI_LOG, ESP_LOG_DEBUG);
// set system time from DS3231
struct tm rtc_time; struct tm rtc_time;
ds3231_get_time(&rtc_time); ds3231_get_time(&rtc_time);
curtime = mktime(&rtc_time);
time_t curtime = mktime(&rtc_time);
struct timeval tv = {0}; struct timeval tv = {0};
tv.tv_sec = curtime; tv.tv_sec = curtime;
settimeofday(&tv, NULL); settimeofday(&tv, NULL);
esp_log_level_set(ENA_STORAGE_LOG, ESP_LOG_INFO);
// Hardcoded timezome of UTC+2 for now (consider POSIX notation!)
setenv("TZ", "UTC-2", 1); setenv("TZ", "UTC-2", 1);
tzset(); tzset();
ssd1306_start(SSD1306_ADDRESS);
ssd1306_clear(SSD1306_ADDRESS);
ena_interface_start();
ena_interface_menu_start();
ena_start(); ena_start();
xTaskCreate(&interface_display_time, "interface_display_time", 4096, NULL, 5, NULL); while (1)
xTaskCreate(&interface_display_status, "interface_display_status", 4096, NULL, 5, NULL); {
xTaskCreate(&interface_display_idle, "interface_display_idle", 4096, NULL, 5, NULL); ena_run();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
} }

172
main/wifi.c Normal file
View File

@ -0,0 +1,172 @@
// Copyright 2020 Lukas Haubaum
//
// Licensed under the GNU Affero General Public License, Version 3;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.gnu.org/licenses/agpl-3.0.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "ena-cwa.h"
#include "wifi.h"
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
static bool connected = false;
void wifi_stop(void)
{
connected = false;
esp_wifi_stop();
esp_wifi_deinit();
esp_event_loop_delete_default();
}
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
if (s_retry_num < WIFI_MAXIMUM_RETRY)
{
esp_wifi_connect();
s_retry_num++;
ESP_LOGD(WIFI_LOG, "retry to connect to the AP");
}
else
{
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGD(WIFI_LOG, "connect to the AP fail");
connected = false;
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
connected = true;
heap_caps_check_integrity_all(true);
}
else
{
ESP_LOGD(WIFI_LOG, "eventbase %s, eventid %d", event_base, event_id);
}
}
void wifi_start(void)
{
// init NVS for WIFI
esp_err_t ret;
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
/* Setting a password implies station will connect to all security modes including WEP/WPA.
* However these modes are deprecated and not advisable to be used. Incase your Access point
* doesn't support WPA2, these mode can be enabled by commenting below line */
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.capable = true,
.required = false},
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT)
{
ESP_LOGV(WIFI_LOG, "connected to ap SSID:%s",
WIFI_SSID);
}
else if (bits & WIFI_FAIL_BIT)
{
ESP_LOGI(WIFI_LOG, "Failed to connect to SSID:%s",
WIFI_SSID);
}
else
{
ESP_LOGE(WIFI_LOG, "UNEXPECTED EVENT");
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
}
bool wifi_is_connected(void)
{
return connected;
}

38
main/wifi.h Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2020 Lukas Haubaum
//
// Licensed under the GNU Affero General Public License, Version 3;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// https://www.gnu.org/licenses/agpl-3.0.html
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _wifi_H_
#define _wifi_H_
#define WIFI_LOG "ESP-ENA-wifi" // TAG for Logging
#define WIFI_SSID (CONFIG_WIFI_SSID)
#define WIFI_PASSWORD (CONFIG_WIFI_PASSWORD)
#define WIFI_MAXIMUM_RETRY (CONFIG_WIFI_MAXIMUM_RETRY)
/**
* @brief start wifi connection to configured AP
*/
void wifi_start(void);
/**
* @brief stop wifi (restart does not work for now!)
*/
void wifi_stop(void);
/**
* @brief check if a wifi is connected
*/
bool wifi_is_connected(void);
#endif

View File

@ -2,5 +2,5 @@
# Name, Type, SubType, Offset, Size, Flags # Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000, nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000, phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M, factory, app, factory, 0x10000, 0x177000,
ena, data, 0xFF, 0x110000,0x2C0000, ena, data, 0xFF, 0x187000,0x261000,
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M, factory, app, factory, 0x10000, 0x177000,
6 ena, data, 0xFF, 0x110000,0x2C0000, ena, data, 0xFF, 0x187000,0x261000,