diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 0236a381..1e03f086 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -75,7 +75,9 @@ class HomeScreen : public UIScreen { RADIO, BLUETOOTH, ADVERT, +#if UI_SENSORS_PAGE == 1 SENSORS, +#endif SHUTDOWN, Count // keep as last }; @@ -114,20 +116,25 @@ class HomeScreen : public UIScreen { display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); } - DynamicJsonDocument _sensors_doc; - JsonArray _sensors_arr; - bool scroll = false; - int scroll_offset = 0; + CayenneLPP sensors_lpp; + int sensors_nb = 0; + bool sensors_scroll = false; + int sensors_scroll_offset = 0; int next_sensors_refresh = 0; void refresh_sensors() { - CayenneLPP lpp(200); if (millis() > next_sensors_refresh) { - lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - sensors.querySensors(0xFF, lpp); - _sensors_arr.clear(); - lpp.decode(lpp.getBuffer(), lpp.getSize(), _sensors_arr); - scroll = _sensors_arr.size() > UI_RECENT_LIST_SIZE; // there is a status line + sensors_lpp.reset(); + sensors_nb = 0; + sensors_lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + sensors.querySensors(0xFF, sensors_lpp); + LPPReader reader (sensors_lpp.getBuffer(), sensors_lpp.getSize()); + uint8_t channel, type; + while(reader.readHeader(channel, type)) { + reader.skipData(type); + sensors_nb ++; + } + sensors_scroll = sensors_nb > UI_RECENT_LIST_SIZE; #if AUTO_OFF_MILLIS > 0 next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec #else @@ -139,7 +146,7 @@ class HomeScreen : public UIScreen { public: HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), - _shutdown_init(false), _sensors_doc(2048) { _sensors_arr=_sensors_doc.to(); } + _shutdown_init(false), sensors_lpp(200) { } void poll() override { if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released @@ -235,34 +242,78 @@ public: display.setColor(DisplayDriver::GREEN); display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL); +#if UI_SENSORS_PAGE == 1 } else if (_page == HomePage::SENSORS) { int y = 18; refresh_sensors(); - char buf[100]; - int s_size = _sensors_arr.size(); - for (int i = 0; i < (scroll?UI_RECENT_LIST_SIZE:s_size); i++) { - JsonObject v = _sensors_arr[(i+scroll_offset)%s_size]; + char buf[30]; + char name[30]; + LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize()); + + for (int i = 0; i < sensors_scroll_offset; i++) { + uint8_t channel, type; + r.readHeader(channel, type); + r.skipData(type); + } + + for (int i = 0; i < (sensors_scroll?UI_RECENT_LIST_SIZE:sensors_nb); i++) { + uint8_t channel, type; + if (!r.readHeader(channel, type)) { // reached end, reset + r.reset(); + r.readHeader(channel, type); + } + display.setCursor(0, y); - switch (v["type"].as()) { - case 136: // GPS - sprintf(buf, "%.4f %.4f", - v["value"]["latitude"].as(), - v["value"]["longitude"].as()); + float v; + switch (type) { + case LPP_GPS: // GPS + float lat, lon, alt; + r.readGPS(lat, lon, alt); + strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon); break; - default: // will be a float for now - sprintf(buf, "%.02f", - v["value"].as()); + case LPP_VOLTAGE: + r.readVoltage(v); + strcpy(name, "voltage"); sprintf(buf, "%6.2f", v); + break; + case LPP_CURRENT: + r.readCurrent(v); + strcpy(name, "current"); sprintf(buf, "%.3f", v); + break; + case LPP_TEMPERATURE: + r.readTemperature(v); + strcpy(name, "temperature"); sprintf(buf, "%.2f", v); + break; + case LPP_RELATIVE_HUMIDITY: + r.readRelativeHumidity(v); + strcpy(name, "humidity"); sprintf(buf, "%.2f", v); + break; + case LPP_BAROMETRIC_PRESSURE: + r.readPressure(v); + strcpy(name, "pressure"); sprintf(buf, "%.2f", v); + break; + case LPP_ALTITUDE: + r.readAltitude(v); + strcpy(name, "altitude"); sprintf(buf, "%.0f", v); + break; + case LPP_POWER: + r.readPower(v); + strcpy(name, "power"); sprintf(buf, "%6.2f", v); + break; + default: + r.skipData(type); + strcpy(name, "unk"); sprintf(buf, ""); } display.setCursor(0, y); - display.print(v["name"].as().c_str()); + display.print(name); display.setCursor( display.width()-display.getTextWidth(buf)-1, y ); display.print(buf); y = y + 12; } - if (scroll) scroll_offset = (scroll_offset+1)%s_size; - else scroll_offset = 0; + if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb; + else sensors_scroll_offset = 0; +#endif } else if (_page == HomePage::SHUTDOWN) { display.setColor(DisplayDriver::GREEN); display.setTextSize(1); @@ -307,11 +358,13 @@ public: } return true; } +#if UI_SENSORS_PAGE == 1 if (c == KEY_ENTER && _page == HomePage::SENSORS) { _task->toggleGPS(); next_sensors_refresh=0; return true; } +#endif if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { _shutdown_init = true; // need to wait for button to be released return true; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 46024b1f..769b2c64 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef PIN_BUZZER #include diff --git a/src/helpers/sensors/LPPDataHelpers.h b/src/helpers/sensors/LPPDataHelpers.h new file mode 100644 index 00000000..b9025de4 --- /dev/null +++ b/src/helpers/sensors/LPPDataHelpers.h @@ -0,0 +1,223 @@ +#pragma once + +#include + +#define LPP_DIGITAL_INPUT 0 // 1 byte +#define LPP_DIGITAL_OUTPUT 1 // 1 byte +#define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed +#define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed +#define LPP_GENERIC_SENSOR 100 // 4 bytes, unsigned +#define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned +#define LPP_PRESENCE 102 // 1 byte, bool +#define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed +#define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned +#define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G +#define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1hPa unsigned +#define LPP_VOLTAGE 116 // 2 bytes 0.01V unsigned +#define LPP_CURRENT 117 // 2 bytes 0.001A unsigned +#define LPP_FREQUENCY 118 // 4 bytes 1Hz unsigned +#define LPP_PERCENTAGE 120 // 1 byte 1-100% unsigned +#define LPP_ALTITUDE 121 // 2 byte 1m signed +#define LPP_CONCENTRATION 125 // 2 bytes, 1 ppm unsigned +#define LPP_POWER 128 // 2 byte, 1W, unsigned +#define LPP_DISTANCE 130 // 4 byte, 0.001m, unsigned +#define LPP_ENERGY 131 // 4 byte, 0.001kWh, unsigned +#define LPP_DIRECTION 132 // 2 bytes, 1deg, unsigned +#define LPP_UNIXTIME 133 // 4 bytes, unsigned +#define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s +#define LPP_COLOUR 135 // 1 byte per RGB Color +#define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter +#define LPP_SWITCH 142 // 1 byte, 0/1 +#define LPP_POLYLINE 240 // 1 byte size, 1 byte delta factor, 3 byte lon/lat 0.0001° * factor, n (size-8) bytes deltas + +// Multipliers +#define LPP_DIGITAL_INPUT_MULT 1 +#define LPP_DIGITAL_OUTPUT_MULT 1 +#define LPP_ANALOG_INPUT_MULT 100 +#define LPP_ANALOG_OUTPUT_MULT 100 +#define LPP_GENERIC_SENSOR_MULT 1 +#define LPP_LUMINOSITY_MULT 1 +#define LPP_PRESENCE_MULT 1 +#define LPP_TEMPERATURE_MULT 10 +#define LPP_RELATIVE_HUMIDITY_MULT 2 +#define LPP_ACCELEROMETER_MULT 1000 +#define LPP_BAROMETRIC_PRESSURE_MULT 10 +#define LPP_VOLTAGE_MULT 100 +#define LPP_CURRENT_MULT 1000 +#define LPP_FREQUENCY_MULT 1 +#define LPP_PERCENTAGE_MULT 1 +#define LPP_ALTITUDE_MULT 1 +#define LPP_POWER_MULT 1 +#define LPP_DISTANCE_MULT 1000 +#define LPP_ENERGY_MULT 1000 +#define LPP_DIRECTION_MULT 1 +#define LPP_UNIXTIME_MULT 1 +#define LPP_GYROMETER_MULT 100 +#define LPP_GPS_LAT_LON_MULT 10000 +#define LPP_GPS_ALT_MULT 100 +#define LPP_SWITCH_MULT 1 +#define LPP_CONCENTRATION_MULT 1 +#define LPP_COLOUR_MULT 1 + +#define LPP_ERROR_OK 0 +#define LPP_ERROR_OVERFLOW 1 +#define LPP_ERROR_UNKOWN_TYPE 2 + +class LPPReader { + const uint8_t* _buf; + uint8_t _len; + uint8_t _pos; + + float getFloat(const uint8_t * buffer, uint8_t size, uint32_t multiplier, bool is_signed) { + uint32_t value = 0; + for (uint8_t i = 0; i < size; i++) { + value = (value << 8) + buffer[i]; + } + + int sign = 1; + if (is_signed) { + uint32_t bit = 1ul << ((size * 8) - 1); + if ((value & bit) == bit) { + value = (bit << 1) - value; + sign = -1; + } + } + return sign * ((float) value / multiplier); + } + +public: + LPPReader(const uint8_t buf[], uint8_t len) : _buf(buf), _len(len), _pos(0) { } + + void reset() { + _pos = 0; + } + + bool readHeader(uint8_t& channel, uint8_t& type) { + if (_pos + 2 < _len) { + channel = _buf[_pos++]; + type = _buf[_pos++]; + + return channel != 0; // channel 0 is End-of-data + } + return false; // end-of-buffer + } + + bool readGPS(float& lat, float& lon, float& alt) { + lat = getFloat(&_buf[_pos], 3, 10000, true); _pos += 3; + lon = getFloat(&_buf[_pos], 3, 10000, true); _pos += 3; + alt = getFloat(&_buf[_pos], 3, 100, true); _pos += 3; + return _pos <= _len; + } + bool readVoltage(float& voltage) { + voltage = getFloat(&_buf[_pos], 2, 100, false); _pos += 2; + return _pos <= _len; + } + bool readCurrent(float& amps) { + amps = getFloat(&_buf[_pos], 2, 1000, false); _pos += 2; + return _pos <= _len; + } + bool readPower(float& watts) { + watts = getFloat(&_buf[_pos], 2, 1, false); _pos += 2; + return _pos <= _len; + } + bool readTemperature(float& degrees_c) { + degrees_c = getFloat(&_buf[_pos], 2, 10, true); _pos += 2; + return _pos <= _len; + } + bool readPressure(float& pa) { + pa = getFloat(&_buf[_pos], 2, 10, false); _pos += 2; + return _pos <= _len; + } + bool readRelativeHumidity(float& pct) { + pct = getFloat(&_buf[_pos], 1, 2, false); _pos += 1; + return _pos <= _len; + } + bool readAltitude(float& m) { + m = getFloat(&_buf[_pos], 2, 1, true); _pos += 2; + return _pos <= _len; + } + + void skipData(uint8_t type) { + switch (type) { + case LPP_GPS: + _pos += 9; break; + case LPP_POLYLINE: + _pos += 8; break; // TODO: this is MINIMIUM + case LPP_GYROMETER: + case LPP_ACCELEROMETER: + _pos += 6; break; + case LPP_GENERIC_SENSOR: + case LPP_FREQUENCY: + case LPP_DISTANCE: + case LPP_ENERGY: + case LPP_UNIXTIME: + _pos += 4; break; + case LPP_COLOUR: + _pos += 3; break; + case LPP_ANALOG_INPUT: + case LPP_ANALOG_OUTPUT: + case LPP_LUMINOSITY: + case LPP_TEMPERATURE: + case LPP_CONCENTRATION: + case LPP_BAROMETRIC_PRESSURE: + case LPP_ALTITUDE: + case LPP_VOLTAGE: + case LPP_CURRENT: + case LPP_DIRECTION: + case LPP_POWER: + _pos += 2; break; + default: + _pos++; + } + } +}; + +class LPPWriter { + uint8_t* _buf; + uint8_t _max_len; + uint8_t _len; + + void write(uint16_t value) { + _buf[_len++] = (value >> 8) & 0xFF; // MSB + _buf[_len++] = value & 0xFF; // LSB + } + +public: + LPPWriter(uint8_t buf[], uint8_t max_len): _buf(buf), _max_len(max_len), _len(0) { } + + bool writeVoltage(uint8_t channel, float voltage) { + if (_len + 4 <= _max_len) { + _buf[_len++] = channel; + _buf[_len++] = LPP_VOLTAGE; + uint16_t value = voltage * 100; + write(value); + return true; + } + return false; + } + + bool writeGPS(uint8_t channel, float lat, float lon, float alt) { + if (_len + 11 <= _max_len) { + _buf[_len++] = channel; + _buf[_len++] = LPP_GPS; + + int32_t lati = lat * 10000; // we lose some precision :-( + int32_t loni = lon * 10000; + int32_t alti = alt * 100; + + _buf[_len++] = lati >> 16; + _buf[_len++] = lati >> 8; + _buf[_len++] = lati; + _buf[_len++] = loni >> 16; + _buf[_len++] = loni >> 8; + _buf[_len++] = loni; + _buf[_len++] = alti >> 16; + _buf[_len++] = alti >> 8; + _buf[_len++] = alti; + return true; + } + return false; + } + + uint8_t length() { return _len; } +}; diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index 7d64fad7..36617495 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -88,6 +88,8 @@ build_flags = -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 @@ -109,6 +111,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 build_src_filter = ${LilyGo_T-Echo.build_src_filter} +<../examples/companion_radio/*.cpp>