Compare commits

...

45 Commits

Author SHA1 Message Date
Torrie Fischer
6582bae0f2 platform: esp32: disable bluetooth serial for now 2023-12-23 11:15:46 +01:00
Torrie Fischer
13a3dd5158 platform: esp32: fix build, implement more crash info output 2023-12-23 11:15:31 +01:00
Torrie Fischer
389da5b115 platform: arduino: mqtt: implement idle state support 2023-12-23 11:12:18 +01:00
Torrie Fischer
83b3af58f7 logservice: fix build 2023-12-23 11:12:02 +01:00
Torrie Fischer
0d3bcb4267 inputs: idle: implement an idletimer 2023-12-23 11:11:49 +01:00
Torrie Fischer
e43b594637 figments: input: remove unused event intents, move stringification into figments lib 2023-12-23 11:11:33 +01:00
Torrie Fischer
89e943b767 inputs: circandianrhythm: tweak late night/early day brightnesses 2023-12-23 11:09:53 +01:00
Torrie Fischer
3f3f9eb801 platformio: update platformio.ini with stream deps, simplify configs 2023-12-23 11:09:20 +01:00
Torrie Fischer
418e13b797 platform: arduino: mqtt: use streamutils buffering for faster mqtt sending 2023-12-23 11:08:52 +01:00
Torrie Fischer
f224a1755e jsoncoordinatemapping: s/verbose/info/ logging 2023-12-23 11:07:25 +01:00
Torrie Fischer
3dd84cfce1 figments: renderer: fix brightness scaling to actually work 2023-12-23 11:06:50 +01:00
Torrie Fischer
3e9d4eb08f platform: esp32: u8display: hide behind CONFIG_U8DISPLAY flag 2023-12-23 11:06:14 +01:00
Torrie Fischer
c4bbeccac0 platform: arduino: ota: prevent watchdog hang on ota update 2023-12-23 11:05:45 +01:00
Torrie Fischer
19f2a4f35c platform: arduino: wifi: use new hostname api to set up mdns 2023-12-23 11:05:24 +01:00
Torrie Fischer
cc9c719e3c inputs: MPU6050: hide behind CONFIG_MPU6050 flag 2023-12-23 11:04:59 +01:00
Torrie Fischer
7e1b3b9500 platform: add api for grabbing the system hostname 2023-12-23 11:04:39 +01:00
Torrie Fischer
0106b09c9b inputs: circadianrhythm: move strings to flashmem, merge duplicate loop algo 2023-12-23 11:04:15 +01:00
Torrie Fischer
a5d468efde platform: esp8266: fix timezone offsets 2023-12-23 11:03:24 +01:00
Torrie Fischer
04bdf5323c build-hal: add RENDERBUG_HAL define, fix logging output 2023-12-23 11:02:52 +01:00
Torrie Fischer
5bb29cc59c main: drop unused keymappping task 2023-12-23 11:01:35 +01:00
Torrie Fischer
cd6c097b76 animations: chimes: drop UserInput event type handling 2023-12-23 11:01:21 +01:00
Torrie Fischer
07e71ad4c0 platformio: use faster baud for esp8266 2023-12-20 12:58:56 +01:00
Torrie Fischer
9233a9113e platformio: update fastled+arduinojson libs 2023-12-20 12:58:39 +01:00
Torrie Fischer
949c90f207 fastled: enable spi on esp8266 2023-12-20 12:58:29 +01:00
Torrie Fischer
3b2951db79 mqtt: refactor big messy functions to smaller messy functions 2023-12-20 12:16:20 +01:00
Torrie Fischer
31e4072305 platform: esp8266: print startup reason on startup 2023-12-20 12:14:36 +01:00
Torrie Fischer
9561b3f4a7 serial: reorganize functions, drop unused char array 2023-12-20 12:13:00 +01:00
Torrie Fischer
1321693182 colors: drop unused color maps, if we want them back we can pregen them with scons 2023-12-20 12:12:38 +01:00
Torrie Fischer
0826970374 platform: cleaner startup logs 2023-12-20 12:12:16 +01:00
Torrie Fischer
924673ada3 main: move startup strings to flash 2023-12-20 12:11:49 +01:00
Torrie Fischer
6df7e938cb figments: mainloop: better startup output 2023-12-20 12:11:36 +01:00
Torrie Fischer
bb0f8619cb figments: drop unused perfcounter class 2023-12-20 10:48:10 +01:00
Torrie Fischer
23993a09cf build: clean up the mess of ifdefs from platform into a scons-configured hal 2023-12-20 10:47:26 +01:00
Torrie Fischer
236795917a figments: renderer: implement brightness/power commands 2023-12-20 09:29:10 +01:00
Torrie Fischer
6edfb2d8e3 main: update timezone to berlin 2023-12-20 09:28:53 +01:00
Torrie Fischer
436950eef2 sequencer: switch to *-scene events isntead of *-pattern, drop the concept of next/previous scene 2023-12-20 09:20:34 +01:00
Torrie Fischer
8c4e0e402c logservice: add text for set-scene event 2023-12-20 09:19:52 +01:00
Torrie Fischer
ce65e7e7f0 config: abstract fs utils to fix esp32 builds 2023-12-20 09:19:37 +01:00
Torrie Fischer
6797889b4c figments: renderer: move power management directly into the renderer 2023-12-20 09:17:50 +01:00
Torrie Fischer
6e138175be figments: command: rewrite command api to use Task instances instead of static functions 2023-12-20 09:13:23 +01:00
Torrie Fischer
214825c1d3 figments: mainloop: warnings-- 2023-12-20 09:09:33 +01:00
Torrie Fischer
90055a07e2 figments: figment: fix build on esp32 2023-12-20 08:32:41 +01:00
Torrie Fischer
b7495404b8 profiles: ponderjar: disable bpm, bluetooth, add more scenes 2023-12-20 08:32:06 +01:00
Torrie Fischer
e436c9aaa3 profiles: add new djstrip profile 2023-12-12 20:00:31 +01:00
Torrie Fischer
c9363ba851 config: build++ 2023-12-12 19:58:10 +01:00
63 changed files with 1034 additions and 1826 deletions

10
build-hal.py Normal file
View File

@ -0,0 +1,10 @@
Import("env")
board = env.get("BOARD_MCU")
frameworks = env.get("PIOFRAMEWORK")
print(f"HAL configuration:")
for framework in frameworks:
env.Append(SRC_FILTER=[f"+<platform/{framework}/{board}/>"])
env.Append(SRC_FILTER=[f"+<platform/{framework}/*.cpp>"])
env.Append(CPPDEFINES=[f"RENDERBUG_HAL=HAL_{board.upper()}"])
print(f"- platform/{framework}/{board}/")

View File

@ -1,6 +1,6 @@
{
"version": 1,
"strides": [
{"x": 0, "y": 0, "pixels": 255}
{"x": 0, "y": 0, "pixels": 512}
]
}

6
data/maps/djstrip.json Normal file
View File

@ -0,0 +1,6 @@
{
"version": 1,
"strides": [
{"x": 0, "y": 0, "pixels": 50}
]
}

View File

