1508 lines
39 KiB
C++
1508 lines
39 KiB
C++
/******************************************************************************
|
|
* 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 <stanley@freematics.com.au>
|
|
* 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 <FreematicsPlus.h>
|
|
#include <httpd.h>
|
|
#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();
|
|
} |