Merge branch 'development' of https://github.com/cyberman54/ESP32-Paxcounter into development

This commit is contained in:
Marius Gripp 2019-10-09 15:57:59 +02:00
commit d8289a002d
40 changed files with 1020 additions and 874 deletions

View File

@ -150,7 +150,13 @@ Paxcounter generates identifiers for sniffed MAC adresses and collects them temp
# Display
If you're using a device with OLED display, or if you add such one to the I2C bus, the device shows live data on the display. You can flip between pages showing pax, time, GPS and BME sensor data by pressing the button of the device.
If you're using a device with OLED display, or if you add such one to the I2C bus, the device shows live data on the display. You can flip display pages showing
- recent count of pax
- histogram
- GPS data
- BME sensor data
- Time of day
by pressing the button of the device.
# Sensors and Peripherals

View File

@ -5,6 +5,7 @@
#include "senddata.h"
#include "rcommand.h"
#include "spislave.h"
#if(HAS_LORA)
#include <lmic.h>
#endif
@ -13,6 +14,10 @@
#include "bmesensor.h"
#endif
#ifdef HAS_DISPLAY
#include "display.h"
#endif
extern Ticker housekeeper;
void housekeeping(void);

View File

@ -1,16 +1,26 @@
#ifndef _DISPLAY_H
#define _DISPLAY_H
#include <U8x8lib.h>
#include "cyclic.h"
#include "qrcode.h"
extern uint8_t DisplayIsOn;
extern HAS_DISPLAY u8x8;
void init_display(const char *Productname, const char *Version);
void refreshTheDisplay(bool nextPage = false);
void init_display(uint8_t verbose = 0);
void draw_page(time_t t, uint8_t page);
void DisplayKey(const uint8_t *key, uint8_t len, bool lsb);
void dp_printf(uint16_t x, uint16_t y, uint8_t font, uint8_t inv,
const char *format, ...);
void dp_printqr(uint16_t offset_x, uint16_t offset_y, const char *Message);
void oledfillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
uint8_t bRender);
void oledScrollBufferHorizontal(uint8_t *buf, const uint16_t width,
const uint16_t height, bool left = true);
void oledScrollBufferVertical(uint8_t *buf, const uint16_t width,
const uint16_t height, int offset = 0);
int oledDrawPixel(uint8_t *buf, const uint16_t x, const uint16_t y,
const uint8_t dot);
void oledPlotCurve(uint16_t count, bool reset);
void oledRescaleBuffer(uint8_t *buf, const int factor);
#endif

View File

@ -13,6 +13,6 @@ void refreshTheMatrixDisplay(bool nextPage = false);
void DrawNumber(String strNum, uint8_t iDotPos = 0);
uint8_t GetCharFromFont(char cChar);
uint8_t GetCharWidth(char cChar);
void ScrollLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows);
void ScrollMatrixLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows);
#endif

View File

