build for esp32 mask project

This commit is contained in:
Torrie Fischer 2021-04-10 11:10:25 -07:00
parent 439a456d1a
commit 75bf48756b
24 changed files with 712 additions and 346 deletions

View File

@ -34,6 +34,12 @@ struct Task : public virtual Loopable {
State state = Running; State state = Running;
}; };
struct TaskFunc: public Task {
TaskFunc(std::function<void()> func) : Task("lambda"), func(func) {}
void loop() override {func();}
std::function<void()> func;
};
struct Figment: public Task { struct Figment: public Task {
Figment() : Task() {} Figment() : Task() {}
Figment(State initialState) : Task(initialState) {} Figment(State initialState) : Task(initialState) {}

View File

@ -137,7 +137,7 @@ protected:
void setEvent(InputEvent::Intent intent, Variant &&v); void setEvent(InputEvent::Intent intent, Variant &&v);
private: private:
Ringbuf<InputEvent, 5> m_eventQueue; Ringbuf<InputEvent, 12> m_eventQueue;
}; };
class InputMapper: public BufferedInputSource { class InputMapper: public BufferedInputSource {
@ -157,12 +157,20 @@ class OnlineTaskMixin : public virtual Loopable {
void handleEvent(const InputEvent &evt) override { void handleEvent(const InputEvent &evt) override {
if (evt.intent == InputEvent::NetworkStatus) { if (evt.intent == InputEvent::NetworkStatus) {
m_online = evt.asInt(); m_online = evt.asInt();
if (m_online) {
onOnline();
} else {
onOffline();
}
} }
if (m_online) { if (m_online) {
handleEventOnline(evt); handleEventOnline(evt);
} }
} }
virtual void onOnline() {}
virtual void onOffline() {}
virtual void handleEventOnline(const InputEvent &evt) {} virtual void handleEventOnline(const InputEvent &evt) {}
void loop() override { void loop() override {

View File

@ -37,10 +37,27 @@ MainLoop::loop()
task->handleEvent(evt); task->handleEvent(evt);
} }
} }
unsigned int slowest = 0;
unsigned int frameSpeed = 0;
unsigned int frameStart = millis();
unsigned int taskCount = 0;
Task* slowestTask = NULL;
for(Task* task : scheduler) { for(Task* task : scheduler) {
//Log.notice("Running %s", task->name); //unsigned int start = millis();
unsigned int start = ESP.getCycleCount();
task->loop(); task->loop();
//Log.notice("next"); //unsigned int runtime = millis() - start;
unsigned int runtime = ESP.getCycleCount() - start;
frameSpeed += runtime;
taskCount++;
if (runtime > slowest) {
slowest = runtime;
slowestTask = task;
}
}
frameSpeed = millis() - frameStart;
if (frameSpeed >= 23) {
Log.notice("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest/160000);
} }
} }

View File

@ -9,11 +9,12 @@ Renderer::loop()
for(Display* dpy : m_displays) { for(Display* dpy : m_displays) {
for(Figment* figment : m_figments) { for(Figment* figment : m_figments) {
if (figment->state == Task::Running) { if (figment->state == Task::Running) {
//Log.notice("Rendering %s", figment->name); unsigned int frameStart = ESP.getCycleCount();
figment->render(dpy); figment->render(dpy);
//Log.notice("next"); unsigned int runtime = (ESP.getCycleCount() - frameStart) / 160000;
} else { if (runtime >= 8) {
//Log.notice("Not rendering %s", figment->name); Log.notice("SLOW RENDER: %s took %dms!", figment->name, runtime);
}
} }
}; };
} }

View File

@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

4
no_ota.csv Normal file
View File

@ -0,0 +1,4 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x1000,
app, app, factory, 0x10000, 2M,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x1000
4 app app factory 0x10000 2M

33
out.log
View File

@ -1,33 +0,0 @@
ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:10124
load:0x40080400,len:5828
entry 0x400806a8
N: 🐛 Booting Renderbug!
N: 🐞 I am built for 255 LEDs running on 2000mA
N: Boot pin configuration:
N: 2: Setup - 0
N: 3: Serial - 0
N: 4: Flash - 0
N: 💡 Starting FastLED...
N: 🌌 Starting Figment...
N: *** Starting 20 tasks...
N: * Starting Configuration...
N: * Starting MPU5060...
N: * Starting Buttons...
N: * Starting ...
N: * Starting SceneSequencer...
N: * Starting CircadianRhythm...
N: * Starting Pulse...
N: * Starting Solid...
N: * Starting Power...
N: * Starting lambda...
N: * Starting UpdateStatusAnimation...
N: * Starting Renderer...
N: 🚀 Setup complete! Ready to rock and roll.

View File

@ -11,13 +11,37 @@
[common_env_data] [common_env_data]
src_filter = "+<*> -<.git/> -<.svn/> -<platform/>" src_filter = "+<*> -<.git/> -<.svn/> -<platform/>"
[env:featheresp32] [env:esp32]
platform = espressif32 platform = espressif32
board = featheresp32 board = featheresp32
framework = arduino framework = arduino
build_flags = build_flags =
-D PLATFORM_ARDUINO -DPLATFORM_ARDUINO
-D BOARD_ESP32 -DBOARD_ESP32
-DCONFIG_NO_COLORDATA
; -DCORE_DEBUG_LEVEL=5
lib_deps =
fastled/FastLED@^3.4.0
thijse/ArduinoLog@^1.0.3
knolleary/PubSubClient@^2.8.0
bblanchon/ArduinoJson@^6.17.3
sstaub/NTP@^1.4.0
arduino-libraries/NTPClient@^3.1.0
src_filter = "${common_env_data.src_filter} +<platform/arduino/>"
board_build.partitions = no_ota.csv
;build_type = debug
[env:cyberplague]
extends = env:esp32
board_build.partitions = no_ota.csv
[env:esp8266]
platform = espressif8266
board = huzzah
framework = arduino
build_flags =
-DPLATFORM_ARDUINO
-DBOARD_ESP8266
lib_deps = lib_deps =
fastled/FastLED@^3.4.0 fastled/FastLED@^3.4.0
thijse/ArduinoLog@^1.0.3 thijse/ArduinoLog@^1.0.3
@ -27,18 +51,7 @@ lib_deps =
arduino-libraries/NTPClient@^3.1.0 arduino-libraries/NTPClient@^3.1.0
src_filter = "${common_env_data.src_filter} +<platform/arduino/>" src_filter = "${common_env_data.src_filter} +<platform/arduino/>"
[env:huzzah] ;[env:photon]
platform = espressif8266 ;platform = particlephoton
board = huzzah ;board = photon
framework = arduino ;framework = arduino
build_flags =
-D PLATFORM_ARDUINO
-D BOARD_ESP8266
lib_deps =
fastled/FastLED@^3.4.0
thijse/ArduinoLog@^1.0.3
knolleary/PubSubClient@^2.8.0
bblanchon/ArduinoJson@^6.17.3
sstaub/NTP@^1.4.0
arduino-libraries/NTPClient@^3.1.0
src_filter = "${common_env_data.src_filter} +<platform/arduino/>"

View File