@ -1,7 +1,6 @@
{
"version": 1,
"tasks": [
"Power",
"Renderer",
"U8Display",
"WiFi",

View File

@ -0,0 +1,24 @@
{
"version": 1,
"tasks": [
"Bluetooth",
"Renderer",
"Serial"
],
"scenes": {
"Rain": ["Rain", "Rainbow"],
"Test": ["Test"],
"Idle": ["Solid", "Pulse", "Rainbow", "CircadianRhythm"],
"Acid": ["Chimes", "Pulse", "IdleColors", "Rainbow"],
"Flashlight": ["Flashlight"]
},
"surfaceMap": "djstrip",
"defaults": {
"mqtt.ip": "10.0.0.2",
"power.volts": 0,
"power.milliamps": 0,
"power.useBPM": false,
"bpm.idle": 45
}
}

View File

@ -1,25 +1,31 @@
{
"version": 1,
"tasks": [
"Bluetooth",
"WiFi",
"Renderer",
"Power",
"BPM",
"MQTT",
"ArduinoOTA",
"UpdateStatusAnimation",
"Serial"
"Serial",
"Weather",
"CircadianRhythm"
],
"scenes": {
"Rain": ["Rain", "Rainbow"],
"Clear": ["Solid", "Rainbow"],
"Rain": ["Rain"],
"Drizzle": ["Rain"],
"Mist": ["Rain"],
"Snow": ["Solid"],
"UnknownWeather": ["Solid", "IdleColors"],
"Test": ["Test"],
"Idle": ["Solid", "Pulse", "Rainbow", "CircadianRhythm"],
"Acid": ["Chimes", "Pulse", "IdleColors", "Rainbow"],
"Idle": ["Solid", "Rainbow"],
"Acid": ["Chimes", "IdleColors", "Rainbow"],
"Flashlight": ["Flashlight"]
},
"surfaceMap": "ponder",
"defaults": {
"mqtt.ip": "10.0.0.2"
"mqtt.ip": "10.0.0.2",
"power.volts": 0,
"power.milliamps": 0
}
}

7
default-partitions.csv Normal file
View File

@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x140000,
app1, app, ota_1, 0x150000,0x140000,
spiffs, data, spiffs, 0x290000,0x160000,
coredump, data, coredump,0x3F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x140000
5 app1 app ota_1 0x150000 0x140000
6 spiffs data spiffs 0x290000 0x160000
7 coredump data coredump 0x3F0000 0x10000

View File

@ -1,7 +1,2 @@
#include "./Command.h"
void
doNothing(Args& args, Print& printer)
{}
Command::Command() : func(doNothing) {}

View File

@ -21,13 +21,25 @@ class Args {
}
};
struct CommandList;
struct Task;
struct Command {
using Executor = std::function<void(Args&, Print& output)>;
Executor func;
const char* name = NULL;
using Executor = void(Task::*)(Args&, Print&);
template<typename T>
using MemberExecutor = void(T::*)(Args&, Print&);
const char* name = NULL;
Executor func = NULL;
void invoke(Task* task, Args& args, Print& printer) const {
if (func) {
(*task.*func)(args, printer);
}
}
Command();
Command(const char* name, Executor func) : name(name), func(func) {}
template<class T>
Command(const char* name, MemberExecutor<T> func) : name(name), func(static_cast<Executor>(func)) {}
};

View File

@ -4,6 +4,8 @@
#include <ArduinoLog.h>
#include "./Command.h"
#include <vector>
#define F_LIKELY(x) __builtin_expect(!!(x), true)
#define F_UNLIKELY(x) __builtin_expect(!!(x), false)

View File

@ -98,3 +98,55 @@ BufferedInputSource::setEvent(InputEvent::Intent intent, Variant &&v)
{
m_eventQueue.insert(InputEvent{intent, std::move(v)});
}
const char*
InputEvent::name() const
{
switch (intent) {
case InputEvent::BeatDetect:
return "beat-detection";
case InputEvent::Beat:
return "beat";
case InputEvent::ReadyToRoll:
return "ready-to-roll";
case InputEvent::PowerToggle:
return "power-toggle";
case InputEvent::SetPower:
return "set-power";
case InputEvent::SetPattern:
return "set-pattern";
case InputEvent::SetScene:
return "set-scene";
case InputEvent::SetColor:
return "set-color";
case InputEvent::Acceleration:
return "acceleration";
case InputEvent::SetBrightness:
return "set-brightness";
case InputEvent::FirmwareUpdate:
return "firmware-update";
case InputEvent::NetworkStatus:
return "network-status";
case InputEvent::NetworkActivity:
return "network-activity";
case InputEvent::StartThing:
return "start-thing";
case InputEvent::StopThing:
return "stop-thing";
case InputEvent::SaveConfigurationRequest:
return "save-configuration";
case InputEvent::ConfigurationChanged:
return "configuration-changed";
case InputEvent::IdleStart:
return "idle-start";
case InputEvent::IdleStop:
return "idle-stop";
case InputEvent::ButtonPress:
return "button";
case InputEvent::LoadConfigurationByName:
return "load-config";
case None:
return "none";
}
return "unknown";
}

View File

@ -55,9 +55,10 @@ struct InputEvent: public Variant {
// An empty non-event
None,
// An input from the user, for other tasks to translate into canonical
// types. Makes for easy button re-mapping on the fly.
UserInput,
// System activity
ReadyToRoll,
IdleStart,
IdleStop,
//
// The canonical types
@ -65,6 +66,8 @@ struct InputEvent: public Variant {
// Hardware inputs
ButtonPress,
Acceleration,
// Network management
NetworkStatus,
NetworkActivity,
@ -74,15 +77,10 @@ struct InputEvent: public Variant {
SetBrightness,
// Animation sequencing
PreviousPattern,
NextPattern,
SetPattern,
PreviousScene,
NextScene,
SetScene,
// Timekeeping
ScheduleChange,
Beat,
BeatDetect,
@ -91,17 +89,13 @@ struct InputEvent: public Variant {
StopThing,
// Configuration
SetDisplayOffset,
SetDisplayLength,
LoadConfigurationByName,
SetColor,
SaveConfigurationRequest,
ConfigurationChanged,
SetColor,
// Firmware events
FirmwareUpdate,
ReadyToRoll,
};
template<typename Value>
@ -118,6 +112,8 @@ struct InputEvent: public Variant {
return intent != otherIntent;
}
const char* name() const;
Intent intent;
};

View File

@ -71,7 +71,7 @@ MainLoop::loop()
Task* slowestTask = NULL;
for(Task* task : scheduler) {
//unsigned int start = millis();
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
#if defined(ESP32) or defined(ESP8266)
unsigned int start = ESP.getCycleCount();
#else
unsigned int start = millis();
@ -79,7 +79,7 @@ MainLoop::loop()
Log.verbose("Running %s", task->name);
s_lastTaskName = task->name;
task->loop();
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
#if defined(ESP32) or defined(ESP8266)
unsigned int runtime = (ESP.getCycleCount() - start) / 160000;
#else
unsigned int runtime = millis() - start;
@ -104,13 +104,14 @@ MainLoop::start()
{
s_instance = this;
Log.notice("Welcome to 🌕 Figment 0.3.0");
Log.notice("*** Starting %d tasks...", scheduler.tasks.size());
Log.notice("*** Scheduling %d tasks...", scheduler.tasks.size());
Serial.flush();
for(auto task: scheduler) {
Log.notice("** Starting %s", task->name);
task->start();
}
loop();
Log.notice("*** Scheduler started.");
dispatch(InputEvent::ReadyToRoll);
}

View File

@ -21,7 +21,7 @@ struct Scheduler {
struct iterator: public std::iterator<std::input_iterator_tag, Task*> {
Scheduler& queue;
int idx = 0;
size_t idx = 0;
explicit iterator(Scheduler& queue) : queue(queue), idx(nextEnabled(0)) {}
iterator(Scheduler& queue, int start) : queue(queue), idx(nextEnabled(start)) {}
iterator& operator++() {
@ -33,7 +33,7 @@ struct Scheduler {
}
int nextEnabled(int start) const {
for(int pos = start; pos < queue.tasks.size();pos++) {
for(size_t pos = start; pos < queue.tasks.size();pos++) {
if (queue.tasks[pos]->state == Task::Running) {
return pos;
}

View File

@ -1,12 +0,0 @@
#pragma once
#include <ArduinoLog.h>
struct PerfCounter {
PerfCounter(const char* name) {}
~PerfCounter() {}
/*PerfCounter(const char* name) : start(millis()), name(name) {}
~PerfCounter() {Log.notice("%s: %d", name, millis() - start);}*/
uint16_t start;
const char* name;
};

View File

@ -1,5 +1,6 @@
#include "./Renderer.h"
#include "./Display.h"
#include "./MainLoop.h"
#include <ArduinoLog.h>
@ -18,7 +19,9 @@ Renderer::lastFigmentName()
void
Renderer::loop()
{
uint16_t totalPower = 0;
for(Display* dpy : m_displays) {
totalPower += calculate_unscaled_power_mW(dpy->pixelBacking(), dpy->pixelCount());
for(Figment* figment : m_figments) {
if (figment->state == Task::Running) {
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
@ -38,10 +41,52 @@ Renderer::loop()
}
};
}
FastLED.show();
static uint8_t videoBrightness = 255;
static uint8_t scaledBrightness = powerScale(videoBrightness, totalPower);
EVERY_N_MILLISECONDS(30) {
m_powerState.update();
m_brightness.update();
videoBrightness = brighten8_video(min((uint8_t)m_brightness, (uint8_t)m_powerState));
scaledBrightness = powerScale(videoBrightness, totalPower);
}
FastLED.show(scaledBrightness);
FastLED.countFPS();
}
void
Renderer::handleEvent(const InputEvent& evt)
{
switch (evt.intent) {
case InputEvent::PowerToggle:
m_powerState = m_powerState.value() <= 128 ? 255 : 0;
Log.notice("Power toggled to %t", m_powerState);
break;
case InputEvent::SetPower:
m_powerState = evt.asInt() == 0 ? 0 : 255;
Log.notice("Power state is now %t", m_powerState);
break;
case InputEvent::SetBrightness:
m_brightness = evt.asInt();
Log.notice("Brightness is now %d (%d requested)", (uint8_t)m_brightness, evt.asInt());
break;
}
}
uint8_t
Renderer::powerScale(uint8_t target, uint32_t totalPower) const
{
if (m_powerManaged) {
const uint32_t maxPower = m_voltage * m_milliamps;
uint32_t requested = ((uint32_t)totalPower * target) / 256;
if (requested > maxPower) {
return (uint32_t)((uint8_t)(target) * (uint32_t)(maxPower)) / ((uint32_t)(requested));
}
}
return target;
}
void
Renderer::onStart()
{
@ -50,3 +95,33 @@ Renderer::onStart()
}
FastLED.show();
}
void
Renderer::doOn(Args& args, Print& print)
{
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetPower, 255});
}
void
Renderer::doOff(Args& args, Print& print)
{
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetPower, 0});
}
void
Renderer::doBrightness(Args& args, Print& print)
{
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetBrightness, atoi(args[1].c_str())});
}
const std::vector<Command>&
Renderer::commands() const
{
static const std::vector<Command> _commands = {
Command{"brightness", &Renderer::doBrightness},
Command{"on", &Renderer::doOn},
Command{"off", &Renderer::doOff}
};
return _commands;
}

View File

@ -1,4 +1,6 @@
#include "./Figment.h"
#include "./Animation.h"
#include "./Input.h"
#include <vector>
class Display;
@ -9,10 +11,26 @@ public:
void loop() override;
void onStart() override;
void handleEvent(const InputEvent& evt) override;
static const char* lastFigmentName();
const std::vector<Command>& commands() const override;
private:
const std::vector<Figment*> m_figments;
const std::vector<Display*> m_displays;
AnimatedNumber m_powerState = 255;
AnimatedNumber m_brightness = 255;
bool m_powerManaged = true;
uint16_t m_voltage = 5;
uint16_t m_milliamps = 500;
uint8_t powerScale(uint8_t target, uint32_t totalPower) const;
void doBrightness(Args& args, Print& print);
void doOn(Args& args, Print& print);
void doOff(Args& args, Print& print);
};

View File

@ -1,7 +1,6 @@
#include "./Surface.h"
#include "./Display.h"
#include <ArduinoLog.h>
#include "Perfcounter.h"
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
@ -53,7 +52,6 @@ Surface::paintWith(std::function<void(CRGB&)> func)
void
Surface::paintShader(Surface::Shader shader)
{
PerfCounter _("paintShader");
uint8_t startX = min(start.x, end.x);
uint8_t startY = min(start.y, end.y);
uint8_t endX = max(start.x, end.x);

View File

@ -8,110 +8,44 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common_env_data]
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> -<inputs/> +<inputs/BPM.cpp> +<inputs/Serial.cpp> +<inputs/CircadianRhythm.cpp>"
[env]
src_filter = +<*>, -<.git/>, -<.svn/>, -<platform/>
lib_ldf_mode = chain+
extra_scripts = verify-configs.py
extra_scripts = pre:verify-configs.py, pre:build-hal.py
check_flags =
cppcheck: --inline-suppr --suppress=*:*/.pio/*
board_build.filesystem = littlefs
upload_speed = 115200
monitor_speed = 115200
build_type = debug
build_flags =
-DFASTLED_ALL_PINS_HARDWARE_SPI
src_build_flags =
-DFASTLED_ALL_PINS_HARDWARE_SPI
-DRENDERBUG_VERSION=3
-DRENDERBUG_LED_PIN=14
-DRENDERBUG_LED_PACKING=RGB
-DDEFAULT_PATTERN_INDEX=0
-fstack-protector
-Wall
lib_deps_external =
fastled/FastLED@^3.5.0
lib_deps =
fastled/FastLED@^3.6.0
thijse/ArduinoLog@^1.1.0
bblanchon/ArduinoJson@^6.17.3
bblanchon/ArduinoJson@^6.21.4
JsonStreamingParser
LittleFS
[config_u8display]
src_build_flags =
-DCONFIG_U8DISPLAY
lib_deps =
olikraus/U8g2@^2.34.15
src_filter = "+<platform/arduino/U8Display.cpp>"
[config_mqtt]
src_build_flags =
-DCONFIG_MQTT
lib_deps =
knolleary/PubSubClient@^2.8.0
src_filter = "+<platform/arduino/MQTTTelemetry.cpp>"
[config_wifi]
src_build_flags =
-DCONFIG_WIFI
src_filter = "+<platform/arduino/WiFiTask.cpp>"
[config_bluetooth]
src_build_flags =
-DCONFIG_BLUETOOTH
src_filter = "+<platform/arduino/BluetoothSerialTelemetry.cpp>"
lib_deps =
BluetoothSerial
[config_ota]
src_build_flags =
-DCONFIG_OTA
src_filter = "+<platform/arduino/OTA.cpp>"
lib_deps =
ArduinoOTA
ESP8266mDNS
[config_nocolor]
src_build_flags =
-DCONFIG_NO_COLORDATA
[config_buttons]
src_build_flags =
-DCONFIG_BUTTONS
src_filter = "+<inputs/Buttons.cpp>"
[config_mpu5060]
src_build_flags =
-DCONFIG_MPU5060
src_filter = "+<inputs/MPU6050.cpp>"
[env:bike]
extends = env:esp32, config_u8display
src_filter = "${env:esp32.src_filter} ${config_u8display.src_filter}"
lib_deps =
${env:esp32.lib_deps}
${config_u8display.lib_deps}
src_build_flags =
${env:esp32.src_build_flags}
${config_u8display.src_build_flags}
build_type = debug
[env:bike_ble]
extends = env:bike
lib_deps =
${env:bike.lib_deps}
nkolban/ESP32 BLE Arduino@1.0.1
src_build_flags =
${env:bike.src_build_flags}
bblanchon/StreamUtils@^1.8.0
[env:esp32]
extends = config_nocolor
extra_scripts = verify-configs.py
board_build.filesystem = littlefs
platform = espressif32
board = featheresp32
framework = arduino
check_flags =
cppcheck: --inline-suppr --suppress=*:*/.pio/*
board_build.partitions = default-partitions.csv
src_build_flags =
${common_env_data.src_build_flags}
${config_nocolor.src_build_flags}
-DPLATFORM_ARDUINO
-DBOARD_ESP32
${env.src_build_flags}
-DCONFIG_THREADED_INPUTS
lib_deps =
${common_env_data.lib_deps_external}
src_filter = "${common_env_data.src_filter}"
monitor_filters = esp32_exception_decoder
monitor_speed = 115200
upload_speed = 115200
[env:esp8266-12f]
extends = env:esp8266
@ -121,104 +55,24 @@ board = esp12e
platform = atmelavr
board = uno
framework = arduino
lib_deps =
${common_env_data.lib_deps_external}
[env:esp8266]
check_flags =
cppcheck: --inline-suppr --suppress=*:*/.pio/*
platform = espressif8266
board = huzzah
framework = arduino
board_build.filesystem = littlefs
src_build_flags =
${common_env_data.src_build_flags}
-DPLATFORM_ARDUINO
-DBOARD_ESP8266
-DCORE_DEBUG_LEVEL=5
-fstack-protector
monitor_filters = esp8266_exception_decoder
lib_deps =
${common_env_data.lib_deps_external}
${env.lib_deps}
arduino-libraries/NTPClient@^3.1.0
src_filter = "${common_env_data.src_filter}"
[env:esp32_bluetooth]
extends = env:esp32, config_bluetooth
src_filter = "${env:esp32.src_filter} ${config_mqtt.src_filter} ${config_wifi.src_filter} ${config_bluetooth.src_filter}"
lib_deps =
${env:esp32.lib_deps}
${config_bluetooth.lib_deps}
${config_mqtt.lib_deps}
src_build_flags =
${env:esp32.src_build_flags}
${config_bluetooth.src_build_flags}
${config_wifi.src_build_flags}
-DRENDERBUG_LED_PIN=14
[env:esp32_wifi]
extends = env:esp32, config_wifi, config_mqtt
src_filter = "${env:esp32.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
buid_type = debug
lib_deps =
${env:esp32.lib_deps}
${config_mqtt.lib_deps}
src_build_flags =
${env:esp32.src_build_flags}
${config_mqtt.src_build_flags}
${config_wifi.src_build_flags}
-DRENDERBUG_LED_PIN=14
ESP8266WiFi
ESP8266mDNS
ArduinoOTA
[env:esp32_display]
extends = env:esp32, config_u8display
src_filter = "${env:esp32.src_filter} ${config_u8display.src_filter}"
build_type = debug
extends = env:esp32
lib_deps =
${env:esp32.lib_deps}
${config_u8display.lib_deps}
olikraus/U8g2@^2.34.15
src_build_flags =
${env:esp32.src_build_flags}
${config_u8display.src_build_flags}
-DRENDERBUG_LED_PIN=14
[env:esp32_wifi_display]
extends = env:esp32, config_wifi, config_mqtt, config_u8display
src_filter = "${env:esp32.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter} ${config_u8display.src_filter}"
build_type = debug
lib_deps =
${env:esp32.lib_deps}
${config_mqtt.lib_deps}
${config_u8display.lib_deps}
src_build_flags =
${env:esp32.src_build_flags}
${config_mqtt.src_build_flags}
${config_wifi.src_build_flags}
${config_u8display.src_build_flags}
-DRENDERBUG_LED_PIN=14
[env:prototype]
extends = env:esp32, config_buttons, config_mpu5060
src_filter = "${env:esp32.src_filter} ${config_buttons.src_filter} ${config_mpu5060.src_filter}"
[env:esp8266_wifi]
extends = env:esp8266, config_wifi, config_mqtt, config_ota
src_filter = "${env:esp32.src_filter} ${config_ota.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
src_build_flags =
${env:esp8266.src_build_flags}
${config_mqtt.src_build_flags}
${config_wifi.src_build_flags}
${config_ota.src_build_flags}
lib_deps =
${env:esp8266.lib_deps}
${config_mqtt.lib_deps}
ESP8266WiFi
${config_ota.lib_deps}
#[env:home_lighting_grb]
#extends = env:esp8266_wifi config_u8display
#src_build_flags =
# ${env:home_lighting.src_build_flags}
# -DRENDERBUG_LED_PACKING=GRB
#[env:home_lighting-12f]
#extends = env:esp8266_wifi config_u8display
#board = esp12e
-DCONFIG_U8DISPLAY

View File

@ -3,10 +3,6 @@
#include <EEPROM.h>
#ifdef BOARD_ESP8266
#include <ESP8266WiFi.h>
#endif
#ifdef PLATFORM_PHOTON
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT);
@ -14,21 +10,6 @@ retained bool LAST_BOOT_WAS_FLASH;
retained bool LAST_BOOT_WAS_SERIAL;
#endif
#ifndef __NOINIT_ATTR // Pre-defined on esp32
#define __NOINIT_ATTR __attribute__ ((section (".noinit")))
#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
BootOptions::initPins()
{
@ -53,40 +34,7 @@ BootOptions::BootOptions()
configStatus.setActive(isSetup);
serialStatus.setActive(isSerial);
#endif
#ifdef BOARD_ESP32
resetReason = esp_reset_reason();
crashCount = s_rebootCount;
if (resetReason >= 4) { // TODO: These values are defined in
// esp32/rom/rtc.h, but not sure if that's included
// on platformio builds
if (crashCount++ >= 3) {
// Boot into safe mode if the watchdog reset us three times in a row.
isSafeMode = true;
}
} else {
crashCount = 0;
}
s_rebootCount = crashCount;
#endif
#ifdef BOARD_ESP8266
struct rst_info resetInfo = *ESP.getResetInfoPtr();
resetReason = resetInfo.reason;
crashCount = s_rebootCount;
if (resetInfo.reason == REASON_SOFT_WDT_RST || resetInfo.reason == REASON_WDT_RST || resetInfo.reason == REASON_EXCEPTION_RST) {
if (crashCount++ >= 3) {
// Boot into safe mode if the watchdog reset us three times in a row.
isSafeMode = true;
}
} else {
crashCount = 0;
}
s_rebootCount = crashCount;
if (resetInfo.reason > 0 && s_forceSafeMode == SAFE_MODE_MAGIC) {
isSafeMode = true;
s_forceSafeMode = 0;
}
#endif
PlatformImpl<>::initBootOptions(*this);
}
void

View File

@ -1,5 +1,6 @@
#pragma once
#include <Arduino.h>
#include "Hal.h"
struct BootOptions {
static void initPins();
@ -7,7 +8,6 @@ struct BootOptions {
BootOptions();
void waitForRelease();
void forceSafeMode();
bool isSetup = false;
bool isSerial = false;

View File

@ -154,11 +154,6 @@ ConfigService::loadMap(const String& mapName)
deserializeJson(jsonConfig, configFile);
configFile.close();
JsonArray strideList = jsonConfig["strides"];
if (jsonConfig.containsKey("rotation")) {
m_jsonMap.rotation = jsonConfig["rotation"];
} else {
m_jsonMap.rotation = 0;
}
m_jsonMap.load(strideList);
return true;
} else {
@ -261,19 +256,18 @@ ConfigService::handleEvent(const InputEvent &evt)
}
void
doMapList(Args& args, Print& out)
ConfigService::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++) {
for(auto it = mapsBegin();it != mapsEnd(); it++) {
out.println(*it);
}
LittleFS.end();
}
void
doSave(Args& args, Print& print)
ConfigService::doSave(Args& args, Print& print)
{
MainLoop::instance()->dispatch(InputEvent::SaveConfigurationRequest);
}
@ -281,17 +275,17 @@ doSave(Args& args, Print& print)
static String s;
void
doSetProfile(Args& args, Print& out)
ConfigService::doSetProfile(Args& args, Print& out)
{
s = args[1];
MainLoop::instance()->dispatch(InputEvent{InputEvent::LoadConfigurationByName, s.c_str()});
}
void
doCoordMap(Args& args, Print& out)
ConfigService::doCoordMap(Args& args, Print& out)
{
VirtualCoordinates coords{atoi(args[1].c_str()), atoi(args[2].c_str())};
auto map = Static<ConfigService>::instance()->coordMap();
auto map = coordMap();
auto pPos = map->virtualToPhysicalCoords(coords);
auto idx = map->physicalCoordsToIndex(pPos);
char buf[32];
@ -303,10 +297,10 @@ const std::vector<Command>&
ConfigService::commands() const
{
static const std::vector<Command> _commands = {
{"save", doSave},
{"profile", doSetProfile},
{"maps", doMapList},
{"coordmap", doCoordMap}
{"save", &ConfigService::doSave},
{"profile", &ConfigService::doSetProfile},
{"maps", &ConfigService::doMapList},
{"coordmap", &ConfigService::doCoordMap}
};
return _commands;
}

View File

@ -2,7 +2,7 @@
#include <Figments.h>
#include "JsonCoordinateMapping.h"
#include <ArduinoJson.h>
#include <LittleFS.h>
#include "FsUtils.h"
class Configuration {
public:
@ -59,55 +59,8 @@ struct ConfigService: public Task {
const CoordinateMapping* coordMap() const { return &m_jsonMap; }
const char* loadedProfile() const;
void overrideProfile(const char* profileName);
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(); }
@ -121,4 +74,9 @@ private:
bool loadProfile(const char* name);
bool loadMap(const String& mapName);
void doSave(Args& args, Print& print);
void doSetProfile(Args& args, Print& print);
void doCoordMap(Args& args, Print& print);
void doMapList(Args& args, Print& print);
};

57
src/FsUtils.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "FsUtils.h"
filename_iterator::filename_iterator()
: suffix(NULL),
valid(false)
{}
filename_iterator::filename_iterator(const char* path, const char* suffix)
#ifdef ESP8266
: dir(LittleFS.openDir(path)),
#endif
#ifdef ESP32
: dir(LittleFS.open(path)),
#endif
valid(true),
suffix(suffix)
{
next();
}
void
filename_iterator::next()
{
if (!valid) {
return;
}
int extPos = -1;
do {
#ifdef ESP8266
valid = dir.next();
if (valid) {
String fname = dir.fileName();
extPos = fname.lastIndexOf(suffix);
if (extPos != -1) {
ret = fname.substring(0, extPos);
}
}
#endif
#ifdef ESP32
File next = dir.openNextFile();
valid = (bool)next;
if (valid && !next.isDirectory()) {
String fname = next.name();
extPos = fname.lastIndexOf(suffix);
if (extPos != -1) {
ret = fname.substring(0, extPos);
}
}
#endif
} while (valid && extPos == -1);
}
filename_iterator& filename_iterator::operator++()
{
next();
return *this;
}

33
src/FsUtils.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <iterator>
#include <LittleFS.h>
struct filename_iterator: public std::iterator<std::input_iterator_tag, const char*> {
public:
filename_iterator();
filename_iterator(const char* path, const char* suffix);
void next();
filename_iterator& operator++();
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();
}
private:
String ret;
bool valid;
const char* suffix;
#ifdef ESP8266
Dir dir;
#endif
#ifdef ESP32
File dir;
#endif
};

30
src/Hal.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
struct BootOptions;
typedef enum {
HAL_UNKNOWN,
HAL_ESP32,
HAL_ESP8266
} HalBackend;
constexpr HalBackend DefaultBackend = RENDERBUG_HAL;
template<HalBackend Backend = DefaultBackend>
class PlatformImpl {
public:
static const char* name();
static const char* version();
static int freeRam();
static void startWatchdog();
static void startNTP();
static bool getLocalTime(struct tm* timedata, int timezone);
static void loop();
static const char* deviceID();
__attribute__((noreturn)) static void restart();
static void bootSplash();
static void printCrashInfo();
static void initBootOptions(BootOptions& opts);
static void forceSafeMode();
};

View File

@ -56,7 +56,7 @@ JsonCoordinateMapping::load(const JsonArray& strideList)
int x = strideObj["x"];
int y = strideObj["y"];
displayMap[i] = Span{length, x, y};
Log.verbose("config: Found stride (%d, %d): %d", x, y, length);
Log.info("config: Found stride (%d, %d): %d", x, y, length);
maxStrideSize = length > maxStrideSize ? length : maxStrideSize;
}
}

View File

@ -4,57 +4,6 @@
static const char* eventValue(const InputEvent& evt);
const char*
LogService::intentName(InputEvent::Intent intent)
{
switch(intent) {
case InputEvent::BeatDetect:
return "beat-detection";
case InputEvent::Beat:
return "beat";
case InputEvent::ReadyToRoll:
return "ready-to-roll";
case InputEvent::PowerToggle:
return "power-toggle";
case InputEvent::SetPower:
return "set-power";
case InputEvent::PreviousPattern:
return "previous-pattern";
case InputEvent::NextPattern:
return "next-pattern";
case InputEvent::SetPattern:
return "set-pattern";
case InputEvent::SetColor:
return "set-color";
case InputEvent::Acceleration:
return "acceleration";
case InputEvent::UserInput:
return "user";
case InputEvent::SetBrightness:
return "set-brightness";
case InputEvent::FirmwareUpdate:
return "firmware-update";
case InputEvent::NetworkStatus:
return "network-status";
case InputEvent::NetworkActivity:
return "network-activity";
case InputEvent::StartThing:
return "start-thing";
case InputEvent::StopThing:
return "stop-thing";
case InputEvent::SetDisplayOffset:
return "set-display-offset";
case InputEvent::SetDisplayLength:
return "set-display-length";
case InputEvent::SaveConfigurationRequest:
return "save-configuration";
case InputEvent::ConfigurationChanged:
return "configuration-changed";
default:
return NULL;
}
}
char LogService::s_valueBuf[255];
const char*
@ -84,7 +33,7 @@ LogService::handleEvent(const InputEvent& evt) {
if (m_duplicateEvents > 0) {
Log.trace("Suppressed reporting %d duplicate events.", m_duplicateEvents);
}
const char* sourceName = intentName(evt.intent);;
const char* sourceName = evt.name();
const char* valueBuf = eventValue(evt);
if (sourceName == 0) {
Log.trace("Event: intent: %d value: %s", evt.intent, valueBuf);

View File

@ -6,7 +6,6 @@ class LogService : public Task {
void handleEvent(const InputEvent& event) override;
void loop() override {}
static const char* intentName(InputEvent::Intent intent);
static const char* eventValue(const InputEvent& evt);
private:

View File

@ -3,40 +3,16 @@
#include "Static.h"
#include <time.h>
#ifdef BOARD_ESP32
#ifdef CONFIG_WIFI
#include <WiFi.h>
#endif
#include <esp_task_wdt.h>
#elif defined(BOARD_ESP8266)
#ifdef CONFIG_WIFI
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <ctime>
WiFiUDP wifiUdp;
NTPClient timeClient(wifiUdp, "pool.ntp.org", 3600 * -7);
#endif
#endif
#ifdef PLATFORM_PHOTON
STARTUP(BootOptions::initPins());
#else
#include "inputs/Serial.h"
#ifdef CONFIG_MQTT
#include "platform/arduino/MQTTTelemetry.h"
#endif
void printNewline(Print* logOutput, int logLevel)
{
(void)logLevel; // unused
logOutput->print("\n");
}
int printEspLog(const char* fmt, va_list args)
{
Log.notice(fmt, args);
return 1;
}
#endif
int Platform::s_timezone = 0;
@ -46,24 +22,18 @@ Platform::TaskRegistration* Platform::lastTask = NULL;
const char*
Platform::name()
{
return PlatformImpl<>::name();
#ifdef PLATFORM_PHOTON
return "Photon";
#elif defined(BOARD_ESP8266)
return "ESP8266";
#elif defined(BOARD_ESP32)
return "ESP32";
#else
return "Unknown!";
#endif
}
const char*
Platform::version()
{
return PlatformImpl<>::version();
#ifdef PLATFORM_PHOTON
return System.version().c_str();
#elif defined(BOARD_ESP32) || defined(BOARD_ESP8266)
return ESP.getSdkVersion();
#else
return "Unknown!";
#endif
@ -72,9 +42,7 @@ Platform::version()
int
Platform::freeRam()
{
#if defined(BOARD_ESP8266) || defined(BOARD_ESP32)
return ESP.getFreeHeap();
#endif
return PlatformImpl<>::freeRam();
}
void
@ -94,26 +62,17 @@ Platform::preSetup()
Log.notice("\xf0\x9f\x94\x8c Serial connected");
}
#else
#ifdef CONFIG_MQTT
Log.begin(LOG_LEVEL_TRACE, Static<MQTTTelemetry>::instance()->logPrinter());
Static<MQTTTelemetry>::instance()->setSequencer(Static<Sequencer>::instance());
#else
Log.begin(LOG_LEVEL_TRACE, Static<SerialInput>::instance()->logPrinter());
#endif
Log.setSuffix(printNewline);
#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);
if (!ESP.checkFlashCRC()) {
Log.fatal("Firmware failed CRC check!!!");
}
#endif
PlatformImpl<>::startWatchdog();
}
void
@ -121,19 +80,15 @@ Platform::setup()
{
#ifdef PLATFORM_PHOTON
Time.zone(Static<Platform>::instance()->getTimezone());
#elif defined(BOARD_ESP32)
constexpr int dst = 1;
configTime(s_timezone* 3600, 3600 * dst, "pool.ntp.org");
#elif defined(BOARD_ESP8266)
#ifdef CONFIG_WIFI
timeClient.begin();
#endif
#endif
PlatformImpl<>::startNTP();
}
void
Platform::bootSplash()
{
PlatformImpl<>::bootSplash();
#ifdef PLATFORM_PHOTON
Log.notice(u8" Boot pin configuration:");
Log.notice(u8" 2: Setup - %d", bootopts.isSetup);
@ -148,29 +103,25 @@ Platform::bootSplash()
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
PlatformImpl<>::printCrashInfo();
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:");
auto it = beginTasks();
while (it != endTasks()) {
if (!(*it)->isFigment()) {
Log.trace(" %s", (*it)->name);
}
++it;
}
Log.trace("Registered Figments:");
it = beginTasks();
while (it != endTasks()) {
if ((*it)->isFigment()) {
Log.trace(" Figment: %s", (*it)->name);
} else {
Log.trace(" %s", (*it)->name);
}
++it;
@ -180,16 +131,7 @@ Platform::bootSplash()
void
Platform::loop()
{
#ifdef BOARD_ESP8266
#ifdef CONFIG_WIFI
if (WiFi.status() == WL_CONNECTED) {
timeClient.update();
}
#endif
ESP.wdtFeed();
#elif defined(BOARD_ESP32)
esp_task_wdt_reset();
#endif
PlatformImpl<>::loop();
}
bool
@ -202,40 +144,19 @@ Platform::getLocalTime(struct tm* timedata)
return true;
}
return false;
#elif defined(BOARD_ESP32)
time_t rawtime;
memset(&rawtime, 0, sizeof(rawtime));
time(&rawtime);
(*timedata) = (*localtime(&rawtime));
return (timedata->tm_year > (2016-1990));
#else
#ifdef CONFIG_WIFI
timedata->tm_hour = timeClient.getHours();
timedata->tm_min = timeClient.getMinutes();
#else
memset(timedata, sizeof(struct tm), 0);
return false;
#endif
return true;
#endif
return PlatformImpl<>::getLocalTime(timedata, s_timezone);
}
const char*
Platform::deviceID()
{
uint64_t chipid;
#ifdef BOARD_ESP32
chipid = ESP.getEfuseMac();
#elif defined(BOARD_ESP8266)
chipid = ESP.getChipId();
#endif
snprintf(s_deviceID, sizeof(s_deviceID), "%08X", (uint32_t)chipid);
return s_deviceID;
return PlatformImpl<>::deviceID();
}
void
Platform::addLEDs(CRGB* leds, uint16_t ledCount) {
FastLED.addLeds<WS2812, RENDERBUG_LED_PIN, RENDERBUG_LED_PACKING>(leds, ledCount);
FastLED.addLeds<NEOPIXEL, RENDERBUG_LED_PIN>(leds, ledCount);
}
const String
@ -247,50 +168,43 @@ Platform::model()
void
Platform::restart() {
#ifdef BOARD_ESP8266
ESP.wdtDisable();
ESP.restart();
#elif defined(BOARD_ESP32)
ESP.restart();
#endif
PlatformImpl<>::restart();
}
__attribute__((noreturn))
void
doReboot(Args& args, Print& out)
Platform::doReboot(Args& args, Print& out)
{
out.println("Rebooting");
Platform::restart();
restart();
}
__attribute__((noreturn))
void
doSafeMode(Args& args, Print& out)
Platform::doSafeMode(Args& args, Print& out)
{
out.println("Rebooting into safe mode");
Platform::bootopts.forceSafeMode();
Platform::restart();
PlatformImpl<>::forceSafeMode();
restart();
}
String s;
void
doTaskStart(Args& args, Print& out)
Platform::doTaskStart(Args& args, Print& out)
{
s = args[1];
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, s.c_str()});
}
void
doTaskStop(Args& args, Print& out)
Platform::doTaskStop(Args& args, Print& out)
{
s = args[1];
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, s.c_str()});
}
void
doTaskList(Args& args, Print& out)
Platform::doTaskList(Args& args, Print& out)
{
auto sched = MainLoop::instance()->scheduler;
auto printer = Static<SerialInput>::instance()->printer();
@ -312,18 +226,16 @@ doTaskList(Args& args, Print& out)
}
const std::vector<Command> _commands = {
{"tasks", doTaskList},
{"safe-mode", doSafeMode},
{"reboot", doReboot},
{"stop", doTaskStop},
{"start", doTaskStart}
};
const std::vector<Command>&
Platform::commands() const
{
static const std::vector<Command> _commands = {
{"tasks", &Platform::doTaskList},
{"safe-mode", &Platform::doSafeMode},
{"reboot", &Platform::doReboot},
{"stop", &Platform::doTaskStop},
{"start", &Platform::doTaskStart}
};
return _commands;
}

View File

@ -2,10 +2,17 @@
#include <FastLED.h>
#include <Figments.h>
#include "BootOptions.h"
#include "Hal.h"
class Platform : public Task {
static int s_timezone;
static char s_deviceID[15];
void doTaskList(Args& args, Print& out);
void doTaskStop(Args& args, Print& out);
void doTaskStart(Args& args, Print& out);
__attribute__((noreturn)) void doSafeMode(Args& args, Print& out);
__attribute__((noreturn)) void doReboot(Args& args, Print& out);
public:
Platform() : Task("Platform") {state = Task::Running;}
static BootOptions bootopts;
@ -20,6 +27,10 @@ class Platform : public Task {
static String devName = model() + " " + Platform::deviceID();
return devName;
}
static const String deviceHostname() {
static String devName = String("renderbug-") + Platform::deviceID();
return devName;
}
static void preSetup();
static void bootSplash();
static void setup();

View File

@ -63,42 +63,35 @@ Sequencer::handleEvent(const InputEvent& evt)
Log.verbose("Starting pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
}
} else if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) {
} else if (evt.intent == InputEvent::SetScene && evt.asString() == m_scenes[m_idx].name) {
return;
} else if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
Log.notice("Switching pattern!");
} else if (evt.intent == InputEvent::SetScene) {
Log.notice("Switching scene to %s!", evt.asString());
m_scenes[m_idx].stop();
int newIdx = 0;
bool found = false;
for(newIdx = 0; newIdx < m_scenes.size(); newIdx++) {
if (!strcmp(evt.asString(), m_scenes[newIdx].name)) {
found = true;
break;
}
}
if (evt.intent == InputEvent::NextPattern) {
m_idx++;
} else if (evt.intent == InputEvent::PreviousPattern) {
m_idx--;
if (found) {
m_scenes[m_idx].stop();
m_idx = newIdx;
m_scenes[m_idx].start();
} else {
for(m_idx = 0; m_idx < m_scenes.size(); m_idx++) {
if (!strcmp(evt.asString(), m_scenes[m_idx].name)) {
break;
}
}
Log.notice("Scene not found!");
}
if (m_idx < 0) {
m_idx = m_scenes.size() - 1;
}
if (m_idx >= m_scenes.size()) {
m_idx = 0;
}
m_scenes[m_idx].start();
}
}
void
doScenes(Args& args, Print& out)
Sequencer::doScenes(Args& args, Print& out)
{
out.println("Available scenes: ");
for (auto scene : Static<Sequencer>::instance()->scenes()) {
for (auto scene : scenes()) {
out.println(scene.name);
}
}
@ -106,20 +99,19 @@ doScenes(Args& args, Print& out)
static String s;
void
doScene(Args& args, Print& out)
Sequencer::doScene(Args& args, Print& out)
{
s = args[1];
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetPattern, s.c_str()});
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, s.c_str()});
}
const std::vector<Command> _commands = {
{"scene", doScene},
{"scenes", doScenes}
};
const std::vector<Command>&
Sequencer::commands() const
{
static const std::vector<Command> _commands = {
{"scene", &Sequencer::doScene},
{"scenes", &Sequencer::doScenes}
};
return _commands;
}

View File

@ -28,4 +28,7 @@ public:
private:
int m_idx;
std::vector<Scene> m_scenes;
void doScene(Args& args, Print& out);
void doScenes(Args& args, Print& out);
};

View File

@ -26,13 +26,7 @@ void ChimesAnimation::randomize() {
}
void ChimesAnimation::handleEvent(const InputEvent& evt) {
if (evt.intent == InputEvent::UserInput) {
if (strcmp(evt.asString(), "blobs") == 0) {
m_blobs.toggle();
} else if (strcmp(evt.asString(), "chimes") == 0) {
m_chimes.toggle();
}
} else if (evt.intent == InputEvent::SetColor) {
if (evt.intent == InputEvent::SetColor) {
m_flashBrightness.set(255, 0);
m_flashColor = evt.asRGB();
uint8_t flashHue = rgb2hsv_approximate(m_flashColor).hue;

View File

@ -1,72 +0,0 @@
#include "./Power.h"
#include "../Static.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
Power::handleConfigChange(const Configuration& config)
{
m_milliamps = config.get("power.milliamps", m_milliamps);
m_voltage = config.get("power.volts", m_voltage);
m_useBPM = config.get("power.useBPM", m_useBPM);
if (m_voltage == 0 || m_milliamps == 0) {
Log.notice("power: Impossible power config: %dma @ %dv", m_milliamps, m_voltage);
m_valid = false;
} else {
Log.notice("power: Configured to use %dma @ %dv", m_milliamps, m_voltage);
m_valid = true;
FastLED.setMaxPowerInVoltsAndMilliamps(m_voltage, m_milliamps);
}
}
STATIC_ALLOC(Power);
STATIC_TASK(Power);

View File

@ -1,68 +0,0 @@
#pragma once
#include <Figments.h>
#include "../Config.h"
class Power: public Figment, ConfigTaskMixin {
public:
Power() : Figment("Power") {state = Task::Running;}
void handleEvent(const InputEvent& evt) override {
switch (evt.intent) {
case InputEvent::PowerToggle:
m_powerState = m_powerState.value() <= 128 ? 255 : 0;
m_forced = false;
Log.notice("Power toggled to %t", m_powerState);
break;
case InputEvent::SetPower:
m_powerState = evt.asInt() == 0 ? 0 : 255;
m_forced = false;
Log.notice("Power state is now %t", m_powerState);
break;
case InputEvent::SetBrightness:
m_brightness = evt.asInt();
m_forced = false;
break;
case InputEvent::Beat:
m_beatDecay.set(0, 255);
break;
default:
ConfigTaskMixin::handleEvent(evt);
}
}
void handleConfigChange(const Configuration& config) override;
void loop() override {
ConfigTaskMixin::loop();
m_powerState.update();
m_brightness.update();
EVERY_N_MILLISECONDS(20) {
m_beatDecay.update(13);
}
}
void render(Display* dpy) const override {
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 clippedBrightness = std::min(decayedBrightness, (uint8_t)255);
const uint8_t scaledBrightness = scale8(m_powerState, clippedBrightness);
const uint8_t videoBrightness = brighten8_video(scaledBrightness);
const uint8_t powerBrightness = calculate_max_brightness_for_power_mW(videoBrightness, m_voltage * m_milliamps);
FastLED.setBrightness(powerBrightness);
}
}
void forceBrightness(uint8_t v);
const std::vector<Command>& commands() const override;
private:
AnimatedNumber m_powerState = 255;
AnimatedNumber m_brightness = 255;
AnimatedNumber m_beatDecay = 255;
uint8_t m_voltage = 5;
uint16_t m_milliamps = 500;
bool m_valid = true;
bool m_useBPM = false;
bool m_forced = false;
};

View File

@ -49,10 +49,7 @@ void SolidAnimation::loop() {
}
}
#include <Perfcounter.h>
void SolidAnimation::render(Display* dpy) const {
PerfCounter _("solidRender");
CRGB color(m_red.value(), m_green.value(), m_blue.value());
CRGB scaledPrev = m_prevColor;
scaledPrev = color.nscale8(30);

View File

@ -1,816 +0,0 @@
#include "colors.h"
const ColorInfo color_data[] = {
#ifdef CONFIG_NO_COLORDATA
#else
{ "Air Superiority Blue", { 114, 160, 193 } },
{ "Alabama Crimson", { 163, 38, 56 } },
{ "Alice Blue", { 240, 248, 255 } },
{ "Alizarin Crimson", { 227, 38, 54 } },
{ "Alloy Orange", { 196, 98, 16 } },
{ "Almond", { 239, 222, 205 } },
{ "Amaranth", { 229, 43, 80 } },
{ "Amber", { 255, 191, 0 } },
{ "American Rose", { 255, 3, 62 } },
{ "Amethyst", { 153, 102, 204 } },
{ "Android Green", { 164, 198, 57 } },
{ "Anti-Flash White", { 242, 243, 244 } },
{ "Antique Brass", { 205, 149, 117 } },
{ "Antique Fuchsia", { 145, 92, 131 } },
{ "Antique Ruby", { 132, 27, 45 } },
{ "Antique White", { 250, 235, 215 } },
{ "Apple Green", { 141, 182, 0 } },
{ "Apricot", { 251, 206, 177 } },
{ "Aqua", { 0, 255, 255 } },
{ "Aquamarine", { 127, 255, 212 } },
{ "Army Green", { 75, 83, 32 } },
{ "Arsenic", { 59, 68, 75 } },
{ "Arylide Yellow", { 233, 214, 107 } },
{ "Ash Grey", { 178, 190, 181 } },
{ "Asparagus", { 135, 169, 107 } },
{ "Atomic Tangerine", { 255, 153, 102 } },
{ "Auburn", { 165, 42, 42 } },
{ "Aureolin", { 253, 238, 0 } },
{ "Aurometalsaurus", { 110, 127, 128 } },
{ "Avocado", { 86, 130, 3 } },
{ "Azure", { 0, 127, 255 } },
{ "Baby Blue", { 137, 207, 240 } },
{ "Baby Blue Eyes", { 161, 202, 241 } },
{ "Baby Pink", { 244, 194, 194 } },
{ "Ball Blue", { 33, 171, 205 } },
{ "Banana Mania", { 250, 231, 181 } },
{ "Banana Yellow", { 255, 225, 53 } },
{ "Barn Red", { 124, 10, 2 } },
{ "Battleship Grey", { 132, 132, 130 } },
{ "Bazaar", { 152, 119, 123 } },
{ "Beau Blue", { 188, 212, 230 } },
{ "Beaver", { 159, 129, 112 } },
{ "Beige", { 245, 245, 220 } },
{ "Big Dip ORuby", { 156, 37, 66 } },
{ "Bisque", { 255, 228, 196 } },
{ "Bistre", { 61, 43, 31 } },
{ "Bittersweet", { 254, 111, 94 } },
{ "Bittersweet Shimmer", { 191, 79, 81 } },
{ "Black", { 0, 0, 0 } },
{ "Black Bean", { 61, 12, 2 } },
{ "Black Leather Jacket", { 37, 53, 41 } },
{ "Black Olive", { 59, 60, 54 } },
{ "Blanched Almond", { 255, 235, 205 } },
{ "Blast-Off Bronze", { 165, 113, 100 } },
{ "Bleu De France", { 49, 140, 231 } },
{ "Blizzard Blue", { 172, 229, 238 } },
{ "Blond", { 250, 240, 190 } },
{ "Blue", { 0, 0, 255 } },
{ "Blue Gray", { 102, 153, 204 } },
{ "Blue-Green", { 13, 152, 186 } },
{ "Blue Sapphire", { 18, 97, 128 } },
{ "Blue-Violet", { 138, 43, 226 } },
{ "Blush", { 222, 93, 131 } },
{ "Bole", { 121, 68, 59 } },
{ "Bondi Blue", { 0, 149, 182 } },
{ "Bone", { 227, 218, 201 } },
{ "Boston University Red", { 204, 0, 0 } },
{ "Bottle Green", { 0, 106, 78 } },
{ "Boysenberry", { 135, 50, 96 } },
{ "Brandeis Blue", { 0, 112, 255 } },
{ "Brass", { 181, 166, 66 } },
{ "Brick Red", { 203, 65, 84 } },
{ "Bright Cerulean", { 29, 172, 214 } },
{ "Bright Green", { 102, 255, 0 } },
{ "Bright Lavender", { 191, 148, 228 } },
{ "Bright Maroon", { 195, 33, 72 } },
{ "Bright Pink", { 255, 0, 127 } },
{ "Bright Turquoise", { 8, 232, 222 } },
{ "Bright Ube", { 209, 159, 232 } },
{ "Brilliant Lavender", { 244, 187, 255 } },
{ "Brilliant Rose", { 255, 85, 163 } },
{ "Brink Pink", { 251, 96, 127 } },
{ "British Racing Green", { 0, 66, 37 } },
{ "Bronze", { 205, 127, 50 } },
{ "Brown", { 150, 75, 0 } },
{ "Bubble Gum", { 255, 193, 204 } },
{ "Bubbles", { 231, 254, 255 } },
{ "Buff", { 240, 220, 130 } },
{ "Bulgarian Rose", { 72, 6, 7 } },
{ "Burgundy", { 128, 0, 32 } },
{ "Burlywood", { 222, 184, 135 } },
{ "Burnt Orange", { 204, 85, 0 } },
{ "Burnt Sienna", { 233, 116, 81 } },
{ "Burnt Umber", { 138, 51, 36 } },
{ "Byzantine", { 189, 51, 164 } },
{ "Byzantium", { 112, 41, 99 } },
{ "Cadet", { 83, 104, 114 } },
{ "Cadet Blue", { 95, 158, 160 } },
{ "Cadet Grey", { 145, 163, 176 } },
{ "Cadmium Green", { 0, 107, 60 } },
{ "Cadmium Orange", { 237, 135, 45 } },
{ "Cadmium Red", { 227, 0, 34 } },
{ "Cadmium Yellow", { 255, 246, 0 } },
{ "Cal Poly Green", { 30, 77, 43 } },
{ "Cambridge Blue", { 163, 193, 173 } },
{ "Camel", { 193, 154, 107 } },
{ "Cameo Pink", { 239, 187, 204 } },
{ "Camouflage Green", { 120, 134, 107 } },
{ "Canary Yellow", { 255, 239, 0 } },
{ "Candy Apple Red", { 255, 8, 0 } },
{ "Candy Pink", { 228, 113, 122 } },
{ "Capri", { 0, 191, 255 } },
{ "Caput Mortuum", { 89, 39, 32 } },
{ "Cardinal", { 196, 30, 58 } },
{ "Caribbean Green", { 0, 204, 153 } },
{ "Carmine", { 150, 0, 24 } },
{ "Carmine Pink", { 235, 76, 66 } },
{ "Carmine Red", { 255, 0, 56 } },
{ "Carnation Pink", { 255, 166, 201 } },
{ "Carnelian", { 179, 27, 27 } },
{ "Carolina Blue", { 153, 186, 221 } },
{ "Carrot Orange", { 237, 145, 33 } },
{ "Catalina Blue", { 6, 42, 120 } },
{ "Ceil", { 146, 161, 207 } },
{ "Celadon", { 172, 225, 175 } },
{ "Celadon Blue", { 0, 123, 167 } },
{ "Celadon Green", { 47, 132, 124 } },
{ "Celeste", { 178, 255, 255 } },
{ "Celestial Blue", { 73, 151, 208 } },
{ "Cerise", { 222, 49, 99 } },
{ "Cerise Pink", { 236, 59, 131 } },
{ "Cerulean", { 0, 123, 167 } },
{ "Cerulean Blue", { 42, 82, 190 } },
{ "Cerulean Frost", { 109, 155, 195 } },
{ "Cg Blue", { 0, 122, 165 } },
{ "Cg Red", { 224, 60, 49 } },
{ "Chamoisee", { 160, 120, 90 } },
{ "Champagne", { 250, 214, 165 } },
{ "Charcoal", { 54, 69, 79 } },
{ "Charm Pink", { 230, 143, 172 } },
{ "Chartreuse", { 223, 255, 0 } },
{ "Cherry", { 222, 49, 99 } },
{ "Cherry Blossom Pink", { 255, 183, 197 } },
{ "Chestnut", { 205, 92, 92 } },
{ "China Pink", { 222, 111, 161 } },
{ "China Rose", { 168, 81, 110 } },
{ "Chinese Red", { 170, 56, 30 } },
{ "Chocolate", { 123, 63, 0 } },
{ "Chrome Yellow", { 255, 167, 0 } },
{ "Cinereous", { 152, 129, 123 } },
{ "Cinnabar", { 227, 66, 52 } },
{ "Cinnamon", { 210, 105, 30 } },
{ "Citrine", { 228, 208, 10 } },
{ "Classic Rose", { 251, 204, 231 } },
{ "Cobalt", { 0, 71, 171 } },
{ "Cocoa Brown", { 210, 105, 30 } },
{ "Coffee", { 111, 78, 55 } },
{ "Columbia Blue", { 155, 221, 255 } },
{ "Congo Pink", { 248, 131, 121 } },
{ "Cool Black", { 0, 46, 99 } },
{ "Cool Grey", { 140, 146, 172 } },
{ "Copper", { 184, 115, 51 } },
{ "Copper Penny", { 173, 111, 105 } },
{ "Copper Red", { 203, 109, 81 } },
{ "Copper Rose", { 153, 102, 102 } },
{ "Coquelicot", { 255, 56, 0 } },
{ "Coral", { 255, 127, 80 } },
{ "Coral Pink", { 248, 131, 121 } },
{ "Coral Red", { 255, 64, 64 } },
{ "Cordovan", { 137, 63, 69 } },
{ "Corn", { 251, 236, 93 } },
{ "Cornell Red", { 179, 27, 27 } },
{ "Cornflower Blue", { 100, 149, 237 } },
{ "Cornsilk", { 255, 248, 220 } },
{ "Cosmic Latte", { 255, 248, 231 } },
{ "Cotton Candy", { 255, 188, 217 } },
{ "Cream", { 255, 253, 208 } },
{ "Crimson", { 220, 20, 60 } },
{ "Crimson Glory", { 190, 0, 50 } },
{ "Cyan", { 0, 255, 255 } },
{ "Daffodil", { 255, 255, 49 } },
{ "Dandelion", { 240, 225, 48 } },
{ "Dark Blue", { 0, 0, 139 } },
{ "Dark Brown", { 101, 67, 33 } },
{ "Dark Byzantium", { 93, 57, 84 } },
{ "Dark Candy Apple Red", { 164, 0, 0 } },
{ "Dark Cerulean", { 8, 69, 126 } },
{ "Dark Chestnut", { 152, 105, 96 } },
{ "Dark Coral", { 205, 91, 69 } },
{ "Dark Cyan", { 0, 139, 139 } },
{ "Dark Electric Blue", { 83, 104, 120 } },
{ "Dark Goldenrod", { 184, 134, 11 } },
{ "Dark Gray", { 169, 169, 169 } },
{ "Dark Green", { 1, 50, 32 } },
{ "Dark Imperial Blue", { 0, 65, 106 } },
{ "Dark Jungle Green", { 26, 36, 33 } },
{ "Dark Khaki", { 189, 183, 107 } },
{ "Dark Lava", { 72, 60, 50 } },
{ "Dark Lavender", { 115, 79, 150 } },
{ "Dark Magenta", { 139, 0, 139 } },
{ "Dark Midnight Blue", { 0, 51, 102 } },
{ "Dark Olive Green", { 85, 107, 47 } },
{ "Dark Orange", { 255, 140, 0 } },
{ "Dark Orchid", { 153, 50, 204 } },
{ "Dark Pastel Blue", { 119, 158, 203 } },
{ "Dark Pastel Green", { 3, 192, 60 } },
{ "Dark Pastel Purple", { 150, 111, 214 } },
{ "Dark Pastel Red", { 194, 59, 34 } },
{ "Dark Pink", { 231, 84, 128 } },
{ "Dark Powder Blue", { 0, 51, 153 } },
{ "Dark Raspberry", { 135, 38, 87 } },
{ "Dark Red", { 139, 0, 0 } },
{ "Dark Salmon", { 233, 150, 122 } },
{ "Dark Scarlet", { 86, 3, 25 } },
{ "Dark Sea Green", { 143, 188, 143 } },
{ "Dark Sienna", { 60, 20, 20 } },
{ "Dark Slate Blue", { 72, 61, 139 } },
{ "Dark Slate Gray", { 47, 79, 79 } },
{ "Dark Spring Green", { 23, 114, 69 } },
{ "Dark Tan", { 145, 129, 81 } },
{ "Dark Tangerine", { 255, 168, 18 } },
{ "Dark Taupe", { 72, 60, 50 } },
{ "Dark Terra Cotta", { 204, 78, 92 } },
{ "Dark Turquoise", { 0, 206, 209 } },
{ "Dark Violet", { 148, 0, 211 } },
{ "Dark Yellow", { 155, 135, 12 } },
{ "Dartmouth Green", { 0, 112, 60 } },
{ "Deep Carmine", { 169, 32, 62 } },
{ "Deep Carmine Pink", { 239, 48, 56 } },
{ "Deep Carrot Orange", { 233, 105, 44 } },
{ "Deep Cerise", { 218, 50, 135 } },
{ "Deep Champagne", { 250, 214, 165 } },
{ "Deep Chestnut", { 185, 78, 72 } },
{ "Deep Coffee", { 112, 66, 65 } },
{ "Deep Fuchsia", { 193, 84, 193 } },
{ "Deep Jungle Green", { 0, 75, 73 } },
{ "Deep Lilac", { 153, 85, 187 } },
{ "Deep Magenta", { 204, 0, 204 } },
{ "Deep Peach", { 255, 203, 164 } },
{ "Deep Pink", { 255, 20, 147 } },
{ "Deep Ruby", { 132, 63, 91 } },
{ "Deep Saffron", { 255, 153, 51 } },
{ "Deep Sky Blue", { 0, 191, 255 } },
{ "Deep Tuscan Red", { 102, 66, 77 } },
{ "Denim", { 21, 96, 189 } },
{ "Desert", { 193, 154, 107 } },
{ "Desert Sand", { 237, 201, 175 } },
{ "Dim Gray", { 105, 105, 105 } },
{ "Dodger Blue", { 30, 144, 255 } },
{ "Dogwood Rose", { 215, 24, 104 } },
{ "Dollar Bill", { 133, 187, 101 } },
{ "Drab", { 150, 113, 23 } },
{ "Duke Blue", { 0, 0, 156 } },
{ "Earth Yellow", { 225, 169, 95 } },
{ "Ebony", { 85, 93, 80 } },
{ "Ecru", { 194, 178, 128 } },
{ "Eggplant", { 97, 64, 81 } },
{ "Eggshell", { 240, 234, 214 } },
{ "Egyptian Blue", { 16, 52, 166 } },
{ "Electric Blue", { 125, 249, 255 } },
{ "Electric Crimson", { 255, 0, 63 } },
{ "Electric Cyan", { 0, 255, 255 } },
{ "Electric Green", { 0, 255, 0 } },
{ "Electric Indigo", { 111, 0, 255 } },
{ "Electric Lavender", { 244, 187, 255 } },
{ "Electric Lime", { 204, 255, 0 } },
{ "Electric Purple", { 191, 0, 255 } },
{ "Electric Ultramarine", { 63, 0, 255 } },
{ "Electric Violet", { 143, 0, 255 } },
{ "Electric Yellow", { 255, 255, 0 } },
{ "Emerald", { 80, 200, 120 } },
{ "English Lavender", { 180, 131, 149 } },
{ "Eton Blue", { 150, 200, 162 } },
{ "Fallow", { 193, 154, 107 } },
{ "Falu Red", { 128, 24, 24 } },
{ "Fandango", { 181, 51, 137 } },
{ "Fashion Fuchsia", { 244, 0, 161 } },
{ "Fawn", { 229, 170, 112 } },
{ "Feldgrau", { 77, 93, 83 } },
{ "Fern Green", { 79, 121, 66 } },
{ "Ferrari Red", { 255, 40, 0 } },
{ "Field Drab", { 108, 84, 30 } },
{ "Fire Engine Red", { 206, 32, 41 } },
{ "Firebrick", { 178, 34, 34 } },
{ "Flame", { 226, 88, 34 } },
{ "Flamingo Pink", { 252, 142, 172 } },
{ "Flavescent", { 247, 233, 142 } },
{ "Flax", { 238, 220, 130 } },
{ "Floral White", { 255, 250, 240 } },
{ "Fluorescent Orange", { 255, 191, 0 } },
{ "Fluorescent Pink", { 255, 20, 147 } },
{ "Fluorescent Yellow", { 204, 255, 0 } },
{ "Folly", { 255, 0, 79 } },
{ "Forest Green", { 1, 68, 33 } },
{ "French Beige", { 166, 123, 91 } },
{ "French Blue", { 0, 114, 187 } },
{ "French Lilac", { 134, 96, 142 } },
{ "French Lime", { 204, 255, 0 } },
{ "French Raspberry", { 199, 44, 72 } },
{ "French Rose", { 246, 74, 138 } },
{ "Fuchsia", { 255, 0, 255 } },
{ "Fuchsia Pink", { 255, 119, 255 } },
{ "Fuchsia Rose", { 199, 67, 117 } },
{ "Fulvous", { 228, 132, 0 } },
{ "Fuzzy Wuzzy", { 204, 102, 102 } },
{ "Gainsboro", { 220, 220, 220 } },
{ "Gamboge", { 228, 155, 15 } },
{ "Ghost White", { 248, 248, 255 } },
{ "Ginger", { 176, 101, 0 } },
{ "Glaucous", { 96, 130, 182 } },
{ "Glitter", { 230, 232, 250 } },
{ "Gold", { 212, 175, 55 } },
{ "Golden Brown", { 153, 101, 21 } },
{ "Golden Poppy", { 252, 194, 0 } },
{ "Golden Yellow", { 255, 223, 0 } },
{ "Goldenrod", { 218, 165, 32 } },
{ "Granny Smith Apple", { 168, 228, 160 } },
{ "Gray", { 128, 128, 128 } },
{ "Gray-Asparagus", { 70, 89, 69 } },
{ "Green", { 0, 255, 0 } },
{ "Green-Yellow", { 173, 255, 47 } },
{ "Grullo", { 169, 154, 134 } },
{ "Guppie Green", { 0, 255, 127 } },
{ "Halayà úBe", { 102, 56, 84 } },
{ "Han Blue", { 68, 108, 207 } },
{ "Han Purple", { 82, 24, 250 } },
{ "Hansa Yellow", { 233, 214, 107 } },
{ "Harlequin", { 63, 255, 0 } },
{ "Harvard Crimson", { 201, 0, 22 } },
{ "Harvest Gold", { 218, 145, 0 } },
{ "Heart Gold", { 128, 128, 0 } },
{ "Heliotrope", { 223, 115, 255 } },
{ "Hollywood Cerise", { 244, 0, 161 } },
{ "Honeydew", { 240, 255, 240 } },
{ "Honolulu Blue", { 0, 127, 191 } },
{ "Hooker'S Green", { 73, 121, 107 } },
{ "Hot Magenta", { 255, 29, 206 } },
{ "Hot Pink", { 255, 105, 180 } },
{ "Hunter Green", { 53, 94, 59 } },
{ "Iceberg", { 113, 166, 210 } },
{ "Icterine", { 252, 247, 94 } },
{ "Imperial Blue", { 0, 35, 149 } },
{ "Inchworm", { 178, 236, 93 } },
{ "India Green", { 19, 136, 8 } },
{ "Indian Red", { 205, 92, 92 } },
{ "Indian Yellow", { 227, 168, 87 } },
{ "Indigo", { 111, 0, 255 } },
{ "International Klein Blue", { 0, 47, 167 } },
{ "International Orange", { 186, 22, 12 } },
{ "Iris", { 90, 79, 207 } },
{ "Isabelline", { 244, 240, 236 } },
{ "Islamic Green", { 0, 144, 0 } },
{ "Ivory", { 255, 255, 240 } },
{ "Jade", { 0, 168, 107 } },
{ "Jasmine", { 248, 222, 126 } },
{ "Jasper", { 215, 59, 62 } },
{ "Jazzberry Jam", { 165, 11, 94 } },
{ "Jet", { 52, 52, 52 } },
{ "Jonquil", { 250, 218, 94 } },
{ "June Bud", { 189, 218, 87 } },
{ "Jungle Green", { 41, 171, 135 } },
{ "Kelly Green", { 76, 187, 23 } },
{ "Kenyan Copper", { 124, 28, 5 } },
{ "Khaki", { 195, 176, 145 } },
{ "Ku Crimson", { 232, 0, 13 } },
{ "La Salle Green", { 8, 120, 48 } },
{ "Languid Lavender", { 214, 202, 221 } },
{ "Lapis Lazuli", { 38, 97, 156 } },
{ "Laser Lemon", { 254, 254, 34 } },
{ "Laurel Green", { 169, 186, 157 } },
{ "Lava", { 207, 16, 32 } },
{ "Lavender Blue", { 204, 204, 255 } },
{ "Lavender Blush", { 255, 240, 245 } },
{ "Lavender Gray", { 196, 195, 208 } },
{ "Lavender Indigo", { 148, 87, 235 } },
{ "Lavender Magenta", { 238, 130, 238 } },
{ "Lavender Mist", { 230, 230, 250 } },
{ "Lavender Pink", { 251, 174, 210 } },
{ "Lavender Purple", { 150, 123, 182 } },
{ "Lavender Rose", { 251, 160, 227 } },
{ "Lawn Green", { 124, 252, 0 } },
{ "Lemon", { 255, 247, 0 } },
{ "Lemon Chiffon", { 255, 250, 205 } },
{ "Lemon Lime", { 227, 255, 0 } },
{ "Licorice", { 26, 17, 16 } },
{ "Light Apricot", { 253, 213, 177 } },
{ "Light Blue", { 173, 216, 230 } },
{ "Light Brown", { 181, 101, 29 } },
{ "Light Carmine Pink", { 230, 103, 113 } },
{ "Light Coral", { 240, 128, 128 } },
{ "Light Cornflower Blue", { 147, 204, 234 } },
{ "Light Crimson", { 245, 105, 145 } },
{ "Light Cyan", { 224, 255, 255 } },
{ "Light Fuchsia Pink", { 249, 132, 239 } },
{ "Light Goldenrod Yellow", { 250, 250, 210 } },
{ "Light Gray", { 211, 211, 211 } },
{ "Light Green", { 144, 238, 144 } },
{ "Light Khaki", { 240, 230, 140 } },
{ "Light Pastel Purple", { 177, 156, 217 } },
{ "Light Pink", { 255, 182, 193 } },
{ "Light Red Ochre", { 233, 116, 81 } },
{ "Light Salmon", { 255, 160, 122 } },
{ "Light Salmon Pink", { 255, 153, 153 } },
{ "Light Sea Green", { 32, 178, 170 } },
{ "Light Sky Blue", { 135, 206, 250 } },
{ "Light Slate Gray", { 119, 136, 153 } },
{ "Light Taupe", { 179, 139, 109 } },
{ "Light Thulian Pink", { 230, 143, 172 } },
{ "Light Yellow", { 255, 255, 224 } },
{ "Lilac", { 200, 162, 200 } },
{ "Lime Green", { 50, 205, 50 } },
{ "Limerick", { 157, 194, 9 } },
{ "Lincoln Green", { 25, 89, 5 } },
{ "Linen", { 250, 240, 230 } },
{ "Lion", { 193, 154, 107 } },
{ "Little Boy Blue", { 108, 160, 220 } },
{ "Liver", { 83, 75, 79 } },
{ "Lust", { 230, 32, 32 } },
{ "Magenta", { 255, 0, 255 } },
{ "Magic Mint", { 170, 240, 209 } },
{ "Magnolia", { 248, 244, 255 } },
{ "Mahogany", { 192, 64, 0 } },
{ "Maize", { 251, 236, 93 } },
{ "Majorelle Blue", { 96, 80, 220 } },
{ "Malachite", { 11, 218, 81 } },
{ "Manatee", { 151, 154, 170 } },
{ "Mango Tango", { 255, 130, 67 } },
{ "Mantis", { 116, 195, 101 } },
{ "Mardi Gras", { 136, 0, 133 } },
{ "Maroon", { 128, 0, 0 } },
{ "Mauve", { 224, 176, 255 } },
{ "Mauve Taupe", { 145, 95, 109 } },
{ "Mauvelous", { 239, 152, 170 } },
{ "Maya Blue", { 115, 194, 251 } },
{ "Meat Brown", { 229, 183, 59 } },
{ "Medium Aquamarine", { 102, 221, 170 } },
{ "Medium Blue", { 0, 0, 205 } },
{ "Medium Candy Apple Red", { 226, 6, 44 } },
{ "Medium Carmine", { 175, 64, 53 } },
{ "Medium Champagne", { 243, 229, 171 } },
{ "Medium Electric Blue", { 3, 80, 150 } },
{ "Medium Jungle Green", { 28, 53, 45 } },
{ "Medium Lavender Magenta", { 221, 160, 221 } },
{ "Medium Orchid", { 186, 85, 211 } },
{ "Medium Persian Blue", { 0, 103, 165 } },
{ "Medium Purple", { 147, 112, 219 } },
{ "Medium Red-Violet", { 187, 51, 133 } },
{ "Medium Ruby", { 170, 64, 105 } },
{ "Medium Sea Green", { 60, 179, 113 } },
{ "Medium Slate Blue", { 123, 104, 238 } },
{ "Medium Spring Bud", { 201, 220, 135 } },
{ "Medium Spring Green", { 0, 250, 154 } },
{ "Medium Taupe", { 103, 76, 71 } },
{ "Medium Turquoise", { 72, 209, 204 } },
{ "Medium Tuscan Red", { 121, 68, 59 } },
{ "Medium Vermilion", { 217, 96, 59 } },
{ "Medium Violet-Red", { 199, 21, 133 } },
{ "Mellow Apricot", { 248, 184, 120 } },
{ "Mellow Yellow", { 248, 222, 126 } },
{ "Melon", { 253, 188, 180 } },
{ "Midnight Blue", { 25, 25, 112 } },
{ "Mikado Yellow", { 255, 196, 12 } },
{ "Mint", { 62, 180, 137 } },
{ "Mint Cream", { 245, 255, 250 } },
{ "Mint Green", { 152, 255, 152 } },
{ "Misty Rose", { 255, 228, 225 } },
{ "Moccasin", { 250, 235, 215 } },
{ "Mode Beige", { 150, 113, 23 } },
{ "Moonstone Blue", { 115, 169, 194 } },
{ "Mordant Red 19", { 174, 12, 0 } },
{ "Moss Green", { 173, 223, 173 } },
{ "Mountain Meadow", { 48, 186, 143 } },
{ "Mountbatten Pink", { 153, 122, 141 } },
{ "Msu Green", { 24, 69, 59 } },
{ "Mulberry", { 197, 75, 140 } },
{ "Mustard", { 255, 219, 88 } },
{ "Myrtle", { 33, 66, 30 } },
{ "Nadeshiko Pink", { 246, 173, 198 } },
{ "Napier Green", { 42, 128, 0 } },
{ "Naples Yellow", { 250, 218, 94 } },
{ "Navajo White", { 255, 222, 173 } },
{ "Navy Blue", { 0, 0, 128 } },
{ "Neon Carrot", { 255, 163, 67 } },
{ "Neon Fuchsia", { 254, 65, 100 } },
{ "Neon Green", { 57, 255, 20 } },
{ "New York Pink", { 215, 131, 127 } },
{ "Non-Photo Blue", { 164, 221, 237 } },
{ "North Texas Green", { 5, 144, 51 } },
{ "Ocean Boat Blue", { 0, 119, 190 } },
{ "Ochre", { 204, 119, 34 } },
{ "Office Green", { 0, 128, 0 } },
{ "Old Gold", { 207, 181, 59 } },
{ "Old Lace", { 253, 245, 230 } },
{ "Old Lavender", { 121, 104, 120 } },
{ "Old Mauve", { 103, 49, 71 } },
{ "Old Rose", { 192, 128, 129 } },
{ "Olive", { 128, 128, 0 } },
{ "Olivine", { 154, 185, 115 } },
{ "Onyx", { 53, 56, 57 } },
{ "Opera Mauve", { 183, 132, 167 } },
{ "Orange", { 255, 127, 0 } },
{ "Orchid", { 218, 112, 214 } },
{ "Otter Brown", { 101, 67, 33 } },
{ "Ou Crimson Red", { 153, 0, 0 } },
{ "Outer Space", { 65, 74, 76 } },
{ "Outrageous Orange", { 255, 110, 74 } },
{ "Oxford Blue", { 0, 33, 71 } },
{ "Pakistan Green", { 0, 102, 0 } },
{ "Palatinate Blue", { 39, 59, 226 } },
{ "Palatinate Purple", { 104, 40, 96 } },
{ "Pale Aqua", { 188, 212, 230 } },
{ "Pale Blue", { 175, 238, 238 } },
{ "Pale Brown", { 152, 118, 84 } },
{ "Pale Carmine", { 175, 64, 53 } },
{ "Pale Cerulean", { 155, 196, 226 } },
{ "Pale Chestnut", { 221, 173, 175 } },
{ "Pale Copper", { 218, 138, 103 } },
{ "Pale Cornflower Blue", { 171, 205, 239 } },
{ "Pale Gold", { 230, 190, 138 } },
{ "Pale Goldenrod", { 238, 232, 170 } },
{ "Pale Green", { 152, 251, 152 } },
{ "Pale Lavender", { 220, 208, 255 } },
{ "Pale Magenta", { 249, 132, 229 } },
{ "Pale Pink", { 250, 218, 221 } },
{ "Pale Plum", { 221, 160, 221 } },
{ "Pale Red-Violet", { 219, 112, 147 } },
{ "Pale Robin Egg Blue", { 150, 222, 209 } },
{ "Pale Silver", { 201, 192, 187 } },
{ "Pale Spring Bud", { 236, 235, 189 } },
{ "Pale Taupe", { 188, 152, 126 } },
{ "Pale Violet-Red", { 219, 112, 147 } },
{ "Pansy Purple", { 120, 24, 74 } },
{ "Papaya Whip", { 255, 239, 213 } },
{ "Paris Green", { 80, 200, 120 } },
{ "Pastel Blue", { 174, 198, 207 } },
{ "Pastel Brown", { 131, 105, 83 } },
{ "Pastel Gray", { 207, 207, 196 } },
{ "Pastel Green", { 119, 221, 119 } },
{ "Pastel Magenta", { 244, 154, 194 } },
{ "Pastel Orange", { 255, 179, 71 } },
{ "Pastel Pink", { 222, 165, 164 } },
{ "Pastel Purple", { 179, 158, 181 } },
{ "Pastel Red", { 255, 105, 97 } },
{ "Pastel Violet", { 203, 153, 201 } },
{ "Pastel Yellow", { 253, 253, 150 } },
{ "Patriarch", { 128, 0, 128 } },
{ "Payne'S Grey", { 83, 104, 120 } },
{ "Peach", { 255, 229, 180 } },
{ "Peach-Orange", { 255, 204, 153 } },
{ "Peach Puff", { 255, 218, 185 } },
{ "Peach-Yellow", { 250, 223, 173 } },
{ "Pear", { 209, 226, 49 } },
{ "Pearl", { 234, 224, 200 } },
{ "Pearl Aqua", { 136, 216, 192 } },
{ "Pearly Purple", { 183, 104, 162 } },
{ "Peridot", { 230, 226, 0 } },
{ "Periwinkle", { 204, 204, 255 } },
{ "Persian Blue", { 28, 57, 187 } },
{ "Persian Green", { 0, 166, 147 } },
{ "Persian Indigo", { 50, 18, 122 } },
{ "Persian Orange", { 217, 144, 88 } },
{ "Persian Pink", { 247, 127, 190 } },
{ "Persian Plum", { 112, 28, 28 } },
{ "Persian Red", { 204, 51, 51 } },
{ "Persian Rose", { 254, 40, 162 } },
{ "Persimmon", { 236, 88, 0 } },
{ "Peru", { 205, 133, 63 } },
{ "Phlox", { 223, 0, 255 } },
{ "Phthalo Blue", { 0, 15, 137 } },
{ "Phthalo Green", { 18, 53, 36 } },
{ "Piggy Pink", { 253, 221, 230 } },
{ "Pine Green", { 1, 121, 111 } },
{ "Pink", { 255, 192, 203 } },
{ "Pink Lace", { 255, 221, 244 } },
{ "Pink-Orange", { 255, 153, 102 } },
{ "Pink Pearl", { 231, 172, 207 } },
{ "Pink Sherbet", { 247, 143, 167 } },
{ "Pistachio", { 147, 197, 114 } },
{ "Platinum", { 229, 228, 226 } },
{ "Plum", { 142, 69, 133 } },
{ "Portland Orange", { 255, 90, 54 } },
{ "Powder Blue", { 176, 224, 230 } },
{ "Princeton Orange", { 255, 143, 0 } },
{ "Prune", { 112, 28, 28 } },
{ "Prussian Blue", { 0, 49, 83 } },
{ "Psychedelic Purple", { 223, 0, 255 } },
{ "Puce", { 204, 136, 153 } },
{ "Pumpkin", { 255, 117, 24 } },
{ "Purple Heart", { 105, 53, 156 } },
{ "Purple", { 128, 0, 128 } },
{ "Purple Mountain Majesty", { 150, 120, 182 } },
{ "Purple Pizzazz", { 254, 78, 218 } },
{ "Purple Taupe", { 80, 64, 77 } },
{ "Quartz", { 81, 72, 79 } },
{ "Rackley", { 93, 138, 168 } },
{ "Radical Red", { 255, 53, 94 } },
{ "Rajah", { 251, 171, 96 } },
{ "Raspberry", { 227, 11, 93 } },
{ "Raspberry Glace", { 145, 95, 109 } },
{ "Raspberry Pink", { 226, 80, 152 } },
{ "Raspberry Rose", { 179, 68, 108 } },
{ "Raw Umber", { 130, 102, 68 } },
{ "Razzle Dazzle Rose", { 255, 51, 204 } },
{ "Razzmatazz", { 227, 37, 107 } },
{ "Red", { 255, 0, 0 } },
{ "Red-Brown", { 165, 42, 42 } },
{ "Red Devil", { 134, 1, 17 } },
{ "Red-Orange", { 255, 83, 73 } },
{ "Red-Violet", { 199, 21, 133 } },
{ "Redwood", { 171, 78, 82 } },
{ "Regalia", { 82, 45, 128 } },
{ "Resolution Blue", { 0, 35, 135 } },
{ "Rich Black", { 0, 64, 64 } },
{ "Rich Brilliant Lavender", { 241, 167, 254 } },
{ "Rich Carmine", { 215, 0, 64 } },
{ "Rich Electric Blue", { 8, 146, 208 } },
{ "Rich Lavender", { 167, 107, 207 } },
{ "Rich Lilac", { 182, 102, 210 } },
{ "Rich Maroon", { 176, 48, 96 } },
{ "Rifle Green", { 65, 72, 51 } },
{ "Robin Egg Blue", { 0, 204, 204 } },
{ "Rose", { 255, 0, 127 } },
{ "Rose Bonbon", { 249, 66, 158 } },
{ "Rose Ebony", { 103, 72, 70 } },
{ "Rose Gold", { 183, 110, 121 } },
{ "Rose Madder", { 227, 38, 54 } },
{ "Rose Pink", { 255, 102, 204 } },
{ "Rose Quartz", { 170, 152, 169 } },
{ "Rose Taupe", { 144, 93, 93 } },
{ "Rose Vale", { 171, 78, 82 } },
{ "Rosewood", { 101, 0, 11 } },
{ "Rosso Corsa", { 212, 0, 0 } },
{ "Rosy Brown", { 188, 143, 143 } },
{ "Royal Azure", { 0, 56, 168 } },
{ "Royal Blue", { 0, 35, 102 } },
{ "Royal Fuchsia", { 202, 44, 146 } },
{ "Royal Purple", { 120, 81, 169 } },
{ "Royal Yellow", { 250, 218, 94 } },
{ "Rubine Red", { 209, 0, 86 } },
{ "Ruby", { 224, 17, 95 } },
{ "Ruby Red", { 155, 17, 30 } },
{ "Ruddy", { 255, 0, 40 } },
{ "Ruddy Brown", { 187, 101, 40 } },
{ "Ruddy Pink", { 225, 142, 150 } },
{ "Rufous", { 168, 28, 7 } },
{ "Russet", { 128, 70, 27 } },
{ "Rust", { 183, 65, 14 } },
{ "Rusty Red", { 218, 44, 67 } },
{ "Sacramento State Green", { 0, 86, 63 } },
{ "Saddle Brown", { 139, 69, 19 } },
{ "Safety Orange", { 255, 103, 0 } },
{ "Saffron", { 244, 196, 48 } },
{ "Salmon", { 255, 140, 105 } },
{ "Salmon Pink", { 255, 145, 164 } },
{ "Sand", { 194, 178, 128 } },
{ "Sand Dune", { 150, 113, 23 } },
{ "Sandstorm", { 236, 213, 64 } },
{ "Sandy Brown", { 244, 164, 96 } },
{ "Sandy Taupe", { 150, 113, 23 } },
{ "Sangria", { 146, 0, 10 } },
{ "Sap Green", { 80, 125, 42 } },
{ "Sapphire", { 15, 82, 186 } },
{ "Sapphire Blue", { 0, 103, 165 } },
{ "Satin Sheen Gold", { 203, 161, 53 } },
{ "Scarlet", { 255, 36, 0 } },
{ "School Bus Yellow", { 255, 216, 0 } },
{ "Screamin' Green", { 118, 255, 122 } },
{ "Sea Blue", { 0, 105, 148 } },
{ "Sea Green", { 46, 139, 87 } },
{ "Seal Brown", { 50, 20, 20 } },
{ "Seashell", { 255, 245, 238 } },
{ "Selective Yellow", { 255, 186, 0 } },
{ "Sepia", { 112, 66, 20 } },
{ "Shadow", { 138, 121, 93 } },
{ "Shamrock Green", { 0, 158, 96 } },
{ "Shocking Pink", { 252, 15, 192 } },
{ "Sienna", { 136, 45, 23 } },
{ "Silver", { 192, 192, 192 } },
{ "Sinopia", { 203, 65, 11 } },
{ "Skobeloff", { 0, 116, 116 } },
{ "Sky Blue", { 135, 206, 235 } },
{ "Sky Magenta", { 207, 113, 175 } },
{ "Slate Blue", { 106, 90, 205 } },
{ "Slate Gray", { 112, 128, 144 } },
{ "Smokey Topaz", { 147, 61, 65 } },
{ "Smoky Black", { 16, 12, 8 } },
{ "Snow", { 255, 250, 250 } },
{ "Spiro Disco Ball", { 15, 192, 252 } },
{ "Spring Bud", { 167, 252, 0 } },
{ "Spring Green", { 0, 255, 127 } },
{ "St. Patrick'S Blue", { 35, 41, 122 } },
{ "Steel Blue", { 70, 130, 180 } },
{ "Stil De Grain Yellow", { 250, 218, 94 } },
{ "Stizza", { 153, 0, 0 } },
{ "Stormcloud", { 79, 102, 106 } },
{ "Straw", { 228, 217, 111 } },
{ "Sunglow", { 255, 204, 51 } },
{ "Sunset", { 250, 214, 165 } },
{ "Tan", { 210, 180, 140 } },
{ "Tangelo", { 249, 77, 0 } },
{ "Tangerine", { 242, 133, 0 } },
{ "Tangerine Yellow", { 255, 204, 0 } },
{ "Tango Pink", { 228, 113, 122 } },
{ "Taupe", { 72, 60, 50 } },
{ "Taupe Gray", { 139, 133, 137 } },
{ "Tea Green", { 208, 240, 192 } },
{ "Teal", { 0, 128, 128 } },
{ "Teal Blue", { 54, 117, 136 } },
{ "Teal Green", { 0, 130, 127 } },
{ "Telemagenta", { 207, 52, 118 } },
{ "Terra Cotta", { 226, 114, 91 } },
{ "Thistle", { 216, 191, 216 } },
{ "Thulian Pink", { 222, 111, 161 } },
{ "Tickle Me Pink", { 252, 137, 172 } },
{ "Tiffany Blue", { 10, 186, 181 } },
{ "Tiger'S Eye", { 224, 141, 60 } },
{ "Timberwolf", { 219, 215, 210 } },
{ "Titanium Yellow", { 238, 230, 0 } },
{ "Tomato", { 255, 99, 71 } },
{ "Toolbox", { 116, 108, 192 } },
{ "Topaz", { 255, 200, 124 } },
{ "Tractor Red", { 253, 14, 53 } },
{ "Trolley Grey", { 128, 128, 128 } },
{ "Tropical Rain Forest", { 0, 117, 94 } },
{ "True Blue", { 0, 115, 207 } },
{ "Tufts Blue", { 65, 125, 193 } },
{ "Tumbleweed", { 222, 170, 136 } },
{ "Turkish Rose", { 181, 114, 129 } },
{ "Turquoise", { 48, 213, 200 } },
{ "Turquoise Blue", { 0, 255, 239 } },
{ "Turquoise Green", { 160, 214, 180 } },
{ "Tuscan Red", { 124, 72, 72 } },
{ "Twilight Lavender", { 138, 73, 107 } },
{ "Tyrian Purple", { 102, 2, 60 } },
{ "Ua Blue", { 0, 51, 170 } },
{ "Ua Red", { 217, 0, 76 } },
{ "Ube", { 136, 120, 195 } },
{ "Ucla Blue", { 83, 104, 149 } },
{ "Ucla Gold", { 255, 179, 0 } },
{ "Ufo Green", { 60, 208, 112 } },
{ "Ultra Pink", { 255, 111, 255 } },
{ "Ultramarine", { 18, 10, 143 } },
{ "Ultramarine Blue", { 65, 102, 245 } },
{ "Umber", { 99, 81, 71 } },
{ "Unbleached Silk", { 255, 221, 202 } },
{ "United Nations Blue", { 91, 146, 229 } },
{ "University Of California Gold", { 183, 135, 39 } },
{ "Unmellow Yellow", { 255, 255, 102 } },
{ "Up Forest Green", { 1, 68, 33 } },
{ "Up Maroon", { 123, 17, 19 } },
{ "Upsdell Red", { 174, 32, 41 } },
{ "Urobilin", { 225, 173, 33 } },
{ "Usafa Blue", { 0, 79, 152 } },
{ "Usc Cardinal", { 153, 0, 0 } },
{ "Usc Gold", { 255, 204, 0 } },
{ "Utah Crimson", { 211, 0, 63 } },
{ "Vanilla", { 243, 229, 171 } },
{ "Vegas Gold", { 197, 179, 88 } },
{ "Venetian Red", { 200, 8, 21 } },
{ "Verdigris", { 67, 179, 174 } },
{ "Vermilion", { 227, 66, 52 } },
{ "Veronica", { 160, 32, 240 } },
{ "Violet", { 143, 0, 255 } },
{ "Violet-Blue", { 50, 74, 178 } },
{ "Violet (Color Wheel)", { 127, 0, 255 } },
{ "Viridian", { 64, 130, 109 } },
{ "Vivid Auburn", { 146, 39, 36 } },
{ "Vivid Burgundy", { 159, 29, 53 } },
{ "Vivid Cerise", { 218, 29, 129 } },
{ "Vivid Tangerine", { 255, 160, 137 } },
{ "Vivid Violet", { 159, 0, 255 } },
{ "Warm Black", { 0, 66, 66 } },
{ "Waterspout", { 164, 244, 249 } },
{ "Wenge", { 100, 84, 82 } },
{ "Wheat", { 245, 222, 179 } },
{ "White", { 255, 255, 255 } },
{ "White Smoke", { 245, 245, 245 } },
{ "Wild Blue Yonder", { 162, 173, 208 } },
{ "Wild Strawberry", { 255, 67, 164 } },
{ "Wild Watermelon", { 252, 108, 133 } },
{ "Wine", { 114, 47, 55 } },
{ "Wine Dregs", { 103, 49, 71 } },
{ "Wisteria", { 201, 160, 220 } },
{ "Wood Brown", { 193, 154, 107 } },
{ "Xanadu", { 115, 134, 120 } },
{ "Yale Blue", { 15, 77, 146 } },
{ "Yellow", { 255, 255, 0 } },
{ "Yellow-Green", { 154, 205, 50 } },
{ "Yellow Orange", { 255, 174, 66 } },
{ "Zaffre", { 0, 20, 168 } },
{ "Zinnwaldite Brown", { 44, 22, 8 } },
#endif
{0, {0, 0, 0}},
};
ColorInfo colorForName(const char *name) {
String needleName(name);
needleName.toLowerCase();
for (int i = 0; color_data[i].name != 0; i++) {
String colorName(color_data[i].name);
colorName.toLowerCase();
if (colorName == needleName) {
return color_data[i];
}
}
return ColorInfo();
}
const ColorInfo* allColors() {
return color_data;
}

View File

@ -1,10 +0,0 @@
#pragma once
#include <FastLED.h>
typedef struct ColorInfo {
const char *name;
CRGB rgb;
} ColorInfo;
ColorInfo colorForName(const char *name);
const ColorInfo* allColors();

View File

@ -2,19 +2,19 @@
#include "../Static.h"
void
doBPM(Args& args, Print& out)
BPM::doSetBPM(Args& args, Print& out)
{
uint8_t newBPM(atoi(args[1].c_str()));
Static<BPM>::instance()->setBPM(newBPM);
setBPM(newBPM);
}
const std::vector<Command> _commands = {
{"bpm", doBPM}
};
const std::vector<Command>&
BPM::commands() const
{
static const std::vector<Command> _commands = {
{"bpm", &BPM::doSetBPM}
};
return _commands;
}

View File

@ -77,4 +77,6 @@ private:
uint16_t trash;
m_timings.take(trash);
}
void doSetBPM(Args& args, Print& print);
};

View File

@ -31,7 +31,7 @@ Buttons::read()
//Log.info("Not emitting release from previous chord");
m_wasChord[i] = false;
} else {
return InputEvent{InputEvent::UserInput, buttonID};
return InputEvent{InputEvent::ButtonPress, buttonID};
}
}
}

View File

@ -10,14 +10,14 @@ struct ScheduleEntry {
std::array<ScheduleEntry, 10> schedule{{
{0, 0},
{5, 0},
{6, 0},
{7, 10},
{6, 10},
{7, 20},
{8, 80},
{11, 120},
{18, 200},
{19, 255},
{22, 120},
{23, 20}
{23, 5}
}};
class CircadianRhythm : public InputSource {
@ -37,14 +37,9 @@ class CircadianRhythm : public InputSource {
// 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) {
} else if (cur.hour > hour) {
end = cur;
break;
}
@ -83,9 +78,9 @@ class CircadianRhythm : public InputSource {
hour = 0;
minute = 0;
}
Log.notice("Current time: %d:%d", hour, minute);
Log.notice(F("Current time: %d:%d"), hour, minute);
auto brightness = brightnessForTime(hour, minute);
Log.notice("Adjusting brightness to %d", brightness);
Log.notice(F("Adjusting brightness to %d"), brightness);
return InputEvent{InputEvent::SetBrightness, brightness};
}
return InputEvent{};

View File

@ -1,5 +0,0 @@
#include "./ConfigInput.h"
#include "./Static.h"
STATIC_ALLOC(ConfigInput);
STATIC_TASK(ConfigInput);

View File

@ -1,77 +0,0 @@
#pragma once
#include <Figments.h>
class ConfigInputTask : public BufferedInputSource {
public:
ConfigInputTask() : BufferedInputSource("ConfigInput") {}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::UserInput) {
Buttons::Chord chord = (Buttons::Chord) evt.asInt();
switch (chord) {
case Buttons::Circle:
m_currentIntent = nextIntent();
//Log.info("Next setting... (%d)", m_currentIntent);
break;
case Buttons::CircleTriangle:
//Log.info("Increment...");
increment();
break;
case Buttons::CircleCross:
//Log.info("Decrement...");
decrement();
break;
case Buttons::Triangle:
//Log.info("Save...");
setEvent(InputEvent::SaveConfigurationRequest);
break;
default:
break;
}
}
}
private:
InputEvent::Intent m_currentIntent = InputEvent::SetDisplayLength;
void decrement() {
int current = 0;
switch (m_currentIntent) {
case InputEvent::SetDisplayLength:
current = Static<ConfigService>::instance()->coordMap()->pixelCount;
break;
case InputEvent::SetDisplayOffset:
current = Static<ConfigService>::instance()->coordMap()->startPixel;
break;
default:
break;
}
setEvent(InputEvent{m_currentIntent, current - 1});
}
void increment() {
int current = 0;
switch (m_currentIntent) {
case InputEvent::SetDisplayLength:
current = Static<ConfigService>::instance()->coordMap()->pixelCount;
break;
case InputEvent::SetDisplayOffset:
current = Static<ConfigService>::instance()->coordMap()->startPixel;
break;
default:
break;
}
setEvent(InputEvent{m_currentIntent, current + 1});
}
InputEvent::Intent nextIntent() {
switch (m_currentIntent) {
case InputEvent::SetDisplayLength:
return InputEvent::SetDisplayOffset;
case InputEvent::SetDisplayOffset:
return InputEvent::SetDisplayLength;
default:
return InputEvent::None;
}
}
};

41
src/inputs/Idle.cpp Normal file
View File

@ -0,0 +1,41 @@
#include "Idle.h"
#include "../Static.h"
IdleTimer::IdleTimer()
: InputSource("IdleTimer"),
m_idleTime(60), m_secondsRemaining(0)
{
}
InputEvent
IdleTimer::read()
{
EVERY_N_SECONDS(1) {
if (m_secondsRemaining > 0) {
m_secondsRemaining -= 1;
if (m_secondsRemaining == 0) {
return InputEvent::IdleStart;
}
}
}
return InputEvent::None;
}
void
IdleTimer::handleEvent(const InputEvent& evt)
{
switch(evt.intent) {
case InputEvent::IdleStart:
m_secondsRemaining = 0;
break;
case InputEvent::IdleStop:
m_secondsRemaining = m_idleTime;
break;
default:
break;
}
}
STATIC_ALLOC(IdleTimer);
STATIC_TASK(IdleTimer);

12
src/inputs/Idle.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <Figments.h>
class IdleTimer : public InputSource {
public:
IdleTimer();
void handleEvent(const InputEvent& evt);
virtual InputEvent read() override;
private:
uint16_t m_idleTime;
uint16_t m_secondsRemaining;
};

View File

@ -1,3 +1,4 @@
#ifdef CONFIG_MPU6050
#include "MPU6050.h"
#include <Wire.h>
#include "../Config.h"
@ -64,3 +65,4 @@ MPU5060::read()
STATIC_ALLOC(MPU5060);
STATIC_TASK(MPU5060);
#endif

View File

@ -126,7 +126,7 @@ SerialInput::read()
}
void
doHelp(Args& args, Print& out)
SerialInput::doHelp(Args& args, Print& out)
{
out.println("Available commands:");
auto sched = MainLoop::instance()->scheduler;
@ -139,14 +139,13 @@ doHelp(Args& args, Print& out)
out.println();
}
const std::vector<Command> serialCommands = {
{"help", doHelp}
};
const std::vector<Command>&
SerialInput::commands() const
{
return serialCommands;
static const std::vector<Command> _commands = {
{"help", &SerialInput::doHelp}
};
return _commands;
}
void
@ -158,7 +157,7 @@ SerialInput::doCommand() {
for(auto task : sched.tasks) {
for(auto &command : task->commands()) {
if (cmdName == command.name) {
command.func(args, m_logPrinter);
command.invoke(task, args, m_logPrinter);
return;
}
}

View File

@ -47,16 +47,18 @@ private:
EscapeSequence,
CSI
};
String m_buf;
ParseState m_state;
char m_escapeSeq[3];
void doCommand();
LogPrinter m_logPrinter;
bool m_canRedraw = true;
Ringbuf<String, 5> m_history;
int m_historyOffset = 0;
void doCommand();
InputEvent parseNormal(char nextChar);
InputEvent parseEscape(char nextChar);
InputEvent parseCSI(char nextChar);
void doHelp(Args& args, Print& out);
};

View File

@ -34,28 +34,6 @@ Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()
}
});*/
InputMapper keyMap([](const InputEvent& evt) {
if (evt.intent == InputEvent::UserInput) {
Buttons::Chord chord = (Buttons::Chord)evt.asInt();
switch(chord) {
case Buttons::Circle:
return InputEvent::PowerToggle;
break;
case Buttons::Triangle:
return InputEvent::NextPattern;
break;
case Buttons::Cross:
return InputEvent::UserInput;
break;
default:
break;
}
}
return InputEvent::None;
}, "Keymap");
REGISTER_TASK(keyMap);
// Cycle some random colors
ColorSequenceInput<9> idleCycle{{
CRGB(0, 123, 167), // Cerulean
@ -83,24 +61,24 @@ MainLoop* runner = &SafeMode::safeModeApp;
void setup() {
// Turn on,
Platform::preSetup();
Log.notice(u8"🐛 Booting Renderbug!");
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(F(u8"\n\n\n🐛 Booting Renderbug!"));
Log.notice(F(u8"🐞 I am built for %d LEDs on pin %d"), HardwareConfig::MAX_LED_NUM, RENDERBUG_LED_PIN);
Log.notice(F(u8"📡 Platform %s version %s"), Platform::name(), Platform::version());
Log.notice(u8"Setting timezone to +2 (CEST)");
Platform::setTimezone(+2);
Log.notice(F(u8"Setting timezone to +2 (CEST)"));
Platform::setTimezone(+1);
Log.trace(u8"Setting up platform...");
Log.trace(F(u8"Setting up platform..."));
Platform::setup();
Platform::bootSplash();
Log.notice(u8"💡 Starting FastLED on %d LEDs...", HardwareConfig::MAX_LED_NUM);
Log.notice(F(u8"💡 Starting FastLED on %d LEDs..."), HardwareConfig::MAX_LED_NUM);
Platform::addLEDs(leds, HardwareConfig::MAX_LED_NUM);
// Tune in,
if (Platform::bootopts.isSafeMode) {
Log.warning(u8"⚠️ Starting Figment in safe mode!!!");
Log.warning(F(u8"⚠️ Starting Figment in safe mode!!!"));
runner = &SafeMode::safeModeApp;
for(auto task : runner->scheduler.tasks) {
task->state = Task::Running;
@ -108,10 +86,10 @@ void setup() {
FastLED.showColor(CRGB(255, 0, 0));
FastLED.show();
} else {
Log.notice(u8"🌌 Starting Figment...");
Log.notice(F(u8"🌌 Starting Figment..."));
if (Platform::bootopts.isSetup) {
Log.warning(u8"🔧 Booting up into setup profile!!!");
Log.warning(F(u8"🔧 Booting up into setup profile!!!"));
Static<ConfigService>::instance()->overrideProfile("setup");
}
@ -124,8 +102,8 @@ void setup() {
Serial.flush();
runner->start();
Log.notice(u8"💽 %l bytes of free RAM", Platform::freeRam());
Log.notice(u8"🚀 Setup complete! Ready to rock and roll.");
Log.notice(F(u8"💽 %l bytes of free RAM"), Platform::freeRam());
Log.notice(F(u8"🚀 Setup complete! Ready to rock and roll."));
Serial.flush();
}

View File

@ -1,12 +1,13 @@
#include "MQTTTelemetry.h"
#include <ArduinoJson.h>
#include <StreamUtils.h>
#include "../../Static.h"
#include "../../Config.h"
#include "../../Platform.h"
StaticJsonDocument<1024> m_json;
static StaticJsonDocument<1024> m_json;
struct MQTTDevice {
const String id;
@ -31,7 +32,7 @@ const MQTTDevice Device{
Platform::deviceID(),
Platform::deviceName(),
Platform::model(),
#ifdef BOARD_ESP8266
#ifdef ESP8266
ESP.getSketchMD5(),
#else
"",
@ -95,8 +96,8 @@ const MQTTEntity Lightswitch {
"light", Device, "lightswitch"
};
const MQTTEntity flashlightSwitch {
"switch", Device, "flashlight"
const MQTTEntity IdleSwitch {
"switch", Device, "idle"
};
const MQTTEntity FPSSensor {
@ -111,70 +112,80 @@ MQTTTelemetry::MQTTTelemetry() : BufferedInputSource("MQTT"),
memset(m_hostBuf, 0, sizeof(m_hostBuf));
}
void
MQTTTelemetry::publishScenes()
{
for(auto scene : Static<Sequencer>::instance()->scenes()) {
m_json.clear();
String strName{scene.name};
MQTTEntity sceneObj {
"scene",
Device,
strName
};
sceneObj.toJson(m_json, false);
m_json["cmd_t"] = "~/set";
m_json["ret"] = false;
m_json["payload_on"] = "active";
publishDoc(sceneObj.configTopic().c_str(), false);
m_mqtt.subscribe(sceneObj.commandTopic().c_str());
Log.info("Published scene %s", sceneObj.configTopic().c_str());
}
}
void
MQTTTelemetry::onMQTTOnline()
{
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_json.clear();
Lightswitch.toJson(m_json);
m_json["brightness"] = true;
m_json["rgb"] = true;
publishDoc(Lightswitch.configTopic().c_str(), true);
m_mqtt.subscribe(Lightswitch.commandTopic().c_str());
m_json.clear();
IdleSwitch.toJson(m_json);
publishDoc(IdleSwitch.configTopic().c_str(), true);
m_mqtt.subscribe(IdleSwitch.commandTopic().c_str());
publishScenes();
m_json.clear();
FPSSensor.toJson(m_json, false);
m_json["unit_of_meas"] = "Frames/s";
publishDoc(FPSSensor.configTopic().c_str(), true);
#ifdef 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
}
void
MQTTTelemetry::handleEventOnline(const InputEvent& evt)
{
if (!m_mqtt.connected()) {
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")) {
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_json.clear();
Lightswitch.toJson(m_json);
int i = 0;
for(const Sequencer::Scene& scene : m_sequencer->scenes()) {
m_json["fx_list"][i++] = scene.name;
}
m_json["brightness"] = true;
m_json["rgb"] = true;
publishDoc(Lightswitch.configTopic().c_str(), true);
//Log.verbose("Publish %s %s", Lightswitch.configTopic().c_str(), buf);
//m_mqtt.publish(Lightswitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
m_mqtt.subscribe(Lightswitch.commandTopic().c_str());
m_json.clear();
flashlightSwitch.toJson(m_json, false);
m_json["cmd_t"] = "~/set";
m_json["ret"] = true;
publishDoc(flashlightSwitch.configTopic().c_str(), true);
//m_mqtt.publish(flashlightSwitch.configTopic().c_str(), (uint8_t*)buf, strlen(buf), true);
m_mqtt.subscribe(flashlightSwitch.commandTopic().c_str());
m_json.clear();
FPSSensor.toJson(m_json, false);
m_json["unit_of_meas"] = "Frames/s";
publishDoc(FPSSensor.configTopic().c_str(), true);
//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
onMQTTOnline();
} else {
Log.warning("Could not connect to MQTT");
}
} else {
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) {
String idleTopic = IdleSwitch.statTopic();
if (evt.intent == InputEvent::SetPower) {
m_json.clear();
m_isOn = evt.asInt() ? true : false;
m_json["state"] = m_isOn ? "ON" : "OFF";
@ -192,11 +203,18 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
m_json["color"]["b"] = color.b;
m_json["state"] = m_isOn ? "ON" : "OFF";
publishDoc(statTopic.c_str());
} else if (evt.intent == InputEvent::SetPattern) {
} else if (evt.intent == InputEvent::SetScene) {
m_json.clear();
m_json["effect"] = evt.asString();
m_json["state"] = m_isOn ? "ON" : "OFF";
publishDoc(statTopic.c_str());
} else if (evt.intent == InputEvent::IdleStart) {
m_json.clear();
m_json["state"] = "ON";
publishDoc(idleTopic.c_str());
} else if (evt.intent == InputEvent::IdleStop) {
m_json.clear();
m_json["state"] = "OFF";
publishDoc(idleTopic.c_str());
}
}
}
@ -255,42 +273,37 @@ MQTTTelemetry::loopOnline()
m_needHeartbeat = true;
}
if (m_needHeartbeat) {
m_json.clear();
m_json["device_id"] = Platform::deviceID();
m_json["sketch_version"] = ESP.getSketchMD5();
m_json["os_version"] = ESP.getSdkVersion();
m_json["localip"] = WiFi.localIP().toString();
m_json["pixelCount"] = Static<ConfigService>::instance()->coordMap()->physicalPixelCount();
m_json["loadedProfile"] = Static<ConfigService>::instance()->loadedProfile();
m_json["RSSI"] = WiFi.RSSI();
m_json["free_ram"] = ESP.getFreeHeap();
m_json["fps"] = FastLED.getFPS();
String availTopic = m_rootTopic + "/available";
publishDoc(Lightswitch.heartbeatTopic().c_str());
m_mqtt.publish(Device.availabilityTopic.c_str(), "online");
String fpsCounter = String(FastLED.getFPS());
m_mqtt.publish(FPSSensor.statTopic().c_str(), fpsCounter.c_str());
publishHeartbeat();
m_needHeartbeat = false;
}
}
void
MQTTTelemetry::publishHeartbeat()
{
m_json.clear();
m_json["device_id"] = Platform::deviceID();
m_json["sketch_version"] = ESP.getSketchMD5();
m_json["os_version"] = Platform::version();
m_json["localip"] = WiFi.localIP().toString();
m_json["pixelCount"] = Static<ConfigService>::instance()->coordMap()->physicalPixelCount();
m_json["loadedProfile"] = Static<ConfigService>::instance()->loadedProfile();
m_json["RSSI"] = WiFi.RSSI();
m_json["free_ram"] = Platform::freeRam();
m_json["fps"] = FastLED.getFPS();
publishDoc(Lightswitch.heartbeatTopic().c_str());
m_mqtt.publish(Device.availabilityTopic.c_str(), "online");
String fpsCounter = String(FastLED.getFPS());
m_mqtt.publish(FPSSensor.statTopic().c_str(), fpsCounter.c_str());
}
void
MQTTTelemetry::callback(char* topic, const char* payload)
{
Log.notice("MQTT: %s", topic);
setEvent(InputEvent::NetworkActivity);
if (flashlightSwitch.isCommandTopic(topic)) {
if (!strncmp((char*)payload, "ON", sizeof("ON"))) {
Log.notice("Turning on flashlight");
setEvent(InputEvent{InputEvent::SetPower, true});
setEvent(InputEvent{InputEvent::SetPattern, "Flashlight"});
setEvent(InputEvent{InputEvent::SetBrightness, 255});
} else if (!strncmp((char*)payload, "OFF", sizeof("OFF"))) {
Log.notice("Turning off flashlight");
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
}
} else if (Lightswitch.isCommandTopic(topic)) {
if (Lightswitch.isCommandTopic(topic)) {
deserializeJson(m_json, payload);
if (m_json.containsKey("state")) {
@ -299,62 +312,11 @@ MQTTTelemetry::callback(char* topic, const char* payload)
setEvent(InputEvent{InputEvent::SetPower, true});
} else if (m_json["state"] == "OFF") {
Log.notice("Turning off power");
setEvent(InputEvent{InputEvent::SetPattern, "Idle"});
setEvent(InputEvent::IdleStart);
setEvent(InputEvent{InputEvent::SetPower, false});
}
}
if (m_json.containsKey("start")) {
strcpy(m_patternBuf, m_json["start"].as<const char*>());
setEvent(InputEvent{InputEvent::StartThing, m_patternBuf});
}
if (m_json.containsKey("stop")) {
if (m_json["stop"] == name) {
Log.warning("You can't kill an idea, or stop the MQTT Task via MQTT.");
} else {
strcpy(m_patternBuf, m_json["stop"].as<const char*>());
setEvent(InputEvent{InputEvent::StopThing, m_patternBuf});
}
}
if (m_json.containsKey("pixelCount")) {
Log.notice("Pixel count changed");
setEvent(InputEvent{InputEvent::SetDisplayLength, m_json["pixelCount"].as<int>()});
}
if (m_json.containsKey("startPixel")) {
Log.notice("Start pixel changed");
setEvent(InputEvent{InputEvent::SetDisplayOffset, m_json["startPixel"].as<int>()});
}
if (m_json.containsKey("loadConfig")) {
Log.notice("Loading new config");
setEvent(InputEvent{InputEvent::LoadConfigurationByName, m_json["loadConfig"].as<const char*>()});
}
if (m_json.containsKey("save")) {
setEvent(InputEvent{InputEvent::SaveConfigurationRequest});
}
if (m_json.containsKey("restart")) {
Platform::restart();
}
if (m_json.containsKey("reconnect")) {
m_mqtt.disconnect();
}
if (m_json.containsKey("ping")) {
m_needHeartbeat = true;
Log.notice("Queuing up heartbeat");
}
if (m_json.containsKey("effect")) {
strcpy(m_patternBuf, m_json["effect"].as<const char*>());
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
}
if (m_json.containsKey("color")) {
uint8_t r = m_json["color"]["r"];
uint8_t g = m_json["color"]["g"];
@ -366,7 +328,26 @@ MQTTTelemetry::callback(char* topic, const char* payload)
setEvent(InputEvent{InputEvent::SetBrightness, m_json["brightness"].as<int>()});
}
Log.notice("Event done.");
} else if (IdleSwitch.isCommandTopic(topic)) {
if (strcmp(payload, "ON") == 0) {
Log.notice("Activating idle switch");
setEvent(InputEvent::IdleStart);
} else if (strcmp(payload, "OFF") == 0) {
Log.notice("Deactivating idle switch");
setEvent(InputEvent::IdleStop);
}
} else {
for(auto scene : Static<Sequencer>::instance()->scenes()) {
MQTTEntity sceneObj {
"scene",
Device,
scene.name
};
if (sceneObj.isCommandTopic(topic)) {
setEvent(InputEvent{InputEvent::SetScene, scene.name.c_str()});
return;
}
}
}
}
@ -386,7 +367,9 @@ void
MQTTTelemetry::publishDoc(const char* topic, bool retain)
{
m_mqtt.beginPublish(topic, measureJson(m_json), retain);
serializeJson(m_json, m_mqtt);
BufferingPrint buf(m_mqtt, 64);
serializeJson(m_json, buf);
buf.flush();
m_mqtt.endPublish();
}

View File

@ -7,9 +7,9 @@
#include "../../Sequencer.h"
#ifdef BOARD_ESP8266
#ifdef ESP8266
#include <ESP8266WiFi.h>
#elif defined(BOARD_ESP32)
#elif defined(ESP32)
#include <WiFi.h>
#endif
@ -73,6 +73,10 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin, ConfigTaskMix
void publishDoc(const char* topic);
void publishDoc(const char* topic, bool retain);
void onMQTTOnline();
void publishScenes();
void publishHeartbeat();
Sequencer *m_sequencer = 0;
WiFiClient m_wifi;
PubSubClient m_mqtt;

View File

@ -4,10 +4,13 @@
ArduinoOTAUpdater::ArduinoOTAUpdater() : BufferedInputSource("ArduinoOTA") {
ArduinoOTA.onStart(&ArduinoOTAUpdater::s_onStart);
ArduinoOTA.onProgress(&ArduinoOTAUpdater::s_onProgress);
ArduinoOTA.onError(&ArduinoOTAUpdater::s_onError);
ArduinoOTA.onEnd(&ArduinoOTAUpdater::s_onFinished);
ArduinoOTA.setRebootOnSuccess(false);
}
void ArduinoOTAUpdater::loop() {
if (m_online) {
if (m_online && !m_updating) {
ArduinoOTA.handle();
}
BufferedInputSource::loop();
@ -20,14 +23,32 @@ void ArduinoOTAUpdater::handleEvent(const InputEvent& evt) {
ArduinoOTA.begin();
}
}
void ArduinoOTAUpdater::s_onFinished()
{
Log.notice("OTA complete!");
Static<ArduinoOTAUpdater>::instance()->m_updating = false;
Platform::restart();
}
void ArduinoOTAUpdater::s_onStart() {
Log.notice("OTA Start!");
Static<ArduinoOTAUpdater>::instance()->m_updating = true;
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent::FirmwareUpdate);
}
void ArduinoOTAUpdater::s_onError(ota_error_t err)
{
Log.notice("OTA failure: %d", (int)err);
Static<ArduinoOTAUpdater>::instance()->m_updating = false;
}
void ArduinoOTAUpdater::s_onProgress(unsigned int progress, unsigned int total) {
Log.notice("OTA Progress! %d / %d", progress, total);
Static<ArduinoOTAUpdater>::instance()->setEvent(InputEvent{InputEvent::FirmwareUpdate, progress});
// Try to run the main loop in case we're not in a threaded updater
// environment. This keeps rendering running, and pets the watchdog.
MainLoop::instance()->loop();
}
STATIC_ALLOC(ArduinoOTAUpdater);

View File

@ -9,6 +9,9 @@ class ArduinoOTAUpdater : public BufferedInputSource {
private:
bool m_online = false;
bool m_updating = false;
static void s_onStart();
static void s_onProgress(unsigned int progress, unsigned int total);
static void s_onError(ota_error_t err);
static void s_onFinished();
};

View File

@ -1,12 +1,15 @@
#include <Input.h>
#include <ArduinoLog.h>
#ifdef BOARD_ESP8266
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#endif
#ifdef BOARD_ESP32
#ifdef ESP32
#include <WiFi.h>
#include "ESPmDNS.h"
#endif
#include "Static.h"
#include "WiFiTask.h"
@ -18,6 +21,11 @@ WiFiTask::onStart()
Log.notice("Starting wifi...");
WiFi.mode(WIFI_STA);
WiFi.begin("The Frequency", "thepasswordkenneth");
if (MDNS.begin(Platform::deviceHostname().c_str())) {
Log.notice("mDNS started with hostname %s", Platform::deviceHostname().c_str());
} else {
Log.error("mDNS failed to start!");
}
}
InputEvent

View File

@ -1,8 +1,9 @@
#if 0
#include "BluetoothSerialTelemetry.h"
#include "../../Static.h"
#include "../../Platform.h"
#include "../../../Static.h"
#include "../../../Platform.h"
#include <ArduinoLog.h>
#include "../../inputs/Buttons.h"
#include "../../../inputs/Buttons.h"
#include <cstdlib>
@ -91,3 +92,4 @@ BluetoothSerialTelemetry::onStart()
STATIC_ALLOC(BluetoothSerialTelemetry);
STATIC_TASK(BluetoothSerialTelemetry);
#endif

View File

@ -0,0 +1,133 @@
#include "../../../Platform.h"
#include <WiFi.h>
#include <esp_task_wdt.h>
__NOINIT_ATTR static uint8_t s_rebootCount;
__NOINIT_ATTR static uint16_t s_forceSafeMode;
#define SAFE_MODE_MAGIC 6942
template<>
void
PlatformImpl<HAL_ESP32>::forceSafeMode()
{
s_forceSafeMode = SAFE_MODE_MAGIC;
}
template<>
void
PlatformImpl<HAL_ESP32>::bootSplash()
{
Log.trace("ESP32 startup reason: %d", Platform::bootopts.resetReason);
}
template<>
void
PlatformImpl<HAL_ESP32>::printCrashInfo()
{
/*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);*/
}
template<>
void
PlatformImpl<HAL_ESP32>::initBootOptions(BootOptions& opts)
{
opts.resetReason = esp_reset_reason();
opts.crashCount = s_rebootCount;
if (opts.resetReason >= 4) { // TODO: These values are defined in
// esp32/rom/rtc.h, but not sure if that's included
// on platformio builds
if (opts.crashCount++ >= 3) {
// Boot into safe mode if the watchdog reset us three times in a row.
opts.isSafeMode = true;
}
} else {
opts.crashCount = 0;
}
s_rebootCount = opts.crashCount;
}
template<>
const char*
PlatformImpl<HAL_ESP32>::name()
{
return "ESP32";
}
template<>
const char*
PlatformImpl<HAL_ESP32>::version()
{
return ESP.getSdkVersion();
}
template<>
const char*
PlatformImpl<HAL_ESP32>::deviceID()
{
static char s_deviceID[15];
uint64_t chipid = ESP.getEfuseMac();
snprintf(s_deviceID, sizeof(s_deviceID), "%08X", (uint32_t)chipid);
return s_deviceID;
}
template<>
int
PlatformImpl<HAL_ESP32>::freeRam()
{
return ESP.getFreeHeap();
}
int printEspLog(const char* fmt, va_list args)
{
Log.notice(fmt, args);
return 1;
}
template<>
void
PlatformImpl<HAL_ESP32>::startWatchdog()
{
esp_task_wdt_init(10, true);
esp_task_wdt_add(NULL);
esp_log_set_vprintf(printEspLog);
}
template<>
void
PlatformImpl<HAL_ESP32>::startNTP()
{
constexpr int dst = 1;
configTime(0, 3600 * dst, "pool.ntp.org");
}
template<>
void
PlatformImpl<HAL_ESP32>::loop()
{
esp_task_wdt_reset();
}
template<>
bool
PlatformImpl<HAL_ESP32>::getLocalTime(struct tm* timedata, int timezone)
{
time_t rawtime;
memset(&rawtime, 0, sizeof(rawtime));
time(&rawtime);
(*timedata) = (*localtime(&rawtime));
timedata->tm_hour += timezone;
return (timedata->tm_year > (2016-1990));
}
template<>
void
PlatformImpl<HAL_ESP32>::restart()
{
ESP.restart();
}

View File

@ -1,8 +1,9 @@
#ifdef CONFIG_U8DISPLAY
#include <Figments.h>
#include <U8g2lib.h>
#include "../../Static.h"
#include "../../../Static.h"
#include <ArduinoLog.h>
#include "../../LogService.h"
#include "../../../LogService.h"
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, 16, 15, 4);
@ -236,3 +237,4 @@ class U8Display : public Task {
STATIC_ALLOC(U8Display);
STATIC_TASK(U8Display);
#endif

View File

@ -0,0 +1,134 @@
#include "../../../Platform.h"
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <ctime>
#define __NOINIT_ATTR __attribute__ ((section (".noinit")))
__NOINIT_ATTR static uint8_t s_rebootCount;
__NOINIT_ATTR static uint16_t s_forceSafeMode;
#define SAFE_MODE_MAGIC 6942
WiFiUDP wifiUdp;
NTPClient timeClient(wifiUdp, "pool.ntp.org", 0);
template<>
const char*
PlatformImpl<HAL_ESP8266>::name()
{
return "ESP8266";
}
template<>
const char*
PlatformImpl<HAL_ESP8266>::version()
{
return ESP.getSdkVersion();
}
template<>
int
PlatformImpl<HAL_ESP8266>::freeRam()
{
return ESP.getFreeHeap();
}
template<>
void
PlatformImpl<HAL_ESP8266>::startWatchdog()
{
ESP.wdtEnable(0);
}
template<>
bool
PlatformImpl<HAL_ESP8266>::getLocalTime(struct tm* timedata, int timezone)
{
timedata->tm_hour = (timeClient.getHours() + timezone) % 23;
timedata->tm_min = timeClient.getMinutes();
return true;
}
template<>
void
PlatformImpl<HAL_ESP8266>::startNTP()
{
timeClient.begin();
}
template<>
void
PlatformImpl<HAL_ESP8266>::loop()
{
if (WiFi.status() == WL_CONNECTED) {
timeClient.update();
}
ESP.wdtFeed();
}
template<>
const char*
PlatformImpl<HAL_ESP8266>::deviceID()
{
static char s_deviceID[15];
static uint16_t chipid = ESP.getChipId();
snprintf(s_deviceID, sizeof(s_deviceID), "%08X", (uint32_t)chipid);
return s_deviceID;
}
template<>
void
PlatformImpl<HAL_ESP8266>::restart()
{
ESP.wdtDisable();
ESP.restart();
}
template<>
void
PlatformImpl<HAL_ESP8266>::bootSplash()
{
Log.trace("ESP8266 startup reason: %d", Platform::bootopts.resetReason);
}
template<>
void
PlatformImpl<HAL_ESP8266>::printCrashInfo()
{
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);
}
template<>
void
PlatformImpl<HAL_ESP8266>::initBootOptions(BootOptions& opts)
{
struct rst_info resetInfo = *ESP.getResetInfoPtr();
opts.resetReason = resetInfo.reason;
opts.crashCount = s_rebootCount;
if (resetInfo.reason == REASON_SOFT_WDT_RST || resetInfo.reason == REASON_WDT_RST || resetInfo.reason == REASON_EXCEPTION_RST) {
if (opts.crashCount++ >= 3) {
// Boot into safe mode if the watchdog reset us three times in a row.
opts.isSafeMode = true;
}
} else {
opts.crashCount = 0;
}
s_rebootCount = opts.crashCount;
if (resetInfo.reason > 0 && s_forceSafeMode == SAFE_MODE_MAGIC) {
opts.isSafeMode = true;
s_forceSafeMode = 0;
}
}
template<>
void
PlatformImpl<HAL_ESP8266>::forceSafeMode()
{
s_forceSafeMode = SAFE_MODE_MAGIC;
}

View File

@ -2,7 +2,6 @@
#include <Figments.h>
#include <ArduinoLog.h>
#include <Perfcounter.h>
class Blob {
uint8_t m_pos;
@ -51,7 +50,6 @@ public:
}
void render(Display* display) const {
PerfCounter _("blobRender");
const uint8_t width = 25;
auto map = display->coordinateMapping();
// Grab the physical pixel we'll start with

View File

@ -11,4 +11,21 @@ def verify_json(source, target, env):
print("Validating " + root + '/' + file + '...')
json.load(open(root + '/' + file, 'r'))
usedTasks = set()
for root, dirnames, files in os.walk("data/profiles/"):
for file in files:
if file.endswith(".json"):
data = json.load(open(root + '/' + file, 'r'))
for task in data['tasks']:
usedTasks.add(task)
for sceneName in data['scenes']:
for task in data['scenes'][sceneName]:
usedTasks.add(task)
print("Used tasks:")
for task in sorted(usedTasks):
print("- " + task)
env.AddPreAction("buildfs", verify_json)