@ -4,6 +4,7 @@
#ifdef USE_OTA
#include "globals.h"
#include <ss_oled.h>
#include <Update.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
@ -14,11 +15,9 @@
int do_ota_update();
void start_ota_update();
int version_compare(const String v1, const String v2);
void display(const uint8_t row, const std::string status,
void ota_display(const uint8_t row, const std::string status,
const std::string msg);
#ifdef HAS_DISPLAY
void show_progress(unsigned long current, unsigned long size);
#endif
#endif // USE_OTA

View File

@ -19,6 +19,8 @@ void power_event_IRQ(void);
void AXP192_power(bool on);
void AXP192_init(void);
void AXP192_showstatus(void);
uint8_t i2c_writeBytes(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len);
uint8_t i2c_readBytes(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len);
#endif // HAS_PMU
#endif

View File

@ -2,10 +2,15 @@
#define _SENDDATA_H
#include "spislave.h"
#include "cyclic.h"
#if(HAS_LORA)
#include "lorawan.h"
#endif
#include "cyclic.h"
#ifdef HAS_DISPLAY
#include "display.h"
#endif
extern Ticker sendcycler;

View File

@ -7,12 +7,15 @@
#include "timekeeper.h"
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length [bytes]
#define TIME_SYNC_FRAME_LENGTH 0x07 // timeserver answer frame length [bytes]
#define TIME_SYNC_FIXUP 4 // calibration to fixup processing time [milliseconds]
#define TIMEREQUEST_MAX_SEQNO 0xf0 // threshold for wrap around seqno
void timesync_init(void);
void send_timesync_req(void);
int recv_timesync_ans(const uint8_t buf[], uint8_t buf_len);
void process_timesync_req(void *taskparameter);
void store_time_sync_req(uint32_t t_millisec);

View File

@ -34,16 +34,16 @@ 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 = usb
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.8.34
release_version = 1.9.30
; 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
@ -51,13 +51,15 @@ extra_scripts = pre:build.py
otakeyfile = ota.conf
lorakeyfile = loraconf.h
lmicconfigfile = lmic_config.h
platform_espressif32 = espressif32@1.9.0
platform_espressif32 = espressif32@1.11.0
monitor_speed = 115200
upload_speed = 115200
lib_deps_lora =
MCCI LoRaWAN LMIC library@>=3.0.99
lib_deps_display =
U8g2@>=2.26.13
ss_oled@>=3.0.0
BitBang_I2C@>=1.2.0
QRCode@>=0.0.1
lib_deps_matrix_display =
https://github.com/Seeed-Studio/Ultrathin_LED_Matrix.git
lib_deps_rgbled =
@ -66,14 +68,13 @@ lib_deps_gps =
1655@>=1.0.2 ;TinyGPSPlus by Mikal Hart
lib_deps_sensors =
Adafruit Unified Sensor@>=1.0.3
Adafruit BME280 Library@>=1.0.9
Adafruit BME280 Library@>=1.0.10
lib_deps_basic =
ArduinoJson@^5.13.1
76@>=1.2.2 ;Timezone by Jack Christensen
274@>=2.3.3 ;RTC by Michael Miller
SimpleButton
;AXP202X_Library@^1.0.1
https://github.com/lewisxhe/AXP202X_Library.git#8045ddf
AXP202X_Library@>=1.0.1
lib_deps_all =
${common.lib_deps_basic}
${common.lib_deps_lora}

View File

@ -1,17 +1,357 @@
[
{
"id":"23a9162c.cb1e6a",
"type":"tab",
"label":"Flow 1",
"disabled":false,
"info":""
"id": "9b4f492d.fbfd18",
"type": "change",
"z": "449c1517.e25f4c",
"name": "Payload",
"rules": [
{
"t": "change",
"p": "topic",
"pt": "msg",
"from": "up",
"fromt": "str",
"to": "down",
"tot": "str"
},
"page-titlebar-backgroundColor":{
"value":"#0094CE",
"edited":false
},
"page-backgroundColor":{
"value":"#fafafa",
"edited":false
},
"page-sidebar-backgroundColor":{
"value":"#ffffff",
"edited":false
},
{
"t": "move",
"p": "port",
"pt": "msg",
"to": "payload.port",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 220,
"y": 520,
"wires": [
[
"53a85e2c.2728d"
]
]
},
{
"id": "9c105726.613a58",
"type": "mqtt in",
"z": "449c1517.e25f4c",
"name": "listen",
"topic": "+/devices/+/up",
"qos": "2",
"broker": "2a15ab6f.ab2244",
"x": 90,
"y": 127,
"wires": [
[
"113ef524.57edeb"
]
]
},
{
"id": "1c9a7438.6e38ec",
"type": "mqtt out",
"z": "449c1517.e25f4c",
"name": "send",
"topic": "",
"qos": "",
"retain": "",
"broker": "2a15ab6f.ab2244",
"x": 710,
"y": 520,
"wires": []
},
{
"id": "113ef524.57edeb",
"type": "json",
"z": "449c1517.e25f4c",
"name": "Convert",
"property": "payload",
"action": "",
"pretty": false,
"x": 240,
"y": 127,
"wires": [
[
"120561a.088359e"
]
]
},
{
"id": "120561a.088359e",
"type": "switch",
"z": "449c1517.e25f4c",
"name": "Timeport",
"property": "payload.port",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "9",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 400,
"y": 127,
"wires": [
[
"d6f27e8e.93242"
]
]
},
{
"id": "90e76b02.6298f8",
"type": "json",
"z": "449c1517.e25f4c",
"name": "Convert",
"property": "payload",
"action": "",
"pretty": false,
"x": 560,
"y": 520,
"wires": [
[
"1c9a7438.6e38ec"
]
]
},
{
"id": "d6f27e8e.93242",
"type": "base64",
"z": "449c1517.e25f4c",
"name": "Decode",
"action": "",
"property": "payload.payload_raw",
"x": 560,
"y": 127,
"wires": [
[
"b8bd33fd.61caa",
"cc245719.3c4cd8"
]
]
},
{
"id": "53a85e2c.2728d",
"type": "base64",
"z": "449c1517.e25f4c",
"name": "Encode",
"action": "",
"property": "payload.payload_raw",
"x": 400,
"y": 520,
"wires": [
[
"90e76b02.6298f8"
]
]
},
{
"id": "15980d22.6f4663",
"type": "comment",
"z": "449c1517.e25f4c",
"name": "LoRaWAN Timeserver v1.21",
"info": "PLEASE NOTE: There is a patent filed for the time sync algorithm used in the\ncode of this file. The shown implementation example is covered by the\nrepository's licencse, but you may not be eligible to deploy the applied\nalgorithm in applications without granted license by the patent holder.",
"x": 160,
"y": 47,
"wires": []
},
{
"id": "b8bd33fd.61caa",
"type": "function",
"z": "449c1517.e25f4c",
"name": "Timeserver Logic",
"func": "/* LoRaWAN Timeserver\n\nconstruct 7 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1 sequence number (taken from node's time_sync_req)\n2 timezone in 15 minutes steps\n3..6 current second (from epoch time 1970)\n7 1/250ths fractions of current second\n\n*/\n\nfunction timecompare(a, b) {\n \n const timeA = a.time;\n const timeB = b.time;\n\n let comparison = 0;\n if (timeA > timeB) {\n comparison = 1;\n } else if (timeA < timeB) {\n comparison = -1;\n }\n return comparison;\n}\n\nlet confidence = 2000; // max millisecond diff gateway time to server time\n\n// guess if we have received a valid time_sync_req command\nif (msg.payload.payload_raw.length != 1)\n return;\n\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar seqNo = msg.payload.payload_raw[0];\nvar seqNoMsg = { payload: seqNo };\nvar gateway_list = msg.payload.metadata.gateways;\n\n// filter all gateway timestamps that have milliseconds part (which we assume have a \".\")\nvar gateways = gateway_list.filter(function (element) {\n return (element.time.includes(\".\"));\n});\n\nvar gateway_time = gateways.map(gw => {\n return {\n time: new Date(gw.time),\n eui: gw.gtw_id,\n }\n });\nvar server_time = new Date(msg.payload.metadata.time);\n\n// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)\nvar gw_timestamps = gateway_time.filter(function (element) {\n return ((element.time > (server_time - confidence) && element.time <= server_time));\n});\n\n// if no timestamp left, we have no valid one and exit\nif (gw_timestamps.length === 0) {\n var notavailMsg = { payload: \"n/a\" };\n var notimeMsg = { payload: 0xff }; \n var buf2 = Buffer.alloc(1);\n msg.payload = new Buffer(buf2.fill(0xff));\n msg.port = 9; // Paxcounter TIMEPORT\n return [notavailMsg, notavailMsg, deviceMsg, seqNoMsg, msg];}\n\n// sort time array in ascending order to find most recent timestamp for time answer\ngw_timestamps.sort(timecompare);\n\nvar timestamp = gw_timestamps[0].time;\nvar eui = gw_timestamps[0].eui;\nvar offset = server_time - timestamp;\n\nvar seconds = Math.floor(timestamp/1000);\nvar fractions = (timestamp % 1000) / 4;\n\nlet buf = new ArrayBuffer(7);\nnew DataView(buf).setUint8(0, seqNo);\n// Timezone (in 15min steps)\nvar timezone = 8; // CET = UTC+2h\nnew DataView(buf).setUint8(1, timezone);\nnew DataView(buf).setUint32(2, seconds);\nnew DataView(buf).setUint8(6, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = 9; // Paxcounter TIMEPORT\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];",
"outputs": 5,
"noerr": 0,
"x": 330,
"y": 327,
"wires": [
[
"c9a83ac9.50fd18",
"6aeb3720.a89618",
"6ac55bbe.12ac54"
],
[
"de908e66.b6fd3"
],
[
"d5a35bab.44cb18"
],
[
"3a661f0a.c61b1"
],
[
"9b4f492d.fbfd18"
]
],
"outputLabels": [
"gw_eui",
"offset_ms",
"device",
"seq_no",
"time_sync_ans"
]
},
{
"id": "c9a83ac9.50fd18",
"type": "debug",
"z": "449c1517.e25f4c",
"name": "Timeserver Gw",
"active": true,
"tosidebar": false,
"console": false,
"tostatus": true,
"complete": "payload",
"x": 680,
"y": 247,
"wires": [],
"icon": "node-red/bridge.png"
},
{
"id": "247204ab.a9f83c",
"type": "ui_text",
"z": "449c1517.e25f4c",
"group": "edb7cc8d.a3817",
"order": 3,
"width": 0,
"height": 0,
"name": "Recent time",
"label": "Last answer at:",
"format": "{{msg.payload}}",
"layout": "col-center",
"x": 790,
"y": 307,
"wires": []
},
{
"id": "de908e66.b6fd3",
"type": "ui_gauge",
"z": "449c1517.e25f4c",
"name": "Timeserver offset",
"group": "edb7cc8d.a3817",
"order": 2,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Offset gateway to server",
"label": "milliseconds",
"format": "{{value}}",
"min": 0,
"max": "2000",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"x": 690,
"y": 387,
"wires": []
},
{
"id": "6aeb3720.a89618",
"type": "ui_text",
"z": "449c1517.e25f4c",
"group": "edb7cc8d.a3817",
"order": 1,
"width": 0,
"height": 0,
"name": "Recent server",
"label": "Gateway",
"format": "{{msg.payload}}",
"layout": "col-center",
"x": 680,
"y": 347,
"wires": []
},
{
"id": "6ac55bbe.12ac54",
"type": "function",
"z": "449c1517.e25f4c",
"name": "Time",
"func": "msg.payload = new Date().toLocaleString('en-GB', {timeZone: 'Europe/Berlin'});\nreturn msg;",
"outputs": 1,
"noerr": 0,
"x": 650,
"y": 307,
"wires": [
[
"247204ab.a9f83c"
]
]
},
{
"id": "d5a35bab.44cb18",
"type": "ui_text",
"z": "449c1517.e25f4c",
"group": "edb7cc8d.a3817",
"order": 1,
"width": 0,
"height": 0,
"name": "Recent Device",
"label": "Device",
"format": "{{msg.payload}}",
"layout": "col-center",
"x": 680,
"y": 427,
"wires": []
},
{
"id": "3a661f0a.c61b1",
"type": "ui_text",
"z": "449c1517.e25f4c",
"group": "edb7cc8d.a3817",
"order": 1,
"width": 0,
"height": 0,
"name": "Sequence No",
"label": "Sequence",
"format": "{{msg.payload}}",
"layout": "col-center",
"x": 680,
"y": 467,
"wires": []
},
{
"id": "cc245719.3c4cd8",
"type": "debug",
"z": "449c1517.e25f4c",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"x": 860,
"y": 140,
"wires": []
},
{
"id": "2a15ab6f.ab2244",
"type": "mqtt-broker",
"z": "",
"name":"mqtt://db-clock-update-test:ttn-account-v2.etVLJSGCzS5ExuDw59HtGUIzv9Kcu8UPT_eWiz8ARs0@eu.thethings.network",
"broker":"mqtt://db-clock-update-test:ttn-account-v2.etVLJSGCzS5ExuDw59HtGUIzv9Kcu8UPT_eWiz8ARs0@eu.thethings.network",
"name": "eu.thethings.network:1883",
"broker": "eu.thethings.network",
"port": "1883",
"tls": "",
"clientid": "",
@ -49,426 +389,5 @@
"order": 3,
"disabled": false,
"hidden": false
},
{
"id":"428b4734.d61248",
"type":"ui_base",
"theme":{
"name":"theme-light",
"lightTheme":{
"default":"#0094CE",
"baseColor":"#0094CE",
"baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
"edited":true,
"reset":false
},
"darkTheme":{
"default":"#097479",
"baseColor":"#097479",
"baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
"edited":false
},
"customTheme":{
"name":"Untitled Theme 1",
"default":"#4B7930",
"baseColor":"#4B7930",
"baseFont":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
},
"themeState":{
"base-color":{
"default":"#0094CE",
"value":"#0094CE",
"edited":false
},
"page-titlebar-backgroundColor":{
"value":"#0094CE",
"edited":false
},
"page-backgroundColor":{
"value":"#fafafa",
"edited":false
},
"page-sidebar-backgroundColor":{
"value":"#ffffff",
"edited":false
},
"group-textColor":{
"value":"#1bbfff",
"edited":false
},
"group-borderColor":{
"value":"#ffffff",
"edited":false
},
"group-backgroundColor":{
"value":"#ffffff",
"edited":false
},
"widget-textColor":{
"value":"#111111",
"edited":false
},
"widget-backgroundColor":{
"value":"#0094ce",
"edited":false
},
"widget-borderColor":{
"value":"#ffffff",
"edited":false
},
"base-font":{
"value":"-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
}
},
"angularTheme":{
"primary":"indigo",
"accents":"blue",
"warn":"red",
"background":"grey"
}
},
"site":{
"name":"Node-RED Dashboard",
"hideToolbar":"false",
"allowSwipe":"false",
"lockMenu":"false",
"allowTempTheme":"true",
"dateFormat":"DD/MM/YYYY",
"sizes":{
"sx":48,
"sy":48,
"gx":6,
"gy":6,
"cx":6,
"cy":6,
"px":0,
"py":0
}
}
},
{
"id":"f8f8391b.7e8728",
"type":"ttn app",
"z":"",
"appId":"db-clock-update-test",
"accessKey":"ttn-account-v2.etVLJSGCzS5ExuDw59HtGUIzv9Kcu8UPT_eWiz8ARs0",
"discovery":"discovery.thethingsnetwork.org:1900"
},
{
"id":"49e3c067.e782e",
"type":"change",
"z":"23a9162c.cb1e6a",
"name":"Payload",
"rules":[
{
"t":"change",
"p":"topic",
"pt":"msg",
"from":"up",
"fromt":"str",
"to":"down",
"tot":"str"
},
{
"t":"set",
"p":"confirmed",
"pt":"msg",
"to":"false",
"tot":"bool"
},
{
"t":"set",
"p":"schedule",
"pt":"msg",
"to":"replace",
"tot":"str"
}
],
"action":"",
"property":"",
"from":"",
"to":"",
"reg":false,
"x":360,
"y":600,
"wires":[
[
"813b9751.dba8a8",
"5aff6f87.f125a"
]
]
},
{
"id":"9f4b8dd3.2f0d2",
"type":"switch",
"z":"23a9162c.cb1e6a",
"name":"Timeport",
"property":"port",
"propertyType":"msg",
"rules":[
{
"t":"eq",
"v":"9",
"vt":"num"
}
],
"checkall":"true",
"repair":false,
"outputs":1,
"x":420,
"y":120,
"wires":[
[
"831ab883.d6a238",
"b1e45e31.61136"
]
]
},
{
"id":"6190967b.01f758",
"type":"comment",
"z":"23a9162c.cb1e6a",
"name":"LoRaWAN Timeserver v1.1",
"info":"PLEASE NOTE: There is a patent filed for the time sync algorithm used in the\ncode of this file. The shown implementation example is covered by the\nrepository's licencse, but you may not be eligible to deploy the applied\nalgorithm in applications without granted license by the patent holder.",
"x":170,
"y":40,
"wires":[
]
},
{
"id":"831ab883.d6a238",
"type":"function",
"z":"23a9162c.cb1e6a",
"name":"Timeserver Logic",
"func":"/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\n\n*/\n\nfunction timecompare(a, b) {\n \n const timeA = a.time;\n const timeB = b.time;\n\n let comparison = 0;\n if (timeA > timeB) {\n comparison = 1;\n } else if (timeA < timeB) {\n comparison = -1;\n }\n return comparison;\n}\n\nlet confidence = 2000; // max millisecond diff gateway time to server time\n\n// guess if we have received a valid time_sync_req command\nif (msg.payload_raw.length != 1)\n return;\n\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar seqNo = msg.payload[\"timesync_seqno\"];\nvar seqNoMsg = { payload: seqNo };\nvar gateway_list = msg.metadata.gateways;\n\n// filter all gateway timestamps that have milliseconds part (which we assume have a \".\")\nvar gateways = gateway_list.filter(function (element) {\n return (element.time.includes(\".\"));\n});\n\nvar gateway_time = gateways.map(gw => {\n return {\n time: new Date(gw.time),\n eui: gw.gtw_id,\n }\n });\nvar server_time = new Date(msg.metadata.time);\n\n// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)\nvar gw_timestamps = gateway_time.filter(function (element) {\n return ((element.time > (server_time - confidence) && element.time <= server_time));\n});\n\n// if no timestamp left, we have no valid one and exit\nif (gw_timestamps.length === 0) {\n var notavailMsg = { payload: \"n/a\" };\n var notimeMsg = { payload: 0xff }; \n var buf2 = Buffer.alloc(1);\n msg.payload = new Buffer(buf2.fill(0xff));\n return [notavailMsg, notavailMsg, deviceMsg, seqNoMsg, msg];}\n\n// sort time array in ascending order to find most recent timestamp for time answer\ngw_timestamps.sort(timecompare);\n\nvar timestamp = gw_timestamps[0].time;\nvar eui = gw_timestamps[0].eui;\nvar offset = server_time - timestamp;\n\nvar seconds = Math.floor(timestamp/1000);\nvar fractions = (timestamp % 1000) / 4;\n\nlet buf = new ArrayBuffer(6);\nnew DataView(buf).setUint8(0, seqNo);\nnew DataView(buf).setUint32(1, seconds);\nnew DataView(buf).setUint8(5, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = 9;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];",
"outputs":5,
"noerr":0,
"x":350,
"y":320,
"wires":[
[
"37722d4b.08e3c2",
"a8a04c7a.c5fbd",
"a15454a9.fa0948"
],
[
"46ce842a.614d5c"
],
[
"a5dbb4ef.019168"
],
[
"1cb58e7f.221362"
],
[
"49e3c067.e782e"
]
],
"outputLabels":[
"gw_eui",
"offset_ms",
"device",
"seq_no",
"time_sync_ans"
]
},
{
"id":"37722d4b.08e3c2",
"type":"debug",
"z":"23a9162c.cb1e6a",
"name":"Timeserver Gw",
"active":true,
"tosidebar":false,
"console":false,
"tostatus":true,
"complete":"payload",
"x":700,
"y":240,
"wires":[
],
"icon":"node-red/bridge.png"
},
{
"id":"8712a5ac.ed18e8",
"type":"ui_text",
"z":"23a9162c.cb1e6a",
"group":"edb7cc8d.a3817",
"order":3,
"width":0,
"height":0,
"name":"Recent time",
"label":"Last answer at:",
"format":"{{msg.payload}}",
"layout":"col-center",
"x":810,
"y":300,
"wires":[
]
},
{
"id":"46ce842a.614d5c",
"type":"ui_gauge",
"z":"23a9162c.cb1e6a",
"name":"Timeserver offset",
"group":"edb7cc8d.a3817",
"order":2,
"width":0,
"height":0,
"gtype":"gage",
"title":"Offset gateway to server",
"label":"milliseconds",
"format":"{{value}}",
"min":0,
"max":"2000",
"colors":[
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1":"",
"seg2":"",
"x":710,
"y":380,
"wires":[
]
},
{
"id":"a8a04c7a.c5fbd",
"type":"ui_text",
"z":"23a9162c.cb1e6a",
"group":"edb7cc8d.a3817",
"order":1,
"width":0,
"height":0,
"name":"Recent server",
"label":"Gateway",
"format":"{{msg.payload}}",
"layout":"col-center",
"x":700,
"y":340,
"wires":[
]
},
{
"id":"a15454a9.fa0948",
"type":"function",
"z":"23a9162c.cb1e6a",
"name":"Time",
"func":"msg.payload = new Date().toLocaleString('en-GB', {timeZone: 'Europe/Berlin'});\nreturn msg;",
"outputs":1,
"noerr":0,
"x":670,
"y":300,
"wires":[
[
"8712a5ac.ed18e8"
]
]
},
{
"id":"a5dbb4ef.019168",
"type":"ui_text",
"z":"23a9162c.cb1e6a",
"group":"edb7cc8d.a3817",
"order":1,
"width":0,
"height":0,
"name":"Recent Device",
"label":"Device",
"format":"{msg.payload}",
"layout":"col-center",
"x":700,
"y":420,
"wires":[
]
},
{
"id":"1cb58e7f.221362",
"type":"ui_text",
"z":"23a9162c.cb1e6a",
"group":"edb7cc8d.a3817",
"order":1,
"width":0,
"height":0,
"name":"Sequence No",
"label":"Sequence",
"format":"{{msg.payload}}",
"layout":"col-center",
"x":700,
"y":460,
"wires":[
]
},
{
"id":"a5356ffb.f0cab",
"type":"ttn uplink",
"z":"23a9162c.cb1e6a",
"name":"",
"app":"f8f8391b.7e8728",
"dev_id":"",
"field":"",
"x":240,
"y":120,
"wires":[
[
"9f4b8dd3.2f0d2"
]
]
},
{
"id":"813b9751.dba8a8",
"type":"debug",
"z":"23a9162c.cb1e6a",
"name":"",
"active":true,
"tosidebar":true,
"console":false,
"tostatus":false,
"complete":"false",
"x":590,
"y":640,
"wires":[
]
},
{
"id":"5aff6f87.f125a",
"type":"ttn downlink",
"z":"23a9162c.cb1e6a",
"name":"",
"app":"f8f8391b.7e8728",
"dev_id":"",
"port":"",
"confirmed":false,
"schedule":"replace",
"x":590,
"y":560,
"wires":[
]
},
{
"id":"b1e45e31.61136",
"type":"debug",
"z":"23a9162c.cb1e6a",
"name":"",
"active":true,
"tosidebar":true,
"console":false,
"tostatus":false,
"complete":"payload",
"targetType":"msg",
"x":590,
"y":100,
"wires":[
]
}
]

View File

@ -20,7 +20,7 @@ void defaultConfig() {
cfg.adrmode = 1; // 0=disabled, 1=enabled
cfg.screensaver = 0; // 0=disabled, 1=enabled
cfg.screenon = 1; // 0=disabled, 1=enabled
cfg.countermode = 0; // 0=cyclic, 1=cumulative, 2=cyclic confirmed
cfg.countermode = COUNTERMODE; // 0=cyclic, 1=cumulative, 2=cyclic confirmed
cfg.rssilimit = 0; // threshold for rssilimiter, negative value!
cfg.sendcycle = SENDCYCLE; // payload send cycle [seconds/2]
cfg.wifichancycle =

View File

@ -128,5 +128,9 @@ void reset_counters() {
macs_total = 0; // reset all counters
macs_wifi = 0;
macs_ble = 0;
#ifdef HAS_DISPLAY
oledPlotCurve(0, true);
#endif
#endif
}

View File

@ -4,116 +4,132 @@
Display-Mask (128 x 64 pixel):
| 111111
|0123456789012345
------------------
0|PAX:aabbccddee
1|PAX:aabbccddee
2|B:a.bcV Sats:ab
3|BLTH:abcde SFab
4|WIFI:abcde ch:ab
5|RLIM:abcd abcdKB
6|20:27:00* 27.Feb
7|yyyyyyyyyyyyyyab
| | |
| 11111111112
|012345678901234567890 Font
----------------------- ---------
0|PAX:aabbccdd STRETCHED
1|PAX:aabbccdd STRETCHED
2|
3|B:a.bcV Sats:ab ch:ab SMALL
4|WIFI:abcde BLTH:abcde SMALL
5|RLIM:abcd Mem:abcdKB SMALL
6|27.Feb 2019 20:27:00* SMALL
7|yyyyyyyyyyyyyyyy SFab SMALL
line 6: * = char {L|G|R|?} indicates time source,
* = char {L|G|R|?} indicates time source,
inverse = clock controller is active,
pulsed = pps input signal is active
line 7: y = LMIC event message; ab = payload queue length
y = LMIC event message; ab = payload queue length
FONT_SMALL: 6x8px = 21 chars / line
FONT_NORMAL: 8x8px = 16 chars / line
FONT_STRETCHED: 16x32px = 8 chars / line
*/
// Basic Config
#include "globals.h"
#include <ss_oled.h>
#include <esp_spi_flash.h> // needed for reading ESP32 chip attributes
#define DISPLAY_PAGES (4) // number of display pages
// local Tag for logging
static const char TAG[] = __FILE__;
HAS_DISPLAY u8x8(MY_OLED_RST, MY_OLED_SCL, MY_OLED_SDA);
#define DISPLAY_PAGES (6) // number of paxcounter display pages
// helper arry for converting month values to text
// settings for oled display library
#define USE_BACKBUFFER
// settings for qr code generator
#define QR_VERSION 3 // 29 x 29px
#define QR_SCALEFACTOR 2 // 29 -> 58x < 64px
// settings for curve plotter
#define DISPLAY_WIDTH 128 // Width in pixels of OLED-display, must be 32X
#define DISPLAY_HEIGHT 64 // Height in pixels of OLED-display, must be 64X
// helper array for converting month values to text
const char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
uint8_t DisplayIsOn = 0;
uint8_t displaybuf[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8] = {0};
// helper function, prints a hex key on display
void DisplayKey(const uint8_t *key, uint8_t len, bool lsb) {
const uint8_t *p;
for (uint8_t i = 0; i < len; i++) {
p = lsb ? key + len - i - 1 : key + i;
u8x8.printf("%02X", *p);
}
u8x8.printf("\n");
}
QRCode qrcode;
void init_display(const char *Productname, const char *Version) {
void init_display(uint8_t verbose) {
// block i2c bus access
if (!I2C_MUTEX_LOCK())
ESP_LOGV(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
// show startup screen
uint8_t buf[32];
u8x8.begin();
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.clear();
u8x8.setFlipMode(0);
u8x8.setInverseFont(1);
u8x8.draw2x2String(0, 0, Productname);
u8x8.setInverseFont(0);
u8x8.draw2x2String(2, 2, Productname);
delay(500);
u8x8.clear();
u8x8.setFlipMode(1);
u8x8.setInverseFont(1);
u8x8.draw2x2String(0, 0, Productname);
u8x8.setInverseFont(0);
u8x8.draw2x2String(2, 2, Productname);
delay(500);
u8x8.setFlipMode(0);
u8x8.clear();
#ifdef DISPLAY_FLIP
u8x8.setFlipMode(1);
// init display
#ifndef DISPLAY_FLIP
oledInit(OLED_128x64, ANGLE_0, false, -1, -1, 400000L);
#else
oledInit(OLED_128x64, ANGLE_FLIPY, false, -1, -1, 400000L);
#endif
// Display chip information
// clear display
oledSetContrast(DISPLAYCONTRAST);
oledFill(0, 1);
if (verbose) {
// show startup screen
// to come -> display .bmp file with logo
// show chip information
#if (VERBOSE)
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
u8x8.printf("ESP32 %d cores\nWiFi%s%s\n", chip_info.cores,
dp_printf(0, 0, 0, 0, "** PAXCOUNTER **");
dp_printf(0, 1, 0, 0, "Software v%s", PROGVERSION);
dp_printf(0, 3, 0, 0, "ESP32 %d cores", chip_info.cores);
dp_printf(0, 4, 0, 0, "Chip Rev.%d", chip_info.revision);
dp_printf(0, 5, 0, 0, "WiFi%s%s",
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
u8x8.printf("ESP Rev.%d\n", chip_info.revision);
u8x8.printf("%dMB %s Flash\n", spi_flash_get_chip_size() / (1024 * 1024),
dp_printf(0, 6, 0, 0, "%dMB %s Flash",
spi_flash_get_chip_size() / (1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "int."
: "ext.");
// give user some time to read or take picture
oledDumpBuffer(NULL);
delay(2000);
oledFill(0x00, 1);
#endif // VERBOSE
u8x8.print(Productname);
u8x8.print(" v");
u8x8.println(PROGVERSION);
#if (HAS_LORA)
u8x8.println("DEVEUI:");
// generate DEVEUI as QR code and text
uint8_t buf[8];
char deveui[17];
os_getDevEui((u1_t *)buf);
DisplayKey(buf, 8, true);
delay(3000);
sprintf(deveui, "%016llX", *((uint64_t *)&buf));
// display DEVEUI as QR code on the left
oledSetContrast(30);
dp_printqr(3, 3, deveui);
// display DEVEUI as plain text on the right
dp_printf(72, 0, FONT_NORMAL, 0, "LORAWAN");
dp_printf(72, 1, FONT_NORMAL, 0, "DEVEUI:");
for (uint8_t i = 0; i <= 3; i++)
dp_printf(80, i + 3, FONT_NORMAL, 0, "%4.4s", deveui + i * 4);
// give user some time to read or take picture
oledDumpBuffer(NULL);
delay(8000);
oledSetContrast(DISPLAYCONTRAST);
oledFill(0x00, 1);
#endif // HAS_LORA
u8x8.clear();
u8x8.setPowerSave(!cfg.screenon); // set display off if disabled
u8x8.draw2x2String(0, 0, "PAX:0");
#if (BLECOUNTER)
u8x8.setCursor(0, 3);
u8x8.printf("BLTH:0");
#endif
u8x8.setCursor(0, 4);
u8x8.printf("WIFI:0");
u8x8.setCursor(0, 5);
u8x8.printf(!cfg.rssilimit ? "RLIM:off " : "RLIM:%d", cfg.rssilimit);
} // verbose
oledPower(cfg.screenon); // set display off if disabled
I2C_MUTEX_UNLOCK(); // release i2c bus access
} // mutex
@ -123,6 +139,9 @@ void refreshTheDisplay(bool nextPage) {
static uint8_t DisplayPage = 0;
// update histogram if we have a display
oledPlotCurve(macs.size(), false);
// if display is switched off we don't refresh it to relax cpu
if (!DisplayIsOn && (DisplayIsOn == cfg.screenon))
return;
@ -137,15 +156,16 @@ void refreshTheDisplay(bool nextPage) {
// set display on/off according to current device configuration
if (DisplayIsOn != cfg.screenon) {
DisplayIsOn = cfg.screenon;
u8x8.setPowerSave(!cfg.screenon);
oledPower(cfg.screenon);
}
if (nextPage) {
DisplayPage = (DisplayPage >= DISPLAY_PAGES - 1) ? 0 : (DisplayPage + 1);
u8x8.clear();
oledFill(0, 1);
}
draw_page(t, DisplayPage);
oledDumpBuffer(NULL);
I2C_MUTEX_UNLOCK(); // release i2c bus access
@ -154,191 +174,164 @@ void refreshTheDisplay(bool nextPage) {
void draw_page(time_t t, uint8_t page) {
char timeState, buff[16];
char timeState;
uint8_t msgWaiting;
#if (HAS_GPS)
static bool wasnofix = true;
#endif
// update counter (lines 0-1)
snprintf(
buff, sizeof(buff), "PAX:%-4d",
(int)macs.size()); // convert 16-bit MAC counter to decimal counter value
u8x8.draw2x2String(0, 0,
buff); // display number on unique macs total Wifi + BLE
// line 1/2: pax counter
dp_printf(0, 0, FONT_STRETCHED, 0, "PAX:%-4d",
macs.size()); // display number of unique macs total Wifi + BLE
switch (page % DISPLAY_PAGES) {
// page 0: parameters overview
// page 1: time
// page 1: pax graph
// page 2: GPS
// page 3: BME280/680
// page 4: time
// page 5: blank screen
// page 0: parameters overview
case 0:
// update Battery status (line 2)
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
u8x8.setCursor(0, 2);
if (batt_voltage == 0xffff)
u8x8.printf("B:USB ");
else
u8x8.printf("B:%.2fV", batt_voltage / 1000.0);
#endif
// update GPS status (line 2)
#if (HAS_GPS)
u8x8.setCursor(9, 2);
if (gps.location.age() < 1500) // if no fix then display Sats value inverse
u8x8.printf("Sats:%.2d", gps.satellites.value());
else {
u8x8.setInverseFont(1);
u8x8.printf("Sats:%.2d", gps.satellites.value());
u8x8.setInverseFont(0);
}
#endif
// update bluetooth counter + LoRa SF (line 3)
// line 3: wifi + bluetooth counters
dp_printf(0, 3, FONT_SMALL, 0, "WIFI:%-5d", macs_wifi);
#if (BLECOUNTER)
u8x8.setCursor(0, 3);
if (cfg.blescan)
u8x8.printf("BLTH:%-5d", macs_ble);
dp_printf(66, 3, FONT_SMALL, 0, "BLTH:%-5d", macs_ble);
else
u8x8.printf("%s", "BLTH:off");
dp_printf(66, 3, FONT_SMALL, 0, "%s", "BLTH:off");
#endif
#if (HAS_LORA)
u8x8.setCursor(12, 3);
if (!cfg.adrmode) // if ADR=off then display SF value inverse
u8x8.setInverseFont(1);
u8x8.printf("%-4s", getSfName(updr2rps(LMIC.datarate)));
if (!cfg.adrmode) // switch off inverse if it was turned on
u8x8.setInverseFont(0);
#endif // HAS_LORA
// line 4: Battery + GPS status + Wifi channel
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
if (batt_voltage == 0xffff)
dp_printf(0, 4, FONT_SMALL, 0, "%s", "USB ");
else if (batt_voltage == 0)
dp_printf(0, 4, FONT_SMALL, 0, "%s", "No batt");
else
dp_printf(0, 4, FONT_SMALL, 0, "B:%.2fV", batt_voltage / 1000.0);
#endif
#if (HAS_GPS)
if (gps.location.age() < 1500) // if no fix then display Sats value inverse
dp_printf(48, 4, FONT_SMALL, 0, "Sats:%.2d", gps.satellites.value());
else
dp_printf(48, 4, FONT_SMALL, 1, "Sats:%.2d", gps.satellites.value());
#endif
dp_printf(96, 4, FONT_SMALL, 0, "ch:%02d", channel);
// line 4: update wifi counter + channel display
u8x8.setCursor(0, 4);
u8x8.printf("WIFI:%-5d", macs_wifi);
u8x8.setCursor(11, 4);
u8x8.printf("ch:%02d", channel);
// line 5: RSSI limiter + free memory
dp_printf(0, 5, FONT_SMALL, 0, !cfg.rssilimit ? "RLIM:off " : "RLIM:%-4d",
cfg.rssilimit);
dp_printf(66, 5, FONT_SMALL, 0, "Mem:%4dKB", getFreeRAM() / 1024);
// line 5: update RSSI limiter status & free memory display
u8x8.setCursor(0, 5);
u8x8.printf(!cfg.rssilimit ? "RLIM:off " : "RLIM:%-4d", cfg.rssilimit);
u8x8.setCursor(10, 5);
u8x8.printf("%4dKB", getFreeRAM() / 1024);
// line 6: update time-of-day or LoRa status display
u8x8.setCursor(0, 6);
// line 6: time + date
#if (TIME_SYNC_INTERVAL)
// we want a systime display instead LoRa status
timeState = TimePulseTick ? ' ' : timeSetSymbols[timeSource];
TimePulseTick = false;
dp_printf(0, 6, FONT_SMALL, 0, "%02d.%3s %4d", day(t), printmonth[month(t)],
year(t));
dp_printf(72, 6, FONT_SMALL, 0, "%02d:%02d:%02d", hour(t), minute(t),
second(t));
// display inverse timeState if clock controller is enabled
#if (defined HAS_DCF77) || (defined HAS_IF482)
u8x8.printf("%02d:%02d:%02d", hour(t), minute(t), second(t));
u8x8.setInverseFont(1);
u8x8.printf("%c", timeState);
u8x8.setInverseFont(0);
dp_printf(120, 6, FONT_SMALL, 1, "%c", timeState);
#else
u8x8.printf("%02d:%02d:%02d%c", hour(t), minute(t), second(t), timeState);
#endif // HAS_DCF77 || HAS_IF482
if (timeSource != _unsynced)
u8x8.printf(" %2d.%3s", day(t), printmonth[month(t)]);
dp_printf(120, 6, FONT_SMALL, 0, "%c", timeState);
#endif
#endif // TIME_SYNC_INTERVAL
// line 7: LORA network status
#if (HAS_LORA)
// line 7: update LMiC event display
u8x8.setCursor(0, 7);
u8x8.printf("%-14s", lmic_event_msg);
// update LoRa send queue display
// LMiC event display, display inverse if sendqueue not empty
msgWaiting = uxQueueMessagesWaiting(LoraSendQueue);
if (msgWaiting) {
sprintf(buff, "%2d", msgWaiting);
u8x8.setCursor(14, 7);
u8x8.printf("%-2s", msgWaiting == SEND_QUEUE_SIZE ? "<>" : buff);
} else
u8x8.printf(" ");
if (msgWaiting)
dp_printf(0, 7, FONT_SMALL, 1, "%-16s", lmic_event_msg);
else
dp_printf(0, 7, FONT_SMALL, 0, "%-16s", lmic_event_msg);
// LORA datarate, display inverse if ADR disabled
if (cfg.adrmode)
dp_printf(100, 7, FONT_SMALL, 0, "%-4s",
getSfName(updr2rps(LMIC.datarate)));
else
dp_printf(100, 7, FONT_SMALL, 1, "%-4s",
getSfName(updr2rps(LMIC.datarate)));
#endif // HAS_LORA
break; // page0
// page 1: pax graph
case 1:
// line 4-5: update time-of-day
snprintf(buff, sizeof(buff), "%02d:%02d:%02d", hour(t), minute(t),
second(t));
u8x8.draw2x2String(0, 4, buff);
oledDumpBuffer(displaybuf);
break; // page1
// page 2: GPS
case 2:
// update counter (lines 0-1)
snprintf(
buff, sizeof(buff), "PAX:%-4d",
(int)
macs.size()); // convert 16-bit MAC counter to decimal counter value
u8x8.draw2x2String(0, 0,
buff); // display number on unique macs total Wifi + BLE
#if (HAS_GPS)
if (gps.location.age() < 1500) {
// line 5: clear "No fix"
if (wasnofix) {
snprintf(buff, sizeof(buff), " ");
u8x8.draw2x2String(2, 5, buff);
dp_printf(16, 5, FONT_STRETCHED, 0, " ");
wasnofix = false;
}
// line 3-4: GPS latitude
snprintf(buff, sizeof(buff), "%c%07.4f",
dp_printf(0, 3, FONT_STRETCHED, 0, "%c%07.4f",
gps.location.rawLat().negative ? 'S' : 'N', gps.location.lat());
u8x8.draw2x2String(0, 3, buff);
// line 6-7: GPS longitude
snprintf(buff, sizeof(buff), "%c%07.4f",
dp_printf(0, 6, FONT_STRETCHED, 0, "%c%07.4f",
gps.location.rawLat().negative ? 'W' : 'E', gps.location.lng());
u8x8.draw2x2String(0, 6, buff);
} else {
snprintf(buff, sizeof(buff), "No fix");
u8x8.setInverseFont(1);
u8x8.draw2x2String(2, 5, buff);
u8x8.setInverseFont(0);
dp_printf(16, 5, FONT_STRETCHED, 1, "No fix");
wasnofix = true;
}
#else
snprintf(buff, sizeof(buff), "No GPS");
u8x8.draw2x2String(2, 5, buff);
dp_printf(16, 5, FONT_STRETCHED, 1, "No GPS");
#endif
break; // page2
// page 3: BME280/680
case 3:
#if (HAS_BME)
// line 2-3: Temp
snprintf(buff, sizeof(buff), "TMP:%-2.1f", bme_status.temperature);
u8x8.draw2x2String(0, 2, buff);
dp_printf(0, 2, FONT_STRETCHED, 0, "TMP:%-2.1f", bme_status.temperature);
// line 4-5: Hum
snprintf(buff, sizeof(buff), "HUM:%-2.1f", bme_status.humidity);
u8x8.draw2x2String(0, 4, buff);
dp_printf(0, 4, FONT_STRETCHED, 0, "HUM:%-2.1f", bme_status.humidity);
#ifdef HAS_BME680
// line 6-7: IAQ
snprintf(buff, sizeof(buff), "IAQ:%-3.0f", bme_status.iaq);
u8x8.draw2x2String(0, 6, buff);
dp_printf(0, 6, FONT_STRETCHED, 0, "IAQ:%-3.0f", bme_status.iaq);
#endif
#else
snprintf(buff, sizeof(buff), "No BME");
u8x8.draw2x2String(2, 5, buff);
dp_printf(16, 5, FONT_STRETCHED, 1, "No BME");
#endif
break; // page3
// page 4: time
case 4:
dp_printf(0, 4, FONT_LARGE, 0, "%02d:%02d:%02d", hour(t), minute(t),
second(t));
break;
// page 5: blank screen
case 5:
oledFill(0, 1);
break;
default:
break; // default
@ -346,4 +339,156 @@ void draw_page(time_t t, uint8_t page) {
} // draw_page
// display helper functions
void dp_printf(uint16_t x, uint16_t y, uint8_t font, uint8_t inv,
const char *format, ...) {
char loc_buf[64];
char *temp = loc_buf;
va_list arg;
va_list copy;
va_start(arg, format);
va_copy(copy, arg);
int len = vsnprintf(temp, sizeof(loc_buf), format, copy);
va_end(copy);
if (len < 0) {
va_end(arg);
return;
};
if (len >= sizeof(loc_buf)) {
temp = (char *)malloc(len + 1);
if (temp == NULL) {
va_end(arg);
return;
}
len = vsnprintf(temp, len + 1, format, arg);
}
va_end(arg);
oledWriteString(0, x, y, temp, font, inv, false);
if (temp != loc_buf) {
free(temp);
}
}
void dp_printqr(uint16_t offset_x, uint16_t offset_y, const char *Message) {
uint8_t qrcodeData[qrcode_getBufferSize(QR_VERSION)];
qrcode_initText(&qrcode, qrcodeData, QR_VERSION, ECC_HIGH, Message);
// draw QR code
for (uint16_t y = 0; y < qrcode.size; y++)
for (uint16_t x = 0; x < qrcode.size; x++)
if (!qrcode_getModule(&qrcode, x, y)) // "black"
oledfillRect(x * QR_SCALEFACTOR + offset_x,
y * QR_SCALEFACTOR + offset_y, QR_SCALEFACTOR,
QR_SCALEFACTOR, false);
// draw horizontal frame lines
oledfillRect(0, 0, qrcode.size * QR_SCALEFACTOR + 2 * offset_x, offset_y,
false);
oledfillRect(0, qrcode.size * QR_SCALEFACTOR + offset_y,
qrcode.size * QR_SCALEFACTOR + 2 * offset_x, offset_y, false);
// draw vertical frame lines
oledfillRect(0, 0, offset_x, qrcode.size * QR_SCALEFACTOR + 2 * offset_y,
false);
oledfillRect(qrcode.size * QR_SCALEFACTOR + offset_x, 0, offset_x,
qrcode.size * QR_SCALEFACTOR + 2 * offset_y, false);
}
void oledfillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
uint8_t bRender) {
for (uint16_t xi = x; xi < x + width; xi++)
oledDrawLine(xi, y, xi, y + height - 1, bRender);
}
int oledDrawPixel(uint8_t *buf, const uint16_t x, const uint16_t y,
const uint8_t dot) {
if (x > DISPLAY_WIDTH || y > DISPLAY_HEIGHT)
return -1;
uint8_t bit = y & 7;
uint16_t idx = y / 8 * DISPLAY_WIDTH + x;
buf[idx] &= ~(1 << bit); // clear pixel
if (dot)
buf[idx] |= (1 << bit); // set pixel
return 0;
}
void oledScrollBufferHorizontal(uint8_t *buf, const uint16_t width,
const uint16_t height, bool left) {
uint16_t col, page, idx;
for (page = 0; page < height / 8; page++) {
if (left) { // scroll left
for (col = 0; col < width - 1; col++) {
idx = page * width + col;
buf[idx] = buf[idx + 1];
}
buf[idx + 1] = 0;
} else // scroll right
{
for (col = width - 1; col > 0; col--) {
idx = page * width + col;
buf[idx] = buf[idx - 1];
}
buf[idx - 1] = 0;
}
}
}
void oledScrollBufferVertical(uint8_t *buf, const uint16_t width,
const uint16_t height, int offset) {
uint64_t buf_col;
if (!offset)
return; // nothing to do
for (uint16_t col = 0; col < DISPLAY_WIDTH; col++) {
// convert column bytes from display buffer to uint64_t
buf_col = *(uint64_t *)&buf[col * DISPLAY_HEIGHT / 8];
if (offset > 0) // scroll down
buf_col <= abs(offset);
else // scroll up
buf_col >= offset;
// write back uint64_t to uint8_t display buffer
*(uint64_t *)&buf[col * DISPLAY_HEIGHT / 8] = buf_col;
}
}
void oledPlotCurve(uint16_t count, bool reset) {
static uint16_t last_count = 0, col = 0, row = 0;
uint16_t v_scroll = 0;
if ((last_count == count) && !reset)
return;
if (reset) { // next count cycle?
if (col < DISPLAY_WIDTH - 1) // matrix not full -> increment column
col++;
else // matrix full -> scroll left 1 dot
oledScrollBufferHorizontal(displaybuf, DISPLAY_WIDTH, DISPLAY_HEIGHT,
true);
} else // clear current dot
oledDrawPixel(displaybuf, col, row, 0);
// scroll down, if necessary
while ((count - v_scroll) > DISPLAY_HEIGHT - 1)
v_scroll++;
if (v_scroll)
oledScrollBufferVertical(displaybuf, DISPLAY_WIDTH, DISPLAY_HEIGHT,
v_scroll);
// set new dot
// row = DISPLAY_HEIGHT - 1 - (count - v_scroll) % DISPLAY_HEIGHT;
row = DISPLAY_HEIGHT - 1 - count - v_scroll;
last_count = count;
oledDrawPixel(displaybuf, col, row, 1);
}
#endif // HAS_DISPLAY

View File

@ -1,5 +1,5 @@
// clang-format off
// upload_speed 115200
// upload_speed 921600
// board esp32dev
#ifndef _EBOX_H

View File

@ -1,5 +1,5 @@
// clang-format off
// upload_speed 115200
// upload_speed 921600
// board esp32dev

View File

@ -12,7 +12,7 @@
//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
//#define DISPLAY_FLIP 1 // use if display is rotated
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
#define BAT_VOLTAGE_DIVIDER ((82.0+220.0)/82.0) // 82k + 220k 1%
@ -29,7 +29,7 @@
// Pins for I2C interface of OLED Display
#define MY_OLED_SDA SDA
#define MY_OLED_SCL SCL
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
// Settings for on board DS3231 RTC chip
// note: to use RTC_INT, capacitor 100nF next to red LED must be removed to sharpen interrupt signal slope

View File

@ -44,7 +44,7 @@
#define BOARD_HAS_PSRAM // use if board has external PSRAM
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
//#define DISPLAY_FLIP 1 // use if display is rotated
#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

@ -16,7 +16,7 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define CFG_sx1276_radio 1
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board
#define HAS_DISPLAY 1 // OLED-Display on board
#define HAS_LED LED_BUILTIN // white LED on board
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board

View File

@ -16,7 +16,7 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define CFG_sx1276_radio 1
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board
#define HAS_DISPLAY 1 // OLED-Display on board
#define HAS_LED LED_BUILTIN // white LED on board
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board

View File

@ -13,7 +13,7 @@
// disable brownout detection (avoid unexpected reset on some boards)
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board
#define HAS_DISPLAY 1 // OLED-Display on board
//#define DISPLAY_FLIP 1 // uncomment this for rotated display
#define HAS_LED 22 // ESP32 GPIO12 (pin22) On Board LED
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
@ -37,7 +37,7 @@
// Pins for I2C interface of OLED Display
#define MY_OLED_SDA (14)
#define MY_OLED_SCL (12)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
// I2C config for Microchip 24AA02E64 DEVEUI unique address
#define MCP_24AA02E64_I2C_ADDRESS 0x50 // I2C address for the 24AA02E64

View File

@ -13,7 +13,7 @@
// disable brownout detection (avoid unexpected reset on some boards)
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board
#define HAS_DISPLAY 1 // OLED-Display on board
//#define DISPLAY_FLIP 1 // uncomment this for rotated display
#define HAS_LED NOT_A_PIN // Led os on same pin as Lora SS pin, to avoid problems, we don't use it
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
@ -39,7 +39,7 @@
// Pins for I2C interface of OLED Display
#define MY_OLED_SDA (21)
#define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
// I2C config for Microchip 24AA02E64 DEVEUI unique address
#define MCP_24AA02E64_I2C_ADDRESS 0x50 // I2C address for the 24AA02E64

View File

@ -41,10 +41,10 @@
#define LORA_IO2 LMIC_UNUSED_PIN
// Pins for I2C interface of OLED Display
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // U8X8_SSD1306_128X32_UNIVISION_SW_I2C //
#define HAS_DISPLAY 1
//#define DISPLAY_FLIP 1 // uncomment this for rotated display
#define MY_OLED_SDA (23)
#define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
#endif

View File

@ -33,10 +33,10 @@
#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !!
// display (if connected)
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
#define MY_OLED_SDA SDA
#define MY_OLED_SCL SCL
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
//#define DISPLAY_FLIP 1 // use if display is rotated
// user defined sensors (if connected)

View File

@ -20,10 +20,10 @@ User, long press -> send LORA message
Reset -> reset device
*/
//#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
#define MY_OLED_SDA SDA
#define MY_OLED_SCL SCL
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
//#define DISPLAY_FLIP 1 // use if display is rotated
#define HAS_LORA 1 // comment out if device shall not send data via LoRa

View File

@ -10,7 +10,7 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
#define HAS_LED NOT_A_PIN // green on board LED is useless, is GPIO25, which switches power for Lora+Display
#define EXT_POWER_SW GPIO_NUM_25 // switches power for LoRa chip
@ -23,7 +23,7 @@
// Pins for I2C interface of OLED Display
#define MY_OLED_SDA (21)
#define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
// Settings for on board DS3231 RTC chip
#define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL

View File

@ -12,7 +12,7 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define CFG_sx1276_radio 1
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C // OLED-Display on board
#define HAS_DISPLAY 1 // OLED-Display on board
//#define DISPLAY_FLIP 1 // uncomment this for rotated display
#define HAS_LED LED_BUILTIN
#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW

View File

@ -12,7 +12,7 @@
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
//#define DISPLAY_FLIP 1 // uncomment this for rotated display
#define HAS_LED NOT_A_PIN // on-board LED is wired to SCL (used by display) therefore totally useless
@ -22,7 +22,7 @@
// Pins for I2C interface of OLED Display
#define MY_OLED_SDA (21)
#define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
// Pins for LORA chip SPI interface come from board file, we need some
// additional definitions for LMIC

View File

@ -20,7 +20,7 @@
//#define HAS_BME 1 // Enable BME sensors in general
//#define HAS_BME280 GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
#define HAS_LED (25) // green on board LED
#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
@ -28,7 +28,7 @@
// Pins for I2C interface of OLED Display
#define MY_OLED_SDA (21)
#define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
// Pins for LORA chip SPI interface, reset line and interrupt lines
#define LORA_SCK (5)

View File

@ -19,7 +19,7 @@
#define HAS_LED NOT_A_PIN // no usable LED on board
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
//#define DISPLAY_FLIP 1 // rotated display
//#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
@ -27,7 +27,7 @@
// Pins for I2C interface of OLED Display
#define MY_OLED_SDA (21)
#define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
// Pins for LORA chip SPI interface, reset line and interrupt lines
#define LORA_SCK (5)

View File

@ -9,10 +9,10 @@
#define HAS_LED NOT_A_PIN // no LED
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_DISPLAY 1
#define MY_OLED_SDA (5)
#define MY_OLED_SCL (4)
#define MY_OLED_RST U8X8_PIN_NONE
#define MY_OLED_RST NOT_A_PIN
#define DISPLAY_FLIP 1 // use if display is rotated
#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature

View File

@ -94,7 +94,7 @@ void refreshTheMatrixDisplay(bool nextPage) {
if (col < (LED_MATRIX_WIDTH - 1))
col++;
else
ScrollLeft(displaybuf, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT);
ScrollMatrixLeft(displaybuf, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT);
} else
matrix.drawPoint(col, row, 0); // clear current dot
@ -204,7 +204,7 @@ uint8_t GetCharWidth(char cChar) {
return CharDescriptor.width;
}
void ScrollLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows) {
void ScrollMatrixLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows) {
uint32_t i, k, idx;
const uint32_t x = cols / 8;

View File

@ -18,7 +18,7 @@
// use interrupts only if LORA_IRQ and LORA_DIO are connected to interrupt
// capable GPIO pins on your board, if not disable interrupts
#define LMIC_USE_INTERRUPTS 1
//#define LMIC_USE_INTERRUPTS 1
// time sync via LoRaWAN network, note: not supported by TTNv2
// #define LMIC_ENABLE_DeviceTimeReq 1
@ -28,7 +28,7 @@
// so consuming more power. You may sharpen (reduce) this value if you are
// limited on battery.
// ATTN: VALUES > 7 WILL CAUSE RECEPTION AND JOIN PROBLEMS WITH HIGH SF RATES
//#define CLOCK_ERROR_PROCENTAGE 7
#define CLOCK_ERROR_PROCENTAGE 7
// Set this to 1 to enable some basic debug output (using printf) about
// RF settings used during transmission and reception. Set to 2 to

View File

@ -484,6 +484,9 @@ void lmictask(void *pvParameters) {
os_init(); // initialize lmic run-time environment
LMIC_reset(); // initialize lmic MAC
// pre-join settings
LMIC_setDrTxpow(assertDR(LORADRDEFAULT), LORATXPOWDEFAULT);
LMIC_setLinkCheckMode(0);
// This tells LMIC to make the receive windows bigger, in case your clock is
@ -494,7 +497,6 @@ void lmictask(void *pvParameters) {
LMIC_setClockError(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100);
#endif
//#if defined(CFG_US915) || defined(CFG_au921)
#if CFG_LMIC_US_like
// in the US, with TTN, it saves join time if we start on subband 1
// (channels 8-15). This will get overridden after the join by parameters
@ -503,11 +505,6 @@ void lmictask(void *pvParameters) {
LMIC_selectSubBand(1);
#endif
// Set the data rate to Spreading Factor 7. This is the fastest supported
// rate for 125 kHz channels, and it minimizes air time and battery power.
// Set the transmission power to 14 dBi (25 mW).
LMIC_setDrTxpow(DR_SF7, 14);
// register a callback for downlink messages. We aren't trying to write
// reentrant code, so pUserData is NULL.
LMIC_registerRxMessageCb(myRxCallback, NULL);
@ -561,8 +558,8 @@ void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
#if (TIME_SYNC_LORASERVER)
// valid timesync answer -> call timesync processor
if ((port >= TIMEANSWERPORT_MIN) && (port <= TIMEANSWERPORT_MAX)) {
recv_timesync_ans(port, pMsg, nMsg);
if (port == TIMEPORT) {
recv_timesync_ans(pMsg, nMsg);
break;
}
#endif

View File

@ -129,7 +129,7 @@ void setup() {
esp_log_level_set("*", ESP_LOG_NONE);
#endif
ESP_LOGI(TAG, "Starting %s v%s", PRODUCTNAME, PROGVERSION);
ESP_LOGI(TAG, "Starting Software v%s", PROGVERSION);
// print chip information on startup if in verbose mode
#if (VERBOSE)
@ -172,9 +172,9 @@ void setup() {
// open i2c bus
#ifdef HAS_DISPLAY
Wire.begin(MY_OLED_SDA, MY_OLED_SCL, 100000);
Wire.begin(MY_OLED_SDA, MY_OLED_SCL, 400000);
#else
Wire.begin(SDA, SCL, 100000);
Wire.begin(SDA, SCL, 400000);
#endif
// setup power on boards with power management logic
@ -200,7 +200,7 @@ void setup() {
#ifdef HAS_DISPLAY
strcat_P(features, " OLED");
DisplayIsOn = cfg.screenon;
init_display(PRODUCTNAME, PROGVERSION); // note: blocking call
init_display(!cfg.runmode); // note: blocking call
#endif
#ifdef BOARD_HAS_PSRAM

View File

@ -48,25 +48,25 @@ void start_ota_update() {
switch_LED(LED_ON);
// init display
#ifdef HAS_DISPLAY
u8x8.begin();
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.clear();
#ifdef DISPLAY_FLIP
u8x8.setFlipMode(1);
#ifndef DISPLAY_FLIP
oledInit(OLED_128x64, ANGLE_0, false, -1, -1, 400000L);
#else
oledInit(OLED_128x64, ANGLE_FLIPY, false, -1, -1, 400000L);
#endif
u8x8.setInverseFont(1);
u8x8.print("SOFTWARE UPDATE \n");
u8x8.setInverseFont(0);
u8x8.print("WiFi connect ..\n");
u8x8.print("Has Update? ..\n");
u8x8.print("Fetching ..\n");
u8x8.print("Downloading ..\n");
u8x8.print("Rebooting ..");
oledFill(0, 1);
dp_printf(0, 0, 0, 1, "SOFTWARE UPDATE");
dp_printf(0, 1, 0, 0, "WiFi connect ..");
dp_printf(0, 2, 0, 0, "Has Update? ..");
dp_printf(0, 3, 0, 0, "Fetching ..");
dp_printf(0, 4, 0, 0, "Downloading ..");
dp_printf(0, 5, 0, 0, "Rebooting ..");
oledDumpBuffer(NULL);
#endif
ESP_LOGI(TAG, "Starting Wifi OTA update");
display(1, "**", WIFI_SSID);
ota_display(1, "**", WIFI_SSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
@ -81,7 +81,7 @@ void start_ota_update() {
if (WiFi.status() == WL_CONNECTED) {
// we now have wifi connection and try to do an OTA over wifi update
ESP_LOGI(TAG, "Connected to %s", WIFI_SSID);
display(1, "OK", "WiFi connected");
ota_display(1, "OK", "WiFi connected");
// do a number of tries to update firmware limited by OTA_MAX_TRY
uint8_t j = OTA_MAX_TRY;
while ((j--) && (ret > 0)) {
@ -97,13 +97,13 @@ void start_ota_update() {
// wifi did not connect
ESP_LOGI(TAG, "Could not connect to %s", WIFI_SSID);
display(1, " E", "no WiFi connect");
ota_display(1, " E", "no WiFi connect");
delay(5000);
end:
switch_LED(LED_OFF);
ESP_LOGI(TAG, "Rebooting to %s firmware", (ret == 0) ? "new" : "current");
display(5, "**", ""); // mark line rebooting
ota_display(5, "**", ""); // mark line rebooting
delay(5000);
ESP.restart();
@ -119,7 +119,7 @@ int do_ota_update() {
// Fetch the latest firmware version
ESP_LOGI(TAG, "Checking latest firmware version on server");
display(2, "**", "checking version");
ota_display(2, "**", "checking version");
if (WiFi.status() != WL_CONNECTED)
return 1;
@ -128,23 +128,23 @@ int do_ota_update() {
if (latest.length() == 0) {
ESP_LOGI(TAG, "Could not fetch info on latest firmware");
display(2, " E", "file not found");
ota_display(2, " E", "file not found");
return -1;
} else if (version_compare(latest, cfg.version) <= 0) {
ESP_LOGI(TAG, "Current firmware is up to date");
display(2, "NO", "no update found");
ota_display(2, "NO", "no update found");
return -1;
}
ESP_LOGI(TAG, "New firmware version v%s available", latest.c_str());
display(2, "OK", latest.c_str());
ota_display(2, "OK", latest.c_str());
display(3, "**", "");
ota_display(3, "**", "");
if (WiFi.status() != WL_CONNECTED)
return 1;
String firmwarePath = bintray.getBinaryPath(latest);
if (!firmwarePath.endsWith(".bin")) {
ESP_LOGI(TAG, "Unsupported binary format");
display(3, " E", "file type error");
ota_display(3, " E", "file type error");
return -1;
}
@ -158,7 +158,7 @@ int do_ota_update() {
if (!client.connect(currentHost.c_str(), port)) {
ESP_LOGI(TAG, "Cannot connect to %s", currentHost.c_str());
display(3, " E", "connection lost");
ota_display(3, " E", "connection lost");
goto abort;
}
@ -169,7 +169,7 @@ int do_ota_update() {
if (!client.connect(currentHost.c_str(), port)) {
ESP_LOGI(TAG, "Redirect detected, but cannot connect to %s",
currentHost.c_str());
display(3, " E", "server error");
ota_display(3, " E", "server error");
goto abort;
}
}
@ -185,7 +185,7 @@ int do_ota_update() {
while (client.available() == 0) {
if ((millis() - timeout) > (RESPONSE_TIMEOUT_MS)) {
ESP_LOGI(TAG, "Client timeout");
display(3, " E", "client timeout");
ota_display(3, " E", "client timeout");
goto abort;
}
}
@ -243,12 +243,12 @@ int do_ota_update() {
} // while (client.available())
} // while (redirect)
display(3, "OK", ""); // line download
ota_display(3, "OK", ""); // line download
// check whether we have everything for OTA update
if (!(contentLength && isValidContentType)) {
ESP_LOGI(TAG, "Invalid OTA server response");
display(4, " E", "response error");
ota_display(4, " E", "response error");
goto retry;
}
@ -262,7 +262,7 @@ int do_ota_update() {
if (!Update.begin(contentLength)) {
#endif
ESP_LOGI(TAG, "Not enough space to start OTA update");
display(4, " E", "disk full");
ota_display(4, " E", "disk full");
goto abort;
}
@ -271,13 +271,13 @@ int do_ota_update() {
Update.onProgress(&show_progress);
#endif
display(4, "**", "writing...");
ota_display(4, "**", "writing...");
written = Update.writeStream(client); // this is a blocking call
if (written == contentLength) {
ESP_LOGI(TAG, "Written %u bytes successfully", written);
snprintf(buf, 17, "%ukB Done!", (uint16_t)(written / 1024));
display(4, "OK", buf);
ota_display(4, "OK", buf);
} else {
ESP_LOGI(TAG, "Written only %u of %u bytes, OTA update attempt cancelled",
written, contentLength);
@ -288,7 +288,7 @@ int do_ota_update() {
} else {
ESP_LOGI(TAG, "An error occurred. Error#: %d", Update.getError());
snprintf(buf, 17, "Error#: %d", Update.getError());
display(4, " E", buf);
ota_display(4, " E", buf);
goto retry;
}
@ -307,27 +307,26 @@ retry:
} // do_ota_update
void display(const uint8_t row, const std::string status,
void ota_display(const uint8_t row, const std::string status,
const std::string msg) {
#ifdef HAS_DISPLAY
u8x8.setCursor(14, row);
u8x8.print((status.substr(0, 2)).c_str());
dp_printf(112, row, 0, 0, status.substr(0, 2).c_str());
if (!msg.empty()) {
u8x8.clearLine(7);
u8x8.setCursor(0, 7);
u8x8.print(msg.substr(0, 16).c_str());
dp_printf(0, 7, 0, 0, " ");
dp_printf(0, 7, 0, 0, msg.substr(0, 16).c_str());
}
oledDumpBuffer(NULL);
#endif
}
#ifdef HAS_DISPLAY
// callback function to show download progress while streaming data
void show_progress(unsigned long current, unsigned long size) {
#ifdef HAS_DISPLAY
char buf[17];
snprintf(buf, 17, "%-9lu (%3lu%%)", current, current * 100 / size);
display(4, "**", buf);
}
ota_display(4, "**", buf);
#endif
}
// helper function to convert strings into lower case
bool comp(char s1, char s2) { return tolower(s1) < tolower(s2); }

View File

@ -6,14 +6,13 @@
//
// Note: After editing, before "build", use "clean" button in PlatformIO!
#define PRODUCTNAME "PAXCNT"
// Verbose enables serial output
#define VERBOSE 1 // set to 0 to silence the device, for mute use build option
// Payload send cycle and encoding
#define SENDCYCLE 30 // payload send cycle [seconds/2], 0 .. 255
#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
// 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
@ -48,14 +47,15 @@
#define MEM_LOW 2048 // [Bytes] low memory threshold triggering a send cycle
#define RETRANSMIT_RCMD 5 // [seconds] wait time before retransmitting rcommand results
#define PAYLOAD_BUFFER_SIZE 51 // maximum size of payload block per transmit
#define LORADRDEFAULT 5 // 0 .. 15, LoRaWAN datarate, according to regional LoRaWAN specs [default = 5]
#define LORATXPOWDEFAULT 7 // 0 .. 255, LoRaWAN TX power in dBm [default = 14]
#define LORADRDEFAULT 3 // 0 .. 15, LoRaWAN datarate, according to regional LoRaWAN specs [use 3 for TTN in EU]
#define LORATXPOWDEFAULT 14 // 0 .. 255, LoRaWAN TX power in dBm [default = 14]
#define MAXLORARETRY 500 // maximum count of TX retries if LoRa busy
#define SEND_QUEUE_SIZE 10 // maximum number of messages in payload send queue [1 = no queue]
// Hardware settings
#define RGBLUMINOSITY 30 // RGB LED luminosity [default = 30%]
#define DISPLAYREFRESH_MS 40 // OLED refresh cycle in ms [default = 40] -> 1000/40 = 25 frames per second
#define DISPLAYCONTRAST 80 // 0 .. 255, OLED display contrast [default = 80]
#define HOMECYCLE 30 // house keeping cycle in seconds [default = 30 secs]
// Settings for BME680 environmental sensor
@ -99,7 +99,6 @@
#define BATTPORT 8 // battery voltage
#define TIMEPORT 9 // time query and response
#define TIMEDIFFPORT 13 // time adjust diff
#define TIMEREQUEST_MAX_SEQNO 250 // time answer, start of port range
#define SENSOR1PORT 10 // user sensor #1
#define SENSOR2PORT 11 // user sensor #2
#define SENSOR3PORT 12 // user sensor #3

View File

@ -11,12 +11,7 @@ AXP20X_Class pmu;
void power_event_IRQ(void) {
if (!I2C_MUTEX_LOCK())
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
pmu.readIRQ();
// put your power event handler code here
if (pmu.isVbusOverVoltageIRQ())
ESP_LOGI(TAG, "USB voltage %.2fV too high.", pmu.getVbusVoltage() / 1000);
@ -51,8 +46,6 @@ void power_event_IRQ(void) {
}
pmu.clearIRQ();
I2C_MUTEX_UNLOCK();
} // mutex
// refresh stored voltage value
read_voltage();
@ -75,10 +68,6 @@ void AXP192_power(bool on) {
void AXP192_showstatus(void) {
if (!I2C_MUTEX_LOCK())
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
if (pmu.isBatteryConnect())
if (pmu.isChargeing())
ESP_LOGI(TAG, "Battery charging, %.2fV @ %.0fmAh",
@ -93,17 +82,11 @@ void AXP192_showstatus(void) {
pmu.getVbusVoltage() / 1000 * pmu.getVbusCurrent());
else
ESP_LOGI(TAG, "USB not present");
I2C_MUTEX_UNLOCK();
} // mutex
}
void AXP192_init(void) {
// block i2c bus access
if (I2C_MUTEX_LOCK()) {
if (pmu.begin(Wire, AXP192_PRIMARY_ADDRESS))
if (pmu.begin(i2c_readBytes, i2c_writeBytes, AXP192_PRIMARY_ADDRESS) == AXP_FAIL)
ESP_LOGI(TAG, "AXP192 PMU initialization failed");
else {
@ -121,8 +104,6 @@ void AXP192_init(void) {
// switch power rails on
AXP192_power(true);
// I2C access of AXP202X library currently is not mutexable
// so we better should disable AXP interrupts... ?
#ifdef PMU_INT
pinMode(PMU_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PMU_INT), PMUIRQ, FALLING);
@ -135,10 +116,55 @@ void AXP192_init(void) {
ESP_LOGI(TAG, "AXP192 PMU initialized");
}
I2C_MUTEX_UNLOCK(); // release i2c bus access
} else
ESP_LOGE(TAG, "I2c bus busy - PMU initialization error");
}
// helper functions for mutexing i2c access
uint8_t i2c_readBytes(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len) {
if (I2C_MUTEX_LOCK()) {
uint8_t ret = 0;
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.endTransmission(false);
uint8_t cnt = Wire.requestFrom(addr, (uint8_t)len, (uint8_t)1);
if (!cnt) {
ret = 0xFF;
}
uint16_t index = 0;
while (Wire.available()) {
if (index > len)
return 0xFF;
data[index++] = Wire.read();
}
I2C_MUTEX_UNLOCK(); // release i2c bus access
return ret;
} else {
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
return 0xFF;
}
}
uint8_t i2c_writeBytes(uint8_t addr, uint8_t reg, uint8_t *data, uint8_t len) {
if (I2C_MUTEX_LOCK()) {
uint8_t ret = 0;
Wire.beginTransmission(addr);
Wire.write(reg);
for (uint16_t i = 0; i < len; i++) {
Wire.write(data[i]);
}
ret = Wire.endTransmission();
I2C_MUTEX_UNLOCK(); // release i2c bus access
return ret ? 0xFF : ret;
//return ret ? ret : 0xFF;
} else {
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
return 0xFF;
}
}
#endif // HAS_PMU
#ifdef BAT_MEASURE_ADC
@ -196,26 +222,28 @@ uint16_t read_voltage() {
uint16_t voltage = 0;
#ifdef HAS_PMU
if (!I2C_MUTEX_LOCK())
ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
else {
// if (!I2C_MUTEX_LOCK())
// ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
// else {
voltage = pmu.isVBUSPlug() ? 0xffff : pmu.getBattVoltage();
I2C_MUTEX_UNLOCK();
}
// I2C_MUTEX_UNLOCK();
// }
#else
#ifdef BAT_MEASURE_ADC
// multisample ADC
uint32_t adc_reading = 0;
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_reading += adc1_get_raw(adc_channel);
}
#else // ADC2
int adc_buf = 0;
for (int i = 0; i < NO_OF_SAMPLES; i++) {
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
adc_reading += adc1_get_raw(adc_channel);
#else // ADC2
ESP_ERROR_CHECK(adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf));
adc_reading += adc_buf;
#endif
}
#endif
adc_reading /= NO_OF_SAMPLES;
// Convert ADC reading to voltage in mV
voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_characs);

View File

@ -191,7 +191,7 @@ void set_loradr(uint8_t val[]) {
if (validDR(val[0])) {
cfg.loradr = val[0];
ESP_LOGI(TAG, "Remote command: set LoRa Datarate to %d", cfg.loradr);
LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower);
LMIC_setDrTxpow(assertDR(cfg.loradr), KEEP_TXPOW);
ESP_LOGI(TAG, "Radio parameters now %s / %s / %s",
getSfName(updr2rps(LMIC.datarate)),
getBwName(updr2rps(LMIC.datarate)),

View File

@ -75,6 +75,10 @@ void sendData() {
get_salt(); // get new salt for salting hashes
ESP_LOGI(TAG, "Counter cleared");
}
#ifdef HAS_DISPLAY
else
oledPlotCurve(macs.size(), true);
#endif
break;
#endif

View File

@ -26,7 +26,7 @@ typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
TaskHandle_t timeSyncReqTask = NULL;
static uint8_t time_sync_seqNo = -1;
static uint8_t time_sync_seqNo = (uint8_t)random(TIMEREQUEST_MAX_SEQNO);
static bool timeSyncPending = false;
static myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
static myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
@ -155,18 +155,30 @@ void store_time_sync_req(uint32_t timestamp) {
timestamp % 1000);
}
// process timeserver timestamp answer, called from lorawan.cpp
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
uint8_t seq_no = buf[0];
buf++;
// process timeserver timestamp answer, called by myRxCallback() in lorawan.cpp
int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) {
/*
parse 7 byte timesync_answer:
byte meaning
1 sequence number (taken from node's time_sync_req)
2 timezone in 15 minutes steps
3..6 current second (from epoch time 1970)
7 1/250ths fractions of current second
*/
// if no timesync handshake is pending then exit
if (!timeSyncPending)
return 0; // failure
// extract 1 byte timerequest sequence number from buffer
uint8_t seq_no = buf[0];
buf++;
// if no time is available or spurious buffer then exit
if (buf_len != TIME_SYNC_FRAME_LENGTH) {
if (buf[0] == 0xff)
if (seq_no == 0xff)
ESP_LOGI(TAG, "[%0.3f] Timeserver error: no confident time available",
millis() / 1000.0);
else
@ -178,19 +190,27 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
else { // we received a probably valid time frame
uint8_t k = seq_no % TIME_SYNC_SAMPLES;
// the 5th byte contains the fractional seconds in 2^-8 second steps
// (= 1/250th sec), we convert this to ms
uint16_t timestamp_msec = 4 * buf[4];
// pointers to 4 bytes containing UTC seconds since unix epoch, msb
uint32_t timestamp_sec, *timestamp_ptr;
// convert buffer to uint32_t, octet order is big endian
// extract 1 byte timezone from buffer (one step being 15min * 60s = 900s)
uint32_t timezone_sec = buf[0] * 900;
buf++;
// extract 4 bytes timestamp from buffer
// and convert it to uint32_t, octet order is big endian
timestamp_ptr = (uint32_t *)buf;
// swap byte order from msb to lsb, note: this is platform dependent
timestamp_sec = __builtin_bswap32(*timestamp_ptr);
buf += 4;
// extract 1 byte fractional seconds in 2^-8 second steps
// (= 1/250th sec), we convert this to ms
uint16_t timestamp_msec = 4 * buf[0];
// construct the timepoint when message was seen on gateway
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec);
time_sync_rx[k] +=
seconds(timestamp_sec + timezone_sec) + milliseconds(timestamp_msec);
// we guess timepoint is recent if it newer than code compile date
if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) {