@ -1,4 +1,9 @@
#include "BootOptions.h" #include "BootOptions.h"
#ifdef BOARD_ESP8266
#include <ESP8266WiFi.h>
#endif
#include <EEPROM.h>
#include "Config.h"
#ifdef PLATFORM_PHOTON #ifdef PLATFORM_PHOTON
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND); LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
@ -31,6 +36,28 @@ BootOptions::BootOptions()
configStatus.setActive(isSetup); configStatus.setActive(isSetup);
serialStatus.setActive(isSerial); serialStatus.setActive(isSerial);
#endif #endif
#ifdef BOARD_ESP8266
struct rst_info resetInfo = *ESP.getResetInfoPtr();
uint8_t crashCount;
EEPROM.begin(sizeof(crashCount));
EEPROM.get(sizeof(HardwareConfig) + 32, crashCount);
EEPROM.end();
if (resetInfo.reason == REASON_WDT_RST) {
if (crashCount++ >= 3) {
// Boot into safe mode if the watchdog reset us three times in a row.
isSafeMode = true;
} else {
EEPROM.begin(sizeof(crashCount));
EEPROM.put(sizeof(HardwareConfig) + 32, crashCount);
EEPROM.end();
}
} else if (crashCount != 0) {
crashCount = 0;
EEPROM.begin(sizeof(crashCount));
EEPROM.put(sizeof(HardwareConfig) + 32, crashCount);
EEPROM.end();
}
#endif
} }
void void

View File

@ -11,4 +11,5 @@ struct BootOptions {
bool isSerial = false; bool isSerial = false;
bool isFlash = false; bool isFlash = false;
bool lastBootWasFlash = false; bool lastBootWasFlash = false;
bool isSafeMode = false;
}; };

View File

@ -8,7 +8,10 @@ constexpr uint16_t HardwareConfig::MAX_LED_NUM;
HardwareConfig HardwareConfig
HardwareConfig::load() { HardwareConfig::load() {
HardwareConfig ret; HardwareConfig ret;
EEPROM.begin(sizeof(ret));
EEPROM.get(0, ret); EEPROM.get(0, ret);
EEPROM.end();
Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum);
return ret; return ret;
} }
@ -16,7 +19,10 @@ void
HardwareConfig::save() { HardwareConfig::save() {
HardwareConfig dataCopy{*this}; HardwareConfig dataCopy{*this};
dataCopy.checksum = getCRC(); dataCopy.checksum = getCRC();
EEPROM.begin(sizeof(dataCopy));
EEPROM.put(0, dataCopy); EEPROM.put(0, dataCopy);
EEPROM.commit();
EEPROM.end();
} }
LinearCoordinateMapping LinearCoordinateMapping
@ -66,13 +72,13 @@ ConfigService::onStart()
m_coordMap = m_config.toCoordMap(); m_coordMap = m_config.toCoordMap();
Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel); Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel);
Log.notice("Loading task states..."); /*Log.notice("Loading task states...");
for(int i = 0; i < 32; i++) { for(int i = 0; i < 32; i++) {
auto svc = m_config.data.serviceStates[i]; auto svc = m_config.data.serviceStates[i];
if (strlen(svc.name) > 0) { if (strnlen(svc.name, 16) > 0) {
Log.notice("* %s: %s", svc.name, svc.isDisabled? "DISABLED" : "ENABLED"); Log.notice("* %s: %s", svc.name, svc.isDisabled? "DISABLED" : "ENABLED");
} }
} }*/
} }
void void

View File

