Compare commits
33 Commits
74c2ddb405
...
9a3186edbd
Author | SHA1 | Date | |
---|---|---|---|
|
9a3186edbd | ||
|
dc1dfd9f4a | ||
|
195b405a3b | ||
|
5668266a23 | ||
|
81bebad459 | ||
|
d36de899fd | ||
|
ef74dc2178 | ||
|
4412fd8f1a | ||
|
4114a8b2b5 | ||
|
7418172f79 | ||
|
63a705ddd4 | ||
|
e9f63e718c | ||
|
d592810b3b | ||
|
57f1ca837c | ||
|
5ea43bc908 | ||
|
9c53d05ab1 | ||
|
58df15702d | ||
|
4e56134dd9 | ||
|
ac94c4be0c | ||
|
63397dc39a | ||
|
7b0434e3df | ||
|
87ac61b061 | ||
|
b1ec20982b | ||
|
50c98bc5b5 | ||
|
5a62b30019 | ||
|
5a6809a723 | ||
|
ad9d6649c9 | ||
|
ddc3804ae0 | ||
|
7970192c1a | ||
|
1e2f60201d | ||
|
3e5cead5ff | ||
|
c91757308d | ||
|
e5d4eea02b |
24
data/maps/ponder.json
Normal file
24
data/maps/ponder.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"rotation": 3,
|
||||||
|
"strides": [
|
||||||
|
{"x": 0, "y": 0, "pixels": 17},
|
||||||
|
{"x": 0, "y": 1, "pixels": 17},
|
||||||
|
{"x": 0, "y": 2, "pixels": 17},
|
||||||
|
{"x": 0, "y": 3, "pixels": 17},
|
||||||
|
{"x": 0, "y": 4, "pixels": 16},
|
||||||
|
{"x": 0, "y": 5, "pixels": 17},
|
||||||
|
{"x": 0, "y": 6, "pixels": 17},
|
||||||
|
{"x": 0, "y": 7, "pixels": 17},
|
||||||
|
{"x": 0, "y": 8, "pixels": 17},
|
||||||
|
{"x": 0, "y": 9, "pixels": 17},
|
||||||
|
{"x": 0, "y": 10, "pixels": 17},
|
||||||
|
{"x": 0, "y": 11, "pixels": 17},
|
||||||
|
{"x": 0, "y": 12, "pixels": 18},
|
||||||
|
{"x": 0, "y": 13, "pixels": 17},
|
||||||
|
{"x": 0, "y": 14, "pixels": 18},
|
||||||
|
{"x": 0, "y": 15, "pixels": 17},
|
||||||
|
{"x": 0, "y": 16, "pixels": 17},
|
||||||
|
{"x": 0, "y": 17, "pixels": 17}
|
||||||
|
]
|
||||||
|
}
|
@ -4,7 +4,8 @@
|
|||||||
"Renderer",
|
"Renderer",
|
||||||
"SerialInput",
|
"SerialInput",
|
||||||
"BPM",
|
"BPM",
|
||||||
"Bluetooth"
|
"Bluetooth",
|
||||||
|
"Serial"
|
||||||
],
|
],
|
||||||
"scenes": {
|
"scenes": {
|
||||||
"Idle": ["Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"],
|
"Idle": ["Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"],
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
"WiFi",
|
"WiFi",
|
||||||
"MQTT",
|
"MQTT",
|
||||||
"ArduinoOTA",
|
"ArduinoOTA",
|
||||||
"BPM"
|
"BPM",
|
||||||
|
"Serial"
|
||||||
],
|
],
|
||||||
"scenes": {
|
"scenes": {
|
||||||
"Idle": ["Solid", "MPU5060", "IdleColors", "CircadianRhythm"],
|
"Idle": ["Solid", "MPU5060", "IdleColors", "CircadianRhythm"],
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
"WiFi",
|
"WiFi",
|
||||||
"MQTT",
|
"MQTT",
|
||||||
"ArduinoOTA",
|
"ArduinoOTA",
|
||||||
"UpdateStatusAnimation"
|
"UpdateStatusAnimation",
|
||||||
|
"Serial"
|
||||||
],
|
],
|
||||||
"scenes": {
|
"scenes": {
|
||||||
"Idle": ["Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"],
|
"Idle": ["Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"],
|
||||||
|
25
data/profiles/ponderjar.json
Normal file
25
data/profiles/ponderjar.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"tasks": [
|
||||||
|
"Bluetooth",
|
||||||
|
"WiFi",
|
||||||
|
"Renderer",
|
||||||
|
"Power",
|
||||||
|
"BPM",
|
||||||
|
"MQTT",
|
||||||
|
"ArduinoOTA",
|
||||||
|
"UpdateStatusAnimation",
|
||||||
|
"Serial"
|
||||||
|
],
|
||||||
|
"scenes": {
|
||||||
|
"Rain": ["Rain", "Rainbow"],
|
||||||
|
"Test": ["Test"],
|
||||||
|
"Idle": ["Solid", "Pulse", "Rainbow", "CircadianRhythm"],
|
||||||
|
"Acid": ["Chimes", "Pulse", "IdleColors", "Rainbow"],
|
||||||
|
"Flashlight": ["Flashlight"]
|
||||||
|
},
|
||||||
|
"surfaceMap": "ponder",
|
||||||
|
"defaults": {
|
||||||
|
"mqtt.ip": "10.0.0.2"
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,8 @@
|
|||||||
"Renderer",
|
"Renderer",
|
||||||
"ConfigInput",
|
"ConfigInput",
|
||||||
"ConfigDisplay",
|
"ConfigDisplay",
|
||||||
"InputBlip"
|
"InputBlip",
|
||||||
|
"Serial"
|
||||||
],
|
],
|
||||||
"scenes": [],
|
"scenes": [],
|
||||||
"surfaceMap": "default"
|
"surfaceMap": "default"
|
||||||
|
7
lib/Figments/Command.cpp
Normal file
7
lib/Figments/Command.cpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "./Command.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
doNothing(Args& args, Print& printer)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Command::Command() : func(doNothing) {}
|
33
lib/Figments/Command.h
Normal file
33
lib/Figments/Command.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class Args {
|
||||||
|
private:
|
||||||
|
String *str;
|
||||||
|
public:
|
||||||
|
Args(String *str) : str(str) {}
|
||||||
|
String operator[](int pos) {
|
||||||
|
char buf[64];
|
||||||
|
strncpy(buf, str->c_str(), sizeof(buf));
|
||||||
|
char *args = strtok(buf, " ");
|
||||||
|
while (pos > 0 && args != NULL) {
|
||||||
|
args = strtok(NULL, " ");
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
if (args == NULL) {
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
return String(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CommandList;
|
||||||
|
|
||||||
|
struct Command {
|
||||||
|
using Executor = std::function<void(Args&, Print& output)>;
|
||||||
|
Executor func;
|
||||||
|
const char* name = NULL;
|
||||||
|
|
||||||
|
Command();
|
||||||
|
Command(const char* name, Executor func) : name(name), func(func) {}
|
||||||
|
};
|
@ -43,6 +43,7 @@ CRGB&
|
|||||||
Display::pixelAt(int idx)
|
Display::pixelAt(int idx)
|
||||||
{
|
{
|
||||||
const int kx = idx % pixelCount();
|
const int kx = idx % pixelCount();
|
||||||
|
assert(abs(kx) < pixelCount());
|
||||||
if (kx < 0) {
|
if (kx < 0) {
|
||||||
return m_pixels[pixelCount() + 1 + kx];
|
return m_pixels[pixelCount() + 1 + kx];
|
||||||
} else {
|
} else {
|
||||||
|
9
lib/Figments/Figment.cpp
Normal file
9
lib/Figments/Figment.cpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#include "./Figment.h"
|
||||||
|
|
||||||
|
const std::vector<Command> emptyCommands;
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
Task::commands() const
|
||||||
|
{
|
||||||
|
return emptyCommands;
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
|
#include "./Command.h"
|
||||||
|
|
||||||
#define F_LIKELY(x) __builtin_expect(!!(x), true)
|
#define F_LIKELY(x) __builtin_expect(!!(x), true)
|
||||||
#define F_UNLIKELY(x) __builtin_expect(!!(x), false)
|
#define F_UNLIKELY(x) __builtin_expect(!!(x), false)
|
||||||
@ -68,6 +69,8 @@ struct Task : public virtual Loopable {
|
|||||||
|
|
||||||
const char* name = "";
|
const char* name = "";
|
||||||
State state = Stopped;
|
State state = Stopped;
|
||||||
|
|
||||||
|
virtual const std::vector<Command> &commands() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -114,6 +114,10 @@ struct InputEvent: public Variant {
|
|||||||
InputEvent()
|
InputEvent()
|
||||||
: Variant(), intent(None) {}
|
: Variant(), intent(None) {}
|
||||||
|
|
||||||
|
bool operator!=(const InputEvent::Intent& otherIntent) {
|
||||||
|
return intent != otherIntent;
|
||||||
|
}
|
||||||
|
|
||||||
Intent intent;
|
Intent intent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,17 +24,23 @@ MainLoop::dispatchSync(const InputEvent& evt)
|
|||||||
{
|
{
|
||||||
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
|
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
|
||||||
const bool jobState = (evt.intent == InputEvent::StartThing);
|
const bool jobState = (evt.intent == InputEvent::StartThing);
|
||||||
|
bool wasFound = false;
|
||||||
for(auto figmentJob: scheduler.tasks) {
|
for(auto figmentJob: scheduler.tasks) {
|
||||||
if (!strcmp(figmentJob->name, evt.asString())) {
|
if (!strcmp(figmentJob->name, evt.asString())) {
|
||||||
if (jobState) {
|
if (jobState) {
|
||||||
Log.trace("** Starting %s", figmentJob->name);
|
Log.trace("** Starting %s", figmentJob->name);
|
||||||
figmentJob->start();
|
figmentJob->start();
|
||||||
|
wasFound = true;
|
||||||
} else {
|
} else {
|
||||||
Log.trace("** Stopping %s", figmentJob->name);
|
Log.trace("** Stopping %s", figmentJob->name);
|
||||||
figmentJob->stop();
|
figmentJob->stop();
|
||||||
|
wasFound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!wasFound) {
|
||||||
|
Log.warning("** Unable to find task %s", evt.asString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Task* task : scheduler) {
|
for(Task* task : scheduler) {
|
||||||
@ -86,7 +92,8 @@ MainLoop::loop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
frameSpeed = millis() - frameStart;
|
frameSpeed = millis() - frameStart;
|
||||||
if (frameSpeed >= 23) { // TODO: Configure max frame time at build
|
if (frameSpeed >= 34) { // TODO: Configure max frame time at build. Default
|
||||||
|
// to 30FPS
|
||||||
const char* slowestName = (slowestTask->name ? slowestTask->name : "(Unnamed)");
|
const char* slowestName = (slowestTask->name ? slowestTask->name : "(Unnamed)");
|
||||||
Log.warning("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestName, slowest);
|
Log.warning("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestName, slowest);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,18 @@
|
|||||||
|
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
|
#ifndef __NOINIT_ATTR // Pre-defined on esp32
|
||||||
|
#define __NOINIT_ATTR __attribute__ ((section (".noinit")))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
__NOINIT_ATTR const char* s_lastFigmentName;
|
||||||
|
|
||||||
|
const char*
|
||||||
|
Renderer::lastFigmentName()
|
||||||
|
{
|
||||||
|
return s_lastFigmentName;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Renderer::loop()
|
Renderer::loop()
|
||||||
{
|
{
|
||||||
@ -13,6 +25,7 @@ Renderer::loop()
|
|||||||
unsigned int frameStart = ESP.getCycleCount();
|
unsigned int frameStart = ESP.getCycleCount();
|
||||||
#endif
|
#endif
|
||||||
Log.verbose("Render %s", figment->name);
|
Log.verbose("Render %s", figment->name);
|
||||||
|
s_lastFigmentName = figment->name;
|
||||||
figment->render(dpy);
|
figment->render(dpy);
|
||||||
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
unsigned int runtime = (ESP.getCycleCount() - frameStart) / 160000;
|
unsigned int runtime = (ESP.getCycleCount() - frameStart) / 160000;
|
||||||
|
@ -10,6 +10,8 @@ public:
|
|||||||
void loop() override;
|
void loop() override;
|
||||||
void onStart() override;
|
void onStart() override;
|
||||||
|
|
||||||
|
static const char* lastFigmentName();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::vector<Figment*> m_figments;
|
const std::vector<Figment*> m_figments;
|
||||||
const std::vector<Display*> m_displays;
|
const std::vector<Display*> m_displays;
|
||||||
|
@ -86,6 +86,8 @@ struct Ringbuf {
|
|||||||
size_t size() {
|
size_t size() {
|
||||||
if (m_tail > m_head) {
|
if (m_tail > m_head) {
|
||||||
return m_tail - m_head;
|
return m_tail - m_head;
|
||||||
|
} else if (m_tail == m_head) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return m_tail + (Size - m_head);
|
return m_tail + (Size - m_head);
|
||||||
}
|
}
|
||||||
|
@ -54,17 +54,20 @@ void
|
|||||||
Surface::paintShader(Surface::Shader shader)
|
Surface::paintShader(Surface::Shader shader)
|
||||||
{
|
{
|
||||||
PerfCounter _("paintShader");
|
PerfCounter _("paintShader");
|
||||||
const uint16_t width = end.x - start.x + 1;
|
uint8_t startX = min(start.x, end.x);
|
||||||
const uint16_t height = end.y - start.y + 1;
|
uint8_t startY = min(start.y, end.y);
|
||||||
|
uint8_t endX = max(start.x, end.x);
|
||||||
|
uint8_t endY = max(start.y, end.y);
|
||||||
|
const uint16_t width = endX - startX + 1;
|
||||||
|
const uint16_t height = endY - startY + 1;
|
||||||
const uint8_t xMod = 255 / width;
|
const uint8_t xMod = 255 / width;
|
||||||
const uint8_t yMod = 255 / height;
|
const uint8_t yMod = 255 / height;
|
||||||
for(auto x = 0; x < width; x++) {
|
for(auto x = 0; x < width; x++) {
|
||||||
for(auto y = 0; y < height; y++) {
|
for(auto y = 0; y < height; y++) {
|
||||||
PhysicalCoordinates coords{x + start.x, y + start.y};
|
PhysicalCoordinates coords{x + startX, y + startY};
|
||||||
VirtualCoordinates virtCoords{m_display->coordinateMapping()->physicalToVirtualCoords(coords)};
|
VirtualCoordinates virtCoords{m_display->coordinateMapping()->physicalToVirtualCoords(coords)};
|
||||||
VirtualCoordinates surfaceCoords{xMod * x, yMod * y};
|
VirtualCoordinates surfaceCoords{xMod * x, yMod * y};
|
||||||
//Log.notice("width=%d height=%d vx=%d vy=%d sx=%d sy=%d x=%d y=%d px=%d py=%d", width, height, start.x, start.y, x, y, coords.x, coords.y);
|
//Log.notice("width=%d height=%d vx=%d vy=%d sx=%d sy=%d x=%d y=%d px=%d py=%d", width, height, startX, startY, x, y, coords.x, coords.y); // 256 = 1.0
|
||||||
// 256 = 1.0
|
|
||||||
// 128 = 0.0
|
// 128 = 0.0
|
||||||
// 0 = 1.0
|
// 0 = 1.0
|
||||||
shader(m_display->pixelAt(coords), virtCoords, coords, surfaceCoords);
|
shader(m_display->pixelAt(coords), virtCoords, coords, surfaceCoords);
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[common_env_data]
|
[common_env_data]
|
||||||
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> -<inputs/> +<inputs/BPM.cpp>"
|
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> -<inputs/> +<inputs/BPM.cpp> +<inputs/Serial.cpp> +<inputs/CircadianRhythm.cpp>"
|
||||||
lib_ldf_mode = chain+
|
lib_ldf_mode = chain+
|
||||||
extra_scripts = verify-configs.py
|
extra_scripts = verify-configs.py
|
||||||
src_build_flags =
|
src_build_flags =
|
||||||
|
@ -14,10 +14,21 @@ retained bool LAST_BOOT_WAS_FLASH;
|
|||||||
retained bool LAST_BOOT_WAS_SERIAL;
|
retained bool LAST_BOOT_WAS_SERIAL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BOARD_ESP32
|
#ifndef __NOINIT_ATTR // Pre-defined on esp32
|
||||||
__NOINIT_ATTR uint8_t s_rebootCount = 0;
|
#define __NOINIT_ATTR __attribute__ ((section (".noinit")))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define SAFE_MODE_MAGIC 6942
|
||||||
|
|
||||||
|
__NOINIT_ATTR uint8_t s_rebootCount;
|
||||||
|
__NOINIT_ATTR uint16_t s_forceSafeMode;
|
||||||
|
|
||||||
|
void
|
||||||
|
BootOptions::forceSafeMode()
|
||||||
|
{
|
||||||
|
s_forceSafeMode = SAFE_MODE_MAGIC;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
BootOptions::initPins()
|
BootOptions::initPins()
|
||||||
{
|
{
|
||||||
@ -60,23 +71,20 @@ BootOptions::BootOptions()
|
|||||||
#ifdef BOARD_ESP8266
|
#ifdef BOARD_ESP8266
|
||||||
struct rst_info resetInfo = *ESP.getResetInfoPtr();
|
struct rst_info resetInfo = *ESP.getResetInfoPtr();
|
||||||
resetReason = resetInfo.reason;
|
resetReason = resetInfo.reason;
|
||||||
EEPROM.begin(sizeof(crashCount));
|
crashCount = s_rebootCount;
|
||||||
EEPROM.get(sizeof(HardwareConfig) + 32, crashCount);
|
if (resetInfo.reason == REASON_SOFT_WDT_RST || resetInfo.reason == REASON_WDT_RST || resetInfo.reason == REASON_EXCEPTION_RST) {
|
||||||
EEPROM.end();
|
|
||||||
if (resetInfo.reason == REASON_WDT_RST || resetInfo.reason == REASON_EXCEPTION_RST) {
|
|
||||||
if (crashCount++ >= 3) {
|
if (crashCount++ >= 3) {
|
||||||
// Boot into safe mode if the watchdog reset us three times in a row.
|
// Boot into safe mode if the watchdog reset us three times in a row.
|
||||||
isSafeMode = true;
|
isSafeMode = true;
|
||||||
} else {
|
|
||||||
EEPROM.begin(sizeof(crashCount));
|
|
||||||
EEPROM.put(sizeof(HardwareConfig) + 32, crashCount);
|
|
||||||
EEPROM.end();
|
|
||||||
}
|
}
|
||||||
} else if (crashCount != 0) {
|
} else {
|
||||||
crashCount = 0;
|
crashCount = 0;
|
||||||
EEPROM.begin(sizeof(crashCount));
|
}
|
||||||
EEPROM.put(sizeof(HardwareConfig) + 32, crashCount);
|
s_rebootCount = crashCount;
|
||||||
EEPROM.end();
|
|
||||||
|
if (resetInfo.reason > 0 && s_forceSafeMode == SAFE_MODE_MAGIC) {
|
||||||
|
isSafeMode = true;
|
||||||
|
s_forceSafeMode = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ struct BootOptions {
|
|||||||
BootOptions();
|
BootOptions();
|
||||||
|
|
||||||
void waitForRelease();
|
void waitForRelease();
|
||||||
|
void forceSafeMode();
|
||||||
|
|
||||||
bool isSetup = false;
|
bool isSetup = false;
|
||||||
bool isSerial = false;
|
bool isSerial = false;
|
||||||
|
@ -53,7 +53,7 @@ Configuration::get(const char* key, bool defaultVal) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StaticJsonDocument<1024> jsonConfig;
|
StaticJsonDocument<2048> jsonConfig;
|
||||||
|
|
||||||
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
||||||
|
|
||||||
@ -154,6 +154,11 @@ ConfigService::loadMap(const String& mapName)
|
|||||||
deserializeJson(jsonConfig, configFile);
|
deserializeJson(jsonConfig, configFile);
|
||||||
configFile.close();
|
configFile.close();
|
||||||
JsonArray strideList = jsonConfig["strides"];
|
JsonArray strideList = jsonConfig["strides"];
|
||||||
|
if (jsonConfig.containsKey("rotation")) {
|
||||||
|
m_jsonMap.rotation = jsonConfig["rotation"];
|
||||||
|
} else {
|
||||||
|
m_jsonMap.rotation = 0;
|
||||||
|
}
|
||||||
m_jsonMap.load(strideList);
|
m_jsonMap.load(strideList);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -213,8 +218,9 @@ ConfigService::loadProfile(const char* profileName)
|
|||||||
m_jsonMap.loadDefault();
|
m_jsonMap.loadDefault();
|
||||||
}
|
}
|
||||||
Log.notice("config: Loaded!");
|
Log.notice("config: Loaded!");
|
||||||
|
strcpy(m_config.data.loadedProfile, profileName);
|
||||||
} else {
|
} else {
|
||||||
Log.warning("config: Could not load profile %s!", profileName);
|
Log.warning("config: Could not find profile json %s!", fname.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,5 +260,56 @@ ConfigService::handleEvent(const InputEvent &evt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doMapList(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
static const auto conf = Static<ConfigService>::instance();
|
||||||
|
out.println("Available maps:");
|
||||||
|
LittleFS.begin();
|
||||||
|
for(auto it = conf->mapsBegin();it != conf->mapsEnd(); it++) {
|
||||||
|
out.println(*it);
|
||||||
|
}
|
||||||
|
LittleFS.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doSave(Args& args, Print& print)
|
||||||
|
{
|
||||||
|
MainLoop::instance()->dispatch(InputEvent::SaveConfigurationRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String s;
|
||||||
|
|
||||||
|
void
|
||||||
|
doSetProfile(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
s = args[1];
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::LoadConfigurationByName, s.c_str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doCoordMap(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
VirtualCoordinates coords{atoi(args[1].c_str()), atoi(args[2].c_str())};
|
||||||
|
auto map = Static<ConfigService>::instance()->coordMap();
|
||||||
|
auto pPos = map->virtualToPhysicalCoords(coords);
|
||||||
|
auto idx = map->physicalCoordsToIndex(pPos);
|
||||||
|
char buf[32];
|
||||||
|
sprintf(buf, "(%d, %d) -> (%d, %d) -> %d", coords.x, coords.y, pPos.x, pPos.y, idx);
|
||||||
|
out.println(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
ConfigService::commands() const
|
||||||
|
{
|
||||||
|
static const std::vector<Command> _commands = {
|
||||||
|
{"save", doSave},
|
||||||
|
{"profile", doSetProfile},
|
||||||
|
{"maps", doMapList},
|
||||||
|
{"coordmap", doCoordMap}
|
||||||
|
};
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(ConfigService);
|
STATIC_ALLOC(ConfigService);
|
||||||
STATIC_TASK(ConfigService);
|
STATIC_TASK(ConfigService);
|
||||||
|
56
src/Config.h
56
src/Config.h
@ -2,6 +2,7 @@
|
|||||||
#include <Figments.h>
|
#include <Figments.h>
|
||||||
#include "JsonCoordinateMapping.h"
|
#include "JsonCoordinateMapping.h"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
class Configuration {
|
class Configuration {
|
||||||
public:
|
public:
|
||||||
@ -39,7 +40,7 @@ struct HardwareConfig {
|
|||||||
void save();
|
void save();
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
|
|
||||||
static constexpr uint16_t MAX_LED_NUM = 255;
|
static constexpr uint16_t MAX_LED_NUM = 512;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t getCRC() const;
|
uint8_t getCRC() const;
|
||||||
@ -59,6 +60,59 @@ struct ConfigService: public Task {
|
|||||||
const char* loadedProfile() const;
|
const char* loadedProfile() const;
|
||||||
void overrideProfile(const char* profileName);
|
void overrideProfile(const char* profileName);
|
||||||
const char* getConfigValue(const char* key) const;
|
const char* getConfigValue(const char* key) const;
|
||||||
|
const std::vector<Command>& commands() const override;
|
||||||
|
|
||||||
|
struct filename_iterator: public std::iterator<std::input_iterator_tag, const char*> {
|
||||||
|
Dir dir;
|
||||||
|
String ret;
|
||||||
|
bool valid;
|
||||||
|
const char* suffix;
|
||||||
|
explicit filename_iterator() : suffix(NULL), valid(false) {}
|
||||||
|
explicit filename_iterator(const char* path, const char* suffix) : dir(LittleFS.openDir(path)), valid(true), suffix(suffix) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
void next() {
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int extPos = -1;
|
||||||
|
do {
|
||||||
|
valid = dir.next();
|
||||||
|
Log.info("valid %F", valid);
|
||||||
|
if (valid) {
|
||||||
|
String fname = dir.fileName();
|
||||||
|
extPos = fname.lastIndexOf(suffix);
|
||||||
|
Log.info("compare %s %d", fname.c_str(), extPos);
|
||||||
|
if (extPos != -1) {
|
||||||
|
ret = fname.substring(0, extPos);
|
||||||
|
Log.info("found %s", ret.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (valid && extPos == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
filename_iterator& operator++() {
|
||||||
|
next();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
filename_iterator& operator++(int) {filename_iterator ret = *this; ++(*this); return ret;}
|
||||||
|
bool operator==(const filename_iterator &other) const { return valid == other.valid;}
|
||||||
|
bool operator!=(const filename_iterator &other) const { return !(*this == other); }
|
||||||
|
const char* operator*() const {
|
||||||
|
if (!valid) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return ret.c_str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
filename_iterator mapsBegin() const { return filename_iterator("/maps/", ".json"); }
|
||||||
|
filename_iterator mapsEnd() const { return filename_iterator(); }
|
||||||
|
|
||||||
|
filename_iterator profilesBegin() const { return filename_iterator("/profiles/", ".json"); }
|
||||||
|
filename_iterator profilesEnd() const { return filename_iterator(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HardwareConfig m_config;
|
HardwareConfig m_config;
|
||||||
|
119
src/Platform.cpp
119
src/Platform.cpp
@ -16,21 +16,21 @@
|
|||||||
#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
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef PLATFORM_PHOTON
|
#ifdef PLATFORM_PHOTON
|
||||||
STARTUP(BootOptions::initPins());
|
STARTUP(BootOptions::initPins());
|
||||||
#else
|
#else
|
||||||
|
#include "inputs/Serial.h"
|
||||||
#ifdef CONFIG_MQTT
|
#ifdef CONFIG_MQTT
|
||||||
#include "platform/arduino/MQTTTelemetry.h"
|
#include "platform/arduino/MQTTTelemetry.h"
|
||||||
#endif
|
#endif
|
||||||
void printNewline(Print* logOutput, int logLevel)
|
void printNewline(Print* logOutput, int logLevel)
|
||||||
{
|
{
|
||||||
(void)logLevel; // unused
|
(void)logLevel; // unused
|
||||||
logOutput->print("\r\n");
|
logOutput->print("\n");
|
||||||
}
|
}
|
||||||
int printEspLog(const char* fmt, va_list args)
|
int printEspLog(const char* fmt, va_list args)
|
||||||
{
|
{
|
||||||
@ -62,7 +62,7 @@ Platform::version()
|
|||||||
{
|
{
|
||||||
#ifdef PLATFORM_PHOTON
|
#ifdef PLATFORM_PHOTON
|
||||||
return System.version().c_str();
|
return System.version().c_str();
|
||||||
#elif defined(BOARD_ESP32)
|
#elif defined(BOARD_ESP32) || defined(BOARD_ESP8266)
|
||||||
return ESP.getSdkVersion();
|
return ESP.getSdkVersion();
|
||||||
#else
|
#else
|
||||||
return "Unknown!";
|
return "Unknown!";
|
||||||
@ -72,10 +72,7 @@ Platform::version()
|
|||||||
int
|
int
|
||||||
Platform::freeRam()
|
Platform::freeRam()
|
||||||
{
|
{
|
||||||
#ifdef BOARD_ESP8266
|
#if defined(BOARD_ESP8266) || defined(BOARD_ESP32)
|
||||||
return ESP.getFreeHeap();
|
|
||||||
#endif
|
|
||||||
#ifdef BOARD_ESP32
|
|
||||||
return ESP.getFreeHeap();
|
return ESP.getFreeHeap();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -84,7 +81,6 @@ 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) {
|
||||||
@ -102,7 +98,7 @@ Platform::preSetup()
|
|||||||
Log.begin(LOG_LEVEL_TRACE, Static<MQTTTelemetry>::instance()->logPrinter());
|
Log.begin(LOG_LEVEL_TRACE, Static<MQTTTelemetry>::instance()->logPrinter());
|
||||||
Static<MQTTTelemetry>::instance()->setSequencer(Static<Sequencer>::instance());
|
Static<MQTTTelemetry>::instance()->setSequencer(Static<Sequencer>::instance());
|
||||||
#else
|
#else
|
||||||
Log.begin(LOG_LEVEL_TRACE, &Serial);
|
Log.begin(LOG_LEVEL_TRACE, Static<SerialInput>::instance()->logPrinter());
|
||||||
#endif
|
#endif
|
||||||
Log.setSuffix(printNewline);
|
Log.setSuffix(printNewline);
|
||||||
#endif
|
#endif
|
||||||
@ -114,6 +110,9 @@ Platform::preSetup()
|
|||||||
#endif
|
#endif
|
||||||
#ifdef BOARD_ESP8266
|
#ifdef BOARD_ESP8266
|
||||||
ESP.wdtEnable(0);
|
ESP.wdtEnable(0);
|
||||||
|
if (!ESP.checkFlashCRC()) {
|
||||||
|
Log.fatal("Firmware failed CRC check!!!");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +141,30 @@ Platform::bootSplash()
|
|||||||
Log.notice(u8" 4: Flash - %d", bootopts.isFlash);
|
Log.notice(u8" 4: Flash - %d", bootopts.isFlash);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (bootopts.crashCount > 0) {
|
||||||
|
Log.warning(u8"Previous crash detected!!!! We're on attempt %d", bootopts.crashCount);
|
||||||
|
char lastTaskBuf[16];
|
||||||
|
strncpy(lastTaskBuf, MainLoop::lastTaskName(), sizeof(lastTaskBuf));
|
||||||
|
lastTaskBuf[15] = 0;
|
||||||
|
Log.error(u8"Crash occurred in task %s", lastTaskBuf);
|
||||||
|
|
||||||
|
#ifdef BOARD_ESP8266
|
||||||
|
auto rInfo = ESP.getResetInfoPtr();
|
||||||
|
if (Platform::bootopts.resetReason == REASON_EXCEPTION_RST) {
|
||||||
|
Log.error("Fatal exception (%d):", rInfo->exccause);
|
||||||
|
}
|
||||||
|
Log.error("epc1=%X, epc2=%X, epc3=%X, excvaddr=%X, depc=%X",
|
||||||
|
rInfo->epc1, rInfo->epc2, rInfo->epc3, rInfo->excvaddr, rInfo->depc);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
strncpy(lastTaskBuf, Renderer::lastFigmentName(), sizeof(lastTaskBuf));
|
||||||
|
lastTaskBuf[15] = 0;
|
||||||
|
Log.error(u8"Last Figment was %s", lastTaskBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.trace("Startup reason: %d", bootopts.resetReason);
|
||||||
|
|
||||||
|
|
||||||
Log.trace("Registered tasks:");
|
Log.trace("Registered tasks:");
|
||||||
auto it = beginTasks();
|
auto it = beginTasks();
|
||||||
while (it != endTasks()) {
|
while (it != endTasks()) {
|
||||||
@ -211,8 +234,8 @@ Platform::deviceID()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Platform::addLEDs(CRGB* leds, unsigned int ledCount) {
|
Platform::addLEDs(CRGB* leds, uint16_t ledCount) {
|
||||||
FastLED.addLeds<WS2812B, RENDERBUG_LED_PIN, RENDERBUG_LED_PACKING>(leds, ledCount);
|
FastLED.addLeds<WS2812, RENDERBUG_LED_PIN, RENDERBUG_LED_PACKING>(leds, ledCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
const String
|
const String
|
||||||
@ -232,6 +255,78 @@ Platform::restart() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__attribute__((noreturn))
|
||||||
|
void
|
||||||
|
doReboot(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
out.println("Rebooting");
|
||||||
|
Platform::restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((noreturn))
|
||||||
|
void
|
||||||
|
doSafeMode(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
out.println("Rebooting into safe mode");
|
||||||
|
Platform::bootopts.forceSafeMode();
|
||||||
|
Platform::restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
String s;
|
||||||
|
|
||||||
|
void
|
||||||
|
doTaskStart(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
s = args[1];
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, s.c_str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doTaskStop(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
s = args[1];
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, s.c_str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doTaskList(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
auto sched = MainLoop::instance()->scheduler;
|
||||||
|
auto printer = Static<SerialInput>::instance()->printer();
|
||||||
|
out.println("Tasks:");
|
||||||
|
for(auto task : sched.tasks) {
|
||||||
|
bool isFigment = task->isFigment();
|
||||||
|
if (task->state == Task::Running) {
|
||||||
|
out.print("+");
|
||||||
|
} else {
|
||||||
|
out.print("-");
|
||||||
|
}
|
||||||
|
if (isFigment) {
|
||||||
|
out.print("F ");
|
||||||
|
} else {
|
||||||
|
out.print("T ");
|
||||||
|
}
|
||||||
|
out.println(task->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const std::vector<Command> _commands = {
|
||||||
|
{"tasks", doTaskList},
|
||||||
|
{"safe-mode", doSafeMode},
|
||||||
|
{"reboot", doReboot},
|
||||||
|
{"stop", doTaskStop},
|
||||||
|
{"start", doTaskStart}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
Platform::commands() const
|
||||||
|
{
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
|
||||||
BootOptions
|
BootOptions
|
||||||
Platform::bootopts;
|
Platform::bootopts;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ class Platform : public Task {
|
|||||||
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; }
|
||||||
static void addLEDs(CRGB* leds, unsigned int ledCount);
|
static void addLEDs(CRGB* leds, uint16_t ledCount);
|
||||||
|
|
||||||
static const char* name();
|
static const char* name();
|
||||||
static const char* version();
|
static const char* version();
|
||||||
@ -105,4 +105,6 @@ class Platform : public Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void restart();
|
static void restart();
|
||||||
|
|
||||||
|
const std::vector<Command>& commands() const override;
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "./Platform.h"
|
#include "./Platform.h"
|
||||||
#include "./Static.h"
|
#include "./Static.h"
|
||||||
#include "./Config.h"
|
#include "./Config.h"
|
||||||
|
#include "./inputs/Serial.h"
|
||||||
|
|
||||||
TaskFunc safeModeNag([]{
|
TaskFunc safeModeNag([]{
|
||||||
static uint8_t frame = 0;
|
static uint8_t frame = 0;
|
||||||
@ -41,6 +42,7 @@ SafeMode::safeModeApp{{
|
|||||||
// System logging
|
// System logging
|
||||||
Static<LogService>::instance(),
|
Static<LogService>::instance(),
|
||||||
&safeModeNag,
|
&safeModeNag,
|
||||||
|
Static<SerialInput>::instance(),
|
||||||
#ifdef CONFIG_WIFI
|
#ifdef CONFIG_WIFI
|
||||||
// ESP Wifi
|
// ESP Wifi
|
||||||
Static<WiFiTask>::instance(),
|
Static<WiFiTask>::instance(),
|
||||||
|
@ -94,5 +94,34 @@ Sequencer::handleEvent(const InputEvent& evt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doScenes(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
out.println("Available scenes: ");
|
||||||
|
for (auto scene : Static<Sequencer>::instance()->scenes()) {
|
||||||
|
out.println(scene.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String s;
|
||||||
|
|
||||||
|
void
|
||||||
|
doScene(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
s = args[1];
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetPattern, s.c_str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command> _commands = {
|
||||||
|
{"scene", doScene},
|
||||||
|
{"scenes", doScenes}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
Sequencer::commands() const
|
||||||
|
{
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(Sequencer);
|
STATIC_ALLOC(Sequencer);
|
||||||
STATIC_TASK(Sequencer);
|
STATIC_TASK(Sequencer);
|
||||||
|
@ -23,6 +23,7 @@ public:
|
|||||||
|
|
||||||
const char* currentSceneName();
|
const char* currentSceneName();
|
||||||
const std::vector<Scene> scenes() const;
|
const std::vector<Scene> scenes() const;
|
||||||
|
const std::vector<Command>& commands() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_idx;
|
int m_idx;
|
||||||
|
@ -2,6 +2,54 @@
|
|||||||
#include "../Static.h"
|
#include "../Static.h"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
doBrightness(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
String nextVal = args[1];
|
||||||
|
uint8_t newBrightness = (uint8_t)atoi(nextVal.c_str());
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetBrightness, newBrightness});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doOn(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetPower, 255});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doOff(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetPower, 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doForceBrightness(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
String nextVal = args[1];
|
||||||
|
uint8_t newBrightness = (uint8_t)atoi(nextVal.c_str());
|
||||||
|
Static<Power>::instance()->forceBrightness(newBrightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Power::forceBrightness(uint8_t v)
|
||||||
|
{
|
||||||
|
m_forced = true;
|
||||||
|
FastLED.setBrightness(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command> _commands = {
|
||||||
|
{"brightness", doBrightness},
|
||||||
|
{"brightness-force", doForceBrightness},
|
||||||
|
{"on", doOn},
|
||||||
|
{"off", doOff}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
Power::commands() const
|
||||||
|
{
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Power::handleConfigChange(const Configuration& config)
|
Power::handleConfigChange(const Configuration& config)
|
||||||
{
|
{
|
||||||
|
@ -10,15 +10,17 @@ public:
|
|||||||
switch (evt.intent) {
|
switch (evt.intent) {
|
||||||
case InputEvent::PowerToggle:
|
case InputEvent::PowerToggle:
|
||||||
m_powerState = m_powerState.value() <= 128 ? 255 : 0;
|
m_powerState = m_powerState.value() <= 128 ? 255 : 0;
|
||||||
//Log.info("POWER TOGGLE %d", m_powerState.value());
|
m_forced = false;
|
||||||
|
Log.notice("Power toggled to %t", m_powerState);
|
||||||
break;
|
break;
|
||||||
case InputEvent::SetPower:
|
case InputEvent::SetPower:
|
||||||
m_powerState = evt.asInt() == 0 ? 0 : 255;
|
m_powerState = evt.asInt() == 0 ? 0 : 255;
|
||||||
Log.notice("Power is now %d", m_powerState);
|
m_forced = false;
|
||||||
|
Log.notice("Power state is now %t", m_powerState);
|
||||||
break;
|
break;
|
||||||
case InputEvent::SetBrightness:
|
case InputEvent::SetBrightness:
|
||||||
m_brightness = evt.asInt();
|
m_brightness = evt.asInt();
|
||||||
m_brightness = 255;
|
m_forced = false;
|
||||||
break;
|
break;
|
||||||
case InputEvent::Beat:
|
case InputEvent::Beat:
|
||||||
m_beatDecay.set(0, 255);
|
m_beatDecay.set(0, 255);
|
||||||
@ -40,7 +42,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void render(Display* dpy) const override {
|
void render(Display* dpy) const override {
|
||||||
if (F_LIKELY(m_valid)) {
|
if (F_LIKELY(m_valid && !m_forced)) {
|
||||||
const uint8_t decayedBrightness = scale8((uint8_t)m_brightness, m_useBPM ? ease8InOutCubic((uint8_t)m_beatDecay) : 255);
|
const uint8_t decayedBrightness = scale8((uint8_t)m_brightness, m_useBPM ? ease8InOutCubic((uint8_t)m_beatDecay) : 255);
|
||||||
const uint8_t clippedBrightness = std::min(decayedBrightness, (uint8_t)255);
|
const uint8_t clippedBrightness = std::min(decayedBrightness, (uint8_t)255);
|
||||||
const uint8_t scaledBrightness = scale8(m_powerState, clippedBrightness);
|
const uint8_t scaledBrightness = scale8(m_powerState, clippedBrightness);
|
||||||
@ -50,6 +52,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void forceBrightness(uint8_t v);
|
||||||
|
|
||||||
|
const std::vector<Command>& commands() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AnimatedNumber m_powerState = 255;
|
AnimatedNumber m_powerState = 255;
|
||||||
@ -59,4 +64,5 @@ private:
|
|||||||
uint16_t m_milliamps = 500;
|
uint16_t m_milliamps = 500;
|
||||||
bool m_valid = true;
|
bool m_valid = true;
|
||||||
bool m_useBPM = false;
|
bool m_useBPM = false;
|
||||||
|
bool m_forced = false;
|
||||||
};
|
};
|
||||||
|
45
src/animations/Rain.cpp
Normal file
45
src/animations/Rain.cpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#include "Rain.h"
|
||||||
|
#include "../Static.h"
|
||||||
|
|
||||||
|
RainAnimation::RainAnimation() : Figment("Rain")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RainAnimation::render(Display* dpy) const
|
||||||
|
{
|
||||||
|
Surface sfc = Surface(dpy, {0, 0}, {255, 255});
|
||||||
|
uint8_t noiseY = sin8(m_noiseOffset % 255);
|
||||||
|
uint8_t noiseX = cos8(m_noiseOffset % 255);
|
||||||
|
sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
||||||
|
pixel = CHSV(m_hue, inoise8(noiseX + coords.x, coords.y), inoise8(m_noiseOffset + coords.x, noiseY + coords.y));
|
||||||
|
});
|
||||||
|
m_drops.render(dpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RainAnimation::loop()
|
||||||
|
{
|
||||||
|
EVERY_N_MILLISECONDS(250) {
|
||||||
|
m_drops.update();
|
||||||
|
}
|
||||||
|
EVERY_N_MILLISECONDS(60) {
|
||||||
|
m_hue.update(1);
|
||||||
|
m_curColor.h = m_hue;
|
||||||
|
}
|
||||||
|
m_noiseOffset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
RainAnimation::handleEvent(const InputEvent& evt) {
|
||||||
|
if (evt.intent == InputEvent::SetColor) {
|
||||||
|
CHSV next = rgb2hsv_approximate(evt.asRGB());
|
||||||
|
m_hue.set(next.h);
|
||||||
|
m_drops.forEach([=](Raindrop& drop) {
|
||||||
|
drop.nextColor = next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC_ALLOC(RainAnimation);
|
||||||
|
STATIC_TASK(RainAnimation);
|
48
src/animations/Rain.h
Normal file
48
src/animations/Rain.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Figments.h>
|
||||||
|
|
||||||
|
class RainAnimation: public Figment {
|
||||||
|
private:
|
||||||
|
struct Raindrop {
|
||||||
|
int size = 20;
|
||||||
|
int x = random(255);
|
||||||
|
int y = random(255);
|
||||||
|
CHSV fg{180, 255, 255};
|
||||||
|
CHSV nextColor{180, 255, 255};
|
||||||
|
|
||||||
|
void render(Display* dpy) const {
|
||||||
|
Surface sfc{dpy, {x - size, y - size}, {x + size, y + size}};
|
||||||
|
paint(sfc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(Surface& sfc) const {
|
||||||
|
sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
||||||
|
int distance = 255 - (min(128, abs(128 - surfaceCoords.x)) + min(128, abs(128 - surfaceCoords.y)));
|
||||||
|
pixel += CHSV{fg.h, fg.s, scale8_video(fg.v, distance)};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() {
|
||||||
|
if (random(255) >= 100) {
|
||||||
|
y++;
|
||||||
|
if (y >= 255) {
|
||||||
|
y = 0;
|
||||||
|
x += 13;
|
||||||
|
x %= 255;
|
||||||
|
fg = nextColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SpriteList<Raindrop, 10> m_drops;
|
||||||
|
uint16_t m_noiseOffset;
|
||||||
|
CHSV m_curColor{180, 255, 255};
|
||||||
|
AnimatedNumber m_hue;
|
||||||
|
public:
|
||||||
|
RainAnimation();
|
||||||
|
//void handleEvent(const InputEvent& evt) override;
|
||||||
|
void loop() override;
|
||||||
|
void render(Display* dpy) const override;
|
||||||
|
void handleEvent(const InputEvent& evt) override;
|
||||||
|
};
|
@ -1,53 +1,32 @@
|
|||||||
#include "./TestAnimation.h"
|
#include "./TestAnimation.h"
|
||||||
|
#include "../Static.h"
|
||||||
#include <FastLED.h>
|
#include <FastLED.h>
|
||||||
|
|
||||||
const char*
|
|
||||||
TestAnimation::name() const
|
|
||||||
{
|
|
||||||
return "Test";
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
TestAnimation::handleEvent(const InputEvent& evt)
|
|
||||||
{
|
|
||||||
if (evt.intent == InputEvent::Acceleration) {
|
|
||||||
if (evt.asInt() > 5) {
|
|
||||||
m_brightness += 15;
|
|
||||||
}
|
|
||||||
m_hue += scale8(evt.asInt(), 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (evt.intent == InputEvent::UserInput) {
|
|
||||||
switch(evt.asInt()) {
|
|
||||||
case 1:
|
|
||||||
m_brightness.set(255, 0);break;
|
|
||||||
case 2:
|
|
||||||
m_saturation.set(255, 128);break;
|
|
||||||
default:
|
|
||||||
m_brightness.set(255, 0);
|
|
||||||
m_saturation.set(255, 128);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
TestAnimation::loop()
|
TestAnimation::loop()
|
||||||
{
|
{
|
||||||
m_x += 4;
|
m_x += 1;
|
||||||
if (m_x % 12 == 0) {
|
m_y += 1;
|
||||||
m_y += 28;
|
|
||||||
}
|
|
||||||
m_hue.update();
|
|
||||||
m_saturation.update();
|
|
||||||
m_brightness.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TestAnimation::render(Display* dpy) const
|
TestAnimation::render(Display* dpy) const
|
||||||
{
|
{
|
||||||
for(uint8_t col = 0; col < 3; col++) {
|
for(unsigned int i = 0; i < dpy->pixelCount(); i++) {
|
||||||
for (uint8_t i = 0; i < 254; i+=10) {
|
dpy->pixelAt(i) = CRGB{255, 255, 255};
|
||||||
dpy->pixelAt(VirtualCoordinates{(uint8_t)(m_x + (col * (254 / 3))), (uint8_t)(i + m_y)}) = CHSV(m_hue, m_saturation + 100, scale8(i, m_brightness));
|
}
|
||||||
}
|
return;
|
||||||
}
|
// Blank the canvas to white
|
||||||
|
Surface{dpy, {0, 0}, {255, 255}} = CRGB{255, 255, 255};
|
||||||
|
// Draw red line on top row
|
||||||
|
Surface{dpy, {0, 0}, {255, 0}} = CRGB{255, 0, 0};
|
||||||
|
// Green line on first column
|
||||||
|
Surface{dpy, {0, 0}, {0, 255}} = CRGB{0, 255, 0};
|
||||||
|
|
||||||
|
//Surface{dpy, {m_x, 0}, {m_x, 255}} = CRGB{255, 0, 0};
|
||||||
|
///Surface{dpy, {0, m_y}, {255, m_y}} = CRGB{255, 0, 0};
|
||||||
|
//dpy->pixelAt(VirtualCoordinates{m_x, m_y}) = CRGB{255, 0, 255};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STATIC_ALLOC(TestAnimation);
|
||||||
|
STATIC_TASK(TestAnimation);
|
||||||
|
@ -2,15 +2,11 @@
|
|||||||
|
|
||||||
class TestAnimation: public Figment {
|
class TestAnimation: public Figment {
|
||||||
public:
|
public:
|
||||||
const char* name() const;
|
TestAnimation() : Figment("Test") {}
|
||||||
void handleEvent(const InputEvent& evt) override;
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void render(Display* dpy) const override;
|
void render(Display* dpy) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AnimatedNumber m_hue;
|
|
||||||
AnimatedNumber m_saturation;
|
|
||||||
AnimatedNumber m_brightness;
|
|
||||||
uint8_t m_x;
|
uint8_t m_x;
|
||||||
uint8_t m_y;
|
uint8_t m_y;
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ UpdateStatus::handleEvent(const InputEvent& evt)
|
|||||||
if (evt.intent == InputEvent::FirmwareUpdate) {
|
if (evt.intent == InputEvent::FirmwareUpdate) {
|
||||||
static int updateCount = 0;
|
static int updateCount = 0;
|
||||||
updateCount++;
|
updateCount++;
|
||||||
//Log.info("Update count %d", updateCount);
|
Log.info("Update count %d", updateCount);
|
||||||
m_updateReady = true;
|
m_updateReady = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -22,11 +22,12 @@ UpdateStatus::loop()
|
|||||||
void
|
void
|
||||||
UpdateStatus::render(Display* dpy) const
|
UpdateStatus::render(Display* dpy) const
|
||||||
{
|
{
|
||||||
|
int pos = m_pos % dpy->pixelCount();
|
||||||
if (m_updateReady) {
|
if (m_updateReady) {
|
||||||
for(int i = 0; i < 12; i+=3) {
|
for(int i = 0; i < 12; i+=3) {
|
||||||
dpy->pixelAt(m_pos + i) = CRGB(255, 0, 0);
|
dpy->pixelAt(pos + i) = CRGB(255, 0, 0);
|
||||||
dpy->pixelAt(m_pos + i + 1) = CRGB(0, 255, 0);
|
dpy->pixelAt(pos + i + 1) = CRGB(0, 255, 0);
|
||||||
dpy->pixelAt(m_pos + i + 2) = CRGB(0, 0, 255);
|
dpy->pixelAt(pos + i + 2) = CRGB(0, 0, 255);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,5 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_updateReady = false;
|
bool m_updateReady = false;
|
||||||
uint8_t m_pos = 0;
|
int m_pos = 0;
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,22 @@
|
|||||||
#include "./BPM.h"
|
#include "./BPM.h"
|
||||||
#include "../Static.h"
|
#include "../Static.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
doBPM(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
uint8_t newBPM(atoi(args[1].c_str()));
|
||||||
|
Static<BPM>::instance()->setBPM(newBPM);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command> _commands = {
|
||||||
|
{"bpm", doBPM}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
BPM::commands() const
|
||||||
|
{
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(BPM);
|
STATIC_ALLOC(BPM);
|
||||||
STATIC_TASK(BPM);
|
STATIC_TASK(BPM);
|
||||||
|
@ -18,6 +18,8 @@ public:
|
|||||||
ConfigTaskMixin::handleEvent(evt);
|
ConfigTaskMixin::handleEvent(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<Command>& commands() const override;
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
InputSource::loop();
|
InputSource::loop();
|
||||||
ConfigTaskMixin::loop();
|
ConfigTaskMixin::loop();
|
||||||
@ -29,6 +31,11 @@ public:
|
|||||||
Log.notice("bpm: idle BPM set to %d (requested %d)", (int)msToBPM(m_msPerBeat), (int)requestedBPM);
|
Log.notice("bpm: idle BPM set to %d (requested %d)", (int)msToBPM(m_msPerBeat), (int)requestedBPM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setBPM(double bpm) {
|
||||||
|
m_msPerBeat = 60000.0 / (double)bpm;
|
||||||
|
Log.notice("bpm: Command changed to %d (requested %d)", (int)msToBPM(m_msPerBeat), (int)bpm);
|
||||||
|
}
|
||||||
|
|
||||||
InputEvent read() override {
|
InputEvent read() override {
|
||||||
if (m_msPerBeat > 0) {
|
if (m_msPerBeat > 0) {
|
||||||
uint16_t now = millis();
|
uint16_t now = millis();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Figments.h>
|
#include <Figments.h>
|
||||||
|
#include "../Platform.h"
|
||||||
|
|
||||||
struct ScheduleEntry {
|
struct ScheduleEntry {
|
||||||
uint8_t hour;
|
uint8_t hour;
|
||||||
@ -83,7 +84,9 @@ class CircadianRhythm : public InputSource {
|
|||||||
minute = 0;
|
minute = 0;
|
||||||
}
|
}
|
||||||
Log.notice("Current time: %d:%d", hour, minute);
|
Log.notice("Current time: %d:%d", hour, minute);
|
||||||
return InputEvent{InputEvent::SetBrightness, brightnessForTime(hour, minute)};
|
auto brightness = brightnessForTime(hour, minute);
|
||||||
|
Log.notice("Adjusting brightness to %d", brightness);
|
||||||
|
return InputEvent{InputEvent::SetBrightness, brightness};
|
||||||
}
|
}
|
||||||
return InputEvent{};
|
return InputEvent{};
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,171 @@
|
|||||||
#include "Serial.h"
|
#include "Serial.h"
|
||||||
|
#include "../Static.h"
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include "../Config.h"
|
||||||
|
#include "../Sequencer.h"
|
||||||
|
|
||||||
InputEvent
|
SerialInput::SerialInput() : InputSource("Serial"),
|
||||||
Serial::read()
|
m_state(ParseState::Normal),
|
||||||
|
m_logPrinter(this)
|
||||||
{
|
{
|
||||||
while (Serial.available() > 0) {
|
|
||||||
char nextChar = Serial.read();
|
|
||||||
if (nextChar == '\n') {
|
|
||||||
doCommand();
|
|
||||||
m_buf = "";
|
|
||||||
} else {
|
|
||||||
m_buf += nextChar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Serial::doCommand() {
|
SerialInput::redrawPrompt()
|
||||||
if (command == "tasks") {
|
{
|
||||||
Serial.println("Tasks:");
|
if (m_canRedraw) {
|
||||||
auto sched = MainLoop::instance()->scheduler;
|
Serial.print((char)8);
|
||||||
for(auto task : sched.tasks) {
|
Serial.print((char)27);
|
||||||
bool isFigment = task->isFigment();
|
Serial.print("[2K");
|
||||||
if (isFigment) {
|
Serial.print((char)8);
|
||||||
Serial.println("F " + task->name);
|
Serial.print((char)27);
|
||||||
} else {
|
Serial.print("[G");
|
||||||
Serial.println("T " + task->name);
|
Serial.print('\r');
|
||||||
}
|
Serial.print("> ");
|
||||||
|
Serial.print(m_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent
|
||||||
|
SerialInput::parseNormal(char nextChar)
|
||||||
|
{
|
||||||
|
if (nextChar == 27) {
|
||||||
|
m_state = ParseState::EscapeSequence;
|
||||||
|
return InputEvent::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextChar == 13) {
|
||||||
|
redrawPrompt();
|
||||||
|
Serial.println();
|
||||||
|
if (m_buf.length() > 0) {
|
||||||
|
m_canRedraw = false;
|
||||||
|
doCommand();
|
||||||
|
m_canRedraw = true;
|
||||||
|
m_history.insert(m_buf);
|
||||||
|
m_buf = "";
|
||||||
|
}
|
||||||
|
m_historyOffset = 0;
|
||||||
|
redrawPrompt();
|
||||||
|
return InputEvent{};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextChar == 8) {
|
||||||
|
if (m_buf.length() > 0) {
|
||||||
|
m_buf.remove(m_buf.length() - 1, 1);
|
||||||
|
}
|
||||||
|
Serial.print((char)8);
|
||||||
|
Serial.print((char)27);
|
||||||
|
Serial.print("[K");
|
||||||
|
redrawPrompt();
|
||||||
|
return InputEvent::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextChar >= 32 && nextChar <= 126) {
|
||||||
|
m_buf += nextChar;
|
||||||
|
Serial.print(nextChar);
|
||||||
|
}
|
||||||
|
return InputEvent::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent
|
||||||
|
SerialInput::parseEscape(char nextChar)
|
||||||
|
{
|
||||||
|
if (nextChar == '[') {
|
||||||
|
m_state = ParseState::CSI;
|
||||||
|
} else {
|
||||||
|
m_state = ParseState::Normal;
|
||||||
|
}
|
||||||
|
return InputEvent::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent
|
||||||
|
SerialInput::parseCSI(char nextChar)
|
||||||
|
{
|
||||||
|
if (nextChar == 'A') {
|
||||||
|
if (m_historyOffset < m_history.size()) {
|
||||||
|
m_historyOffset += 1;
|
||||||
|
m_buf = m_history.peek(m_historyOffset);
|
||||||
|
redrawPrompt();
|
||||||
|
} else {
|
||||||
|
Serial.print((char)7);
|
||||||
|
}
|
||||||
|
} else if (nextChar == 'B') {
|
||||||
|
if (m_historyOffset > 0) {
|
||||||
|
m_historyOffset -= 1;
|
||||||
|
m_buf = m_history.peek(m_historyOffset);
|
||||||
|
redrawPrompt();
|
||||||
|
} else {
|
||||||
|
Serial.print((char)7);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Serial.print((char)7);
|
||||||
|
}
|
||||||
|
m_state = ParseState::Normal;
|
||||||
|
return InputEvent::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent
|
||||||
|
SerialInput::read()
|
||||||
|
{
|
||||||
|
while (Serial.available() > 0) {
|
||||||
|
char nextChar = Serial.read();
|
||||||
|
InputEvent ret = InputEvent::None;
|
||||||
|
switch (m_state) {
|
||||||
|
case ParseState::Normal:
|
||||||
|
ret = parseNormal(nextChar);break;
|
||||||
|
case ParseState::EscapeSequence:
|
||||||
|
ret = parseEscape(nextChar);break;
|
||||||
|
case ParseState::CSI:
|
||||||
|
ret = parseCSI(nextChar);break;
|
||||||
|
}
|
||||||
|
if (ret != InputEvent::None) {
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return InputEvent::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
doHelp(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
out.println("Available commands:");
|
||||||
|
auto sched = MainLoop::instance()->scheduler;
|
||||||
|
for(auto task : sched.tasks) {
|
||||||
|
for(auto &command : task->commands()) {
|
||||||
|
out.print(command.name);
|
||||||
|
out.print(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command> serialCommands = {
|
||||||
|
{"help", doHelp}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
SerialInput::commands() const
|
||||||
|
{
|
||||||
|
return serialCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SerialInput::doCommand() {
|
||||||
|
auto sched = MainLoop::instance()->scheduler;
|
||||||
|
Args args = Args(&m_buf);
|
||||||
|
const auto cmdName = args[0];
|
||||||
|
|
||||||
|
for(auto task : sched.tasks) {
|
||||||
|
for(auto &command : task->commands()) {
|
||||||
|
if (cmdName == command.name) {
|
||||||
|
command.func(args, m_logPrinter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_logPrinter.println("Unknown command");
|
||||||
|
doHelp(args, m_logPrinter);
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(SerialInput);
|
STATIC_ALLOC(SerialInput);
|
||||||
|
@ -3,14 +3,60 @@
|
|||||||
|
|
||||||
class SerialInput: public InputSource {
|
class SerialInput: public InputSource {
|
||||||
public:
|
public:
|
||||||
void onStart() override {
|
SerialInput();
|
||||||
//Serial.begin();
|
InputEvent read() override;
|
||||||
|
|
||||||
|
class LogPrinter : public Print {
|
||||||
|
private:
|
||||||
|
SerialInput* serial;
|
||||||
|
Ringbuf<char, 512> buf;
|
||||||
|
public:
|
||||||
|
LogPrinter(SerialInput* serial) : serial(serial) {};
|
||||||
|
size_t write(uint8_t byte) {
|
||||||
|
if (byte == '\n') {
|
||||||
|
char c;
|
||||||
|
Serial.print('\r');
|
||||||
|
while (buf.take(c)) {
|
||||||
|
Serial.write(c);
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
serial->redrawPrompt();
|
||||||
|
} else {
|
||||||
|
buf.insert(byte);
|
||||||
|
}
|
||||||
|
return sizeof(byte);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void redrawPrompt();
|
||||||
|
|
||||||
|
Print* logPrinter() {
|
||||||
|
return &m_logPrinter;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputEvent read();
|
//static SerialInput::Command *s_root;
|
||||||
|
LogPrinter* printer() {
|
||||||
|
return &m_logPrinter;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command> &commands() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum ParseState {
|
||||||
|
Normal,
|
||||||
|
EscapeSequence,
|
||||||
|
CSI
|
||||||
|
};
|
||||||
String m_buf;
|
String m_buf;
|
||||||
|
ParseState m_state;
|
||||||
|
char m_escapeSeq[3];
|
||||||
void doCommand();
|
void doCommand();
|
||||||
|
LogPrinter m_logPrinter;
|
||||||
|
bool m_canRedraw = true;
|
||||||
|
Ringbuf<String, 5> m_history;
|
||||||
|
int m_historyOffset = 0;
|
||||||
|
|
||||||
}
|
InputEvent parseNormal(char nextChar);
|
||||||
|
InputEvent parseEscape(char nextChar);
|
||||||
|
InputEvent parseCSI(char nextChar);
|
||||||
|
};
|
||||||
|
21
src/main.cpp
21
src/main.cpp
@ -87,16 +87,6 @@ void setup() {
|
|||||||
Log.notice(u8"🐞 I am built for %d LEDs on pin %d", HardwareConfig::MAX_LED_NUM, RENDERBUG_LED_PIN);
|
Log.notice(u8"🐞 I am built for %d LEDs on pin %d", HardwareConfig::MAX_LED_NUM, RENDERBUG_LED_PIN);
|
||||||
Log.notice(u8"📡 Platform %s version %s", Platform::name(), Platform::version());
|
Log.notice(u8"📡 Platform %s version %s", Platform::name(), Platform::version());
|
||||||
|
|
||||||
if (Platform::bootopts.crashCount > 0) {
|
|
||||||
Log.warning(u8"Previous crash detected!!!! We're on attempt %d", Platform::bootopts.crashCount);
|
|
||||||
char lastTaskBuf[16];
|
|
||||||
strncpy(lastTaskBuf, MainLoop::lastTaskName(), sizeof(lastTaskBuf));
|
|
||||||
lastTaskBuf[15] = 0;
|
|
||||||
Log.error(u8"Crash occurred in task %s", lastTaskBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.trace("Startup reason: %d", Platform::bootopts.resetReason);
|
|
||||||
|
|
||||||
Log.notice(u8"Setting timezone to +2 (CEST)");
|
Log.notice(u8"Setting timezone to +2 (CEST)");
|
||||||
Platform::setTimezone(+2);
|
Platform::setTimezone(+2);
|
||||||
|
|
||||||
@ -105,14 +95,17 @@ void setup() {
|
|||||||
Platform::setup();
|
Platform::setup();
|
||||||
Platform::bootSplash();
|
Platform::bootSplash();
|
||||||
|
|
||||||
Log.notice(u8"💡 Starting FastLED...");
|
Log.notice(u8"💡 Starting FastLED on %d LEDs...", HardwareConfig::MAX_LED_NUM);
|
||||||
Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM);
|
Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM);
|
||||||
|
|
||||||
// Tune in,
|
// Tune in,
|
||||||
if (Platform::bootopts.isSafeMode) {
|
if (Platform::bootopts.isSafeMode) {
|
||||||
Log.error(u8"⚠️ Starting Figment in safe mode!!!");
|
Log.warning(u8"⚠️ Starting Figment in safe mode!!!");
|
||||||
runner = &SafeMode::safeModeApp;
|
runner = &SafeMode::safeModeApp;
|
||||||
FastLED.showColor(CRGB(5, 0, 0));
|
for(auto task : runner->scheduler.tasks) {
|
||||||
|
task->state = Task::Running;
|
||||||
|
}
|
||||||
|
FastLED.showColor(CRGB(255, 0, 0));
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
} else {
|
} else {
|
||||||
Log.notice(u8"🌌 Starting Figment...");
|
Log.notice(u8"🌌 Starting Figment...");
|
||||||
@ -131,7 +124,7 @@ void setup() {
|
|||||||
Serial.flush();
|
Serial.flush();
|
||||||
runner->start();
|
runner->start();
|
||||||
|
|
||||||
Log.notice(u8"💽 %lu bytes of free RAM", Platform::freeRam());
|
Log.notice(u8"💽 %l bytes of free RAM", Platform::freeRam());
|
||||||
Log.notice(u8"🚀 Setup complete! Ready to rock and roll.");
|
Log.notice(u8"🚀 Setup complete! Ready to rock and roll.");
|
||||||
Serial.flush();
|
Serial.flush();
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,8 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
|
|||||||
Log.notice("Connecting to MQTT as %s on %s...", Platform::deviceID(), Device.availabilityTopic.c_str());
|
Log.notice("Connecting to MQTT as %s on %s...", Platform::deviceID(), Device.availabilityTopic.c_str());
|
||||||
if (m_mqtt.connect(Platform::deviceID(), NULL, NULL, Device.availabilityTopic.c_str(), 0, true, "offline")) {
|
if (m_mqtt.connect(Platform::deviceID(), NULL, NULL, Device.availabilityTopic.c_str(), 0, true, "offline")) {
|
||||||
Log.notice("Connected to MQTT");
|
Log.notice("Connected to MQTT");
|
||||||
|
String logTopic = m_debugTopic + "/log";
|
||||||
|
Log.info("MQTT logs are available at %s", logTopic.c_str());
|
||||||
m_needHeartbeat = true;
|
m_needHeartbeat = true;
|
||||||
|
|
||||||
m_json.clear();
|
m_json.clear();
|
||||||
|
Loading…
Reference in New Issue
Block a user