diff --git a/.gitmodules b/.gitmodules index a9fff2f..1fbe591 100644 --- a/.gitmodules +++ b/.gitmodules @@ -66,7 +66,7 @@ url = ../../0xFEEDC0DE64/esp-protocols.git [submodule "components/TFT_eSPI"] path = components/TFT_eSPI - url = ../../0xFEEDC0DE64/TFT_eSPI.git + url = ../../bobbycar-graz/TFT_eSPI.git [submodule "esp_boost"] path = esp_boost url = ../../0xFEEDC0DE64/esp_boost.git diff --git a/components/TFT_eSPI b/components/TFT_eSPI index 4dacb61..9fa26a0 160000 --- a/components/TFT_eSPI +++ b/components/TFT_eSPI @@ -1 +1 @@ -Subproject commit 4dacb617bec71ef793b14b69629b2214e06bea28 +Subproject commit 9fa26a02fb6bcd52090e20f964e75b515dd77f2c diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 5227b44..55c5832 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -228,6 +228,7 @@ set(headers presets.h profilesettings.h qrimport.h + remotedisplaywebsocket.h rotary.h screens.h serial_bobby.h @@ -480,6 +481,7 @@ set(sources presets.cpp profilesettings.cpp qrimport.cpp + remotedisplaywebsocket.cpp rotary.cpp screens.cpp serial_bobby.cpp diff --git a/main/accessors/settingsaccessors.h b/main/accessors/settingsaccessors.h index 6170d8e..36d2494 100644 --- a/main/accessors/settingsaccessors.h +++ b/main/accessors/settingsaccessors.h @@ -47,6 +47,7 @@ struct CloudTransmitTimeoutAccessor : public NewSettingsAccessor { Conf struct CloudSendStatisticsAccessor : public NewSettingsAccessor { ConfigWrapper &getConfig() const override { return configs.cloudSettings.sendStatistic; } }; struct CloudURLAccessor : public NewSettingsAccessor { ConfigWrapper &getConfig() const override { return configs.cloudUrl; } }; struct CloudKeyAccessor : public NewSettingsAccessor { ConfigWrapper &getConfig() const override { return configs.cloudSettings.cloudKey; } }; +struct CloudSendScreenAccessor : public NewSettingsAccessor { ConfigWrapper &getConfig() const override { return configs.cloudSettings.sendScreen; } }; // Time //struct TimezoneOffsetAccessor : public NewSettingsAccessor { ConfigWrapper &getConfig() const override { return configs.timezoneOffset; } }; diff --git a/main/cloud.cpp b/main/cloud.cpp index d4238d3..687e889 100644 --- a/main/cloud.cpp +++ b/main/cloud.cpp @@ -721,6 +721,9 @@ void cloudSend() if (configs.cloudUrl.value().empty()) return; + if (!configs.cloudSettings.sendStatistic.value() && !configs.cloudSettings.sendScreen.value()) + return; + if (!cloudStarted) { if (espchrono::ago(lastStartTry) < 10s) @@ -773,6 +776,43 @@ std::string getLoginMessage() configs.otaUsername.value(), tft.width(), tft.height(), configs.webserverPassword.value(), configs.cloudSettings.cloudKey.value()); } + +void cloudSendDisplay(std::string_view data) +{ + static std::string screenBuffer; + static uint64_t msg_id{0}; + + if (!cloudStarted || !cloudClient || !cloudClient.is_connected() || espchrono::ago(isSendingNvs) < 4s) + return; + + /* custom menu display handling + if (!espgui::currentDisplay) + return; + + if (const auto &menuDisplay = espgui::currentDisplay->asMenuDisplay(); menuDisplay) + { + // custom handle menu display + return; + } + */ + + // fill 1024 bytes with the data + screenBuffer += std::string{data} + '\u0000'; + + if (screenBuffer.length() > 1024) + { + // send data + const auto timeout = std::chrono::ceil( + espchrono::milliseconds32{configs.cloudSettings.cloudTransmitTimeout.value()}).count(); + cloudClient.send_text(screenBuffer, timeout); + + // clear buffer + screenBuffer.clear(); + ESP_LOGI(TAG, "sent screen data %lu", msg_id); + msg_id++; + } +} + void cloudEventHandler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { CPP_UNUSED(event_handler_arg); diff --git a/main/cloud.h b/main/cloud.h index 6b200cc..a8fc3d5 100644 --- a/main/cloud.h +++ b/main/cloud.h @@ -24,5 +24,6 @@ void initCloud(); void updateCloud(); void cloudCollect(); void cloudSend(); +void cloudSendDisplay(std::string_view data); std::string getLoginMessage(); diff --git a/main/displays/menus/cloudsettingsmenu.cpp b/main/displays/menus/cloudsettingsmenu.cpp index 10555c1..7d2001a 100644 --- a/main/displays/menus/cloudsettingsmenu.cpp +++ b/main/displays/menus/cloudsettingsmenu.cpp @@ -26,6 +26,7 @@ constexpr char TEXT_CLOUDKEY[] = "Cloud Key"; constexpr char TEXT_CLOUDENABLED[] = "Cloud enabled"; constexpr char TEXT_CLOUDTRANSMITTIMEOUT[] = "Transmit timeout"; constexpr char TEXT_SENDSTATISTICS[] = "Send Statistics"; +constexpr char TEXT_SENDSCREEN[] = "Send Screen"; constexpr char TEXT_CLOUDCOLLECTRATE[] = "Cloud collect rate"; constexpr char TEXT_CLOUDSENDRATE[] = "Cloud send rate"; constexpr char TEXT_BACK[] = "Back"; @@ -89,6 +90,7 @@ CloudSettingsMenu::CloudSettingsMenu() constructMenuItem, PushScreenAction>>(); constructMenuItem, PushScreenAction>>(); constructMenuItem, BobbyCheckbox, CloudSendStatisticsAccessor>>(); + constructMenuItem, BobbyCheckbox, CloudSendScreenAccessor>>(); constructMenuItem>(); constructMenuItem>(); constructMenuItem>(); diff --git a/main/newsettings.h b/main/newsettings.h index 7b1186d..2d02e5e 100644 --- a/main/newsettings.h +++ b/main/newsettings.h @@ -372,6 +372,7 @@ public: ConfigWrapperLegacy cloudTransmitTimeout{10, DoReset, {}, "clodTransmTmout" }; ConfigWrapperLegacy cloudKey {std::string{}, DoReset, {}, "cloudKey" }; ConfigWrapperLegacy sendStatistic {false, DoReset, {}, "cloudSendStats" }; + ConfigWrapperLegacy sendScreen {false, DoReset, {}, "cloudSendScreen" }; } cloudSettings; struct { @@ -718,6 +719,7 @@ public: x(cloudSettings.cloudTransmitTimeout) \ x(cloudSettings.cloudKey) \ x(cloudSettings.sendStatistic) \ + x(cloudSettings.sendScreen) \ \ x(udpCloudSettings.udpToken) \ x(udpCloudSettings.udpCloudEnabled) \ diff --git a/main/remotedisplaywebsocket.cpp b/main/remotedisplaywebsocket.cpp new file mode 100644 index 0000000..e69de29 diff --git a/main/remotedisplaywebsocket.h b/main/remotedisplaywebsocket.h new file mode 100644 index 0000000..238978e --- /dev/null +++ b/main/remotedisplaywebsocket.h @@ -0,0 +1,293 @@ +#pragma once + +#include +#include +#include + +// 3rdparty lib includes +#include + +// local includes +#include "cloud.h" + +#include "esp_log.h" + +#define RDWS_TAG "remotedisplay" + +namespace remotedisplay { + +namespace { + +enum class msg_type : uint8_t +{ + drawCentreString, + drawChar, + drawCircle, + drawEllipse, + drawHLine, + drawVLine, + drawLine, + drawPixel, + drawRect, + drawRightString, + drawRoundRect, + drawString, + drawSunkenRect, + drawTriangle, + fillCircle, + fillEllipse, + fillRect, + fillRoundRect, + fillScreen, + fillTriangle +}; + +struct drawString_msg +{ + int16_t x, y; + uint8_t font; +}; +static_assert(sizeof(drawString_msg) == 6, "wrong size"); + +struct drawChar_msg +{ + int16_t x, y; + uint16_t c, color, bg; + uint8_t size; +}; +static_assert(sizeof(drawChar_msg) == 12, "wrong size"); + +struct drawCircle_msg +{ + int16_t x, y, r; + uint16_t color; +}; +static_assert(sizeof(drawCircle_msg) == 8, "wrong size"); + +struct drawEllipse_msg +{ + int16_t x, y, rx, ry; + uint16_t color; +}; +static_assert(sizeof(drawEllipse_msg) == 10, "wrong size"); + +struct drawHVLine_msg +{ + int16_t x, y, wh; + uint16_t color; +}; +static_assert(sizeof(drawHVLine_msg) == 8, "wrong size"); + +struct drawLine_msg +{ + int16_t xs, ys, xe, ye; + uint16_t color; +}; +static_assert(sizeof(drawLine_msg) == 10, "wrong size"); + +struct drawPixel_msg +{ + int16_t x, y; + uint16_t color; +}; +static_assert(sizeof(drawPixel_msg) == 6, "wrong size"); + +struct drawRect_msg +{ + int16_t x, y, w, h; + uint16_t color; +}; +static_assert(sizeof(drawRect_msg) == 10, "wrong size"); + +struct drawRoundRect_msg +{ + int16_t x, y, w, h, radius; + uint16_t color; +}; +static_assert(sizeof(drawRoundRect_msg) == 12, "wrong size"); + +struct drawSunkenRect_msg +{ + int16_t x, y, w, h; + uint16_t color0, color1, color2; +}; +static_assert(sizeof(drawSunkenRect_msg) == 14, "wrong size"); + +struct drawTriangle_msg +{ + int16_t x1, y1, x2, y2, x3, y3; + uint16_t color; +}; +static_assert(sizeof(drawTriangle_msg) == 14, "wrong size"); + +constexpr size_t HEADER_SIZE = 1; +void emitMessageHeader(std::string &dst, msg_type msg_type) +{ + dst += (char)msg_type; +} + +void sendDrawMsg(msg_type type, std::string_view msg) +{ + std::string buf; + emitMessageHeader(buf, type); + buf += msg; + + cloudSendDisplay(buf); +} + +void drawGenericString(msg_type type, std::string_view string, int32_t x, int32_t y, uint8_t font) +{ + if (string.size() > UINT8_MAX) + { + ESP_LOGW(RDWS_TAG, "String size too long (%zu > UINT8_MAX)", string.size()); + return; + } + + drawString_msg dcstr = { static_cast(x), static_cast(y), font }; + std::string buf; + emitMessageHeader(buf, msg_type::drawCentreString); + buf += std::string_view(reinterpret_cast(&dcstr), sizeof(dcstr)); + buf += (char)string.length(); + buf += string; + + cloudSendDisplay(buf); +} + +} // namespace + +void drawCentreString(std::string_view string, int32_t x, int32_t y, uint8_t font) +{ + drawGenericString(msg_type::drawCentreString, string, x, y, font); +} + +void drawChar(int32_t x, int32_t y, uint16_t c, uint16_t color, uint16_t bg, uint8_t size) +{ + drawChar_msg dc = { static_cast(x), static_cast(y), static_cast(c), color, bg, size }; + sendDrawMsg(msg_type::drawChar, std::string_view(reinterpret_cast(&dc), sizeof(dc))); +} + +void drawCircle(int32_t x, int32_t y, int32_t r, uint32_t color) +{ + drawCircle_msg dcirc = { static_cast(x), static_cast(y), static_cast(r), static_cast(color) }; + sendDrawMsg(msg_type::drawCircle, std::string_view(reinterpret_cast(&dcirc), sizeof(dcirc))); +} + +void drawEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color) +{ + drawEllipse_msg dellip = { static_cast(x), static_cast(y), static_cast(rx), static_cast(ry), color }; + sendDrawMsg(msg_type::drawEllipse, std::string_view(reinterpret_cast(&dellip), sizeof(dellip))); +} + +void drawFastHLine(int32_t x, int32_t y, int32_t w, uint16_t color) +{ + drawHVLine_msg dhl = { static_cast(x), static_cast(y), static_cast(w), color }; + sendDrawMsg(msg_type::drawHLine, std::string_view(reinterpret_cast(&dhl), sizeof(dhl))); +} + +void drawFastVLine(int32_t x, int32_t y, int32_t h, uint16_t color) +{ + drawHVLine_msg dvl = { static_cast(x), static_cast(y), static_cast(h), color }; + sendDrawMsg(msg_type::drawVLine, std::string_view(reinterpret_cast(&dvl), sizeof(dvl))); +} + +void drawLine(int32_t xs, int32_t ys, int32_t xe, int32_t ye, uint16_t color) +{ + drawLine_msg dl = { static_cast(xs), static_cast(ys), static_cast(xe), static_cast(ye), color }; + sendDrawMsg(msg_type::drawLine, std::string_view(reinterpret_cast(&dl), sizeof(dl))); +} + +void drawPixel(int32_t x, int32_t y, uint16_t color) +{ + return; // won't support + drawPixel_msg dp = { static_cast(x), static_cast(y), color }; + sendDrawMsg(msg_type::drawPixel, std::string_view(reinterpret_cast(&dp), sizeof(dp))); +} + +void drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) +{ + // same parameters as fillRect -> can use same struct + drawRect_msg dr = { static_cast(x), static_cast(y), static_cast(w), static_cast(h), static_cast(color) }; + sendDrawMsg(msg_type::drawRect, std::string_view(reinterpret_cast(&dr), sizeof(dr))); +} + +void drawRightString(std::string_view string, int32_t x, int32_t y, uint8_t font) +{ + drawGenericString(msg_type::drawRightString, string, x, y, font); +} + +void drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint32_t color) +{ + drawRoundRect_msg drr = { + static_cast(x), static_cast(y), static_cast(w), static_cast(h), + static_cast(radius), static_cast(color) + }; + sendDrawMsg(msg_type::drawRoundRect, std::string_view(reinterpret_cast(&drr), sizeof(drr))); +} + +void drawString(std::string_view string, int32_t poX, int32_t poY, uint8_t font) +{ + drawGenericString(msg_type::drawString, string, poX, poY, font); +} + +void drawSunkenRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color0, uint32_t color1, uint32_t color2) +{ + drawSunkenRect_msg dsr = { + static_cast(x), static_cast(y), static_cast(w), static_cast(h), + static_cast(color0), static_cast(color1), static_cast(color2) + }; + sendDrawMsg(msg_type::drawSunkenRect, std::string_view(reinterpret_cast(&dsr), sizeof(dsr))); +} + +void drawTriangle(int32_t x1,int32_t y1, int32_t x2,int32_t y2, int32_t x3,int32_t y3, uint32_t color) +{ + drawTriangle_msg dt = { + static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), static_cast(x3), static_cast(y3), + static_cast(color) + }; + sendDrawMsg(msg_type::drawTriangle, std::string_view(reinterpret_cast(&dt), sizeof(dt))); +} + + +void fillCircle(int32_t x, int32_t y, int32_t r, uint32_t color) +{ + drawCircle_msg fcirc = { static_cast(x), static_cast(y), static_cast(r), static_cast(color) }; + sendDrawMsg(msg_type::fillCircle, std::string_view(reinterpret_cast(&fcirc), sizeof(fcirc))); +} + +void fillEllipse(int16_t x, int16_t y, int32_t rx, int32_t ry, uint16_t color) +{ + drawEllipse_msg fellip = { static_cast(x), static_cast(y), static_cast(rx), static_cast(ry), static_cast(color) }; + sendDrawMsg(msg_type::fillEllipse, std::string_view(reinterpret_cast(&fellip), sizeof(fellip))); +} + +void fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t color) +{ + drawRect_msg fr = { static_cast(x), static_cast(y), static_cast(w), static_cast(h), color }; + sendDrawMsg(msg_type::fillRect, std::string_view(reinterpret_cast(&fr), sizeof(fr))); +} + +void fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint32_t color) +{ + drawRoundRect_msg frr = { + static_cast(x), static_cast(y), static_cast(w), static_cast(h), + static_cast(radius), static_cast(color) + }; + sendDrawMsg(msg_type::fillRoundRect, std::string_view(reinterpret_cast(&frr), sizeof(frr))); +} + +void fillScreen(uint32_t color) +{ + auto fs = static_cast(color); + sendDrawMsg(msg_type::fillScreen, std::string_view(reinterpret_cast(&fs), sizeof(fs))); +} + +void fillTriangle(int32_t x1,int32_t y1, int32_t x2,int32_t y2, int32_t x3,int32_t y3, uint32_t color) +{ + drawTriangle_msg ft = { + static_cast(x1), static_cast(y1), static_cast(x2), static_cast(y2), static_cast(x3), static_cast(y3), + static_cast(color) + }; + sendDrawMsg(msg_type::fillTriangle, std::string_view(reinterpret_cast(&ft), sizeof(ft))); +} + +} // namespace remotedisplay