@ -81,7 +81,7 @@ LogService::handleEvent(const InputEvent& evt) {
} }
if (evt.intent != m_lastEvent.intent) { if (evt.intent != m_lastEvent.intent) {
if (m_duplicateEvents > 0) { if (m_duplicateEvents > 0) {
Log.notice("Suppressed reporting %u duplicate events.", m_duplicateEvents); Log.notice("Suppressed reporting %d duplicate events.", m_duplicateEvents);
} }
Log.verbose("Event: %s", buf); Log.verbose("Event: %s", buf);
m_duplicateEvents = 0; m_duplicateEvents = 0;

View File

@ -4,6 +4,8 @@
#ifdef BOARD_ESP32 #ifdef BOARD_ESP32
#include <WiFi.h> #include <WiFi.h>
#include <esp_task_wdt.h>
#include <time.h>
#elif defined(BOARD_ESP8266) #elif defined(BOARD_ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
@ -11,16 +13,23 @@
#include <ctime> #include <ctime>
WiFiUDP wifiUdp; WiFiUDP wifiUdp;
NTPClient timeClient(wifiUdp, "pool.ntp.org", 3600 * -7); //NTPClient timeClient(wifiUdp, "pool.ntp.org", 3600 * -7);
NTPClient timeClient(wifiUdp, "10.0.0.1", 3600 * -7);
#endif #endif
#ifdef PLATFORM_PHOTON #ifdef PLATFORM_PHOTON
STARTUP(BootOptions::initPins()); STARTUP(BootOptions::initPins());
#else #else
#include "platform/arduino/MQTTTelemetry.h" #include "platform/arduino/MQTTTelemetry.h"
void printNewline(Print* logOutput) { void printNewline(Print* logOutput)
{
logOutput->print("\r\n"); logOutput->print("\r\n");
} }
int printEspLog(const char* fmt, va_list args)
{
Log.notice(fmt, args);
return 1;
}
#endif #endif
int Platform::s_timezone = 0; int Platform::s_timezone = 0;
@ -30,6 +39,10 @@ Platform::name()
{ {
#ifdef PLATFORM_PHOTON #ifdef PLATFORM_PHOTON
return "Photon"; return "Photon";
#elif defined(BOARD_ESP8266)
return "ESP8266";
#elif defined(BOARD_ESP32)
return "ESP32";
#else #else
return "Unknown!"; return "Unknown!";
#endif #endif
@ -40,6 +53,8 @@ Platform::version()
{ {
#ifdef PLATFORM_PHOTON #ifdef PLATFORM_PHOTON
return System.version().c_str(); return System.version().c_str();
#elif defined(BOARD_ESP32)
return ESP.getSdkVersion();
#else #else
return "Unknown!"; return "Unknown!";
#endif #endif
@ -49,6 +64,7 @@ void
Platform::preSetup() Platform::preSetup()
{ {
Serial.begin(115200); Serial.begin(115200);
delay(5000);
#ifdef PLATFORM_PHOTON #ifdef PLATFORM_PHOTON
System.enableFeature(FEATURE_RETAINED_MEMORY); System.enableFeature(FEATURE_RETAINED_MEMORY);
if (bootopts.isFlash) { if (bootopts.isFlash) {
@ -65,6 +81,15 @@ Platform::preSetup()
Log.begin(LOG_LEVEL_VERBOSE, Static<MQTTTelemetry>::instance()->logPrinter()); Log.begin(LOG_LEVEL_VERBOSE, Static<MQTTTelemetry>::instance()->logPrinter());
Log.setSuffix(printNewline); Log.setSuffix(printNewline);
#endif #endif
#ifdef BOARD_ESP32
esp_task_wdt_init(10, true);
esp_task_wdt_add(NULL);
esp_log_set_vprintf(printEspLog);
#endif
#ifdef BOARD_ESP8266
ESP.wdtEnable(0);
#endif
} }
void void
@ -95,7 +120,12 @@ void
Platform::loop() Platform::loop()
{ {
#ifdef BOARD_ESP8266 #ifdef BOARD_ESP8266
timeClient.update(); if (WiFi.status() == WL_CONNECTED) {
timeClient.update();
}
ESP.wdtFeed();
#elif defined(BOARD_ESP32)
esp_task_wdt_reset();
#endif #endif
} }
@ -110,9 +140,12 @@ Platform::getLocalTime(struct tm* timedata)
} }
return false; return false;
#elif defined(BOARD_ESP32) #elif defined(BOARD_ESP32)
return getLocalTime(timedata); time_t rawtime;
time(&rawtime);
(*timedata) = (*localtime(&rawtime));
return (timedata->tm_year > (2016-1990));
//return getLocalTime(timedata);
#else #else
timeClient.update();
timedata->tm_hour = timeClient.getHours(); timedata->tm_hour = timeClient.getHours();
timedata->tm_min = timeClient.getMinutes(); timedata->tm_min = timeClient.getMinutes();
return true; return true;
@ -137,3 +170,5 @@ Platform::bootopts;
char char
Platform::s_deviceID[15]; Platform::s_deviceID[15];
STATIC_ALLOC(Platform);

View File

@ -1,11 +1,13 @@
#pragma once #pragma once
#include <FastLED.h> #include <FastLED.h>
#include <Figments.h>
#include "BootOptions.h" #include "BootOptions.h"
class Platform { class Platform : public Task {
static int s_timezone; static int s_timezone;
static char s_deviceID[15]; static char s_deviceID[15];
public: public:
Platform() : Task("Platform") {}
static BootOptions bootopts; static BootOptions bootopts;
static void setTimezone(int tz) { s_timezone = tz; } static void setTimezone(int tz) { s_timezone = tz; }
static int getTimezone() { return s_timezone; } static int getTimezone() { return s_timezone; }
@ -14,18 +16,28 @@ class Platform {
#ifdef PLATFORM_PHOTON #ifdef PLATFORM_PHOTON
FastLED.addLeds<NEOPIXEL, 6>(leds, ledCount); FastLED.addLeds<NEOPIXEL, 6>(leds, ledCount);
#elif defined(BOARD_ESP32) #elif defined(BOARD_ESP32)
FastLED.addLeds<WS2812B, 13, RGB>(leds, ledCount); FastLED.addLeds<WS2812B, 13, GRB>(leds, ledCount);
#else #else
FastLED.addLeds<WS2812B, 14, GRB>(leds, ledCount); //FastLED.addLeds<WS2812B, 14, GRB>(leds, ledCount);
FastLED.addLeds<WS2812B, 14, RGB>(leds, ledCount);
#endif #endif
} }
static const char* name(); static const char* name();
static const char* version(); static const char* version();
static const String model() {
static String modelName = String("Renderbug " ) + Platform::name();
return modelName;
}
static const String deviceName() {
static String devName = model() + " " + Platform::deviceID();
return devName;
}
static void preSetup(); static void preSetup();
static void bootSplash(); static void bootSplash();
static void setup(); static void setup();
static void loop(); void loop() override;
static bool getLocalTime(struct tm* timedata); static bool getLocalTime(struct tm* timedata);
static const char* deviceID(); static const char* deviceID();
}; };

View File

@ -25,10 +25,13 @@ Sequencer::scenes() const
void void
Sequencer::handleEvent(const InputEvent& evt) Sequencer::handleEvent(const InputEvent& evt)
{ {
if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) {
return;
}
if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) { if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
Log.notice("Switching pattern!"); Log.notice("Switching pattern!");
for(const char* pattern : m_scenes[m_idx].patterns) { for(const char* pattern : m_scenes[m_idx].patterns) {
Log.notice("Stopping %s", pattern); //Log.notice("Stopping %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern}); MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
} }
@ -54,7 +57,7 @@ Sequencer::handleEvent(const InputEvent& evt)
} }
for(const char* pattern : m_scenes[m_idx].patterns) { for(const char* pattern : m_scenes[m_idx].patterns) {
Log.notice("Starting %s", pattern); //Log.notice("Starting %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
} }
} }

View File

@ -17,21 +17,7 @@ WiFiTask::onStart()
{ {
Log.notice("Starting wifi..."); Log.notice("Starting wifi...");
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
int n = WiFi.scanNetworks();
if (n == 0) {
Log.notice("No wifi found");
} else {
for(int i = 0; i < n; ++i) {
Serial.print("WiFi: ");
Serial.println(WiFi.SSID(i));
}
}
WiFi.mode(WIFI_STA);
WiFi.begin("The Frequency", "thepasswordkenneth"); WiFi.begin("The Frequency", "thepasswordkenneth");
while(WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
} }
InputEvent InputEvent
@ -42,7 +28,7 @@ WiFiTask::read()
m_lastStatus = curStatus; m_lastStatus = curStatus;
Log.verbose("WiFi Status: %d", curStatus); Log.verbose("WiFi Status: %d", curStatus);
if (curStatus == WL_CONNECTED) { if (curStatus == WL_CONNECTED) {
Log.notice("Connected!"); Log.notice("Connected! IP address is %s", WiFi.localIP().toString().c_str());
return InputEvent{InputEvent::NetworkStatus, true}; return InputEvent{InputEvent::NetworkStatus, true};
} else if (curStatus == WL_CONNECTION_LOST || curStatus == WL_DISCONNECTED) { } else if (curStatus == WL_CONNECTION_LOST || curStatus == WL_DISCONNECTED) {
Log.notice("Lost wifi connection!"); Log.notice("Lost wifi connection!");

View File

@ -1,6 +1,8 @@
#include "colors.h" #include "colors.h"
const ColorInfo color_data[] = { const ColorInfo color_data[] = {
#ifdef CONFIG_NO_COLORDATA
#else
{ "Air Superiority Blue", { 114, 160, 193 } }, { "Air Superiority Blue", { 114, 160, 193 } },
{ "Alabama Crimson", { 163, 38, 56 } }, { "Alabama Crimson", { 163, 38, 56 } },
{ "Alice Blue", { 240, 248, 255 } }, { "Alice Blue", { 240, 248, 255 } },
@ -792,6 +794,7 @@ const ColorInfo color_data[] = {
{ "Yellow Orange", { 255, 174, 66 } }, { "Yellow Orange", { 255, 174, 66 } },
{ "Zaffre", { 0, 20, 168 } }, { "Zaffre", { 0, 20, 168 } },
{ "Zinnwaldite Brown", { 44, 22, 8 } }, { "Zinnwaldite Brown", { 44, 22, 8 } },
#endif
{0, {0, 0, 0}}, {0, {0, 0, 0}},
}; };

View File

@ -1 +0,0 @@
firmware

View File

@ -12,7 +12,6 @@
#include "Static.h" #include "Static.h"
#include "Config.h" #include "Config.h"
#include "colors.h"
#include "Sequencer.h" #include "Sequencer.h"
#include "LogService.h" #include "LogService.h"
@ -36,6 +35,7 @@
#include "platform/particle/MDNSService.cpp" #include "platform/particle/MDNSService.cpp"
#else #else
#include "WiFiTask.h" #include "WiFiTask.h"
#include "platform/arduino/BluetoothSerialTelemetry.h"
#include "platform/arduino/MQTTTelemetry.h" #include "platform/arduino/MQTTTelemetry.h"
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#endif #endif
@ -55,9 +55,6 @@
CRGB leds[HardwareConfig::MAX_LED_NUM]; CRGB leds[HardwareConfig::MAX_LED_NUM];
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap()); Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
LinearCoordinateMapping neckMap{60, 0};
Display neckDisplay(leds, HardwareConfig::MAX_LED_NUM, &neckMap);
// Setup power management // Setup power management
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power; Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
@ -113,7 +110,7 @@ class ArduinoOTAUpdater : public BufferedInputSource {
} }
void handleEvent(const InputEvent& evt) { void handleEvent(const InputEvent& evt) {
if (evt.intent == InputEvent::NetworkStatus) { if (evt.intent == InputEvent::NetworkStatus && evt.asInt()) {
Log.notice("Booting OTA"); Log.notice("Booting OTA");
m_online = true; m_online = true;
ArduinoOTA.begin(); ArduinoOTA.begin();
@ -176,19 +173,17 @@ DrainAnimation drain{Task::Stopped};
Flashlight flashlight{Task::Stopped}; Flashlight flashlight{Task::Stopped};
Sequencer sequencer{{ Sequencer sequencer{{
{"Idle", {"Solid", "MPU5060", "Pulse", "Hackerbots", "Kieryn", "CircadianRhythm"}}, {"Idle", {"Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"}},
{"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}}, {"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
{"Interactive", {"Drain", "CircadianRhythm"}}, {"Interactive", {"Drain", "MPU5060", "CircadianRhythm"}},
{"Flashlight", {"Flashlight"}}, {"Flashlight", {"Flashlight"}},
{"Nightlight", {"Drain", "Pulse", "Noisebridge"}}, {"Gay", {"Solid", "Pulse", "Rainbow", "Rainbow"}},
{"Gay", {"Solid", "Pulse", "Rainbow", "Hackerbots", "Kieryn"}}, {"Acid", {"Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"}},
{"Acid", {"Chimes", "Pulse", "MPU5060", "Hackerbots", "Rainbow"}},
}}; }};
// Render all layers to the displays // Render all layers to the displays
Renderer renderer{ Renderer renderer{
//{&dpy, &neckDisplay},
{&dpy}, {&dpy},
{ {
&chimes, &chimes,
@ -207,29 +202,22 @@ Renderer configRenderer{
}; };
// Cycle some random colors // Cycle some random colors
ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped}; ColorSequenceInput<9> idleCycle{{
ColorSequenceInput<13> kierynCycle{{
CRGB(0, 123, 167), // Cerulean CRGB(0, 123, 167), // Cerulean
CRGB(80, 200, 120), // Emerald CRGB(80, 200, 120), // Emerald
CRGB(207, 113, 175), // Sky Magenta CRGB(207, 113, 175), // Sky Magenta
}, "Kieryn", Task::Running};
ColorSequenceInput<7> rainbowCycle{{
colorForName("Red").rgb,
colorForName("Orange").rgb,
colorForName("Yellow").rgb,
colorForName("Green").rgb,
colorForName("Blue").rgb,
colorForName("Purple").rgb,
colorForName("White").rgb,
}, "Rainbow", Task::Stopped};
ColorSequenceInput<7> hackerbotsCycle{{
CRGB(128, 0, 128), // Purple CRGB(128, 0, 128), // Purple
CRGB(255, 255, 255), // White CRGB(255, 255, 255), // White
CRGB(0, 255, 255), // Cyan CRGB(0, 255, 255), // Cyan
}, "Hackerbots", Task::Running}; }, "IdleColors", Task::Running};
ColorSequenceInput<7> rainbowCycle{{
CRGB(255, 0, 0), // Red
CRGB(255, 127, 0), // Yellow
CRGB(0, 255, 0), // Green
CRGB(0, 0, 255), // Blue
CRGB(128, 0, 128), // Purple
}, "Rainbow", Task::Stopped};
struct ConfigInputTask: public BufferedInputSource { struct ConfigInputTask: public BufferedInputSource {
public: public:
@ -324,48 +312,53 @@ std::array<ScheduleEntry, 10> schedule{{
{23, 20} {23, 20}
}}; }};
uint8_t brightnessForTime(uint8_t hour, uint8_t minute) {
ScheduleEntry start = schedule.back();
ScheduleEntry end = schedule.front();
for(ScheduleEntry cur : schedule) {
// Find the last hour that is prior to or equal to now
if (cur.hour <= hour) {
start = cur;
} else {
break;
}
}
for(ScheduleEntry cur : schedule) {
// Find the first hour that is after now
// If no such hour exists, we should automatically wrap back to hour 0
if (cur.hour > hour) {
end = cur;
break;
}
}
if (start.hour > end.hour) {
end.hour += 24;
}
uint16_t startTime = start.hour * 60;
uint16_t endTime = end.hour * 60;
uint16_t nowTime = hour * 60 + minute;
uint16_t duration = endTime - startTime;
uint16_t curDuration = nowTime - startTime;
uint8_t frac = ((double)curDuration / (double)duration) * 255.0;
return lerp8by8(start.brightness, end.brightness, frac);
}
class CircadianRhythm : public InputSource { class CircadianRhythm : public InputSource {
private: private:
bool needsUpdate = true; bool needsUpdate = true;
public: public:
CircadianRhythm() : InputSource("CircadianRhythm") {} CircadianRhythm() : InputSource("CircadianRhythm") {}
void onStart() {
needsUpdate = true;
}
uint8_t brightnessForTime(uint8_t hour, uint8_t minute) const {
ScheduleEntry start = schedule.back();
ScheduleEntry end = schedule.front();
for(ScheduleEntry cur : schedule) {
// Find the last hour that is prior to or equal to now
if (cur.hour <= hour) {
start = cur;
} else {
break;
}
}
for(ScheduleEntry cur : schedule) {
// Find the first hour that is after now
// If no such hour exists, we should automatically wrap back to hour 0
if (cur.hour > hour) {
end = cur;
break;
}
}
if (start.hour > end.hour) {
end.hour += 24;
}
uint16_t startTime = start.hour * 60;
uint16_t endTime = end.hour * 60;
uint16_t nowTime = hour * 60 + minute;
uint16_t duration = endTime - startTime;
uint16_t curDuration = nowTime - startTime;
uint8_t frac = map8(curDuration, 0, duration);
return lerp8by8(start.brightness, end.brightness, frac);
}
InputEvent read() { InputEvent read() {
EVERY_N_SECONDS(60) { EVERY_N_SECONDS(60) {
needsUpdate = true; needsUpdate = true;
@ -390,6 +383,8 @@ STATIC_ALLOC(CircadianRhythm);
// A special mainloop app for configuring hardware settings that reboots the // A special mainloop app for configuring hardware settings that reboots the
// device when the user is finished. // device when the user is finished.
MainLoop configApp{{ MainLoop configApp{{
Static<Platform>::instance(),
// Manage read/write of configuration data // Manage read/write of configuration data
Static<ConfigService>::instance(), Static<ConfigService>::instance(),
@ -415,9 +410,45 @@ MainLoop configApp{{
&configRenderer, &configRenderer,
}}; }};
TaskFunc safeModeNag([]{
static uint8_t frame = 0;
EVERY_N_SECONDS(30) {
Log.notice("I am running in safe mode!");
}
EVERY_N_MILLISECONDS(16) {
frame++;
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
leds[i] = CRGB(0, 0, 0);
}
for(int idx = 0; idx < 3; idx++) {
uint8_t length = beatsin8(5, 3, HardwareConfig::MAX_LED_NUM, 0, idx * 5);
for(int i = 0; i < length; i++) {
leds[i] += CRGB(scale8(5, beatsin8(5 + i * 7, 0, 255, 0, i*3)), 0, 0);
}
}
FastLED.show();
}
});
MainLoop safeModeApp({
Static<Platform>::instance(),
// ESP Wifi
Static<WiFiTask>::instance(),
// System logging
Static<LogService>::instance(),
// MQTT
Static<MQTTTelemetry>::instance(),
// OTA Updates
Static<ArduinoOTAUpdater>::instance(),
&safeModeNag,
});
// Turn on, // Turn on,
MainLoop renderbugApp{{ MainLoop renderbugApp{{
Static<Platform>::instance(),
// Load/update graphics configuration from EEPROM // Load/update graphics configuration from EEPROM
Static<ConfigService>::instance(), Static<ConfigService>::instance(),
@ -431,18 +462,28 @@ MainLoop renderbugApp{{
Static<PhotonInput>::instance(), Static<PhotonInput>::instance(),
#else #else
// ESP Wifi // ESP Wifi
Static<WiFiTask>::instance(), //Static<WiFiTask>::instance(),
#endif
#ifdef BOARD_ESP32
// ESP32 Bluetooth
Static<BluetoothSerialTelemetry>::instance(),
#endif #endif
// System logging // System logging
Static<LogService>::instance(), Static<LogService>::instance(),
#ifdef CONFIG_MPU5060
// Hardware drivers // Hardware drivers
Static<MPU5060>::instance(), Static<MPU5060>::instance(),
#endif
#ifdef CONFIG_BUTTONS
Static<Buttons>::instance(), Static<Buttons>::instance(),
// Map buttons to events // Map buttons to events
&keyMap, &keyMap,
#endif
// Pattern sequencer // Pattern sequencer
&sequencer, &sequencer,
@ -451,13 +492,11 @@ MainLoop renderbugApp{{
Static<CircadianRhythm>::instance(), Static<CircadianRhythm>::instance(),
// Periodic motion input // Periodic motion input
&randomPulse, //&randomPulse,
// Periodic color inputs // Periodic color inputs
&noisebridgeCycle, &idleCycle,
&kierynCycle,
&rainbowCycle, &rainbowCycle,
&hackerbotsCycle,
// Animations // Animations
&chimes, &chimes,
@ -521,8 +560,13 @@ void setup() {
Log.notice(u8"💡 Starting FastLED..."); Log.notice(u8"💡 Starting FastLED...");
Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM); Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM);
if (Platform::bootopts.isSetup) { if (Platform::bootopts.isSafeMode) {
Log.notice(u8"🌌 Starting Figment in configuration mode..."); Log.notice(u8"⚠️ Starting Figment in safe mode!!!");
runner = safeModeApp;
FastLED.showColor(CRGB(5, 0, 0));
FastLED.show();
} else if (Platform::bootopts.isSetup) {
Log.notice(u8"🔧 Starting Figment in configuration mode...");
runner = configApp; runner = configApp;
} else { } else {
Log.notice(u8"🌌 Starting Figment..."); Log.notice(u8"🌌 Starting Figment...");
@ -537,5 +581,6 @@ void setup() {
// Drop out. // Drop out.
void loop() { void loop() {
//Platform::loop();
runner.loop(); runner.loop();
} }

View File

@ -0,0 +1,86 @@
#include "BluetoothSerialTelemetry.h"
#include "../../Static.h"
#include "../../Platform.h"
#include <ArduinoLog.h>
#include "../../inputs/Buttons.h"
#include <cstdlib>
BluetoothSerialTelemetry::BluetoothSerialTelemetry() : InputSource("Bluetooth")
{
//m_serial.setPin("0000");
m_serial.enableSSP();
}
InputEvent
BluetoothSerialTelemetry::read()
{
bool didRead = false;
while (m_serial.available()) {
didRead = true;
char charRead = m_serial.read();
m_ringbuf.insert(charRead);
if (charRead == '*') {
static char commandBuf[32];
size_t cmdSize = m_ringbuf.write(commandBuf);
// Overwrite the '*' character, to leave us with a complete command
commandBuf[cmdSize-1] = 0;
//Log.notice("Bluetooth read %s", commandBuf);
if (commandBuf[0] == 'R') {
m_color = CRGB(std::atoi(&commandBuf[1]), m_color.g, m_color.b);
return InputEvent{InputEvent::SetColor, m_color};
} else if (commandBuf[0] == 'G') {
m_color = CRGB(m_color.r, std::atoi(&commandBuf[1]), m_color.b);
return InputEvent{InputEvent::SetColor, m_color};
} else if (commandBuf[0] == 'B') {
m_color = CRGB(m_color.r, m_color.g, std::atoi(&commandBuf[1]));
return InputEvent{InputEvent::SetColor, m_color};
} else if (commandBuf[0] == 'O') {
return InputEvent{InputEvent::UserInput, Buttons::Circle};
} else if (commandBuf[0] == 'S') {
return InputEvent{InputEvent::UserInput, Buttons::Triangle};
} else if (commandBuf[0] == 'X') {
return InputEvent{InputEvent::UserInput, Buttons::Cross};
} else if (commandBuf[0] == '+') {
return InputEvent{InputEvent::SetPower, 1};
} else if (commandBuf[0] == '-') {
return InputEvent{InputEvent::SetPower, 0};
} else if (commandBuf[0] == 'p') {
return InputEvent{InputEvent::SetPattern, &commandBuf[1]};
} else if (commandBuf[0] == 'A') {
char* axisVal = strtok(&commandBuf[1], ",");
const uint8_t accelX = std::atof(axisVal) * 10;
axisVal = strtok(NULL, ",");
const uint8_t accelY = std::atof(axisVal) * 10;
axisVal = strtok(NULL, ",");
const uint8_t accelZ = std::atof(axisVal) * 10;
const uint16_t accelSum = abs(accelX) + abs(accelY) + abs(accelZ);
const uint16_t delta = abs(m_value.value() - accelSum);
m_value.add(accelSum);
if (delta > 32) {
return InputEvent{InputEvent::Acceleration, delta};
}
}
}
}
if (didRead) {
return InputEvent::NetworkActivity;
} else {
return InputEvent{};
}
}
void
BluetoothSerialTelemetry::onStart()
{
Log.notice("Starting up Bluetooth...");
if (m_serial.begin(Platform::deviceName())) {
Log.notice("Bluetooth started!");
} else {
Log.warning("Bluetooth could not be started!");
}
}
STATIC_ALLOC(BluetoothSerialTelemetry);

View File

@ -0,0 +1,41 @@
#include <Figments.h>
#include <BluetoothSerial.h>
#include <Ringbuf.h>
class BluetoothSerialTelemetry : public InputSource {
public:
BluetoothSerialTelemetry();
void onStart() override;
InputEvent read() override;
template<typename T, uint8_t Size = 8>
struct Averager {
std::array<T, Size> buf;
unsigned int idx = 0;
unsigned int count = 0;
void add(const T &value) {
buf[idx] = value;
idx = (idx + 1) % Size;
if (count < Size) {
count += 1;
}
}
T value() const {
if (count == 0) {
return T{};
}
long long int sum = 0;
for(unsigned int i = 0; i < count; i++) {
sum += buf[i];
}
return sum / count;
}
};
private:
BluetoothSerial m_serial;
Ringbuf<char, 32> m_ringbuf;
CRGB m_color;
Averager<int16_t, 32> m_value;
};

View File

@ -1,107 +1,190 @@
#include "MQTTTelemetry.h" #include "MQTTTelemetry.h"
#ifdef BOARD_ESP8266
#include <ESP8266WiFi.h>
#elif defined(BOARD_ESP32)
#include <WiFi.h>
#endif
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include "../../Static.h" #include "../../Static.h"
#include "../../Config.h" #include "../../Config.h"
#include "../../Platform.h" #include "../../Platform.h"
WiFiClient wifiClient; struct MQTTDevice {
const String id;
const String name;
const String model;
const String softwareVersion;
const String manufacturer;
const String availabilityTopic;
void toJson(const JsonObject& json) const {
json["name"] = name;
json["mdl"] = model;
json["sw"] = softwareVersion;
json["mf"] = manufacturer;
json["ids"][0] = id;
}
};
const String availTopic = String("renderbug/") + Platform::deviceID() + "/availability";
const MQTTDevice Device{
Platform::deviceID(),
Platform::deviceName(),
Platform::model(),
#ifdef BOARD_ESP8266
ESP.getSketchMD5(),
#else
"",
#endif
"Phong Robotics",
availTopic
};
struct MQTTEntity {
const MQTTDevice& device;
String name;
String entityId;
String rootTopic;
MQTTEntity(const String& domain, const MQTTDevice& device, const String& name) : device(device), name(Platform::deviceName() + " " + name) {
entityId = String(device.id) + "-" + name;
rootTopic = String("homeassistant/") + domain + String("/renderbug/") + entityId;
}
String configTopic() const {
return rootTopic + "/config";
}
String commandTopic() const {
return rootTopic + "/set";
}
String heartbeatTopic() const {
return String("renderbug/") + Device.id + "/heartbeat";
}
String statTopic() const {
return rootTopic + "/state";
}
bool isCommandTopic(const char* topic) const {
if (strncmp(topic, rootTopic.c_str(), rootTopic.length()) == 0) {
return strncmp(&topic[rootTopic.length()], "/set", sizeof("/set")) == 0;
}
return false;
}
void toJson(JsonDocument& jsonBuf, bool isInteractive = true) const {
jsonBuf["~"] = rootTopic.c_str();
jsonBuf["name"] = name;
jsonBuf["unique_id"] = entityId;
if (isInteractive) {
jsonBuf["cmd_t"] = "~/set";
jsonBuf["ret"] = true;
jsonBuf["schema"] = "json";
} else {
}
jsonBuf["stat_t"] = "~/state";
jsonBuf["json_attr_t"] = heartbeatTopic();
jsonBuf["avty_t"] = device.availabilityTopic;
device.toJson(jsonBuf.createNestedObject("dev"));
}
};
const MQTTEntity Lightswitch {
"light", Device, "lightswitch"
};
const MQTTEntity flashlightSwitch {
"switch", Device, "flashlight"
};
const MQTTEntity FPSSensor {
"sensor", Device, "fps"
};
MQTTTelemetry::MQTTTelemetry() : BufferedInputSource("MQTT"), MQTTTelemetry::MQTTTelemetry() : BufferedInputSource("MQTT"),
m_mqtt(PubSubClient(wifiClient)), m_mqtt(m_wifi),
m_logPrinter(this) m_logPrinter(this)
{} {
m_debugTopic = String("renderbug/") + Platform::deviceID();
}
void void
MQTTTelemetry::handleEventOnline(const InputEvent& evt) MQTTTelemetry::handleEventOnline(const InputEvent& evt)
{ {
if (!m_mqtt.connected()) { if (!m_mqtt.connected()) {
Log.notice("Connecting to MQTT..."); Log.notice("Connecting to MQTT as %s on %s...", Platform::deviceID(), Device.availabilityTopic.c_str());
const IPAddress server(10, 0, 0, 2); if (m_mqtt.connect(Platform::deviceID(), NULL, NULL, Device.availabilityTopic.c_str(), 0, true, "offline")) {
const char* deviceID = Platform::deviceID();
Log.verbose("Device ID %s", deviceID);
m_mqtt.setServer(server, 1883);
m_mqtt.setBufferSize(512);
m_mqtt.setCallback(&MQTTTelemetry::s_callback);
if (m_mqtt.connect(deviceID)) {
Log.notice("Connected to MQTT"); Log.notice("Connected to MQTT");
m_needHeartbeat = true; m_needHeartbeat = true;
const String deviceName = String("Renderbug ESP8266") + (char*)deviceID;
const String rootTopic = String("homeassistant/light/renderbug/") + (char*)deviceID;
const String configTopic = rootTopic + "/config";
Log.verbose("root topic %s", rootTopic.c_str());
Log.verbose("config topic %s", configTopic.c_str());
const String statTopic = rootTopic + "/state";
const String cmdTopic = rootTopic + "/set";
const String attrTopic = rootTopic + "/attributes";
const String logTopic = rootTopic + "/log";
const String heartbeatTopic = rootTopic + "/heartbeat";
strcpy(m_statTopic, statTopic.c_str());
strcpy(m_attrTopic, attrTopic.c_str());
strcpy(m_cmdTopic, cmdTopic.c_str());
strcpy(m_logTopic, logTopic.c_str());
strcpy(m_heartbeatTopic, heartbeatTopic.c_str());
StaticJsonDocument<1024> configJson; StaticJsonDocument<1024> configJson;
configJson["~"] = rootTopic;
configJson["name"] = deviceName; Lightswitch.toJson(configJson);
configJson["ret"] = true;
configJson["unique_id"] = (char*) deviceID;
configJson["cmd_t"] = "~/set";
configJson["stat_t"] = "~/state";
configJson["json_attr_t"] = "~/attributes";
configJson["schema"] = "json";
configJson["brightness"] = true;
configJson["rgb"] = true;
int i = 0; int i = 0;
for(const Sequencer::Scene& scene : m_sequencer->scenes()) { for(const Sequencer::Scene& scene : m_sequencer->scenes()) {
configJson["fx_list"][i++] = scene.name; configJson["fx_list"][i++] = scene.name;
} }
configJson["brightness"] = true;
configJson["rgb"] = true;
configJson["dev"]["name"] = "Renderbug";
#ifdef PLATFORM_PHOTON
configJson["dev"]["mdl"] = "Photon";
#elif defined(BOARD_ESP32)
configJson["dev"]["mdl"] = "ESP32";
#elif defined(BOARD_ESP8266)
configJson["dev"]["mdl"] = "ESP8266";
#else
configJson["dev"]["mdl"] = "Unknown";
#endif
configJson["dev"]["sw"] = RENDERBUG_VERSION;
configJson["dev"]["mf"] = "Phong Robotics";
configJson["dev"]["ids"][0] = (char*)deviceID;
char buf[1024]; char buf[1024];
serializeJson(configJson, buf, sizeof(buf)); serializeJson(configJson, buf, sizeof(buf));
Log.verbose("Publish %s %s", configTopic.c_str(), buf);
m_mqtt.publish(configTopic.c_str(), buf, true); Log.verbose("Publish %s %s", Lightswitch.configTopic().c_str(), buf);
m_mqtt.subscribe(m_cmdTopic); m_mqtt.publish(Lightswitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
m_mqtt.subscribe(Lightswitch.commandTopic().c_str());
configJson.clear();
flashlightSwitch.toJson(configJson, false);
configJson["cmd_t"] = "~/set";
configJson["ret"] = true;
serializeJson(configJson, buf, sizeof(buf));
m_mqtt.publish(flashlightSwitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
m_mqtt.subscribe(flashlightSwitch.commandTopic().c_str());
configJson.clear();
FPSSensor.toJson(configJson, false);
configJson["unit_of_meas"] = "Frames/s";
serializeJson(configJson, buf, sizeof(buf));
Log.verbose("Publish %s %s", FPSSensor.configTopic().c_str(), buf);
m_mqtt.publish(FPSSensor.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
m_mqtt.subscribe(FPSSensor.commandTopic().c_str());
#ifdef BOARD_ESP8266
struct rst_info resetInfo = *ESP.getResetInfoPtr();
if (resetInfo.reason != 0) {
char buff[200];
sprintf(&buff[0], "Fatal exception:%d flag:%d (%s) epc1:0x%08x epc2:0x%08x epc3:0x%08x excvaddr:0x%08x depc:0x%08x", resetInfo.exccause, resetInfo.reason, (resetInfo.reason == 0 ? "DEFAULT" : resetInfo.reason == 1 ? "WDT" : resetInfo.reason == 2 ? "EXCEPTION" : resetInfo.reason == 3 ? "SOFT_WDT" : resetInfo.reason == 4 ? "SOFT_RESTART" : resetInfo.reason == 5 ? "DEEP_SLEEP_AWAKE" : resetInfo.reason == 6 ? "EXT_SYS_RST" : "???"), resetInfo.epc1, resetInfo.epc2, resetInfo.epc3, resetInfo.excvaddr, resetInfo.depc);
Log.warning("Previous crash detected! %s", buff);
}
#endif
} else { } else {
Log.warning("Could not connect to MQTT"); Log.warning("Could not connect to MQTT");
} }
} else { } else {
if (evt.intent == InputEvent::SetPower) { String statTopic = Lightswitch.statTopic();
if (evt.intent == InputEvent::StopThing && String(evt.asString()) == "Flashlight") {
String flashlightStatTopic = flashlightSwitch.statTopic();
m_mqtt.publish(flashlightStatTopic.c_str(), "OFF");
} else if (evt.intent == InputEvent::StartThing && String(evt.asString()) == "Flashlight") {
String flashlightStatTopic = flashlightSwitch.statTopic();
m_mqtt.publish(flashlightStatTopic.c_str(), "ON");
} else if (evt.intent == InputEvent::SetPower) {
StaticJsonDocument<256> doc; StaticJsonDocument<256> doc;
char buf[256]; char buf[256];
doc["state"] = evt.asInt() ? "ON" : "OFF"; m_isOn = evt.asInt() ? true : false;
doc["state"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf)); serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(m_statTopic, buf); m_mqtt.publish(statTopic.c_str(), buf);
} else if (evt.intent == InputEvent::SetBrightness) { } else if (evt.intent == InputEvent::SetBrightness) {
StaticJsonDocument<256> doc; StaticJsonDocument<256> doc;
char buf[256]; char buf[256];
doc["brightness"] = evt.asInt(); doc["brightness"] = evt.asInt();
doc["state"] = "ON"; doc["state"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf)); serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(m_statTopic, buf); m_mqtt.publish(statTopic.c_str(), buf);
} else if (evt.intent == InputEvent::SetColor) { } else if (evt.intent == InputEvent::SetColor) {
StaticJsonDocument<256> doc; StaticJsonDocument<256> doc;
char buf[256]; char buf[256];
@ -109,18 +192,19 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
doc["color"]["r"] = color.r; doc["color"]["r"] = color.r;
doc["color"]["g"] = color.g; doc["color"]["g"] = color.g;
doc["color"]["b"] = color.b; doc["color"]["b"] = color.b;
doc["state"] = "ON"; doc["state"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf)); serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(m_statTopic, buf); m_mqtt.publish(statTopic.c_str(), buf);
} else if (evt.intent == InputEvent::SetPattern) { } else if (evt.intent == InputEvent::SetPattern) {
StaticJsonDocument<256> doc; StaticJsonDocument<256> doc;
char buf[256]; char buf[256];
doc["effect"] = evt.asString(); doc["effect"] = evt.asString();
doc["state"] = "ON"; doc["state"] = m_isOn ? "ON" : "OFF";
serializeJson(doc, buf, sizeof(buf)); serializeJson(doc, buf, sizeof(buf));
m_mqtt.publish(m_statTopic, buf); m_mqtt.publish(statTopic.c_str(), buf);
} else if (evt.intent == InputEvent::FirmwareUpdate) { } else if (evt.intent == InputEvent::FirmwareUpdate) {
m_mqtt.publish("renderbug/debug/firmware", "firmware update!"); String updateTopic = m_debugTopic + "/firmware";
m_mqtt.publish(updateTopic.c_str(), "firmware update!");
} }
} }
} }
@ -132,6 +216,23 @@ MQTTTelemetry::loop()
OnlineTaskMixin::loop(); OnlineTaskMixin::loop();
} }
void
MQTTTelemetry::onOnline()
{
const IPAddress server(10, 0, 0, 2);
m_needHeartbeat = true;
m_mqtt.setServer(server, 1883);
m_mqtt.setBufferSize(1024);
m_mqtt.setCallback(&MQTTTelemetry::s_callback);
}
void
MQTTTelemetry::onOffline()
{
m_mqtt.disconnect();
}
void void
MQTTTelemetry::loopOnline() MQTTTelemetry::loopOnline()
{ {
@ -142,89 +243,126 @@ MQTTTelemetry::loopOnline()
if (m_needHeartbeat) { if (m_needHeartbeat) {
char buf[512]; char buf[512];
StaticJsonDocument<512> response; StaticJsonDocument<512> response;
response["fps"] = FastLED.getFPS(); response["device_id"] = Platform::deviceID();
response["RSSI"] = WiFi.RSSI();
response["localip"] = WiFi.localIP().toString();
response["free_ram"] = ESP.getFreeHeap();
response["os_version"] = ESP.getSdkVersion();
response["sketch_version"] = ESP.getSketchMD5(); response["sketch_version"] = ESP.getSketchMD5();
response["os_version"] = ESP.getSdkVersion();
response["localip"] = WiFi.localIP().toString();
response["pixelCount"] = Static<ConfigService>::instance()->coordMap()->pixelCount;
response["startPixel"] = Static<ConfigService>::instance()->coordMap()->startPixel;
response["RSSI"] = WiFi.RSSI();
response["free_ram"] = ESP.getFreeHeap();
response["fps"] = FastLED.getFPS();
serializeJson(response, buf, sizeof(buf)); serializeJson(response, buf, sizeof(buf));
m_mqtt.publish(m_attrTopic, buf); String availTopic = m_rootTopic + "/available";
m_mqtt.publish(m_heartbeatTopic, buf); m_mqtt.publish(Lightswitch.heartbeatTopic().c_str(), buf);
Log.notice("Heartbeat: %s", buf); m_mqtt.publish(Device.availabilityTopic.c_str(), "online");
//Log.notice("Heartbeat: %s", buf);
String fpsCounter = String(FastLED.getFPS());
m_mqtt.publish(FPSSensor.statTopic().c_str(), fpsCounter.c_str());
response.clear();
auto sched = MainLoop::instance()->scheduler;
for(auto task : sched.tasks) {
response[task->name] = task->state == Task::Running;
}
serializeJson(response, buf, sizeof(buf));
m_mqtt.publish(m_heartbeatTopic, buf);
m_needHeartbeat = false; m_needHeartbeat = false;
} }
} }
void void
MQTTTelemetry::callback(char* topic, byte* payload, unsigned int length) MQTTTelemetry::callback(char* topic, const char* payload)
{ {
DynamicJsonDocument doc(1024); setEvent(InputEvent::NetworkActivity);
deserializeJson(doc, payload, length); if (flashlightSwitch.isCommandTopic(topic)) {
if (!strncmp((char*)payload, "ON", sizeof("ON"))) {
if (doc.containsKey("state")) { Log.notice("Turning on flashlight");
if (doc["state"] == "ON") { setEvent(InputEvent{InputEvent::SetPower, true});
setEvent(InputEvent{InputEvent::SetPower, true}); setEvent(InputEvent{InputEvent::SetPattern, "Flashlight"});
} else if (doc["state"] == "OFF") { setEvent(InputEvent{InputEvent::SetBrightness, 255});
setEvent(InputEvent{InputEvent::SetPower, false}); } else if (!strncmp((char*)payload, "OFF", sizeof("OFF"))) {
} Log.notice("Turning off flashlight");
} setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
if (doc.containsKey("start")) {
strcpy(m_patternBuf, doc["start"].as<const char*>());
setEvent(InputEvent{InputEvent::StartThing, m_patternBuf});
}
if (doc.containsKey("stop")) {
if (doc["stop"] == name) {
Log.notice("You can't kill an idea, or stop the MQTT Task via MQTT.");
} else {
strcpy(m_patternBuf, doc["stop"].as<const char*>());
setEvent(InputEvent{InputEvent::StopThing, m_patternBuf});
} }
} } else if (Lightswitch.isCommandTopic(topic)) {
StaticJsonDocument<512> doc;
deserializeJson(doc, payload);
if (doc.containsKey("pixelCount")) { if (doc.containsKey("state")) {
setEvent(InputEvent{InputEvent::SetDisplayLength, (int)doc["pixelCount"]}); if (doc["state"] == "ON") {
} Log.notice("Turning on power");
setEvent(InputEvent{InputEvent::SetPower, true});
} else if (doc["state"] == "OFF") {
Log.notice("Turning off power");
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
setEvent(InputEvent{InputEvent::SetPower, false});
}
}
if (doc.containsKey("startPixel")) { if (doc.containsKey("start")) {
setEvent(InputEvent{InputEvent::SetDisplayOffset, (int)doc["startPixel"]}); strcpy(m_patternBuf, doc["start"].as<const char*>());
} setEvent(InputEvent{InputEvent::StartThing, m_patternBuf});
}
if (doc.containsKey("save")) { if (doc.containsKey("stop")) {
setEvent(InputEvent{InputEvent::SaveConfigurationRequest}); if (doc["stop"] == name) {
} Log.notice("You can't kill an idea, or stop the MQTT Task via MQTT.");
} else {
strcpy(m_patternBuf, doc["stop"].as<const char*>());
setEvent(InputEvent{InputEvent::StopThing, m_patternBuf});
}
}
if (doc.containsKey("effect")) { if (doc.containsKey("pixelCount")) {
strcpy(m_patternBuf, doc["effect"].as<const char*>()); setEvent(InputEvent{InputEvent::SetDisplayLength, (int)doc["pixelCount"]});
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf}); }
}
if (doc.containsKey("color")) { if (doc.containsKey("startPixel")) {
uint8_t r = doc["color"]["r"]; setEvent(InputEvent{InputEvent::SetDisplayOffset, (int)doc["startPixel"]});
uint8_t g = doc["color"]["g"]; }
uint8_t b = doc["color"]["b"];
setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)});
}
if (doc.containsKey("brightness")) { if (doc.containsKey("save")) {
setEvent(InputEvent{InputEvent::SetBrightness, (int)doc["brightness"]}); setEvent(InputEvent{InputEvent::SaveConfigurationRequest});
}
if (doc.containsKey("restart")) {
#ifdef BOARD_ESP8266
ESP.wdtDisable();
ESP.restart();
#endif
}
if (doc.containsKey("reconnect")) {
m_mqtt.disconnect();
}
if (doc.containsKey("ping")) {
m_needHeartbeat = true;
Log.notice("Queuing up heartbeat");
}
if (doc.containsKey("effect")) {
strcpy(m_patternBuf, doc["effect"].as<const char*>());
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
}
if (doc.containsKey("color")) {
uint8_t r = doc["color"]["r"];
uint8_t g = doc["color"]["g"];
uint8_t b = doc["color"]["b"];
setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)});
}
if (doc.containsKey("brightness")) {
setEvent(InputEvent{InputEvent::SetBrightness, (int)doc["brightness"]});
}
} }
} }
void void
MQTTTelemetry::s_callback(char* topic, byte* payload, unsigned int length) MQTTTelemetry::s_callback(char* topic, byte* payload, unsigned int length)
{ {
Static<MQTTTelemetry>::instance()->callback(topic, payload, length); char topicBuf[128];
char payloadBuf[512];
strcpy(topicBuf, topic);
memcpy(payloadBuf, payload, length);
payloadBuf[std::min(sizeof(payloadBuf) - 1, length)] = 0;
Static<MQTTTelemetry>::instance()->callback(topicBuf, payloadBuf);
} }
STATIC_ALLOC(MQTTTelemetry); STATIC_ALLOC(MQTTTelemetry);

View File

@ -6,6 +6,13 @@
#include "../../Sequencer.h" #include "../../Sequencer.h"
#ifdef BOARD_ESP8266
#include <ESP8266WiFi.h>
#elif defined(BOARD_ESP32)
#include <WiFi.h>
#endif
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin { class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
public: public:
MQTTTelemetry(); MQTTTelemetry();
@ -22,7 +29,9 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
if (byte == '\n') { if (byte == '\n') {
size_t bufSize = buf.write(outBuf); size_t bufSize = buf.write(outBuf);
outBuf[std::min(sizeof(outBuf), bufSize)] = 0; outBuf[std::min(sizeof(outBuf), bufSize)] = 0;
telemetry->m_mqtt.publish(telemetry->m_logTopic, outBuf); Serial.println(outBuf);
String logTopic = telemetry->m_debugTopic + "/log";
telemetry->m_mqtt.publish(logTopic.c_str(), outBuf);
} else { } else {
buf.insert(byte); buf.insert(byte);
} }
@ -39,20 +48,22 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
void loopOnline() override; void loopOnline() override;
private: void onOnline() override;
char m_statTopic[100]; void onOffline() override;
char m_attrTopic[100];
char m_cmdTopic[100];
char m_logTopic[100];
char m_heartbeatTopic[100];
void callback(char* topic, byte* payload, unsigned int length); private:
String m_rootTopic;
String m_debugTopic;
void callback(char* topic, const char* payload);
static void s_callback(char* topic, byte* payload, unsigned int length); static void s_callback(char* topic, byte* payload, unsigned int length);
char m_patternBuf[48]; char m_patternBuf[48];
bool m_needHeartbeat = false; bool m_needHeartbeat = false;
bool m_isOn = true;
Sequencer *m_sequencer = 0; Sequencer *m_sequencer = 0;
WiFiClient m_wifi;
PubSubClient m_mqtt; PubSubClient m_mqtt;
LogPrinter m_logPrinter; LogPrinter m_logPrinter;
}; };

View File

@ -55,13 +55,16 @@ public:
// Grab the physical pixel we'll start with // Grab the physical pixel we'll start with
PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, 0}); PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, 0});
PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, 0}); PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, 0});
int scaledWidth = std::abs(endPos.x - startPos.x); uint8_t scaledWidth = std::abs(endPos.x - startPos.x);
//Log.notice("blob w=%d x=%d", scaledWidth, startPos.x); //Log.notice("blob w=%d x=%d", scaledWidth, startPos.x);
for(uint8_t i = 0;i < scaledWidth; i++) { for(uint8_t i = 0;i < scaledWidth; i++) {
// Blobs desaturate towards their tail // Blobs desaturate towards their tail
//Log.notice("blob i=%d w=%d x=%d", i, scaledWidth, startPos.x); //Log.notice("blob i=%d w=%d x=%d", i, scaledWidth, startPos.x);
CHSV blobColor(m_hue, m_saturation, quadwave8((i / (double)scaledWidth) * m_brightness)); uint8_t scalePct = map8(i, 0, scaledWidth);
uint8_t val = lerp8by8(0, m_brightness, scalePct);
//CHSV blobColor(m_hue, m_saturation, quadwave8((i / (double)scaledWidth) * m_brightness));
CHSV blobColor(m_hue, m_saturation, quadwave8(val));
PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0}; PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0};