diff --git a/esp32/README.md b/esp32/README.md new file mode 100644 index 0000000..23877e8 --- /dev/null +++ b/esp32/README.md @@ -0,0 +1,4 @@ +```shell +git clone https://github.com/rweather/arduinolibs +cp -r arduinolibs/libraries/Crypto Freematics/firmware_v5/telelogger/lib +``` \ No newline at end of file diff --git a/esp32/telelogger/.gitignore b/esp32/telelogger/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/esp32/telelogger/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/esp32/telelogger/Kconfig.projbuild b/esp32/telelogger/Kconfig.projbuild new file mode 100644 index 0000000..eaa6c25 --- /dev/null +++ b/esp32/telelogger/Kconfig.projbuild @@ -0,0 +1,473 @@ +menu "Telelogger Configuration" + + config ENABLE_OBD + bool "Enable OBD Connection" + default "y" + + config ENABLE_MEMS + bool "Enable MEMS Motion Sensor" + default "y" + + choice STORAGE + bool "Storage Option" + default STORAGE_SD + config STORAGE_NONE + bool "No Storage" + config STORAGE_SPIFFS + bool "SPIFFS" + config STORAGE_SD + bool "MicroSD" + endchoice + + config STORAGE + int + default 0 if STORAGE_NONE + default 1 if STORAGE_SPIFFS + default 2 if STORAGE_SD + + choice GNSS + bool "GNSS Option" + default GNSS_INTERNAL + config GNSS_NONE + bool "No GNSS" + config GNSS_INTERNAL + bool "Internal GNSS" + config GNSS_EXTERNAL + bool "External GNSS" + config GNSS_CELLULAR + bool "Cellular GNSS" + + endchoice + + config GNSS + int + default 0 if GNSS_NONE + default 1 if GNSS_INTERNAL + default 2 if GNSS_EXTERNAL + default 3 if GNSS_CELLULAR + + config BOARD_HAS_PSRAM + bool "Enable PSRAM" + default "n" + help + Enabling PSRAM and use it for data buffer + + config ENABLE_BLE + bool "Enable BLE" + default "n" + help + Enabling BLE GATT server working with Freematics Controller app + + config ENABLE_WIFI + bool "Enable Wi-Fi" + default "n" + help + Enabling seamless Wi-Fi and cellular network co-working + + config WIFI_SSID + string "WiFi Hotspot SSID" + default "" + + config WIFI_PASSWORD + string "WiFi Hotspot Password" + default "" + + config ENABLE_HTTPD + bool "Enable Wi-Fi AP and HTTPd" + default "n" + help + Enabling Wi-Fi AP and HTTPd + + config CELL_APN + string "Cellular Network APN" + default "" + help + If left blank, APN is obtained automatically if supported + + choice SERVER_PROTOCOL + bool "Server Transport Protocol" + default PROTOCOL_UDP + config PROTOCOL_UDP + bool "UDP Protocol" + config PROTOCOL_HTTP + bool "HTTP Protocol" + config PROTOCOL_HTTPS + bool "HTTPS Protocol" + endchoice + + config SERVER_PROTOCOL + int + default 1 if PROTOCOL_UDP + default 2 if PROTOCOL_HTTP + default 3 if PROTOCOL_HTTPS + + config SERVER_HOST + string "Server Host/IP" + default "hub.freematics.com" + + config SERVER_PORT + int "Server Port (0 for auto)" + range 0 65535 + default 0 + +endmenu + +menu "Arduino Configuration" + + config ENABLE_ARDUINO_DEPENDS + bool + select LWIP_SO_RCVBUF + select ETHERNET + select WIFI_ENABLED + select ESP32_PHY_CALIBRATION_AND_DATA_STORAGE if IDF_TARGET_ESP32 + select MEMMAP_SMP + default "y" + + config AUTOSTART_ARDUINO + bool "Autostart Arduino setup and loop on boot" + default "y" + help + Enabling this option will implement app_main and start Arduino. + All you need to implement in your main.cpp is setup() and loop() + and include Arduino.h + If disabled, you can call initArduino() to run any preparations + required by the framework + + choice ARDUINO_RUNNING_CORE + bool "Core on which Arduino's setup() and loop() are running" + default ARDUINO_RUN_CORE1 + help + Select on which core Arduino's setup() and loop() functions run + + config ARDUINO_RUN_CORE0 + bool "CORE 0" + config ARDUINO_RUN_CORE1 + bool "CORE 1" + config ARDUINO_RUN_NO_AFFINITY + bool "BOTH" + + endchoice + + config ARDUINO_RUNNING_CORE + int + default 0 if ARDUINO_RUN_CORE0 + default 1 if ARDUINO_RUN_CORE1 + default -1 if ARDUINO_RUN_NO_AFFINITY + + config ARDUINO_LOOP_STACK_SIZE + int "Loop thread stack size" + default 8192 + help + Amount of stack available for the Arduino task. + + choice ARDUINO_EVENT_RUNNING_CORE + bool "Core on which Arduino's event handler is running" + default ARDUINO_EVENT_RUN_CORE1 + help + Select on which core Arduino's WiFi.onEvent() run + + config ARDUINO_EVENT_RUN_CORE0 + bool "CORE 0" + config ARDUINO_EVENT_RUN_CORE1 + bool "CORE 1" + config ARDUINO_EVENT_RUN_NO_AFFINITY + bool "BOTH" + + endchoice + + config ARDUINO_EVENT_RUNNING_CORE + int + default 0 if ARDUINO_EVENT_RUN_CORE0 + default 1 if ARDUINO_EVENT_RUN_CORE1 + default -1 if ARDUINO_EVENT_RUN_NO_AFFINITY + + choice ARDUINO_UDP_RUNNING_CORE + bool "Core on which Arduino's UDP is running" + default ARDUINO_UDP_RUN_CORE1 + help + Select on which core Arduino's UDP run + + config ARDUINO_UDP_RUN_CORE0 + bool "CORE 0" + config ARDUINO_UDP_RUN_CORE1 + bool "CORE 1" + config ARDUINO_UDP_RUN_NO_AFFINITY + bool "BOTH" + + endchoice + + config ARDUINO_UDP_TASK_PRIORITY + int "Priority of the UDP task" + default 3 + help + Select at what priority you want the UDP task to run. + + config ARDUINO_UDP_RUNNING_CORE + int + default 0 if ARDUINO_UDP_RUN_CORE0 + default 1 if ARDUINO_UDP_RUN_CORE1 + default -1 if ARDUINO_UDP_RUN_NO_AFFINITY + + config ARDUINO_ISR_IRAM + bool "Run interrupts in IRAM" + default "n" + help + Enabling this option will Attach all interrupts with the IRAm flag. + It will also make some HAL function, like, digitalRead/Write and more + be loaded into IRAM for access inside ISRs. + Beware that this is a very dangerous setting. Enable it only if you + are fully aware of the consequences. + + config DISABLE_HAL_LOCKS + bool "Disable mutex locks for HAL" + default "n" + help + Enabling this option will run all hardware abstraction without locks. + While communication with external hardware will be faster, you need to + make sure that there is no option to use the same bus from another thread + or interrupt at the same time. Option is best used with Arduino enabled + and code implemented only in setup/loop and Arduino callbacks + + menu "Debug Log Configuration" + choice ARDUHAL_LOG_DEFAULT_LEVEL + bool "Default log level" + default ARDUHAL_LOG_DEFAULT_LEVEL_ERROR + help + Specify how much output to see in logs by default. + + config ARDUHAL_LOG_DEFAULT_LEVEL_NONE + bool "No output" + config ARDUHAL_LOG_DEFAULT_LEVEL_ERROR + bool "Error" + config ARDUHAL_LOG_DEFAULT_LEVEL_WARN + bool "Warning" + config ARDUHAL_LOG_DEFAULT_LEVEL_INFO + bool "Info" + config ARDUHAL_LOG_DEFAULT_LEVEL_DEBUG + bool "Debug" + config ARDUHAL_LOG_DEFAULT_LEVEL_VERBOSE + bool "Verbose" + endchoice + + config ARDUHAL_LOG_DEFAULT_LEVEL + int + default 0 if ARDUHAL_LOG_DEFAULT_LEVEL_NONE + default 1 if ARDUHAL_LOG_DEFAULT_LEVEL_ERROR + default 2 if ARDUHAL_LOG_DEFAULT_LEVEL_WARN + default 3 if ARDUHAL_LOG_DEFAULT_LEVEL_INFO + default 4 if ARDUHAL_LOG_DEFAULT_LEVEL_DEBUG + default 5 if ARDUHAL_LOG_DEFAULT_LEVEL_VERBOSE + + config ARDUHAL_LOG_COLORS + bool "Use ANSI terminal colors in log output" + default "n" + help + Enable ANSI terminal color codes in bootloader output. + In order to view these, your terminal program must support ANSI color codes. + + config ARDUHAL_ESP_LOG + bool "Forward ESP_LOGx to Arduino log output" + default "n" + help + This option will redefine the ESP_LOGx macros to Arduino's log_x macros. + To enable for your application, add the follwing after your includes: + #ifdef ARDUINO_ARCH_ESP32 + #include "esp32-hal-log.h" + #endif + + endmenu + + choice ARDUHAL_PARTITION_SCHEME + bool "Used partition scheme" + default ARDUHAL_PARTITION_SCHEME_DEFAULT + help + Specify which partition scheme to be used. + + config ARDUHAL_PARTITION_SCHEME_DEFAULT + bool "Default" + config ARDUHAL_PARTITION_SCHEME_MINIMAL + bool "Minimal (for 2MB FLASH)" + config ARDUHAL_PARTITION_SCHEME_NO_OTA + bool "No OTA (for large apps)" + config ARDUHAL_PARTITION_SCHEME_HUGE_APP + bool "Huge App (for very large apps)" + config ARDUHAL_PARTITION_SCHEME_MIN_SPIFFS + bool "Minimal SPIFFS (for large apps with OTA)" + endchoice + + config ARDUHAL_PARTITION_SCHEME + string + default "default" if ARDUHAL_PARTITION_SCHEME_DEFAULT + default "minimal" if ARDUHAL_PARTITION_SCHEME_MINIMAL + default "no_ota" if ARDUHAL_PARTITION_SCHEME_NO_OTA + default "huge_app" if ARDUHAL_PARTITION_SCHEME_HUGE_APP + default "min_spiffs" if ARDUHAL_PARTITION_SCHEME_MIN_SPIFFS + + + config AUTOCONNECT_WIFI + bool "Autoconnect WiFi on boot" + default "n" + depends on AUTOSTART_ARDUINO + select ARDUINO_SELECTIVE_WiFi + help + If enabled, WiFi will connect to the last used SSID (if station was enabled), + else connection will be started only after calling WiFi.begin(ssid, password) + + config ARDUINO_SELECTIVE_COMPILATION + bool "Include only specific Arduino libraries" + default n + + config ARDUINO_SELECTIVE_ArduinoOTA + bool "Enable ArduinoOTA" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_WiFi + select ARDUINO_SELECTIVE_ESPmDNS + default y + + config ARDUINO_SELECTIVE_AsyncUDP + bool "Enable AsyncUDP" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_AzureIoT + bool "Enable AzureIoT" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_HTTPClient + default y + + config ARDUINO_SELECTIVE_BLE + bool "Enable BLE" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_BluetoothSerial + bool "Enable BluetoothSerial" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_DNSServer + bool "Enable DNSServer" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_WiFi + default y + + config ARDUINO_SELECTIVE_EEPROM + bool "Enable EEPROM" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_ESP32 + bool "Enable ESP32" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_ESPmDNS + bool "Enable ESPmDNS" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_WiFi + default y + + config ARDUINO_SELECTIVE_FFat + bool "Enable FFat" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_FS + default y + + config ARDUINO_SELECTIVE_FS + bool "Enable FS" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_HTTPClient + bool "Enable HTTPClient" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_WiFi + select ARDUINO_SELECTIVE_WiFiClientSecure + default y + + config ARDUINO_SELECTIVE_LITTLEFS + bool "Enable LITTLEFS" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_FS + default y + + config ARDUINO_SELECTIVE_NetBIOS + bool "Enable NetBIOS" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_WiFi + default y + + config ARDUINO_SELECTIVE_Preferences + bool "Enable Preferences" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_SD + bool "Enable SD" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_FS + default y + + config ARDUINO_SELECTIVE_SD_MMC + bool "Enable SD_MMC" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_FS + default y + + config ARDUINO_SELECTIVE_SimpleBLE + bool "Enable SimpleBLE" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_SPI + bool "Enable SPI" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_SPIFFS + bool "Enable SPIFFS" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_FS + default y + + config ARDUINO_SELECTIVE_Ticker + bool "Enable Ticker" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_Update + bool "Enable Update" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_WebServer + bool "Enable WebServer" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + select ARDUINO_SELECTIVE_FS + + config ARDUINO_SELECTIVE_WiFi + bool "Enable WiFi" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + config ARDUINO_SELECTIVE_WiFiClientSecure + bool "Enable WiFiClientSecure" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_WiFi + default y + + config ARDUINO_SELECTIVE_WiFiProv + bool "Enable WiFiProv" + depends on ARDUINO_SELECTIVE_COMPILATION + select ARDUINO_SELECTIVE_WiFi + default y + + config ARDUINO_SELECTIVE_Wire + bool "Enable Wire" + depends on ARDUINO_SELECTIVE_COMPILATION + default y + + + endmenu + \ No newline at end of file diff --git a/esp32/telelogger/README.md b/esp32/telelogger/README.md new file mode 100644 index 0000000..69aa720 --- /dev/null +++ b/esp32/telelogger/README.md @@ -0,0 +1,49 @@ +This Arduino sketch is developed for [Freematics ONE+](https://freematics.com/products/freematics-one-plus/) to collect vehicle telemetry data from OBD, GPS, motion sensor, to log the data in local storage and to transmit the data to a remote server in real-time. It demonstrates most capabilities of Freematics ONE+ and works well with [Traccar](https://www.traccar.org) GPS tracking platform. + +Data Collection +--------------- + +The sketch collects following data. + +* Vehicle OBD data (from OBD port) +* Battery voltage (from OBD port) +* Geolocation data (from internal or external GNSS) +* Accelerometer and gyroscope data (from internal MEMS motion sensor) +* Cellular or WiFi network signal level +* Device temperature + +Collected data are stored in a circular buffer in ESP32's IRAM or PSRAM. When PSRAM is enabled, hours of data can be buffered in case of temporary network outage and transmitted when network connection resumes. + +Data Transmission +----------------- + +Data transmission over UDP and HTTP(s) protocols are implemented for the followings. + +* WiFi (ESP32 built-in) +* 3G WCDMA (SIM5360) +* 4G LTE CAT-4 (SIM7600) +* 4G LTE CAT-M (SIM7070) + +UDP mode implements a telemetry client for [Freematics Hub](https://hub.freematics.com) and [Traccar](https://www.traccar.org). HTTP(s) mode implements [OsmAnd](https://www.traccar.org/osmand/) protocol with additional data sent as POST payload. + +Seamless WiFi and cellular network co-working is implemented. When defined WiFi hotspot is available, data is transmitted via WiFi and cellular module is switched off. When no WiFi hotspot can be reached, cellular module is switched on for data transmission until WiFi hotspot available again. + +Data Storage +------------ + +Following types of data storage are supported. + +* MicroSD card storage +* ESP32 built-in Flash memory storage (SPIFFS) + +BLE & App +--------- + +A BLE SPP server is implemented in [FreematicsPlus](https://github.com/stanleyhuangyc/Freematics/blob/master/libraries/FreematicsPlus) library. To enable BLE support, change ENABLE_BLE to 1 [config.h](config.h). This will enable remote control and data monitoring via [Freematics Controller App](https://freematics.com/software/freematics-controller/). + +Prerequisites +------------- + +* Freematics ONE+ [Model A](https://freematics.com/products/freematics-one-plus/), [Model B](https://freematics.com/products/freematics-one-plus-model-b/), [Model H](https://freematics.com/products/freematics-one-plus-model-h/) +* A micro SIM card if cellular network connectivity required +* [PlatformIO](http://platformio.org/), [Arduino IDE](https://github.com/espressif/arduino-esp32#installation-instructions), [Freematics Builder](https://freematics.com/software/arduino-builder) or [ESP-IDF](https://github.com/espressif/esp-idf) for compiling and uploading code diff --git a/esp32/telelogger/config.h b/esp32/telelogger/config.h new file mode 100644 index 0000000..3d3da3c --- /dev/null +++ b/esp32/telelogger/config.h @@ -0,0 +1,201 @@ +#ifndef CONFIG_H_INCLUDED +#define CONFIG_H_INCLUDED + +#ifdef CONFIG_ENABLE_OBD +#define ENABLE_OBD CONFIG_ENABLE_OBD +#endif +#ifdef CONFIG_ENABLE_MEMS +#define ENABLE_MEMS CONFIG_ENABLE_MEMS +#endif +#ifdef CONFIG_GNSS +#define GNSS CONFIG_GNSS +#endif +#ifdef CONFIG_STORAGE +#define STORAGE CONFIG_STORAGE +#endif +#ifdef CONFIG_BOARD_HAS_PSRAM +#define BOARD_HAS_PSRAM 1 +#endif +#ifdef CONFIG_ENABLE_WIFI +#define ENABLE_WIFI CONFIG_ENABLE_WIFI +#define WIFI_SSID CONFIG_WIFI_SSID +#define WIFI_PASSWORD CONFIG_WIFI_PASSWORD +#endif +#ifdef CONFIG_ENABLE_BLE +#define ENABLE_BLE CONFIG_ENABLE_BLE +#endif +#ifdef CONFIG_ENABLE_HTTPD +#define ENABLE_HTTPD CONFIG_ENABLE_HTTPD +#endif +#ifdef CONFIG_SERVER_HOST +#define SERVER_HOST CONFIG_SERVER_HOST +#define SERVER_PORT CONFIG_SERVER_PORT +#define SERVER_PROTOCOL CONFIG_SERVER_PROTOCOL +#endif +#ifdef CONFIG_CELL_APN +#define CELL_APN CONFIG_CELL_APN +#endif + +/************************************** +* Circular Buffer Configuration +**************************************/ +#if BOARD_HAS_PSRAM +#define BUFFER_SLOTS 1024 /* max number of buffer slots */ +#define BUFFER_LENGTH 384 /* bytes per slot */ +#define SERIALIZE_BUFFER_SIZE 4096 /* bytes */ +#else +#define BUFFER_SLOTS 32 /* max number of buffer slots */ +#define BUFFER_LENGTH 256 /* bytes per slot */ +#define SERIALIZE_BUFFER_SIZE 1024 /* bytes */ +#endif + +/************************************** +* Configuration Definitions +**************************************/ +#define STORAGE_NONE 0 +#define STORAGE_SPIFFS 1 +#define STORAGE_SD 2 + +#define GNSS_NONE 0 +#define GNSS_STANDALONE 1 +#define GNSS_CELLULAR 2 + +#define PROTOCOL_UDP 1 +#define PROTOCOL_HTTP 2 +#define PROTOCOL_HTTPS 3 + +#define PROTOCOL_METHOD_GET 0 +#define PROTOCOL_METHOD_POST 1 + +/************************************** +* OBD-II configurations +**************************************/ +#ifndef ENABLE_OBD +#define ENABLE_OBD 1 +#endif + +// maximum consecutive OBD access errors before entering standby +#define MAX_OBD_ERRORS 3 + +/************************************** +* Networking configurations +**************************************/ +#ifndef ENABLE_WIFI +#define ENABLE_WIFI 1 +// WiFi settings +#define WIFI_SSID "example" +#define WIFI_PASSWORD "1234" +#endif + +#ifndef SERVER_HOST +// cellular network settings +#define CELL_APN "hologram" +// Freematics Hub server settings +#define SERVER_HOST "192.168.1.114" +#define SERVER_PROTOCOL PROTOCOL_UDP +#endif + +#define SERVER_ENCRYPTION_ENABLE 1 +#define CHACHA20_KEY "d38a3b96a26d0b1139bd30c174884f5dbc8eaaf492493725633ecebfa4ab19e9" + +#ifndef CONFIG_MBEDTLS_CHACHAPOLY_C +#define CONFIG_MBEDTLS_CHACHAPOLY_C y +#endif + +// SIM card setting +#define SIM_CARD_PIN "" + +// HTTPS settings +#define SERVER_METHOD PROTOCOL_METHOD_POST +#define SERVER_PATH "/hub/api" + +#if !SERVER_PORT +#undef SERVER_PORT +#if SERVER_PROTOCOL == PROTOCOL_UDP +#define SERVER_PORT 5171 +#elif SERVER_PROTOCOL == PROTOCOL_HTTP +#define SERVER_PORT 80 +#elif SERVER_PROTOCOL == PROTOCOL_HTTPS +#define SERVER_PORT 443 +#endif +#endif + +// WiFi Mesh settings +#define WIFI_MESH_ID "123456" +#define WIFI_MESH_CHANNEL 13 + +// WiFi AP settings +#define WIFI_AP_SSID "TELELOGGER" +#define WIFI_AP_PASSWORD "PASSWORD" + +// maximum consecutive communication errors before resetting network +#define MAX_CONN_ERRORS_RECONNECT 5 +// maximum allowed connecting time +#define MAX_CONN_TIME 10000 /* ms */ +// data receiving timeout +#define DATA_RECEIVING_TIMEOUT 5000 /* ms */ +// expected maximum server sync signal interval +#define SERVER_SYNC_INTERVAL 120 /* seconds, 0 to disable */ +// data interval settings +#define STATIONARY_TIME_TABLE {10, 60, 180} /* seconds */ +#define DATA_INTERVAL_TABLE {1000, 2000, 5000} /* ms */ +#define PING_BACK_INTERVAL 900 /* seconds */ +#define SIGNAL_CHECK_INTERVAL 10 /* seconds */ + +/************************************** +* Data storage configurations +**************************************/ +#ifndef STORAGE +// change the following line to change storage type +#define STORAGE STORAGE_NONE +#endif + +/************************************** +* MEMS sensors +**************************************/ +#ifndef ENABLE_MEMS +#define ENABLE_MEMS 1 +#endif + +/************************************** +* GPS +**************************************/ +#ifndef GNSS +// change the following line to change GNSS setting +#define GNSS GNSS_STANDALONE +#endif +// keeping GNSS power on during standby +#define GNSS_ALWAYS_ON 0 +// GNSS reset timeout while no signal +#define GNSS_RESET_TIMEOUT 300 /* seconds */ + +/************************************** +* Standby/wakeup +**************************************/ +// motion threshold for waking up +#define MOTION_THRESHOLD 0.4f /* vehicle motion threshold in G */ +// engine jumpstart voltage for waking up (when MEMS unavailable) +#define JUMPSTART_VOLTAGE 14 /* V */ +// reset device after waking up +#define RESET_AFTER_WAKEUP 1 + +/************************************** +* Additional features +**************************************/ +#define PIN_SENSOR1 34 +#define PIN_SENSOR2 26 + +#define COOLING_DOWN_TEMP 75 /* celsius degrees */ + +// enable(1)/disable(0) http server +#ifndef ENABLE_HTTPD +#define ENABLE_HTTPD 0 +#endif + +// enable(1)/disable(0) BLE SPP server (for Freematics Controller App). +#ifndef ENABLE_BLE +#define ENABLE_BLE 0 +#endif + + +#endif // CONFIG_H_INCLUDED diff --git a/esp32/telelogger/config.xml b/esp32/telelogger/config.xml new file mode 100644 index 0000000..900c2f7 --- /dev/null +++ b/esp32/telelogger/config.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/esp32/telelogger/dashboard/cross.png b/esp32/telelogger/dashboard/cross.png new file mode 100644 index 0000000..b3829c9 Binary files /dev/null and b/esp32/telelogger/dashboard/cross.png differ diff --git a/esp32/telelogger/dashboard/dashboard.js b/esp32/telelogger/dashboard/dashboard.js new file mode 100644 index 0000000..4c39f96 --- /dev/null +++ b/esp32/telelogger/dashboard/dashboard.js @@ -0,0 +1,126 @@ +var source; +var origin; + +window.addEventListener("message", receiveMessage, false); + +function receiveMessage(event) { + //event.source.postMessage(event.data,event.origin); + source = event.source; + origin = event.origin; + processInput(event.data); +} + +function sendMessage(data) { + if (source) source.postMessage(data, origin); +} + +function checkData(data, key) +{ + var i; + if ((i = data.lastIndexOf(key)) >= 0) { + var j = data.indexOf("\r", i); + return j >= 0 ? data.substr(i + key.length, j - i - key.length) : data.substr(i + key.length); + } + return null; +} + +var imgTick = ""; +var imgCross = ""; + +var con = ""; +var inited = false; + +function processInput(data) +{ + var i; + var ret; + if (con.length > 1024) con = con.substr(512); + con += data; + if (!inited) { + if (ret = checkData(con, "FLASH:")) { + document.getElementById("flash_size").innerText = ret; + } + if (ret = checkData(con, "PSRAM:")) { + document.getElementById("psram_size").innerText = ret.substr(0, 2) == "E " ? "N/A" : ret; + } + if (ret = checkData(con, "TYPE:")) { + var type = parseInt(ret); + var typeName = "Unknown"; + if (type == 11 || type == 16) { + typeName = "Freematics ONE+ Model A"; + } else if (type >= 12 && type <= 14) { + typeName = "Freematics ONE+ Model B"; + } else if (type == 15) { + typeName = "Freematics ONE+ Model H"; + } + document.getElementById("devtype").innerText = typeName; + } + if (ret = checkData(con, "DEVICE ID:")) { + document.getElementById("devid").value = ret; + } + if (ret = checkData(con, "RTC:")) { + document.getElementById("rtc").innerText = ret; + } + if (ret = checkData(con, "SD:")) { + document.getElementById("sd_size").innerHTML = ret; + } + if (ret = checkData(con, "NO SD CARD") != null) { + document.getElementById("sd_size").innerHTML = "NO CARD"; + } + if (ret = checkData(con, "GNSS:")) { + document.getElementById("gps").innerHTML = ret.indexOf("NO") >= 0 ? imgCross : imgTick; + } + if (ret = checkData(con, "OBD:")) { + document.getElementById("obd").innerHTML = ret.indexOf("NO") >= 0 ? imgCross : imgTick; + } + if (ret = checkData(con, "MEMS:")) { + document.getElementById("mems").innerHTML = ret.indexOf("NO") >= 0 ? imgCross : (imgTick + " " + ret); + } + if (ret = checkData(con, "HTTPD:")) { + document.getElementById("wifi").innerHTML = ret.indexOf("NO") >= 0 ? imgCross : imgTick; + } + if (ret = checkData(con, "WiFi IP:")) { + document.getElementById("wifi").innerHTML = imgTick + " IP:" + ret; + } + if (ret = checkData(con, "IMEI:")) { + document.getElementById("imei").innerText = "IMEI:" + ret; + } + if (ret = checkData(con, "CELL:")) { + document.getElementById("cellinfo").innerHTML = ret == "NO" ? imgCross : (imgTick + " " + ret); + } + if ((ret = checkData(con, "NO SIM CARD")) != null) { + document.getElementById("imei").innerHTML = "| NO SIM CARD"; + } + if (ret = checkData(con, "Operator:")) { + document.getElementById("imei").innerText = "| " + ret; + } + if (ret = checkData(con, "Unable to connect") != null) { + document.getElementById("server").innerHTML = imgCross; + } + if (ret = checkData(con, "LOGIN")) { + document.getElementById("server").innerText = "Connecting to server" + ret; + } + } + if (ret = checkData(con, "[NET]")) { + document.getElementById("server").innerText = ret; + inited = true; + } + if (ret = checkData(con, "[BUF]")) { + document.getElementById("buffer").innerText = ret; + } + if (ret = checkData(con, "[FILE]")) { + document.getElementById("file").innerText = ret; + } + if (ret = checkData(con, "[GPS]")) { + document.getElementById("gps").innerText = ret; + } + if (ret = checkData(con, "[WIFI]")) { + document.getElementById("wifi").innerText = ret; + } + if (ret = checkData(con, "[CELL]")) { + document.getElementById("cell").innerText = ret; + } + if (ret = checkData(con, "RSSI:")) { + document.getElementById("rssi").innerText = "| RSSI:" + ret; + } +} diff --git a/esp32/telelogger/dashboard/index.html b/esp32/telelogger/dashboard/index.html new file mode 100644 index 0000000..89248b6 --- /dev/null +++ b/esp32/telelogger/dashboard/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/esp32/telelogger/dashboard/tick.png b/esp32/telelogger/dashboard/tick.png new file mode 100644 index 0000000..b7eed91 Binary files /dev/null and b/esp32/telelogger/dashboard/tick.png differ diff --git a/esp32/telelogger/dataserver.cpp b/esp32/telelogger/dataserver.cpp new file mode 100644 index 0000000..9ba43ae --- /dev/null +++ b/esp32/telelogger/dataserver.cpp @@ -0,0 +1,353 @@ +/************************************************************************* +* Vehicle Telemetry Data Logger for Freematics ONE+ +* +* Developed by Stanley Huang +* Distributed under BSD license +* Visit https://freematics.com/products/freematics-one-plus for more info +* +* 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. +* +* Implemented HTTP APIs: +* /api/info - device info +* /api/live - live data (OBD/GPS/MEMS) +* /api/control - issue a control command +* /api/list - list of log files +* /api/log/ - raw CSV format log file +* /api/delete/ - delete file +* /api/data/?pid= - JSON array of PID data +*************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" + +#if ENABLE_HTTPD + +#define WIFI_TIMEOUT 5000 + +extern uint32_t fileid; + +extern "C" +{ +uint8_t temprature_sens_read(); +uint32_t hall_sens_read(); +} + +HttpParam httpParam; + +int handlerLiveData(UrlHandlerParam* param); +int handlerControl(UrlHandlerParam* param); + +uint16_t hex2uint16(const char *p); + +int handlerInfo(UrlHandlerParam* param) +{ + char *buf = param->pucBuffer; + int bufsize = param->bufSize; + int bytes = snprintf(buf, bufsize, "{\"httpd\":{\"uptime\":%u,\"clients\":%u,\"requests\":%u,\"traffic\":%u},\n", + (unsigned int)millis(), httpParam.stats.clientCount, (unsigned int)httpParam.stats.reqCount, (unsigned int)(httpParam.stats.totalSentBytes >> 10)); + + time_t now; + time(&now); + struct tm timeinfo = { 0 }; + localtime_r(&now, &timeinfo); + if (timeinfo.tm_year) { + bytes += snprintf(buf + bytes, bufsize - bytes, "\"rtc\":{\"date\":\"%04u-%02u-%02u\",\"time\":\"%02u:%02u:%02u\"},\n", + timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + } + + int deviceTemp = (int)temprature_sens_read() * 165 / 255 - 40; + bytes += snprintf(buf + bytes, bufsize - bytes, "\"cpu\":{\"temperature\":%d,\"magnetic\":%d},\n", + deviceTemp, hall_sens_read()); + +#if STORAGE == STORAGE_SPIFFS + bytes += snprintf(buf + bytes, bufsize - bytes, "\"spiffs\":{\"total\":%u,\"used\":%u}", + SPIFFS.totalBytes(), SPIFFS.usedBytes()); +#else + bytes += snprintf(buf + bytes, bufsize - bytes, "\"sd\":{\"total\":%llu,\"used\":%llu}", + SD.totalBytes(), SD.usedBytes()); +#endif + + if (bytes < bufsize - 1) buf[bytes++] = '}'; + + param->contentLength = bytes; + param->contentType=HTTPFILETYPE_JSON; + return FLAG_DATA_RAW; +} + +class LogDataContext { +public: + File file; + uint32_t tsStart; + uint32_t tsEnd; + uint16_t pid; +}; + +int handlerLogFile(UrlHandlerParam* param) +{ + LogDataContext* ctx = (LogDataContext*)param->hs->ptr; + param->contentType = HTTPFILETYPE_TEXT; + if (ctx) { + if (!param->pucBuffer) { + // connection to be closed, final calling, cleanup + ctx->file.close(); + delete ctx; + param->hs->ptr = 0; + return 0; + } + } else { + int id = 0; + if (param->pucRequest[0] == '/') { + id = atoi(param->pucRequest + 1); + } + sprintf(param->pucBuffer, "/DATA/%u.CSV", id == 0 ? fileid : id); + ctx = new LogDataContext; +#if STORAGE == STORAGE_SPIFFS + ctx->file = SPIFFS.open(param->pucBuffer, FILE_READ); +#else + ctx->file = SD.open(param->pucBuffer, FILE_READ); +#endif + if (!ctx->file) { + strcat(param->pucBuffer, " not found"); + param->contentLength = strlen(param->pucBuffer); + delete ctx; + return FLAG_DATA_RAW; + } + param->hs->ptr = (void*)ctx; + } + + if (!ctx->file.available()) { + // EOF + return 0; + } + param->contentLength = ctx->file.readBytes(param->pucBuffer, param->bufSize); + param->contentType = HTTPFILETYPE_TEXT; + return FLAG_DATA_STREAM; +} + +int handlerLogData(UrlHandlerParam* param) +{ + uint32_t duration = 0; + LogDataContext* ctx = (LogDataContext*)param->hs->ptr; + param->contentType = HTTPFILETYPE_JSON; + if (ctx) { + if (!param->pucBuffer) { + // connection to be closed, final calling, cleanup + ctx->file.close(); + delete ctx; + param->hs->ptr = 0; + return 0; + } + } else { + int id = 0; + if (param->pucRequest[0] == '/') { + id = atoi(param->pucRequest + 1); + } + sprintf(param->pucBuffer, "/DATA/%u.CSV", id == 0 ? fileid : id); + ctx = new LogDataContext; +#if STORAGE == STORAGE_SPIFFS + ctx->file = SPIFFS.open(param->pucBuffer, FILE_READ); +#else + ctx->file = SD.open(param->pucBuffer, FILE_READ); +#endif + if (!ctx->file) { + param->contentLength = sprintf(param->pucBuffer, "{\"error\":\"Data file not found\"}"); + delete ctx; + return FLAG_DATA_RAW; + } + ctx->pid = mwGetVarValueHex(param->pxVars, "pid", 0); + ctx->tsStart = mwGetVarValueInt(param->pxVars, "start", 0); + ctx->tsEnd = 0xffffffff; + duration = mwGetVarValueInt(param->pxVars, "duration", 0); + if (ctx->tsStart && duration) { + ctx->tsEnd = ctx->tsStart + duration; + duration = 0; + } + param->hs->ptr = (void*)ctx; + // JSON head + param->contentLength = sprintf(param->pucBuffer, "["); + } + + int len = 0; + char buf[64]; + uint32_t ts = 0; + + for (;;) { + int c = ctx->file.read(); + if (c == -1) { + if (param->contentLength == 0) { + // EOF + return 0; + } + // JSON tail + if (param->pucBuffer[param->contentLength - 1] == ',') param->contentLength--; + param->pucBuffer[param->contentLength++] = ']'; + break; + } + if (c == '\n') { + // line end, process the line + buf[len] = 0; + char *value = strchr(buf, ','); + if (value++) { + uint16_t pid = hex2uint16(buf); + if (pid == 0) { + // timestamp + ts = atoi(value); + if (duration) { + ctx->tsEnd = ts + duration; + duration = 0; + } + } else if (pid == ctx->pid && ts >= ctx->tsStart && ts < ctx->tsEnd) { + // generate json array element + param->contentLength += snprintf(param->pucBuffer + param->contentLength, param->bufSize - param->contentLength, + "[%u,%s],", ts, value); + } + } + len = 0; + if (param->contentLength + 32 > param->bufSize) break; + } else if (len < sizeof(buf) - 1) { + buf[len++] = c; + } + } + return FLAG_DATA_STREAM; +} + +int handlerLogList(UrlHandlerParam* param) +{ + char *buf = param->pucBuffer; + int bufsize = param->bufSize; + File file; +#if STORAGE == STORAGE_SPIFFS + File root = SPIFFS.open("/"); +#elif STORAGE == STORAGE_SD + File root = SD.open("/DATA"); +#endif + int n = snprintf(buf, bufsize, "["); + if (root) { + while(file = root.openNextFile()) { + const char *fn = file.name(); + if (!strncmp(fn, "/DATA/", 6)) { + fn += 6; + unsigned int size = file.size(); + Serial.print(fn); + Serial.print(' '); + Serial.print(size); + Serial.println(" bytes"); + unsigned int id = atoi(fn); + if (id) { + n += snprintf(buf + n, bufsize - n, "{\"id\":%u,\"size\":%u", + id, size); + if (id == fileid) { + n += snprintf(buf + n, bufsize - n, ",\"active\":true"); + } + n += snprintf(buf + n, bufsize - n, "},"); + } + } + } + if (buf[n - 1] == ',') n--; + } + n += snprintf(buf + n, bufsize - n, "]"); + param->contentType=HTTPFILETYPE_JSON; + param->contentLength = n; + return FLAG_DATA_RAW; +} + +int handlerLogDelete(UrlHandlerParam* param) +{ + int id = 0; + if (param->pucRequest[0] == '/') { + id = atoi(param->pucRequest + 1); + } + sprintf(param->pucBuffer, "/DATA/%u.CSV", id); + if (id == fileid) { + strcat(param->pucBuffer, " still active"); + } else { +#if STORAGE == STORAGE_SPIFFS + bool removal = SPIFFS.remove(param->pucBuffer); +#else + bool removal = SD.remove(param->pucBuffer); +#endif + if (removal) { + strcat(param->pucBuffer, " deleted"); + } else { + strcat(param->pucBuffer, " not found"); + } + } + param->contentLength = strlen(param->pucBuffer); + param->contentType = HTTPFILETYPE_TEXT; + return FLAG_DATA_RAW; +} + +UrlHandler urlHandlerList[]={ + {"api/live", handlerLiveData}, + {"api/info", handlerInfo}, +#if STORAGE != STORAGE_NONE + {"api/list", handlerLogList}, + {"api/data", handlerLogData}, + {"api/log", handlerLogFile}, + {"api/delete", handlerLogDelete}, +#endif + {0} +}; + +void obtainTime() +{ + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_setservername(0, (char*)"pool.ntp.org"); + sntp_init(); +} + +void serverProcess(int timeout) +{ + mwHttpLoop(&httpParam, timeout); +} + +bool serverSetup(IPAddress& ip) +{ +#if NET_DEVICE == NET_WIFI + WiFi.mode (WIFI_AP_STA); +#else + WiFi.mode (WIFI_AP); +#endif + + WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PASSWORD); + ip = WiFi.softAPIP(); + + mwInitParam(&httpParam, 80, "/spiffs"); + httpParam.pxUrlHandler = urlHandlerList; + httpParam.maxClients = 4; + + if (mwServerStart(&httpParam)) { + return false; + } + +#if NET_DEVICE == NET_WIFI + obtainTime(); +#endif + return true; +} + +#else + +void serverProcess(int timeout) +{ + delay(timeout); +} + +#endif diff --git a/esp32/telelogger/platformio.ini b/esp32/telelogger/platformio.ini new file mode 100644 index 0000000..8b13c72 --- /dev/null +++ b/esp32/telelogger/platformio.ini @@ -0,0 +1,34 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:esp32dev] +platform = espressif32 +; build_flags = -DBOARD_HAS_PSRAM +; build_flags = -DCORE_DEBUG_LEVEL=5 -DSTACK_SIZE=16384 +board=esp-wrover-kit +board_build.f_cpu = 160000000L +framework = arduino +monitor_speed = 115200 +board_build.flash_mode = qio +board_build.partitions = huge_app.csv + +;[env:esp32c3] +;platform = espressif32 +;framework = arduino +;board = esp32-c3-devkitm-1 +;board_build.mcu = esp32c3 +;monitor_speed=115200 +;board_build.partitions = huge_app.csv + +[platformio] +src_dir=. + +[env] +lib_extra_dirs=../../libraries diff --git a/esp32/telelogger/teleclient.cpp b/esp32/telelogger/teleclient.cpp new file mode 100644 index 0000000..4119266 --- /dev/null +++ b/esp32/telelogger/teleclient.cpp @@ -0,0 +1,737 @@ +/****************************************************************************** +* Freematics Hub client and Traccar client implementations +* Works with Freematics ONE+ +* Developed by Stanley Huang +* Distributed under BSD license +* Visit https://freematics.com/products for hardware information +* Visit https://hub.freematics.com to view live and history telemetry data +* +* 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. +******************************************************************************/ + +#include +#include "telestore.h" +#include "telemesh.h" +#include "teleclient.h" +#include "config.h" + +#if SERVER_ENCRYPTION_ENABLE == 1 +#include "telecrypt.h" +#endif + +extern int16_t rssi; +extern char devid[]; +extern char vin[]; +extern GPS_DATA* gd; +extern char isoTime[]; + +CBuffer::CBuffer(uint8_t* mem) +{ + m_data = mem; + purge(); +} + +void CBuffer::add(uint16_t pid, uint8_t type, void* values, int bytes, uint8_t count) +{ + if (offset < BUFFER_LENGTH - sizeof(ELEMENT_HEAD) - bytes) { + ELEMENT_HEAD hdr = {pid, type, count}; + *(ELEMENT_HEAD*)(m_data + offset) = hdr; + offset += sizeof(ELEMENT_HEAD); + memcpy(m_data + offset, values, bytes); + offset += bytes; + total++; + } else { + Serial.println("FULL"); + } +} + +void CBuffer::purge() +{ + state = BUFFER_STATE_EMPTY; + timestamp = 0; + offset = 0; + total = 0; +} + +void CBuffer::serialize(CStorage& store) +{ + uint16_t of = 0; + for (int n = 0; n < total && of < offset; n++) { + ELEMENT_HEAD* hdr = (ELEMENT_HEAD*)(m_data + of); + of += sizeof(ELEMENT_HEAD); + switch (hdr->type) { + case ELEMENT_UINT8: + store.log(hdr->pid, (uint8_t*)(m_data + of), hdr->count); + of += (uint16_t)hdr->count * sizeof(uint8_t); + break; + case ELEMENT_UINT16: + store.log(hdr->pid, (uint16_t*)(m_data + of), hdr->count); + of += (uint16_t)hdr->count * sizeof(uint16_t); + break; + case ELEMENT_UINT32: + store.log(hdr->pid, (uint32_t*)(m_data + of), hdr->count); + of += (uint16_t)hdr->count * sizeof(uint32_t); + break; + case ELEMENT_INT32: + store.log(hdr->pid, (int32_t*)(m_data + of), hdr->count); + of += (uint16_t)hdr->count * sizeof(int32_t); + break; + case ELEMENT_FLOAT: + store.log(hdr->pid, (float*)(m_data + of), hdr->count); + of += (uint16_t)hdr->count * sizeof(float); + break; + case ELEMENT_FLOAT_D1: + store.log(hdr->pid, (float*)(m_data + of), hdr->count, "%.1f"); + of += (uint16_t)hdr->count * sizeof(float); + break; + case ELEMENT_FLOAT_D2: + store.log(hdr->pid, (float*)(m_data + of), hdr->count, "%.2f"); + of += (uint16_t)hdr->count * sizeof(float); + break; + default: + return; + } + } +} + +void CBufferManager::init() +{ + total = BUFFER_SLOTS; +#if BOARD_HAS_PSRAM + slots = (CBuffer**)heap_caps_malloc(BUFFER_SLOTS * sizeof(void*), MALLOC_CAP_SPIRAM); +#else + slots = (CBuffer**)malloc(BUFFER_SLOTS * sizeof(void*)); +#endif + for (int n = 0; n < BUFFER_SLOTS; n++) { + void* mem; +#if BOARD_HAS_PSRAM + mem = heap_caps_malloc(BUFFER_LENGTH, MALLOC_CAP_SPIRAM); +#else + mem = malloc(BUFFER_LENGTH); +#endif + if (!mem) { + Serial.println("OUT OF RAM"); + total = n; + break; + } + slots[n] = new CBuffer((uint8_t*)mem); + } + assert(total > 0); +} + +void CBufferManager::purge() +{ + for (int n = 0; n < total; n++) slots[n]->purge(); +} + +CBuffer* CBufferManager::getFree() +{ + if (last) { + CBuffer* slot = last; + last = 0; + if (slot->state == BUFFER_STATE_EMPTY) return slot; + } + uint32_t ts = 0xffffffff; + int m = 0; + // search for free slot, if none, mark the oldest one + for (int n = 0; n < total; n++) { + if (slots[n]->state == BUFFER_STATE_EMPTY) { + return slots[n]; + } else if (slots[n]->state == BUFFER_STATE_FILLED && slots[n]->timestamp < ts) { + m = n; + ts = slots[n]->timestamp; + } + } + // dispose oldest data when buffer is full + while (slots[m]->state == BUFFER_STATE_LOCKED) delay(1); + slots[m]->purge(); + return slots[m]; +} + +CBuffer* CBufferManager::getOldest() +{ + uint32_t ts = 0xffffffff; + int m = -1; + for (int n = 0; n < total; n++) { + if (slots[n]->state == BUFFER_STATE_FILLED && slots[n]->timestamp < ts) { + m = n; + ts = slots[n]->timestamp; + } + } + if (m >= 0) { + slots[m]->state = BUFFER_STATE_LOCKED; + return slots[m]; + } + return 0; +} + +CBuffer* CBufferManager::getNewest() +{ + uint32_t ts = 0; + int m = -1; + for (int n = 0; n < total; n++) { + if (slots[n]->state == BUFFER_STATE_FILLED && slots[n]->timestamp > ts) { + m = n; + ts = slots[n]->timestamp; + } + } + if (m >= 0) { + slots[m]->state = BUFFER_STATE_LOCKED; + return slots[m]; + } + return 0; +} + +void CBufferManager::free(CBuffer* slot) +{ + slot->purge(); + last = slot; +} + +void CBufferManager::printStats() +{ + int bytes = 0; + int count = 0; + int samples = 0; + for (int n = 0; n < total; n++) { + if (slots[n]->state != BUFFER_STATE_FILLED) continue; + bytes += slots[n]->offset; + samples += slots[n]->total; + count++; + } + if (slots) { + Serial.print("[BUF] "); + Serial.print(samples); + Serial.print(" samples | "); + Serial.print(bytes); + Serial.print(" bytes | "); + Serial.print(count); + Serial.print('/'); + Serial.println(total); + } +} + +bool TeleClientUDP::verifyChecksum(char* data) +{ + uint8_t sum = 0; + char *s = strrchr(data, '*'); + if (!s) return false; + for (char *p = data; p < s; p++) sum += *p; + if (hex2uint8(s + 1) == sum) { + *s = 0; + return true; + } + return false; +} + +bool TeleClientUDP::notify(byte event, const char* payload) +{ + char buf[48]; + char cache[128]; + CStorageRAM netbuf; + netbuf.init(cache, 128); + netbuf.header(devid); + netbuf.dispatch(buf, sprintf(buf, "EV=%X", (unsigned int)event)); + netbuf.dispatch(buf, sprintf(buf, "TS=%lu", millis())); + netbuf.dispatch(buf, sprintf(buf, "ID=%s", devid)); + if (rssi) { + netbuf.dispatch(buf, sprintf(buf, "SSI=%d", (int)rssi)); + } + if (vin[0]) { + netbuf.dispatch(buf, sprintf(buf, "VIN=%s", vin)); + } + if (payload) { + netbuf.dispatch(payload, strlen(payload)); + } + netbuf.tailer(); + //Serial.println(netbuf.buffer()); + for (byte attempts = 0; attempts < 3; attempts++) { + // send notification datagram +#if ENABLE_WIFI + if (wifi.connected()) + { + #if SERVER_ENCRYPTION_ENABLE == 1 + char *orig_send_buf = netbuf.buffer(); + unsigned int orig_send_buf_len = netbuf.length(); + unsigned char encrypted_buf[12 + orig_send_buf_len + 16]; + encrypt_string((unsigned char *)orig_send_buf, orig_send_buf_len, encrypted_buf); + if (!wifi.send((const char *)encrypted_buf, sizeof(encrypted_buf))) break; + #else + if (!wifi.send(netbuf.buffer(), netbuf.length())) break; + #endif + } + else +#endif + { + #if SERVER_ENCRYPTION_ENABLE == 1 + char *orig_send_buf = netbuf.buffer(); + unsigned int orig_send_buf_len = netbuf.length(); + unsigned char encrypted_buf[12 + orig_send_buf_len + 16]; + encrypt_string((unsigned char *)orig_send_buf, orig_send_buf_len, encrypted_buf); + if (!cell.send((const char *)encrypted_buf, sizeof(encrypted_buf))) break; + #else + if (!cell.send(netbuf.buffer(), netbuf.length())) break; + #endif + } + if (event == EVENT_ACK) return true; // no reply for ACK + char *data = 0; + int bytesRecv = 0; + // receive reply +#if ENABLE_WIFI + if (wifi.connected()) + { + data = cell.getBuffer(); + bytesRecv = wifi.receive(data, RECV_BUF_SIZE - 1); + if (bytesRecv > 0) { + data[bytesRecv] = 0; + } + } + else +#endif + { + data = cell.receive(&bytesRecv); + } + if (!data || bytesRecv == 0) { + Serial.println("[UDP] Timeout"); + continue; + } + rxBytes += bytesRecv; + + // decrypt received data +#if SERVER_ENCRYPTION_ENABLE == 1 + Serial.println("decrypting data"); + + if (bytesRecv >= 12 + 16) { + char decrypted_data[bytesRecv - 12 - 16 + 1]; // +1 for null-terminator + decrypt_string((unsigned char *)data, bytesRecv, (unsigned char *)decrypted_data); + Serial.println("decrytion function exited"); + if (decrypted_data[0] == '\0') { + continue; + } + data = decrypted_data; + bytesRecv = strlen(decrypted_data); + } else { + Serial.println("[CHACHA] Received data is too short to be decrypted"); + continue; + } + + Serial.println("decrypted data"); +#endif + + // verify checksum + if (!verifyChecksum(data)) { + Serial.print("[UDP] Checksum mismatch:"); + Serial.println(data); + continue; + } + char pattern[16]; + sprintf(pattern, "EV=%u", event); + if (!strstr(data, pattern)) { + Serial.print("[UDP] Invalid reply: "); + Serial.println(data); + continue; + } + if (event == EVENT_LOGIN) { + // extract info from server response + char *p = strstr(data, "TM="); + if (p) { + // set local time from server + unsigned long tm = atol(p + 3); + struct timeval tv = { .tv_sec = (time_t)tm, .tv_usec = 0 }; + settimeofday(&tv, NULL); + } + p = strstr(data, "SN="); + if (p) { + char *q = strchr(p, ','); + if (q) *q = 0; + } + feedid = hex2uint16(data); + login = true; + } else if (event == EVENT_LOGOUT) { + login = false; + } + // success + return true; + } + return false; +} + +bool TeleClientUDP::connect(bool quick) +{ + byte event = login ? EVENT_RECONNECT : EVENT_LOGIN; + bool success = false; +#if ENABLE_WIFI + if (wifi.connected()) + { + if (quick) return wifi.open(SERVER_HOST, SERVER_PORT); + } + else +#endif + { + cell.close(); + if (quick) { + return cell.open(0, 0); + } + } + + packets = 0; + + // connect to telematics server + for (byte attempts = 0; attempts < 3; attempts++) { + Serial.print(event == EVENT_LOGIN ? "LOGIN(" : "RECONNECT("); + Serial.print(SERVER_HOST); + Serial.print(':'); + Serial.print(SERVER_PORT); + Serial.println(")..."); +#if ENABLE_WIFI + if (wifi.connected()) + { + if (!wifi.open(SERVER_HOST, SERVER_PORT)) { + Serial.println("[WIFI] Unable to connect"); + delay(1000); + continue; + } + } + else +#endif + { + if (!cell.open(SERVER_HOST, SERVER_PORT)) { + if (!cell.check()) break; + Serial.println("[NET] Unable to connect"); + delay(3000); + continue; + } + } + // log in or reconnect to Freematics Hub + if (!notify(event)) { +#if ENABLE_WIFI + if (wifi.connected()) + { + wifi.close(); + } + else +#endif + { + if (!cell.check()) break; + cell.close(); + } + Serial.println("[NET] Server timeout"); + continue; + } + success = true; + break; + } + if (event == EVENT_LOGIN) startTime = millis(); + if (success) { + lastSyncTime = millis(); + } + return success; +} + +bool TeleClientUDP::ping() +{ + bool success = false; + for (byte n = 0; n < 3 && !success; n++) { +#if ENABLE_WIFI + if (wifi.connected()) + { + success = wifi.open(SERVER_HOST, SERVER_PORT); + } + else +#endif + { + success = cell.open(SERVER_HOST, SERVER_PORT); + } + if (success) { + if ((success = notify(EVENT_PING))) break; +#if ENABLE_WIFI + if (wifi.connected()) + { + wifi.close(); + } + else +#endif + { + cell.close(); + } + delay(1000); + } + } + if (success) lastSyncTime = millis(); + return success; +} + +bool TeleClientUDP::transmit(const char* packetBuffer, unsigned int packetSize) +{ +#if ENABLE_WIFI + // transmit data via wifi + if (wifi.connected()) { + if (wifi.send(packetBuffer, packetSize)) { + txBytes += packetSize; + txCount++; + Serial.print("[WIFI] "); + Serial.print(packetSize); + Serial.println(" bytes sent"); + return true; + } + return false; + } +#endif + + // transmit data via cellular + if (++packets >= 64) { + cell.close(); + cell.open(0, 0); + packets = 0; + } + Serial.print("[CELL] "); + Serial.print(packetSize); + Serial.println(" bytes being sent"); + if (cell.send(packetBuffer, packetSize)) { + txBytes += packetSize; + txCount++; + return true; + } + return false; +} + +void TeleClientUDP::inbound() +{ + // check incoming datagram + do { + int len = 0; + char *data = 0; +#if ENABLE_WIFI + if (wifi.connected()) + { + data = cell.getBuffer(); + len = wifi.receive(data, RECV_BUF_SIZE - 1, 10); + } + else +#endif + { + data = cell.receive(&len, 50); + } + if (!data || len == 0) break; + data[len] = 0; + Serial.print("[UDP] "); + Serial.println(data); + rxBytes += len; + if (!verifyChecksum(data)) { + Serial.print("[UDP] Checksum mismatch:"); + Serial.println(data); + break; + } + char *p = strstr(data, "EV="); + if (!p) break; + int eventID = atoi(p + 3); + switch (eventID) { + case EVENT_SYNC: + feedid = hex2uint16(data); + Serial.print("[UDP] FEED ID:"); + Serial.println(feedid); + break; + } + lastSyncTime = millis(); + } while(0); +} + +void TeleClientUDP::shutdown() +{ + if (login) { + notify(EVENT_LOGOUT); + login = false; + Serial.println("[NET] Logout"); + } +#if ENABLE_WIFI + if (wifi.connected()) { + wifi.end(); + Serial.println("[WIFI] Deactivated"); + return; + } +#endif + cell.end(); + Serial.println("[CELL] Deactivated"); +} + +bool TeleClientHTTP::notify(byte event, const char* payload) +{ + char url[256]; + snprintf(url, sizeof(url), "%s/notify/%s?EV=%u&SSI=%d&VIN=%s", SERVER_PATH, devid, + (unsigned int)event, (int)rssi, vin); + if (event == EVENT_LOGOUT) login = false; +#if ENABLE_WIFI + if (wifi.connected()) + { + return wifi.send(METHOD_GET, url, true) && wifi.receive(cell.getBuffer(), RECV_BUF_SIZE - 1) && wifi.code() == 200; + } + else +#endif + { + return cell.send(METHOD_GET, url, true) && cell.receive() && cell.code() == 200; + } +} + +bool TeleClientHTTP::transmit(const char* packetBuffer, unsigned int packetSize) +{ +#if ENABLE_WIFI + if ((wifi.connected() && wifi.state() != HTTP_CONNECTED) || cell.state() != HTTP_CONNECTED) { +#else + if (cell.state() != HTTP_CONNECTED) { +#endif + // reconnect if disconnected + if (!connect(true)) { + return false; + } + } + + char url[256]; + bool success = false; + int len; +#if SERVER_METHOD == PROTOCOL_METHOD_GET + if (gd && gd->ts) { + len = snprintf(url, sizeof(url), "%s/push?id=%s×tamp=%s&lat=%f&lon=%f&altitude=%d&speed=%f&heading=%d", + SERVER_PATH, devid, isoTime, + gd->lat, gd->lng, (int)gd->alt, gd->speed, (int)gd->heading); + } else { + len = snprintf(url, sizeof(url), "%s/push?id=%s", SERVER_PATH, devid); + } + success = cell.send(METHOD_GET, url, true); +#else + len = snprintf(url, sizeof(url), "%s/post/%s", SERVER_PATH, devid); +#if ENABLE_WIFI + if (wifi.connected()) { + Serial.print("[WIFI] "); + Serial.println(url); + success = wifi.send(METHOD_POST, url, true, packetBuffer, packetSize); + } + else +#endif + { + Serial.print("[CELL] "); + Serial.println(url); + success = cell.send(METHOD_POST, url, true, packetBuffer, packetSize); + } + len += packetSize; +#endif + if (!success) { + Serial.println("Connection closed"); + return false; + } else { + txBytes += len; + txCount++; + } + + // check response + int recvBytes = 0; + char* content = 0; +#if ENABLE_WIFI + if (wifi.connected()) + { + content = wifi.receive(cell.getBuffer(), RECV_BUF_SIZE - 1, &recvBytes); + } + else +#endif + { + content = cell.receive(&recvBytes); + } + if (!content) { + // close connection on receiving timeout + Serial.println("No HTTP response"); + return false; + } + Serial.print("[HTTP] "); + Serial.println(content); +#if ENABLE_WIFI + if ((wifi.connected() && wifi.code() == 200) || cell.code() == 200) { +#else + if (cell.code() == 200) { +#endif + // successful + lastSyncTime = millis(); + rxBytes += recvBytes; + } + return true; +} + +bool TeleClientHTTP::connect(bool quick) +{ + if (!quick) { +#if ENABLE_WIFI + if (!wifi.connected()) cell.init(); +#else + cell.init(); +#endif + } else { +#if ENABLE_WIFI + if (!wifi.connected()) cell.close(); +#else + cell.close(); +#endif + } + + // connect to HTTP server + bool success = false; + +#if ENABLE_WIFI + if (wifi.connected()) success = wifi.open(SERVER_HOST, SERVER_PORT); +#endif + if (!success) { + for (byte attempts = 0; !success && attempts < 3; attempts++) { + success = cell.open(SERVER_HOST, SERVER_PORT); + if (!success) { + if (!cell.check()) break; + cell.close(); + cell.init(); + } + } + } + if (!success) { + Serial.println("[CELL] Unable to connect"); + return false; + } + if (quick) return true; + if (!login) { + Serial.print("LOGIN("); + Serial.print(SERVER_HOST); + Serial.print(':'); + Serial.print(SERVER_PORT); + Serial.println(")..."); + // log in or reconnect to Freematics Hub + if (notify(EVENT_LOGIN)) { + lastSyncTime = millis(); + login = true; + } + } + return true; +} + +bool TeleClientHTTP::ping() +{ + return connect(); +} + +void TeleClientHTTP::shutdown() +{ + if (login) { + notify(EVENT_LOGOUT); + login = false; + Serial.println("[NET] Logout"); + } +#if ENABLE_WIFI + if (wifi.connected()) { + wifi.end(); + Serial.println("[WIFI] Deactivated"); + return; + } +#endif + cell.close(); + cell.end(); + Serial.println("[CELL] Deactivated"); +} diff --git a/esp32/telelogger/teleclient.h b/esp32/telelogger/teleclient.h new file mode 100644 index 0000000..6b3631c --- /dev/null +++ b/esp32/telelogger/teleclient.h @@ -0,0 +1,114 @@ +#include "config.h" + +#define EVENT_LOGIN 1 +#define EVENT_LOGOUT 2 +#define EVENT_SYNC 3 +#define EVENT_RECONNECT 4 +#define EVENT_COMMAND 5 +#define EVENT_ACK 6 +#define EVENT_PING 7 + +#define BUFFER_STATE_EMPTY 0 +#define BUFFER_STATE_FILLING 1 +#define BUFFER_STATE_FILLED 2 +#define BUFFER_STATE_LOCKED 3 + +#define ELEMENT_UINT8 0 +#define ELEMENT_UINT16 1 +#define ELEMENT_UINT32 2 +#define ELEMENT_INT32 3 +#define ELEMENT_FLOAT 4 +#define ELEMENT_FLOAT_D1 5 /* floating-point data with 1 decimal place*/ +#define ELEMENT_FLOAT_D2 6 /* floating-point data with 2 decimal places*/ + +typedef struct { + uint16_t pid; + uint8_t type; + uint8_t count; +} ELEMENT_HEAD; + +class CBuffer +{ +public: + CBuffer(uint8_t* mem); + void add(uint16_t pid, uint8_t type, void* values, int bytes, uint8_t count = 1); + void purge(); + void serialize(CStorage& store); + uint32_t timestamp; + uint16_t offset; + uint8_t total; + uint8_t state; +private: + uint8_t* m_data; +}; + +class CBufferManager +{ +public: + void init(); + void purge(); + void free(CBuffer* slot); + CBuffer* getFree(); + CBuffer* getOldest(); + CBuffer* getNewest(); + void printStats(); +private: + CBuffer** slots = 0; + CBuffer* last = 0; + uint32_t total = 0; +}; + +class TeleClient +{ +public: + virtual void reset() + { + txCount = 0; + txBytes = 0; + rxBytes = 0; + login = false; + startTime = millis(); + } + virtual bool notify(byte event, const char* payload = 0) { return true; } + virtual bool connect() { return true; } + virtual bool transmit(const char* packetBuffer, unsigned int packetSize) { return true; } + virtual void inbound() {} + uint32_t txCount = 0; + uint32_t txBytes = 0; + uint32_t rxBytes = 0; + uint32_t lastSyncTime = 0; + uint16_t feedid = 0; + uint32_t startTime = 0; + uint8_t packets = 0; + bool login = false; +}; + +class TeleClientUDP : public TeleClient +{ +public: + bool notify(byte event, const char* payload = 0); + bool connect(bool quick = false); + bool transmit(const char* packetBuffer, unsigned int packetSize); + bool ping(); + void inbound(); + bool verifyChecksum(char* data); + void shutdown(); +#if ENABLE_WIFI + WifiUDP wifi; +#endif + CellUDP cell; +}; + +class TeleClientHTTP : public TeleClient +{ +public: + bool notify(byte event, const char* payload = 0); + bool connect(bool quick = false); + bool transmit(const char* packetBuffer, unsigned int packetSize); + bool ping(); + void shutdown(); +#if ENABLE_WIFI + WifiHTTP wifi; +#endif + CellHTTP cell; +}; \ No newline at end of file diff --git a/esp32/telelogger/telecrypt.cpp b/esp32/telelogger/telecrypt.cpp new file mode 100644 index 0000000..30b87fa --- /dev/null +++ b/esp32/telelogger/telecrypt.cpp @@ -0,0 +1,120 @@ +#include "config.h" +#include +#include +#include +#include + +void print_hex(const unsigned char *data, size_t length) { + for (size_t i = 0; i < length; ++i) { + Serial.printf("%02x", data[i]); + } + Serial.println(); +} + +void encrypt_string(const unsigned char *input, size_t length, unsigned char *output) { + // Create an instance of the ChaChaPoly class + ChaChaPoly chachaPoly; + + // Initialize the encryption key + unsigned char key[32]; + for (int i = 0; i < 32; ++i) { + sscanf(CHACHA20_KEY + 2*i, "%02x", &key[i]); + } + + // Set the encryption key + chachaPoly.setKey(key, sizeof(key)); + + // Generate a random nonce (IV) + unsigned char nonce[12]; + esp_fill_random(nonce, sizeof(nonce)); // Use the ESP-IDF random number generator + chachaPoly.setIV(nonce, sizeof(nonce)); + + // Encrypt the input data + chachaPoly.encrypt(output + sizeof(nonce), input, length); + + // Compute the authentication tag + chachaPoly.computeTag(output + sizeof(nonce) + length, chachaPoly.tagSize()); + + // Prepend the nonce to the output + memcpy(output, nonce, sizeof(nonce)); + + // Clear the encryption context + chachaPoly.clear(); +} + +void decrypt_string(const unsigned char *input, size_t length, unsigned char *output) { + // Create an instance of the ChaChaPoly class + ChaChaPoly chachaPoly; + + // Initialize the decryption key + unsigned char key[32]; + for (int i = 0; i < 32; ++i) { + sscanf(CHACHA20_KEY + 2*i, "%02x", &key[i]); + } + + Serial.println("loaded key"); + + // Set the decryption key + chachaPoly.setKey(key, sizeof(key)); + + Serial.println("set key"); + + // Extract the nonce (IV) from the input + unsigned char nonce[12]; + memcpy(nonce, input, sizeof(nonce)); + chachaPoly.setIV(nonce, sizeof(nonce)); + + // Check that length is long enough to contain a nonce and a tag. + if (length < sizeof(nonce) + chachaPoly.tagSize()) { + Serial.print("[CHACHA] Input too short to contain nonce and tag: "); + print_hex(input, length); + output[0] = '\0'; // Set output to an empty string + return; + } + + Serial.println("did nonce"); + + // Decrypt the input data + size_t decryptedLength = length - sizeof(nonce) - chachaPoly.tagSize(); + chachaPoly.decrypt(output, input + sizeof(nonce), decryptedLength); + + Serial.println("did decryption"); + + // Print the decrypted data as hex values + String decryptedString = ""; + for (size_t i = 0; i < decryptedLength; i++) { + decryptedString += (char)output[i]; + } + Serial.println(decryptedString); + + // Verify the authentication tag + + + const unsigned char *tagPtr = input + sizeof(nonce) + decryptedLength; + Serial.print("Tag: "); + for (size_t i = 0; i < chachaPoly.tagSize(); i++) { + Serial.print(tagPtr[i], HEX); + Serial.print(" "); + } + Serial.println(); + + Serial.print("Computed Tag: "); + uint8_t computedTag[16]; + chachaPoly.computeTag(computedTag, sizeof(computedTag)); + for (size_t i = 0; i < sizeof(computedTag); i++) { + Serial.print(computedTag[i], HEX); + Serial.print(" "); + } + Serial.println(); + + if (!chachaPoly.checkTag(tagPtr, chachaPoly.tagSize())) { + Serial.println("Authentication failed!"); + output[0] = '\0'; // Set output to an empty string + return; + } + + /// + + // Clear the decryption context + chachaPoly.clear(); +} \ No newline at end of file diff --git a/esp32/telelogger/telecrypt.h b/esp32/telelogger/telecrypt.h new file mode 100644 index 0000000..37098b4 --- /dev/null +++ b/esp32/telelogger/telecrypt.h @@ -0,0 +1,9 @@ +#include "config.h" +#include +#include +#include +#include + +void encrypt_string(const unsigned char *input, size_t length, unsigned char *output); +void decrypt_string(const unsigned char *input, size_t length, unsigned char *output); +void print_hex(const unsigned char *data, size_t length); \ No newline at end of file diff --git a/esp32/telelogger/telelogger.bin b/esp32/telelogger/telelogger.bin new file mode 100644 index 0000000..06f8d77 Binary files /dev/null and b/esp32/telelogger/telelogger.bin differ diff --git a/esp32/telelogger/telelogger.ino b/esp32/telelogger/telelogger.ino new file mode 100644 index 0000000..7f92b10 --- /dev/null +++ b/esp32/telelogger/telelogger.ino @@ -0,0 +1,1508 @@ +/****************************************************************************** +* Arduino sketch of a vehicle data data logger and telemeter for Freematics Hub +* Works with Freematics ONE+ Model A and Model B +* Developed by Stanley Huang +* Distributed under BSD license +* Visit https://freematics.com/products for hardware information +* Visit https://hub.freematics.com to view live and history telemetry data +* +* 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. +******************************************************************************/ + +#include +#include +#include "config.h" +#include "telestore.h" +#include "teleclient.h" +#include "telemesh.h" +#if BOARD_HAS_PSRAM +#include "esp32/himem.h" +#endif +#include "driver/adc.h" +#include "nvs_flash.h" +#include "nvs.h" +#if ENABLE_OLED +#include "FreematicsOLED.h" +#endif + +#if SERVER_ENCRYPTION_ENABLE == 1 +#include "telecrypt.h" +#endif + +// states +#define STATE_STORAGE_READY 0x1 +#define STATE_OBD_READY 0x2 +#define STATE_GPS_READY 0x4 +#define STATE_MEMS_READY 0x8 +#define STATE_NET_READY 0x10 +#define STATE_CELL_CONNECTED 0x20 +#define STATE_WIFI_CONNECTED 0x40 +#define STATE_WORKING 0x80 +#define STATE_STANDBY 0x100 + +typedef struct { + byte pid; + byte tier; + int value; + uint32_t ts; +} PID_POLLING_INFO; + +PID_POLLING_INFO obdData[]= { + {PID_SPEED, 1}, + {PID_RPM, 1}, + {PID_THROTTLE, 1}, + {PID_ENGINE_LOAD, 1}, + {PID_FUEL_PRESSURE, 2}, + {PID_TIMING_ADVANCE, 2}, + {PID_COOLANT_TEMP, 3}, + {PID_INTAKE_TEMP, 3}, +}; + +CBufferManager bufman; +Task subtask; + +#if ENABLE_MEMS +float accBias[3] = {0}; // calibrated reference accelerometer data +float accSum[3] = {0}; +float acc[3] = {0}; +float gyr[3] = {0}; +float mag[3] = {0}; +uint8_t accCount = 0; +#endif +int deviceTemp = 0; + +// config data +char apn[32]; +#if ENABLE_WIFI +char wifiSSID[32] = WIFI_SSID; +char wifiPassword[32] = WIFI_PASSWORD; +#endif +nvs_handle_t nvs; + +// live data +String netop; +String ip; +int16_t rssi = 0; +int16_t rssiLast = 0; +char vin[18] = {0}; +uint16_t dtc[6] = {0}; +float batteryVoltage = 0; +GPS_DATA* gd = 0; + +char devid[12] = {0}; +char isoTime[32] = {0}; + +// stats data +uint32_t lastMotionTime = 0; +uint32_t timeoutsOBD = 0; +uint32_t timeoutsNet = 0; +uint32_t lastStatsTime = 0; + +int32_t syncInterval = SERVER_SYNC_INTERVAL * 1000; +int32_t dataInterval = 1000; + +#if STORAGE != STORAGE_NONE +int fileid = 0; +uint16_t lastSizeKB = 0; +#endif + +byte ledMode = 0; + +bool serverSetup(IPAddress& ip); +void serverProcess(int timeout); +void processMEMS(CBuffer* buffer); +bool processGPS(CBuffer* buffer); +void processBLE(int timeout); + +class State { +public: + bool check(uint16_t flags) { return (m_state & flags) == flags; } + void set(uint16_t flags) { m_state |= flags; } + void clear(uint16_t flags) { m_state &= ~flags; } + uint16_t m_state = 0; +}; + +FreematicsESP32 sys; + +class OBD : public COBD +{ +protected: + void idleTasks() + { + // do some quick tasks while waiting for OBD response +#if ENABLE_MEMS + processMEMS(0); +#endif + processBLE(0); + } +}; + +OBD obd; + +MEMS_I2C* mems = 0; + +#if STORAGE == STORAGE_SPIFFS +SPIFFSLogger logger; +#elif STORAGE == STORAGE_SD +SDLogger logger; +#endif + +#if SERVER_PROTOCOL == PROTOCOL_UDP +TeleClientUDP teleClient; +#else +TeleClientHTTP teleClient; +#endif + +#if ENABLE_OLED +OLED_SH1106 oled; +#endif + +State state; + +void printTimeoutStats() +{ + Serial.print("Timeouts: OBD:"); + Serial.print(timeoutsOBD); + Serial.print(" Network:"); + Serial.println(timeoutsNet); +} + +void beep(int duration) +{ + // turn on buzzer at 2000Hz frequency + sys.buzzer(2000); + delay(duration); + // turn off buzzer + sys.buzzer(0); +} + +#if LOG_EXT_SENSORS +void processExtInputs(CBuffer* buffer) +{ +#if LOG_EXT_SENSORS == 1 + uint8_t levels[2] = {(uint8_t)digitalRead(PIN_SENSOR1), (uint8_t)digitalRead(PIN_SENSOR2)}; + buffer->add(PID_EXT_SENSORS, ELEMENT_UINT8, levels, sizeof(levels), 2); +#elif LOG_EXT_SENSORS == 2 + uint16_t reading[] = {adc1_get_raw(ADC1_CHANNEL_0), adc1_get_raw(ADC1_CHANNEL_1)}; + Serial.print("GPIO0:"); + Serial.print((float)reading[0] * 3.15 / 4095 - 0.01); + Serial.print(" GPIO1:"); + Serial.println((float)reading[1] * 3.15 / 4095 - 0.01); + buffer->add(PID_EXT_SENSORS, ELEMENT_UINT16, reading, sizeof(reading), 2); +#endif +} +#endif + +/******************************************************************************* + HTTP API +*******************************************************************************/ +#if ENABLE_HTTPD +int handlerLiveData(UrlHandlerParam* param) +{ + char *buf = param->pucBuffer; + int bufsize = param->bufSize; + int n = snprintf(buf, bufsize, "{\"obd\":{\"vin\":\"%s\",\"battery\":%.1f,\"pid\":[", vin, batteryVoltage); + uint32_t t = millis(); + for (int i = 0; i < sizeof(obdData) / sizeof(obdData[0]); i++) { + n += snprintf(buf + n, bufsize - n, "{\"pid\":%u,\"value\":%d,\"age\":%u},", + 0x100 | obdData[i].pid, obdData[i].value, (unsigned int)(t - obdData[i].ts)); + } + n--; + n += snprintf(buf + n, bufsize - n, "]}"); +#if ENABLE_MEMS + if (accCount) { + n += snprintf(buf + n, bufsize - n, ",\"mems\":{\"acc\":[%d,%d,%d],\"stationary\":%u}", + (int)((accSum[0] / accCount - accBias[0]) * 100), (int)((accSum[1] / accCount - accBias[1]) * 100), (int)((accSum[2] / accCount - accBias[2]) * 100), + (unsigned int)(millis() - lastMotionTime)); + } +#endif + if (gd && gd->ts) { + n += snprintf(buf + n, bufsize - n, ",\"gps\":{\"utc\":\"%s\",\"lat\":%f,\"lng\":%f,\"alt\":%f,\"speed\":%f,\"sat\":%d,\"age\":%u}", + isoTime, gd->lat, gd->lng, gd->alt, gd->speed, (int)gd->sat, (unsigned int)(millis() - gd->ts)); + } + buf[n++] = '}'; + param->contentLength = n; + param->contentType=HTTPFILETYPE_JSON; + return FLAG_DATA_RAW; +} +#endif + +/******************************************************************************* + Reading and processing OBD data +*******************************************************************************/ +#if ENABLE_OBD +void processOBD(CBuffer* buffer) +{ + static int idx[2] = {0, 0}; + int tier = 1; + for (byte i = 0; i < sizeof(obdData) / sizeof(obdData[0]); i++) { + if (obdData[i].tier > tier) { + // reset previous tier index + idx[tier - 2] = 0; + // keep new tier number + tier = obdData[i].tier; + // move up current tier index + i += idx[tier - 2]++; + // check if into next tier + if (obdData[i].tier != tier) { + idx[tier - 2]= 0; + i--; + continue; + } + } + byte pid = obdData[i].pid; + if (!obd.isValidPID(pid)) continue; + int value; + if (obd.readPID(pid, value)) { + obdData[i].ts = millis(); + obdData[i].value = value; + buffer->add((uint16_t)pid | 0x100, ELEMENT_INT32, &value, sizeof(value)); + } else { + timeoutsOBD++; + printTimeoutStats(); + break; + } + if (tier > 1) break; + } + int kph = obdData[0].value; + if (kph >= 2) lastMotionTime = millis(); +} +#endif + +bool initGPS() +{ + // start GNSS receiver + if (sys.gpsBeginExt()) { + Serial.println("GNSS:OK(E)"); + } else if (sys.gpsBegin()) { + Serial.println("GNSS:OK(I)"); + } else { + Serial.println("GNSS:NO"); + return false; + } + return true; +} + +bool processGPS(CBuffer* buffer) +{ + static uint32_t lastGPStime = 0; + static uint32_t lastGPStick = 0; + static float lastGPSLat = 0; + static float lastGPSLng = 0; + + if (!gd) { + lastGPStime = 0; + lastGPSLat = 0; + lastGPSLng = 0; + } +#if GNSS == GNSS_STANDALONE + if (state.check(STATE_GPS_READY)) { + // read parsed GPS data + if (!sys.gpsGetData(&gd)) { + return false; + } + } +#else + if (!teleClient.cell.getLocation(&gd)) { + return false; + } +#endif + + + if (!gd || lastGPStime == gd->time || (gd->lng == 0 && gd->lat == 0)) { +#if GNSS_RESET_TIMEOUT + if (millis() - lastGPStick > GNSS_RESET_TIMEOUT * 1000) { + sys.gpsEnd(); + delay(50); + initGPS(); + lastGPStick = millis(); + } +#endif + return false; + } + lastGPStick = millis(); + + if ((lastGPSLat || lastGPSLng) && (abs(gd->lat - lastGPSLat) > 0.001 || abs(gd->lng - lastGPSLng) > 0.001)) { + // invalid coordinates data + lastGPSLat = 0; + lastGPSLng = 0; + return false; + } + lastGPSLat = gd->lat; + lastGPSLng = gd->lng; + + float kph = gd->speed * 1.852f; + if (kph >= 2) lastMotionTime = millis(); + + if (buffer) { + buffer->add(PID_GPS_TIME, ELEMENT_UINT32, &gd->time, sizeof(uint32_t)); + buffer->add(PID_GPS_LATITUDE, ELEMENT_FLOAT, &gd->lat, sizeof(float)); + buffer->add(PID_GPS_LONGITUDE, ELEMENT_FLOAT, &gd->lng, sizeof(float)); + buffer->add(PID_GPS_ALTITUDE, ELEMENT_FLOAT_D1, &gd->alt, sizeof(float)); /* m */ + buffer->add(PID_GPS_SPEED, ELEMENT_FLOAT_D1, &kph, sizeof(kph)); + buffer->add(PID_GPS_HEADING, ELEMENT_UINT16, &gd->heading, sizeof(uint16_t)); + if (gd->sat) buffer->add(PID_GPS_SAT_COUNT, ELEMENT_UINT8, &gd->sat, sizeof(uint8_t)); + if (gd->hdop) buffer->add(PID_GPS_HDOP, ELEMENT_UINT8, &gd->hdop, sizeof(uint8_t)); + } + + // generate ISO time string + char *p = isoTime + sprintf(isoTime, "%04u-%02u-%02uT%02u:%02u:%02u", + (unsigned int)(gd->date % 100) + 2000, (unsigned int)(gd->date / 100) % 100, (unsigned int)(gd->date / 10000), + (unsigned int)(gd->time / 1000000), (unsigned int)(gd->time % 1000000) / 10000, (unsigned int)(gd->time % 10000) / 100); + unsigned char tenth = (gd->time % 100) / 10; + if (tenth) p += sprintf(p, ".%c00", '0' + tenth); + *p = 'Z'; + *(p + 1) = 0; + + Serial.print("[GPS] "); + Serial.print(gd->lat, 6); + Serial.print(' '); + Serial.print(gd->lng, 6); + Serial.print(' '); + Serial.print((int)kph); + Serial.print("km/h"); + Serial.print(" SATS:"); + Serial.print(gd->sat); + Serial.print(" HDOP:"); + Serial.print(gd->hdop); + Serial.print(" Course:"); + Serial.print(gd->heading); + + Serial.print(' '); + Serial.println(isoTime); + //Serial.println(gd->errors); + lastGPStime = gd->time; + return true; +} + +bool waitMotionGPS(int timeout) +{ + unsigned long t = millis(); + lastMotionTime = 0; + do { + serverProcess(100); + if (!processGPS(0)) continue; + if (lastMotionTime) return true; + } while (millis() - t < timeout); + return false; +} + +#if ENABLE_MEMS +void processMEMS(CBuffer* buffer) +{ + if (!state.check(STATE_MEMS_READY)) return; + + // load and store accelerometer data + float temp; +#if ENABLE_ORIENTATION + ORIENTATION ori; + if (!mems->read(acc, gyr, mag, &temp, &ori)) return; +#else + if (!mems->read(acc, gyr, mag, &temp)) return; +#endif + deviceTemp = (int)temp; + + accSum[0] += acc[0]; + accSum[1] += acc[1]; + accSum[2] += acc[2]; + accCount++; + + if (buffer) { + if (accCount) { + float value[3]; + value[0] = accSum[0] / accCount - accBias[0]; + value[1] = accSum[1] / accCount - accBias[1]; + value[2] = accSum[2] / accCount - accBias[2]; + buffer->add(PID_ACC, ELEMENT_FLOAT_D2, value, sizeof(value), 3); +/* + Serial.print("[ACC] "); + Serial.print(value[0]); + Serial.print('/'); + Serial.print(value[1]); + Serial.print('/'); + Serial.println(value[2]); +*/ +#if ENABLE_ORIENTATION + value[0] = ori.yaw; + value[1] = ori.pitch; + value[2] = ori.roll; + buffer->add(PID_ORIENTATION, ELEMENT_FLOAT_D2, value, sizeof(value), 3); +#endif +#if 0 + // calculate motion + float motion = 0; + for (byte i = 0; i < 3; i++) { + motion += value[i] * value[i]; + } + if (motion >= MOTION_THRESHOLD * MOTION_THRESHOLD) { + lastMotionTime = millis(); + Serial.print("Motion:"); + Serial.println(motion); + } +#endif + } + accSum[0] = 0; + accSum[1] = 0; + accSum[2] = 0; + accCount = 0; + } +} + +void calibrateMEMS() +{ + if (state.check(STATE_MEMS_READY)) { + accBias[0] = 0; + accBias[1] = 0; + accBias[2] = 0; + int n; + unsigned long t = millis(); + for (n = 0; millis() - t < 1000; n++) { + float acc[3]; + if (!mems->read(acc)) continue; + accBias[0] += acc[0]; + accBias[1] += acc[1]; + accBias[2] += acc[2]; + delay(10); + } + accBias[0] /= n; + accBias[1] /= n; + accBias[2] /= n; + Serial.print("ACC BIAS:"); + Serial.print(accBias[0]); + Serial.print('/'); + Serial.print(accBias[1]); + Serial.print('/'); + Serial.println(accBias[2]); + } +} +#endif + +void printTime() +{ + time_t utc; + time(&utc); + struct tm *btm = gmtime(&utc); + if (btm->tm_year > 100) { + // valid system time available + char buf[64]; + sprintf(buf, "%04u-%02u-%02u %02u:%02u:%02u", + 1900 + btm->tm_year, btm->tm_mon + 1, btm->tm_mday, btm->tm_hour, btm->tm_min, btm->tm_sec); + Serial.print("UTC:"); + Serial.println(buf); + } +} + +/******************************************************************************* + Initializing all data logging components +*******************************************************************************/ +void initialize() +{ + // dump buffer data + bufman.purge(); + +#if ENABLE_MEMS + if (state.check(STATE_MEMS_READY)) { + calibrateMEMS(); + } +#endif + +#if GNSS == GNSS_STANDALONE + if (!state.check(STATE_GPS_READY)) { + if (initGPS()) { + state.set(STATE_GPS_READY); + } + } +#endif + +#if ENABLE_OBD + // initialize OBD communication + if (!state.check(STATE_OBD_READY)) { + timeoutsOBD = 0; + if (obd.init()) { + Serial.println("OBD:OK"); + state.set(STATE_OBD_READY); +#if ENABLE_OLED + oled.println("OBD OK"); +#endif + } else { + Serial.println("OBD:NO"); + //state.clear(STATE_WORKING); + //return; + } + } +#endif + +#if SERVER_ENCRYPTION_ENABLE == 1 + Serial.println("CHACHA:YES"); +#endif + +#if STORAGE != STORAGE_NONE + if (!state.check(STATE_STORAGE_READY)) { + // init storage + if (logger.init()) { + state.set(STATE_STORAGE_READY); + } + } + if (state.check(STATE_STORAGE_READY)) { + fileid = logger.begin(); + } +#endif + + // re-try OBD if connection not established +#if ENABLE_OBD + if (state.check(STATE_OBD_READY)) { + char buf[128]; + if (obd.getVIN(buf, sizeof(buf))) { + memcpy(vin, buf, sizeof(vin) - 1); + Serial.print("VIN:"); + Serial.println(vin); + } + int dtcCount = obd.readDTC(dtc, sizeof(dtc) / sizeof(dtc[0])); + if (dtcCount > 0) { + Serial.print("DTC:"); + Serial.println(dtcCount); + } +#if ENABLE_OLED + oled.print("VIN:"); + oled.println(vin); +#endif + } +#endif + + // check system time + printTime(); + + lastMotionTime = millis(); + state.set(STATE_WORKING); + +#if ENABLE_OLED + delay(1000); + oled.clear(); + oled.print("DEVICE ID: "); + oled.println(devid); + oled.setCursor(0, 7); + oled.print("Packets"); + oled.setCursor(80, 7); + oled.print("KB Sent"); + oled.setFontSize(FONT_SIZE_MEDIUM); +#endif +} + +void showStats() +{ + uint32_t t = millis() - teleClient.startTime; + char buf[32]; + sprintf(buf, "%02u:%02u.%c ", t / 60000, (t % 60000) / 1000, (t % 1000) / 100 + '0'); + Serial.print("[NET] "); + Serial.print(buf); + Serial.print("| Packet #"); + Serial.print(teleClient.txCount); + Serial.print(" | Out: "); + Serial.print(teleClient.txBytes >> 10); + Serial.print(" KB | In: "); + Serial.print(teleClient.rxBytes); + Serial.print(" bytes | "); + Serial.print((unsigned int)((uint64_t)(teleClient.txBytes + teleClient.rxBytes) * 3600 / (millis() - teleClient.startTime))); + Serial.print(" KB/h"); + + Serial.println(); +#if ENABLE_OLED + oled.setCursor(0, 2); + oled.println(timestr); + oled.setCursor(0, 5); + oled.printInt(teleClient.txCount, 2); + oled.setCursor(80, 5); + oled.printInt(teleClient.txBytes >> 10, 3); +#endif +} + +bool waitMotion(long timeout) +{ +#if ENABLE_MEMS + unsigned long t = millis(); + if (state.check(STATE_MEMS_READY)) { + do { + // calculate relative movement + float motion = 0; + float acc[3]; + if (!mems->read(acc)) continue; + if (accCount == 10) { + accCount = 0; + accSum[0] = 0; + accSum[1] = 0; + accSum[2] = 0; + } + accSum[0] += acc[0]; + accSum[1] += acc[1]; + accSum[2] += acc[2]; + accCount++; + for (byte i = 0; i < 3; i++) { + float m = (acc[i] - accBias[i]); + motion += m * m; + } +#if ENABLE_HTTTPD + serverProcess(100); +#endif + processBLE(100); + // check movement + if (motion >= MOTION_THRESHOLD * MOTION_THRESHOLD) { + //lastMotionTime = millis(); + Serial.println(motion); + return true; + } + } while (state.check(STATE_STANDBY) && ((long)(millis() - t) < timeout || timeout == -1)); + return false; + } +#endif + serverProcess(timeout); + return false; +} + +/******************************************************************************* + Collecting and processing data +*******************************************************************************/ +void process() +{ + uint32_t startTime = millis(); + + CBuffer* buffer = bufman.getFree(); + buffer->state = BUFFER_STATE_FILLING; + +#if ENABLE_OBD + // process OBD data if connected + if (state.check(STATE_OBD_READY)) { + processOBD(buffer); + if (obd.errors >= MAX_OBD_ERRORS) { + if (!obd.init()) { + Serial.println("[OBD] ECU OFF"); + state.clear(STATE_OBD_READY | STATE_WORKING); + return; + } + } + } else if (obd.init(PROTO_AUTO, true)) { + state.set(STATE_OBD_READY); + Serial.println("[OBD] ECU ON"); + } +#endif + + if (rssi != rssiLast) { + int val = (rssiLast = rssi); + buffer->add(PID_CSQ, ELEMENT_INT32, &val, sizeof(val)); + } +#if ENABLE_OBD + if (sys.devType > 12) { + batteryVoltage = (float)(analogRead(A0) * 45) / 4095; + } else { + batteryVoltage = obd.getVoltage(); + } + if (batteryVoltage) { + uint16_t v = batteryVoltage * 100; + buffer->add(PID_BATTERY_VOLTAGE, ELEMENT_UINT16, &v, sizeof(v)); + } +#endif + +#if LOG_EXT_SENSORS + processExtInputs(buffer); +#endif + +#if ENABLE_MEMS + processMEMS(buffer); +#endif + + processGPS(buffer); + + if (!state.check(STATE_MEMS_READY)) { + deviceTemp = readChipTemperature(); + } + buffer->add(PID_DEVICE_TEMP, ELEMENT_INT32, &deviceTemp, sizeof(deviceTemp)); + + buffer->timestamp = millis(); + buffer->state = BUFFER_STATE_FILLED; + + // display file buffer stats + if (startTime - lastStatsTime >= 3000) { + bufman.printStats(); + lastStatsTime = startTime; + } + +#if STORAGE != STORAGE_NONE + if (state.check(STATE_STORAGE_READY)) { + buffer->serialize(logger); + uint16_t sizeKB = (uint16_t)(logger.size() >> 10); + if (sizeKB != lastSizeKB) { + logger.flush(); + lastSizeKB = sizeKB; + Serial.print("[FILE] "); + Serial.print(sizeKB); + Serial.println("KB"); + } + } +#endif + + const int dataIntervals[] = DATA_INTERVAL_TABLE; +#if ENABLE_OBD || ENABLE_MEMS + // motion adaptive data interval control + const uint16_t stationaryTime[] = STATIONARY_TIME_TABLE; + unsigned int motionless = (millis() - lastMotionTime) / 1000; + bool stationary = true; + for (byte i = 0; i < sizeof(stationaryTime) / sizeof(stationaryTime[0]); i++) { + dataInterval = dataIntervals[i]; + if (motionless < stationaryTime[i] || stationaryTime[i] == 0) { + stationary = false; + break; + } + } + if (stationary) { + // stationery timeout + Serial.print("Stationary for "); + Serial.print(motionless); + Serial.println(" secs"); + // trip ended, go into standby + state.clear(STATE_WORKING); + return; + } +#else + dataInterval = dataIntervals[0]; +#endif + do { + long t = dataInterval - (millis() - startTime); + processBLE(t > 0 ? t : 0); + } while (millis() - startTime < dataInterval); +} + +bool initCell(bool quick = false) +{ + Serial.println("[CELL] Activating..."); + // power on network module + if (!teleClient.cell.begin(&sys)) { + Serial.println("[CELL] No supported module"); +#if ENABLE_OLED + oled.println("No Cell Module"); +#endif + return false; + } + if (quick) return true; +#if ENABLE_OLED + oled.print(teleClient.cell.deviceName()); + oled.println(" OK\r"); + oled.print("IMEI:"); + oled.println(teleClient.cell.IMEI); +#endif + Serial.print("CELL:"); + Serial.println(teleClient.cell.deviceName()); + if (!teleClient.cell.checkSIM(SIM_CARD_PIN)) { + Serial.println("NO SIM CARD"); + //return false; + } + Serial.print("IMEI:"); + Serial.println(teleClient.cell.IMEI); + Serial.println("[CELL] Searching..."); + if (*apn) { + Serial.print("APN:"); + Serial.println(apn); + } + if (teleClient.cell.setup(apn)) { + netop = teleClient.cell.getOperatorName(); + if (netop.length()) { + Serial.print("Operator:"); + Serial.println(netop); +#if ENABLE_OLED + oled.println(op); +#endif + } + +#if GNSS == GNSS_CELLULAR + if (teleClient.cell.setGPS(true)) { + Serial.println("CELL GNSS:OK"); + } +#endif + + ip = teleClient.cell.getIP(); + if (ip.length()) { + Serial.print("[CELL] IP:"); + Serial.println(ip); +#if ENABLE_OLED + oled.print("IP:"); + oled.println(ip); +#endif + } + state.set(STATE_CELL_CONNECTED); + } else { + char *p = strstr(teleClient.cell.getBuffer(), "+CPSI:"); + if (p) { + char *q = strchr(p, '\r'); + if (q) *q = 0; + Serial.print("[CELL] "); + Serial.println(p + 7); +#if ENABLE_OLED + oled.println(p + 7); +#endif + } else { + Serial.print(teleClient.cell.getBuffer()); + } + } + timeoutsNet = 0; + return state.check(STATE_CELL_CONNECTED); +} + +/******************************************************************************* + Initializing network, maintaining connection and doing transmissions +*******************************************************************************/ +void telemetry(void* inst) +{ + uint32_t lastRssiTime = 0; + uint8_t connErrors = 0; + CStorageRAM store; + store.init( +#if BOARD_HAS_PSRAM + (char*)heap_caps_malloc(SERIALIZE_BUFFER_SIZE, MALLOC_CAP_SPIRAM), +#else + (char*)malloc(SERIALIZE_BUFFER_SIZE), +#endif + SERIALIZE_BUFFER_SIZE + ); + teleClient.reset(); + + for (;;) { + if (state.check(STATE_STANDBY)) { + if (state.check(STATE_CELL_CONNECTED) || state.check(STATE_WIFI_CONNECTED)) { + teleClient.shutdown(); + netop = ""; + ip = ""; + rssi = 0; + } + state.clear(STATE_NET_READY | STATE_CELL_CONNECTED | STATE_WIFI_CONNECTED); + teleClient.reset(); + bufman.purge(); + + uint32_t t = millis(); + do { + delay(1000); + } while (state.check(STATE_STANDBY) && millis() - t < 1000L * PING_BACK_INTERVAL); + if (state.check(STATE_STANDBY)) { + // start ping +#if ENABLE_WIFI + if (wifiSSID[0]) { + Serial.print("[WIFI] Joining SSID:"); + Serial.println(wifiSSID); + teleClient.wifi.begin(wifiSSID, wifiPassword); + } + if (teleClient.wifi.setup()) { + Serial.println("[WIFI] Ping..."); + teleClient.ping(); + } + else +#endif + { + if (initCell()) { + Serial.println("[CELL] Ping..."); + teleClient.ping(); + } + } + teleClient.shutdown(); + state.clear(STATE_CELL_CONNECTED | STATE_WIFI_CONNECTED); + } + continue; + } + +#if ENABLE_WIFI + if (wifiSSID[0] && !state.check(STATE_WIFI_CONNECTED)) { + Serial.print("[WIFI] Joining SSID:"); + Serial.println(wifiSSID); + teleClient.wifi.begin(wifiSSID, wifiPassword); + teleClient.wifi.setup(); + } +#endif + + while (state.check(STATE_WORKING)) { +#if ENABLE_WIFI + if (wifiSSID[0]) { + if (!state.check(STATE_WIFI_CONNECTED) && teleClient.wifi.connected()) { + ip = teleClient.wifi.getIP(); + if (ip.length()) { + Serial.print("[WIFI] IP:"); + Serial.println(ip); + } + connErrors = 0; + if (teleClient.connect()) { + state.set(STATE_WIFI_CONNECTED | STATE_NET_READY); + beep(50); + // switch off cellular module when wifi connected + if (state.check(STATE_CELL_CONNECTED)) { + teleClient.cell.end(); + state.clear(STATE_CELL_CONNECTED); + Serial.println("[CELL] Deactivated"); + } + } + } else if (state.check(STATE_WIFI_CONNECTED) && !teleClient.wifi.connected()) { + Serial.println("[WIFI] Disconnected"); + state.clear(STATE_WIFI_CONNECTED); + } + } +#endif + if (!state.check(STATE_WIFI_CONNECTED) && !state.check(STATE_CELL_CONNECTED)) { + connErrors = 0; + if (!initCell() || !teleClient.connect()) { + teleClient.cell.end(); + state.clear(STATE_NET_READY | STATE_CELL_CONNECTED); + break; + } + Serial.println("[CELL] In service"); + state.set(STATE_NET_READY); + beep(50); + } + + if (millis() - lastRssiTime > SIGNAL_CHECK_INTERVAL * 1000) { +#if ENABLE_WIFI + if (state.check(STATE_WIFI_CONNECTED)) + { + rssi = teleClient.wifi.RSSI(); + } + else +#endif + { + rssi = teleClient.cell.RSSI(); + } + if (rssi) { + Serial.print("RSSI:"); + Serial.print(rssi); + Serial.println("dBm"); + } + lastRssiTime = millis(); + +#if ENABLE_WIFI + if (wifiSSID[0] && !state.check(STATE_WIFI_CONNECTED)) { + teleClient.wifi.begin(wifiSSID, wifiPassword); + } +#endif + } + + // get data from buffer + CBuffer* buffer = bufman.getNewest(); + if (!buffer) { + delay(50); + continue; + } +#if SERVER_PROTOCOL == PROTOCOL_UDP + store.header(devid); +#endif + store.timestamp(buffer->timestamp); + buffer->serialize(store); + bufman.free(buffer); + store.tailer(); + Serial.print("[DAT] "); + Serial.println(store.buffer()); + + // start transmission +#ifdef PIN_LED + if (ledMode == 0) digitalWrite(PIN_LED, HIGH); +#endif + + #if SERVER_ENCRYPTION_ENABLE == 1 + char *orig_send_buf = store.buffer(); + unsigned int orig_send_buf_len = store.length(); + // Increase the size of encrypted_buf to hold the nonce, the encrypted data, and the authentication tag. + unsigned char encrypted_buf[12 + orig_send_buf_len + 16]; // 12 bytes for nonce and 16 bytes for tag + encrypt_string((unsigned char *)orig_send_buf, orig_send_buf_len, encrypted_buf); + if (teleClient.transmit((const char *)encrypted_buf, sizeof(encrypted_buf))) { + #else + if (teleClient.transmit(store.buffer(), store.length())) { + #endif + // successfully sent + connErrors = 0; + showStats(); + } else { + timeoutsNet++; + connErrors++; + printTimeoutStats(); + if (connErrors < MAX_CONN_ERRORS_RECONNECT) { + // quick reconnect + teleClient.connect(true); + } + } +#ifdef PIN_LED + if (ledMode == 0) digitalWrite(PIN_LED, LOW); +#endif + store.purge(); + + teleClient.inbound(); + + if (state.check(STATE_CELL_CONNECTED) && !teleClient.cell.check(1000)) { + Serial.println("[CELL] Not in service"); + state.clear(STATE_NET_READY | STATE_CELL_CONNECTED); + break; + } + + if (syncInterval > 10000 && millis() - teleClient.lastSyncTime > syncInterval) { + Serial.println("[NET] Poor connection"); + timeoutsNet++; + if (!teleClient.connect()) { + connErrors++; + } + } + + if (connErrors >= MAX_CONN_ERRORS_RECONNECT) { +#if ENABLE_WIFI + if (state.check(STATE_WIFI_CONNECTED)) { + teleClient.wifi.end(); + state.clear(STATE_NET_READY | STATE_WIFI_CONNECTED); + break; + } +#endif + if (state.check(STATE_CELL_CONNECTED)) { + teleClient.cell.end(); + state.clear(STATE_NET_READY | STATE_CELL_CONNECTED); + break; + } + } + + if (deviceTemp >= COOLING_DOWN_TEMP) { + // device too hot, cool down by pause transmission + Serial.print("HIGH DEVICE TEMP: "); + Serial.println(deviceTemp); + bufman.purge(); + } + + } + } +} + +/******************************************************************************* + Implementing stand-by mode +*******************************************************************************/ +void standby() +{ + state.set(STATE_STANDBY); +#if STORAGE != STORAGE_NONE + if (state.check(STATE_STORAGE_READY)) { + logger.end(); + } +#endif + +#if !GNSS_ALWAYS_ON && GNSS == GNSS_STANDALONE + if (state.check(STATE_GPS_READY)) { + Serial.println("[GPS] OFF"); + sys.gpsEnd(true); + state.clear(STATE_GPS_READY); + gd = 0; + } +#endif + + state.clear(STATE_WORKING | STATE_OBD_READY | STATE_STORAGE_READY); + // this will put co-processor into sleep mode +#if ENABLE_OLED + oled.print("STANDBY"); + delay(1000); + oled.clear(); +#endif + Serial.println("STANDBY"); + obd.enterLowPowerMode(); +#if ENABLE_MEMS + calibrateMEMS(); + waitMotion(-1); +#elif ENABLE_OBD + do { + delay(5000); + } while (obd.getVoltage() < JUMPSTART_VOLTAGE); +#else + delay(5000); +#endif + Serial.println("WAKEUP"); + sys.resetLink(); +#if RESET_AFTER_WAKEUP +#if ENABLE_MEMS + if (mems) mems->end(); +#endif + ESP.restart(); +#endif + state.clear(STATE_STANDBY); +} + +/******************************************************************************* + Tasks to perform in idle/waiting time +*******************************************************************************/ +void genDeviceID(char* buf) +{ + uint64_t seed = ESP.getEfuseMac() >> 8; + for (int i = 0; i < 8; i++, seed >>= 5) { + byte x = (byte)seed & 0x1f; + if (x >= 10) { + x = x - 10 + 'A'; + switch (x) { + case 'B': x = 'W'; break; + case 'D': x = 'X'; break; + case 'I': x = 'Y'; break; + case 'O': x = 'Z'; break; + } + } else { + x += '0'; + } + buf[i] = x; + } + buf[8] = 0; +} + +void showSysInfo() +{ + Serial.print("CPU:"); + Serial.print(ESP.getCpuFreqMHz()); + Serial.print("MHz FLASH:"); + Serial.print(ESP.getFlashChipSize() >> 20); + Serial.println("MB"); + Serial.print("IRAM:"); + Serial.print(ESP.getHeapSize() >> 10); + Serial.print("KB"); +#if BOARD_HAS_PSRAM + if (psramInit()) { + Serial.print(" PSRAM:"); + Serial.print(esp_spiram_get_size() >> 20); + Serial.print("MB"); + } +#endif + Serial.println(); + + int rtc = rtc_clk_slow_freq_get(); + if (rtc) { + Serial.print("RTC:"); + Serial.println(rtc); + } + +#if ENABLE_OLED + oled.clear(); + oled.print("CPU:"); + oled.print(ESP.getCpuFreqMHz()); + oled.print("Mhz "); + oled.print(getFlashSize() >> 10); + oled.println("MB Flash"); +#endif + + Serial.print("DEVICE ID:"); + Serial.println(devid); +#if ENABLE_OLED + oled.print("DEVICE ID:"); + oled.println(devid); +#endif +} + +void loadConfig() +{ + size_t len; + len = sizeof(apn); + apn[0] = 0; + nvs_get_str(nvs, "CELL_APN", apn, &len); + if (!apn[0]) { + strcpy(apn, CELL_APN); + } + +#if ENABLE_WIFI + len = sizeof(wifiSSID); + nvs_get_str(nvs, "WIFI_SSID", wifiSSID, &len); + len = sizeof(wifiPassword); + nvs_get_str(nvs, "WIFI_PWD", wifiPassword, &len); +#endif +} + +void processBLE(int timeout) +{ +#if ENABLE_BLE + static byte echo = 0; + char* cmd; + if (!(cmd = ble_recv_command(timeout))) { + return; + } + + char *p = strchr(cmd, '\r'); + if (p) *p = 0; + char buf[48]; + int bufsize = sizeof(buf); + int n = 0; + if (echo) n += snprintf(buf + n, bufsize - n, "%s\r", cmd); + Serial.print("[BLE] "); + Serial.print(cmd); + if (!strcmp(cmd, "UPTIME") || !strcmp(cmd, "TICK")) { + n += snprintf(buf + n, bufsize - n, "%lu", millis()); + } else if (!strcmp(cmd, "BATT")) { + n += snprintf(buf + n, bufsize - n, "%.2f", (float)(analogRead(A0) * 42) / 4095); + } else if (!strcmp(cmd, "RESET")) { +#if STORAGE + logger.end(); +#endif + ESP.restart(); + // never reach here + } else if (!strcmp(cmd, "OFF")) { + state.set(STATE_STANDBY); + state.clear(STATE_WORKING); + n += snprintf(buf + n, bufsize - n, "OK"); + } else if (!strcmp(cmd, "ON")) { + state.clear(STATE_STANDBY); + n += snprintf(buf + n, bufsize - n, "OK"); + } else if (!strcmp(cmd, "ON?")) { + n += snprintf(buf + n, bufsize - n, "%u", state.check(STATE_STANDBY) ? 0 : 1); + } else if (!strcmp(cmd, "APN?")) { + n += snprintf(buf + n, bufsize - n, "%s", *apn ? apn : "DEFAULT"); + } else if (!strncmp(cmd, "APN=", 4)) { + n += snprintf(buf + n, bufsize - n, nvs_set_str(nvs, "CELL_APN", strcmp(cmd + 4, "DEFAULT") ? cmd + 4 : "") == ESP_OK ? "OK" : "ERR"); + loadConfig(); + } else if (!strcmp(cmd, "NET_OP")) { + if (state.check(STATE_WIFI_CONNECTED)) { +#if ENABLE_WIFI + n += snprintf(buf + n, bufsize - n, "%s", wifiSSID[0] ? wifiSSID : "-"); +#endif + } else { + snprintf(buf + n, bufsize - n, "%s", netop.length() ? netop.c_str() : "-"); + char *p = strchr(buf + n, ' '); + if (p) *p = 0; + n += strlen(buf + n); + } + } else if (!strcmp(cmd, "NET_IP")) { + n += snprintf(buf + n, bufsize - n, "%s", ip.length() ? ip.c_str() : "-"); + } else if (!strcmp(cmd, "NET_PACKET")) { + n += snprintf(buf + n, bufsize - n, "%u", teleClient.txCount); + } else if (!strcmp(cmd, "NET_DATA")) { + n += snprintf(buf + n, bufsize - n, "%u", teleClient.txBytes); + } else if (!strcmp(cmd, "NET_RATE")) { + n += snprintf(buf + n, bufsize - n, "%u", teleClient.startTime ? (unsigned int)((uint64_t)(teleClient.txBytes + teleClient.rxBytes) * 3600 / (millis() - teleClient.startTime)) : 0); + } else if (!strcmp(cmd, "RSSI")) { + n += snprintf(buf + n, bufsize - n, "%d", rssi); +#if ENABLE_WIFI + } else if (!strcmp(cmd, "SSID?")) { + n += snprintf(buf + n, bufsize - n, "%s", wifiSSID[0] ? wifiSSID : "-"); + } else if (!strncmp(cmd, "SSID=", 5)) { + const char* p = cmd + 5; + n += snprintf(buf + n, bufsize - n, nvs_set_str(nvs, "WIFI_SSID", strcmp(p, "-") ? p : "") == ESP_OK ? "OK" : "ERR"); + loadConfig(); + } else if (!strcmp(cmd, "WPWD?")) { + n += snprintf(buf + n, bufsize - n, "%s", wifiPassword[0] ? wifiPassword : "-"); + } else if (!strncmp(cmd, "WPWD=", 5)) { + const char* p = cmd + 5; + n += snprintf(buf + n, bufsize - n, nvs_set_str(nvs, "WIFI_PWD", strcmp(p, "-") ? p : "") == ESP_OK ? "OK" : "ERR"); + loadConfig(); +#else + } else if (!strcmp(cmd, "SSID?") || !strcmp(cmd, "WPWD?")) { + n += snprintf(buf + n, bufsize - n, "-"); +#endif +#if ENABLE_MEMS + } else if (!strcmp(cmd, "TEMP")) { + n += snprintf(buf + n, bufsize - n, "%d", (int)deviceTemp); + } else if (!strcmp(cmd, "ACC")) { + n += snprintf(buf + n, bufsize - n, "%.1f/%.1f/%.1f", acc[0], acc[1], acc[2]); + } else if (!strcmp(cmd, "GYRO")) { + n += snprintf(buf + n, bufsize - n, "%.1f/%.1f/%.1f", gyr[0], gyr[1], gyr[2]); + } else if (!strcmp(cmd, "GF")) { + n += snprintf(buf + n, bufsize - n, "%f", (float)sqrt(acc[0]*acc[0] + acc[1]*acc[1] + acc[2]*acc[2])); +#endif + } else if (!strcmp(cmd, "ATE0")) { + echo = 0; + n += snprintf(buf + n, bufsize - n, "OK"); + } else if (!strcmp(cmd, "ATE1")) { + echo = 1; + n += snprintf(buf + n, bufsize - n, "OK"); + } else if (!strcmp(cmd, "FS")) { + n += snprintf(buf + n, bufsize - n, "%u", +#if STORAGE == STORAGE_NONE + 0 +#else + logger.size() +#endif + ); + } else if (!memcmp(cmd, "01", 2)) { + byte pid = hex2uint8(cmd + 2); + for (byte i = 0; i < sizeof(obdData) / sizeof(obdData[0]); i++) { + if (obdData[i].pid == pid) { + n += snprintf(buf + n, bufsize - n, "%d", obdData[i].value); + pid = 0; + break; + } + } + if (pid) { + int value; + if (obd.readPID(pid, value)) { + n += snprintf(buf + n, bufsize - n, "%d", value); + } else { + n += snprintf(buf + n, bufsize - n, "N/A"); + } + } + } else if (!strcmp(cmd, "VIN")) { + n += snprintf(buf + n, bufsize - n, "%s", vin[0] ? vin : "N/A"); + } else if (!strcmp(cmd, "LAT") && gd) { + n += snprintf(buf + n, bufsize - n, "%f", gd->lat); + } else if (!strcmp(cmd, "LNG") && gd) { + n += snprintf(buf + n, bufsize - n, "%f", gd->lng); + } else if (!strcmp(cmd, "ALT") && gd) { + n += snprintf(buf + n, bufsize - n, "%d", (int)gd->alt); + } else if (!strcmp(cmd, "SAT") && gd) { + n += snprintf(buf + n, bufsize - n, "%u", (unsigned int)gd->sat); + } else if (!strcmp(cmd, "SPD") && gd) { + n += snprintf(buf + n, bufsize - n, "%d", (int)(gd->speed * 1852 / 1000)); + } else if (!strcmp(cmd, "CRS") && gd) { + n += snprintf(buf + n, bufsize - n, "%u", (unsigned int)gd->heading); + } else { + n += snprintf(buf + n, bufsize - n, "ERROR"); + } + Serial.print(" -> "); + Serial.println((p = strchr(buf, '\r')) ? p + 1 : buf); + if (n < bufsize - 1) { + buf[n++] = '\r'; + } else { + n = bufsize - 1; + } + buf[n] = 0; + ble_send_response(buf, n, cmd); +#else + if (timeout) delay(timeout); +#endif +} + +void setup() +{ + delay(500); + + // Initialize NVS + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + // NVS partition was truncated and needs to be erased + // Retry nvs_flash_init + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK( err ); + err = nvs_open("storage", NVS_READWRITE, &nvs); + if (err == ESP_OK) { + loadConfig(); + } + +#if ENABLE_OLED + oled.begin(); + oled.setFontSize(FONT_SIZE_SMALL); +#endif + // initialize USB serial + Serial.begin(115200); + + // init LED pin +#ifdef PIN_LED + pinMode(PIN_LED, OUTPUT); + if (ledMode == 0) digitalWrite(PIN_LED, HIGH); +#endif + + // generate unique device ID + genDeviceID(devid); + +#if CONFIG_MODE_TIMEOUT + configMode(); +#endif + +#if LOG_EXT_SENSORS == 1 + pinMode(PIN_SENSOR1, INPUT); + pinMode(PIN_SENSOR2, INPUT); +#elif LOG_EXT_SENSORS == 2 + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); + adc1_config_channel_atten(ADC1_CHANNEL_1, ADC_ATTEN_DB_11); +#endif + + // show system information + showSysInfo(); + + bufman.init(); + + //Serial.print(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) >> 10); + //Serial.println("KB"); + +#if ENABLE_OBD + if (sys.begin()) { + Serial.print("TYPE:"); + Serial.println(sys.devType); + obd.begin(sys.link); + } +#else + sys.begin(false, true); +#endif + +#if ENABLE_MEMS +if (!state.check(STATE_MEMS_READY)) do { + Serial.print("MEMS:"); + mems = new ICM_42627; + byte ret = mems->begin(); + if (ret) { + state.set(STATE_MEMS_READY); + Serial.println("ICM-42627"); + break; + } + delete mems; + mems = new ICM_20948_I2C; + ret = mems->begin(); + if (ret) { + state.set(STATE_MEMS_READY); + Serial.println("ICM-20948"); + break; + } + delete mems; + /* + mems = new MPU9250; + ret = mems->begin(); + if (ret) { + state.set(STATE_MEMS_READY); + Serial.println("MPU-9250"); + break; + } + */ + mems = 0; + Serial.println("NO"); +} while (0); +#endif + +#if ENABLE_HTTPD + IPAddress ip; + if (serverSetup(ip)) { + Serial.println("HTTPD:"); + Serial.println(ip); +#if ENABLE_OLED + oled.println(ip); +#endif + } else { + Serial.println("HTTPD:NO"); + } +#endif + + state.set(STATE_WORKING); + +#if ENABLE_BLE + // init BLE + ble_init("FreematicsPlus"); +#endif + + // initialize components + initialize(); + + // initialize network and maintain connection + subtask.create(telemetry, "telemetry", 2, 8192); + +#ifdef PIN_LED + digitalWrite(PIN_LED, LOW); +#endif +} + +void loop() +{ + // error handling + if (!state.check(STATE_WORKING)) { + standby(); +#ifdef PIN_LED + if (ledMode == 0) digitalWrite(PIN_LED, HIGH); +#endif + initialize(); +#ifdef PIN_LED + digitalWrite(PIN_LED, LOW); +#endif + return; + } + + // collect and log data + process(); +} \ No newline at end of file diff --git a/esp32/telelogger/telemesh.cpp b/esp32/telelogger/telemesh.cpp new file mode 100644 index 0000000..fe852ae --- /dev/null +++ b/esp32/telelogger/telemesh.cpp @@ -0,0 +1,113 @@ +#include "FreematicsPlus.h" +#include "config.h" +#include "telemesh.h" +#if 0 +#include +#include + +static mdf_err_t event_loop_cb(mdf_event_loop_t event, void *ctx) +{ + switch (event) { + case MDF_EVENT_MWIFI_STARTED: + Serial.println("MESH is started"); + break; + + case MDF_EVENT_MWIFI_PARENT_CONNECTED: + Serial.println("Parent is connected on station interface"); + break; + + case MDF_EVENT_MWIFI_PARENT_DISCONNECTED: + Serial.println("Parent is disconnected on station interface"); + break; + + default: + break; + } + + return MDF_OK; +} + +static mdf_err_t wifi_init() +{ + mdf_err_t ret = nvs_flash_init(); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + nvs_flash_erase(); + ret = nvs_flash_init(); + } + + //MDF_ERROR_ASSERT(ret); + + tcpip_adapter_init(); + esp_event_loop_init(NULL, NULL); + esp_wifi_init(&cfg); + esp_wifi_set_storage(WIFI_STORAGE_FLASH); + esp_wifi_set_mode(WIFI_MODE_STA); + esp_wifi_set_ps(WIFI_PS_NONE); + esp_mesh_set_6m_rate(false); + return esp_wifi_start(); +} + +bool ClientWiFiMesh::begin(CFreematics* device) +{ + if (m_inited) return true; + + mwifi_init_config_t cfg = MWIFI_INIT_CONFIG_DEFAULT(); + mwifi_config_t config = {0}; + config.channel = WIFI_MESH_CHANNEL; + config.mesh_type = MWIFI_MESH_NODE; + memcpy(config.mesh_id, WIFI_MESH_ID, sizeof(WIFI_MESH_ID) - 1); + + mdf_event_loop_init(event_loop_cb); + wifi_init(); + mwifi_init(&cfg); + mwifi_set_config(&config); + if (mwifi_start() == MDF_OK) { + m_inited = true; + return true; + } + return false; +} + + bool ClientWiFiMesh::open(const char* host, uint16_t port) + { + return mwifi_is_connected(); + } + +bool ClientWiFiMesh::send(const char* data, unsigned int len) +{ + if (mwifi_is_connected()) { + mwifi_data_type_t data_type = {0x0}; + if (mwifi_write(NULL, &data_type, data, len, true) == MDF_OK) + return true; + } + return false; +} + +char* ClientWiFiMesh::receive(int* pbytes, unsigned int timeout) +{ + if (mwifi_is_connected()) { + uint8_t src_addr[MWIFI_ADDR_LEN] = {0x0}; + mwifi_data_type_t data_type = {0x0}; + size_t size = MESH_RECV_BUF_SIZE - 1; + if (mwifi_read(src_addr, &data_type, m_buffer, &size, timeout / portTICK_PERIOD_MS) == MDF_OK) { + m_buffer[size] = 0; + if (pbytes) *pbytes = size; + return m_buffer; + } + } + return 0; +} + +ClientWiFiMesh::ClientWiFiMesh() +{ + m_buffer = (char*)malloc(MESH_RECV_BUF_SIZE); +} + +ClientWiFiMesh::~ClientWiFiMesh() +{ + free(m_buffer); +} + +#endif \ No newline at end of file diff --git a/esp32/telelogger/telemesh.h b/esp32/telelogger/telemesh.h new file mode 100644 index 0000000..0746800 --- /dev/null +++ b/esp32/telelogger/telemesh.h @@ -0,0 +1,43 @@ +class ClientSerial +{ +public: + bool begin(CFreematics* device) { return true; } + void end() {} + bool open(const char* host, uint16_t port) { return true; } + void close() {} + bool send(const char* data, unsigned int len) + { + Serial.write((uint8_t*)data, len); + Serial.println(); + return true; + } + char* receive(int* pbytes = 0, unsigned int timeout = 5000) + { + Serial.setTimeout(timeout); + int bytes = Serial.readBytes((uint8_t*)m_buffer, sizeof(m_buffer) - 1); + if (pbytes) *pbytes = bytes; + return bytes > 0 ? m_buffer : 0; + } + const char* deviceName() { return "Serial"; } +private: + char m_buffer[128] = {0}; +}; + +#define MESH_RECV_BUF_SIZE 256 + +class ClientWiFiMesh +{ +public: + ClientWiFiMesh(); + ~ClientWiFiMesh(); + bool begin(CFreematics* device); + void end() {} + bool open(const char* host, uint16_t port); + void close() {} + bool send(const char* data, unsigned int len); + char* receive(int* pbytes = 0, unsigned int timeout = 5000); + const char* deviceName() { return "WiFi Mesh"; } +private: + char* m_buffer; + bool m_inited = false; +}; diff --git a/esp32/telelogger/telestore.cpp b/esp32/telelogger/telestore.cpp new file mode 100644 index 0000000..4eeec8c --- /dev/null +++ b/esp32/telelogger/telestore.cpp @@ -0,0 +1,263 @@ +#include +#include "telestore.h" + +void CStorage::log(uint16_t pid, uint8_t values[], uint8_t count) +{ + char buf[256]; + byte n = snprintf(buf, sizeof(buf), "%X%c%u", pid, m_delimiter, (unsigned int)values[0]); + for (byte m = 1; m < count; m++) { + n += snprintf(buf + n, sizeof(buf) - n, ";%u", (unsigned int)values[m]); + } + dispatch(buf, n); +} + +void CStorage::log(uint16_t pid, uint16_t values[], uint8_t count) +{ + char buf[256]; + byte n = snprintf(buf, sizeof(buf), "%X%c%u", pid, m_delimiter, (unsigned int)values[0]); + for (byte m = 1; m < count; m++) { + n += snprintf(buf + n, sizeof(buf) - n, ";%u", (unsigned int)values[m]); + } + dispatch(buf, n); +} + +void CStorage::log(uint16_t pid, uint32_t values[], uint8_t count) +{ + char buf[256]; + byte n = snprintf(buf, sizeof(buf), "%X%c%u", pid, m_delimiter, values[0]); + for (byte m = 1; m < count; m++) { + n += snprintf(buf + n, sizeof(buf) - n, ";%u", values[m]); + } + dispatch(buf, n); +} + +void CStorage::log(uint16_t pid, int32_t values[], uint8_t count) +{ + char buf[256]; + byte n = snprintf(buf, sizeof(buf), "%X%c%d", pid, m_delimiter, values[0]); + for (byte m = 1; m < count; m++) { + n += snprintf(buf + n, sizeof(buf) - n, ";%d", values[m]); + } + dispatch(buf, n); +} + +void CStorage::log(uint16_t pid, float values[], uint8_t count, const char* fmt) +{ + char buf[256]; + char *p = buf + snprintf(buf, sizeof(buf), "%X%c", pid, m_delimiter); + for (byte m = 0; m < count && (p - buf) < sizeof(buf) - 3; m++) { + if (m > 0) *(p++) = ';'; + int l = snprintf(p, sizeof(buf) - (p - buf), fmt, values[m]); + char *q = strchr(p, '.'); + if (q && atoi(q + 1) == 0) { + *q = 0; + if (*p == '-' && *(p + 1) == '0') { + *p = '0'; + *(++p) = 0; + } else { + p = q; + } + } else { + p += l; + } + } + dispatch(buf, (int)(p - buf)); +} + +void CStorage::timestamp(uint32_t ts) +{ + log(PID_TIMESTAMP, &ts, 1); +} + +void CStorage::dispatch(const char* buf, byte len) +{ + // output data via serial + Serial.write((uint8_t*)buf, len); + Serial.write(' '); + m_samples++; +} + +byte CStorage::checksum(const char* data, int len) +{ + byte sum = 0; + for (int i = 0; i < len; i++) sum += data[i]; + return sum; +} + +void CStorageRAM::dispatch(const char* buf, byte len) +{ + // reserve some space for checksum + int remain = m_cacheSize - m_cacheBytes - len - 3; + if (remain < 0) { + // m_cache full + return; + } + // store data in m_cache + memcpy(m_cache + m_cacheBytes, buf, len); + m_cacheBytes += len; + m_cache[m_cacheBytes++] = ','; + m_samples++; +} + +void CStorageRAM::header(const char* devid) +{ + m_cacheBytes = sprintf(m_cache, "%s#", devid); +} + +void CStorageRAM::tailer() +{ + if (m_cache[m_cacheBytes - 1] == ',') m_cacheBytes--; + m_cacheBytes += sprintf(m_cache + m_cacheBytes, "*%X", (unsigned int)checksum(m_cache, m_cacheBytes)); +} + +void CStorageRAM::untailer() +{ + char *p = strrchr(m_cache, '*'); + if (p) { + *p = ','; + m_cacheBytes = p + 1 - m_cache; + } +} + +void FileLogger::dispatch(const char* buf, byte len) +{ + if (m_id == 0) return; + + if (m_file.write((uint8_t*)buf, len) != len) { + // try again + if (m_file.write((uint8_t*)buf, len) != len) { + Serial.println("Error writing. End file logging."); + end(); + return; + } + } + m_file.write('\n'); + m_size += (len + 1); +} + +int FileLogger::getFileID(File& root) +{ + if (root) { + File file; + int id = 0; + while(file = root.openNextFile()) { + char *p = strrchr(file.name(), '/'); + unsigned int n = atoi(p ? p + 1 : file.name()); + if (n > id) id = n; + } + return id + 1; + } else { + return 0; + } +} + +bool SDLogger::init() +{ + SPI.begin(); + if (SD.begin(PIN_SD_CS, SPI, SPI_FREQ)) { + unsigned int total = SD.totalBytes() >> 20; + unsigned int used = SD.usedBytes() >> 20; + Serial.print("SD:"); + Serial.print(total); + Serial.print(" MB total, "); + Serial.print(used); + Serial.println(" MB used"); + return true; + } else { + Serial.println("NO SD CARD"); + return false; + } +} + +uint32_t SDLogger::begin() +{ + File root = SD.open("/DATA"); + m_id = getFileID(root); + if (m_id == 0) { + SD.mkdir("/DATA"); + m_id = 1; + } + char path[24]; + sprintf(path, "/DATA/%u.CSV", m_id); + Serial.print("File: "); + Serial.println(path); + m_file = SD.open(path, FILE_WRITE); + if (!m_file) { + Serial.println("File error"); + m_id = 0; + } + m_dataCount = 0; + return m_id; +} + +void SDLogger::flush() +{ + char path[24]; + sprintf(path, "/DATA/%u.CSV", m_id); + m_file.close(); + m_file = SD.open(path, FILE_APPEND); + if (!m_file) { + Serial.println("File error"); + } +} + +bool SPIFFSLogger::init() +{ + bool mounted = SPIFFS.begin(); + if (!mounted) { + Serial.println("Formatting SPIFFS..."); + mounted = SPIFFS.begin(true); + } + if (mounted) { + Serial.print("SPIFFS:"); + Serial.print(SPIFFS.totalBytes()); + Serial.print(" bytes total, "); + Serial.print(SPIFFS.usedBytes()); + Serial.println(" bytes used"); + } else { + Serial.println("No SPIFFS"); + } + return mounted; +} + +uint32_t SPIFFSLogger::begin() +{ + File root = SPIFFS.open("/"); + m_id = getFileID(root); + char path[24]; + sprintf(path, "/DATA/%u.CSV", m_id); + Serial.print("File: "); + Serial.println(path); + m_file = SPIFFS.open(path, FILE_WRITE); + if (!m_file) { + Serial.println("File error"); + m_id = 0; + } + m_dataCount = 0; + return m_id; +} + +void SPIFFSLogger::purge() +{ + // remove oldest file when unused space is insufficient + File root = SPIFFS.open("/"); + File file; + int idx = 0; + while(file = root.openNextFile()) { + if (!strncmp(file.name(), "/DATA/", 6)) { + unsigned int n = atoi(file.name() + 6); + if (n != 0 && (idx == 0 || n < idx)) idx = n; + } + } + if (idx) { + m_file.close(); + char path[32]; + sprintf(path, "/DATA/%u.CSV", idx); + SPIFFS.remove(path); + Serial.print(path); + Serial.println(" removed"); + sprintf(path, "/DATA/%u.CSV", m_id); + m_file = SPIFFS.open(path, FILE_APPEND); + if (!m_file) m_id = 0; + } +} diff --git a/esp32/telelogger/telestore.h b/esp32/telelogger/telestore.h new file mode 100644 index 0000000..5926b29 --- /dev/null +++ b/esp32/telelogger/telestore.h @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +class CStorage; + +class CStorage { +public: + virtual bool init() { return true; } + virtual void uninit() {} + virtual void log(uint16_t pid, uint8_t values[], uint8_t count); + virtual void log(uint16_t pid, uint16_t values[], uint8_t count); + virtual void log(uint16_t pid, uint32_t values[], uint8_t count); + virtual void log(uint16_t pid, int32_t values[], uint8_t count); + virtual void log(uint16_t pid, float values[], uint8_t count, const char* fmt = "%f"); + virtual void timestamp(uint32_t ts); + virtual void purge() { m_samples = 0; } + virtual uint16_t samples() { return m_samples; } + virtual void dispatch(const char* buf, byte len); +protected: + byte checksum(const char* data, int len); + virtual void header(const char* devid) {} + virtual void tailer() {} + int m_samples = 0; + char m_delimiter = ':'; +}; + +class CStorageRAM: public CStorage { +public: + void init(char* cache, unsigned int cacheSize) + { + m_cacheSize = cacheSize; + m_cache = cache; + } + void uninit() + { + if (m_cache) { + delete m_cache; + m_cache = 0; + m_cacheSize = 0; + } + } + void purge() { m_cacheBytes = 0; m_samples = 0; } + unsigned int length() { return m_cacheBytes; } + char* buffer() { return m_cache; } + void dispatch(const char* buf, byte len); + void header(const char* devid); + void tailer(); + void untailer(); +protected: + unsigned int m_cacheSize = 0; + unsigned int m_cacheBytes = 0; + char* m_cache = 0; +}; + +class FileLogger : public CStorage { +public: + FileLogger() { m_delimiter = ','; } + virtual void dispatch(const char* buf, byte len); + virtual uint32_t size() { return m_size; } + virtual void end() + { + m_file.close(); + m_id = 0; + m_size = 0; + } + virtual void flush() + { + m_file.flush(); + } +protected: + int getFileID(File& root); + uint32_t m_dataTime = 0; + uint32_t m_dataCount = 0; + uint32_t m_size = 0; + uint32_t m_id = 0; + File m_file; +}; + +class SDLogger : public FileLogger { +public: + bool init(); + uint32_t begin(); + void flush(); +}; + +class SPIFFSLogger : public FileLogger { +public: + bool init(); + uint32_t begin(); +private: + void purge(); +};