Compare commits
20 Commits
22cf849e15
...
bbc01f7cea
Author | SHA1 | Date | |
---|---|---|---|
bbc01f7cea | |||
4a75e09792 | |||
cb938d768a | |||
64666bbfb6 | |||
53d5775c6a | |||
39190d6506 | |||
ccb58082a2 | |||
ea058a33da | |||
d824dbfa45 | |||
8223688d7b | |||
0700dfaf92 | |||
d89630a340 | |||
f9add1f684 | |||
2ee332914a | |||
bfbbeffcfd | |||
de5d3e836a | |||
1426b1524b | |||
160cbc5ea9 | |||
ae3abc3aa3 | |||
2a0d72f0a1 |
@ -1,15 +1,24 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"tasks": [
|
"tasks": [
|
||||||
|
"Power",
|
||||||
"Renderer",
|
"Renderer",
|
||||||
"MQTT",
|
"U8Display",
|
||||||
"WiFi",
|
"WiFi",
|
||||||
|
"MQTT",
|
||||||
"ArduinoOTA",
|
"ArduinoOTA",
|
||||||
"UpdateStatusAnimation"
|
"BPM"
|
||||||
],
|
],
|
||||||
"scenes": {
|
"scenes": {
|
||||||
"Idle": ["Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"],
|
"Idle": ["Solid", "MPU5060", "IdleColors", "CircadianRhythm"],
|
||||||
"Flashlight": ["Flashlight"]
|
"Flashlight": ["Flashlight"]
|
||||||
},
|
},
|
||||||
"surfaceMap": "default"
|
"surfaceMap": "default",
|
||||||
|
"logLevel": 6,
|
||||||
|
"defaults": {
|
||||||
|
"mqtt.ip": "10.0.0.2",
|
||||||
|
"power.milliamps": 500,
|
||||||
|
"power.volts": 5,
|
||||||
|
"bpm.idle": 25
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
|
#define F_LIKELY(x) __builtin_expect(!!(x), true)
|
||||||
|
#define F_UNLIKELY(x) __builtin_expect(!!(x), false)
|
||||||
|
|
||||||
class Display;
|
class Display;
|
||||||
class InputEvent;
|
class InputEvent;
|
||||||
class InputSource;
|
class InputSource;
|
||||||
|
@ -37,8 +37,8 @@ struct Variant {
|
|||||||
CRGB asRGB() const;
|
CRGB asRGB() const;
|
||||||
int asInt() const;
|
int asInt() const;
|
||||||
bool asBool() const;
|
bool asBool() const;
|
||||||
template<typename T> const T& as() const {
|
template<typename T> const T* as() const {
|
||||||
return *static_cast<const T*>(m_value.asPointer);
|
return static_cast<const T*>(m_value.asPointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -175,19 +175,6 @@ private:
|
|||||||
std::function<InputEvent(const InputEvent)> m_func;
|
std::function<InputEvent(const InputEvent)> m_func;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ConfigTaskMixin : public virtual Loopable {
|
|
||||||
public:
|
|
||||||
void handleEvent(const InputEvent &evt) override {
|
|
||||||
if (evt.intent == InputEvent::ConfigurationChanged) {
|
|
||||||
handleConfigChange(evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() override {}
|
|
||||||
|
|
||||||
virtual void handleConfigChange(const InputEvent& evt) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class OnlineTaskMixin : public virtual Loopable {
|
class OnlineTaskMixin : public virtual Loopable {
|
||||||
public:
|
public:
|
||||||
void handleEvent(const InputEvent &evt) override {
|
void handleEvent(const InputEvent &evt) override {
|
||||||
|
@ -13,12 +13,13 @@ MainLoop::dispatch(const InputEvent& evt)
|
|||||||
m_eventBuf.insert(evt);
|
m_eventBuf.insert(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#ifdef BOARD_ESP32
|
||||||
|
__NOINIT_ATTR const char* s_lastTaskName;
|
||||||
|
//#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
MainLoop::loop()
|
MainLoop::dispatchSync(const InputEvent& evt)
|
||||||
{
|
{
|
||||||
s_instance = this;
|
|
||||||
InputEvent evt;
|
|
||||||
while (m_eventBuf.take(evt)) {
|
|
||||||
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
|
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
|
||||||
const bool jobState = (evt.intent == InputEvent::StartThing);
|
const bool jobState = (evt.intent == InputEvent::StartThing);
|
||||||
for(auto figmentJob: scheduler.tasks) {
|
for(auto figmentJob: scheduler.tasks) {
|
||||||
@ -36,9 +37,25 @@ MainLoop::loop()
|
|||||||
|
|
||||||
for(Task* task : scheduler) {
|
for(Task* task : scheduler) {
|
||||||
Log.verbose("** Eventing %s", task->name);
|
Log.verbose("** Eventing %s", task->name);
|
||||||
|
s_lastTaskName = task->name;
|
||||||
task->handleEvent(evt);
|
task->handleEvent(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
MainLoop::lastTaskName()
|
||||||
|
{
|
||||||
|
return s_lastTaskName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MainLoop::loop()
|
||||||
|
{
|
||||||
|
s_instance = this;
|
||||||
|
InputEvent evt;
|
||||||
|
while (m_eventBuf.take(evt)) {
|
||||||
|
dispatchSync(evt);
|
||||||
|
}
|
||||||
unsigned int slowest = 0;
|
unsigned int slowest = 0;
|
||||||
unsigned int frameSpeed = 0;
|
unsigned int frameSpeed = 0;
|
||||||
unsigned int frameStart = millis();
|
unsigned int frameStart = millis();
|
||||||
@ -52,6 +69,7 @@ MainLoop::loop()
|
|||||||
unsigned int start = millis();
|
unsigned int start = millis();
|
||||||
#endif
|
#endif
|
||||||
Log.verbose("Running %s", task->name);
|
Log.verbose("Running %s", task->name);
|
||||||
|
s_lastTaskName = task->name;
|
||||||
task->loop();
|
task->loop();
|
||||||
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
unsigned int runtime = (ESP.getCycleCount() - start) / 160000;
|
unsigned int runtime = (ESP.getCycleCount() - start) / 160000;
|
||||||
@ -66,7 +84,7 @@ MainLoop::loop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
frameSpeed = millis() - frameStart;
|
frameSpeed = millis() - frameStart;
|
||||||
if (frameSpeed >= 23) {
|
if (frameSpeed >= 23) { // TODO: Configure max frame time at build
|
||||||
const char* slowestName = (slowestTask->name ? slowestTask->name : "(Unnamed)");
|
const char* slowestName = (slowestTask->name ? slowestTask->name : "(Unnamed)");
|
||||||
Log.warning("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest);
|
Log.warning("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest);
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,9 @@ struct MainLoop {
|
|||||||
void start();
|
void start();
|
||||||
void loop();
|
void loop();
|
||||||
void dispatch(const InputEvent& event);
|
void dispatch(const InputEvent& event);
|
||||||
|
void dispatchSync(const InputEvent& event);
|
||||||
static MainLoop* instance() { return s_instance; }
|
static MainLoop* instance() { return s_instance; }
|
||||||
|
static const char* lastTaskName();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ringbuf<InputEvent, 32> m_eventBuf;
|
Ringbuf<InputEvent, 32> m_eventBuf;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
; https://docs.platformio.org/page/projectconf.html
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
[common_env_data]
|
[common_env_data]
|
||||||
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> -<inputs/>"
|
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> -<inputs/> +<inputs/BPM.cpp>"
|
||||||
lib_ldf_mode = chain+
|
lib_ldf_mode = chain+
|
||||||
extra_scripts = verify-configs.py
|
extra_scripts = verify-configs.py
|
||||||
src_build_flags =
|
src_build_flags =
|
||||||
@ -27,7 +27,7 @@ lib_deps_external =
|
|||||||
src_build_flags =
|
src_build_flags =
|
||||||
-DCONFIG_U8DISPLAY
|
-DCONFIG_U8DISPLAY
|
||||||
lib_deps =
|
lib_deps =
|
||||||
olikraus/U8g2@2.28.8
|
olikraus/U8g2@2.34.13
|
||||||
src_filter = "+<platform/arduino/U8Display.cpp>"
|
src_filter = "+<platform/arduino/U8Display.cpp>"
|
||||||
|
|
||||||
[config_mqtt]
|
[config_mqtt]
|
||||||
@ -93,6 +93,7 @@ src_build_flags =
|
|||||||
[env:esp32]
|
[env:esp32]
|
||||||
extends = config_nocolor
|
extends = config_nocolor
|
||||||
extra_scripts = verify-configs.py
|
extra_scripts = verify-configs.py
|
||||||
|
board_build.filesystem = littlefs
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = featheresp32
|
board = featheresp32
|
||||||
framework = arduino
|
framework = arduino
|
||||||
@ -101,12 +102,12 @@ src_build_flags =
|
|||||||
${config_nocolor.src_build_flags}
|
${config_nocolor.src_build_flags}
|
||||||
-DPLATFORM_ARDUINO
|
-DPLATFORM_ARDUINO
|
||||||
-DBOARD_ESP32
|
-DBOARD_ESP32
|
||||||
; -DCONFIG_THREADED_INPUTS
|
-DCONFIG_THREADED_INPUTS
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${common_env_data.lib_deps_external}
|
${common_env_data.lib_deps_external}
|
||||||
src_filter = "${common_env_data.src_filter}"
|
src_filter = "${common_env_data.src_filter}"
|
||||||
board_build.partitions = no_ota.csv
|
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
|
monitor_speed = 115200
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
|
|
||||||
[env:esp8266-12f]
|
[env:esp8266-12f]
|
||||||
@ -140,11 +141,12 @@ src_build_flags =
|
|||||||
${env:esp32.src_build_flags}
|
${env:esp32.src_build_flags}
|
||||||
${config_bluetooth.src_build_flags}
|
${config_bluetooth.src_build_flags}
|
||||||
${config_wifi.src_build_flags}
|
${config_wifi.src_build_flags}
|
||||||
-DRENDERBUG_LED_PIN=13
|
-DRENDERBUG_LED_PIN=14
|
||||||
|
|
||||||
[env:esp32_wifi]
|
[env:esp32_wifi]
|
||||||
extends = env:esp32, config_wifi, config_mqtt
|
extends = env:esp32, config_wifi, config_mqtt
|
||||||
src_filter = "${env:esp32.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
|
src_filter = "${env:esp32.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
|
||||||
|
buid_type = debug
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${env:esp32.lib_deps}
|
${env:esp32.lib_deps}
|
||||||
${config_mqtt.lib_deps}
|
${config_mqtt.lib_deps}
|
||||||
@ -152,7 +154,34 @@ src_build_flags =
|
|||||||
${env:esp32.src_build_flags}
|
${env:esp32.src_build_flags}
|
||||||
${config_mqtt.src_build_flags}
|
${config_mqtt.src_build_flags}
|
||||||
${config_wifi.src_build_flags}
|
${config_wifi.src_build_flags}
|
||||||
-DRENDERBUG_LED_PIN=13
|
-DRENDERBUG_LED_PIN=14
|
||||||
|
|
||||||
|
[env:esp32_display]
|
||||||
|
extends = env:esp32, config_u8display
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_u8display.src_filter}"
|
||||||
|
build_type = debug
|
||||||
|
lib_deps =
|
||||||
|
${env:esp32.lib_deps}
|
||||||
|
${config_u8display.lib_deps}
|
||||||
|
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]
|
[env:prototype]
|
||||||
extends = env:esp32, config_buttons, config_mpu5060
|
extends = env:esp32, config_buttons, config_mpu5060
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#include "BootOptions.h"
|
#include "BootOptions.h"
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
#ifdef BOARD_ESP8266
|
#ifdef BOARD_ESP8266
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#endif
|
#endif
|
||||||
#include <EEPROM.h>
|
|
||||||
#include "Config.h"
|
|
||||||
|
|
||||||
#ifdef PLATFORM_PHOTON
|
#ifdef PLATFORM_PHOTON
|
||||||
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
|
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
|
||||||
@ -12,6 +14,10 @@ retained bool LAST_BOOT_WAS_FLASH;
|
|||||||
retained bool LAST_BOOT_WAS_SERIAL;
|
retained bool LAST_BOOT_WAS_SERIAL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef BOARD_ESP32
|
||||||
|
__NOINIT_ATTR uint8_t s_rebootCount = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
void
|
void
|
||||||
BootOptions::initPins()
|
BootOptions::initPins()
|
||||||
{
|
{
|
||||||
@ -36,9 +42,24 @@ BootOptions::BootOptions()
|
|||||||
configStatus.setActive(isSetup);
|
configStatus.setActive(isSetup);
|
||||||
serialStatus.setActive(isSerial);
|
serialStatus.setActive(isSerial);
|
||||||
#endif
|
#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
|
#ifdef BOARD_ESP8266
|
||||||
struct rst_info resetInfo = *ESP.getResetInfoPtr();
|
struct rst_info resetInfo = *ESP.getResetInfoPtr();
|
||||||
uint8_t crashCount;
|
resetReason = resetInfo.reason;
|
||||||
EEPROM.begin(sizeof(crashCount));
|
EEPROM.begin(sizeof(crashCount));
|
||||||
EEPROM.get(sizeof(HardwareConfig) + 32, crashCount);
|
EEPROM.get(sizeof(HardwareConfig) + 32, crashCount);
|
||||||
EEPROM.end();
|
EEPROM.end();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
struct BootOptions {
|
struct BootOptions {
|
||||||
static void initPins();
|
static void initPins();
|
||||||
@ -12,4 +13,6 @@ struct BootOptions {
|
|||||||
bool isFlash = false;
|
bool isFlash = false;
|
||||||
bool lastBootWasFlash = false;
|
bool lastBootWasFlash = false;
|
||||||
bool isSafeMode = false;
|
bool isSafeMode = false;
|
||||||
|
uint8_t crashCount = 0;
|
||||||
|
uint8_t resetReason = 0;
|
||||||
};
|
};
|
||||||
|
106
src/Config.cpp
106
src/Config.cpp
@ -9,7 +9,51 @@
|
|||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
StaticJsonDocument<256> jsonConfig;
|
void
|
||||||
|
ConfigTaskMixin::handleEvent(const InputEvent &evt)
|
||||||
|
{
|
||||||
|
if (evt.intent == InputEvent::ConfigurationChanged) {
|
||||||
|
const JsonObject& cfg = *evt.as<JsonObject>();
|
||||||
|
handleConfigChange(Configuration(cfg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration::Configuration(const JsonObject& data)
|
||||||
|
: m_json(data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
Configuration::get(const char* key, const char* defaultVal) const
|
||||||
|
{
|
||||||
|
if (m_json.containsKey(key)) {
|
||||||
|
return m_json[key];
|
||||||
|
} else {
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
Configuration::get(const char* key, int defaultVal) const
|
||||||
|
{
|
||||||
|
if (m_json.containsKey(key)) {
|
||||||
|
return m_json[key];
|
||||||
|
} else {
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Configuration::get(const char* key, bool defaultVal) const
|
||||||
|
{
|
||||||
|
if (m_json.containsKey(key)) {
|
||||||
|
return m_json[key];
|
||||||
|
} else {
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StaticJsonDocument<1024> jsonConfig;
|
||||||
|
|
||||||
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
||||||
|
|
||||||
@ -77,14 +121,20 @@ ConfigService::onStart()
|
|||||||
m_config = HardwareConfig{};
|
m_config = HardwareConfig{};
|
||||||
m_config.save();
|
m_config.save();
|
||||||
}
|
}
|
||||||
if (strlen(m_config.data.loadedProfile) == 0) {
|
|
||||||
strcpy(m_config.data.loadedProfile, "default");
|
bool loaded = false;
|
||||||
}
|
|
||||||
|
|
||||||
if (m_overrideProfile != nullptr) {
|
if (m_overrideProfile != nullptr) {
|
||||||
loadProfile(m_overrideProfile);
|
loaded = loadProfile(m_overrideProfile);
|
||||||
} else {
|
}
|
||||||
loadProfile(m_config.data.loadedProfile);
|
|
||||||
|
if (!loaded && strlen(m_config.data.loadedProfile) > 0) {
|
||||||
|
loaded = loadProfile(m_config.data.loadedProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loaded && !loadProfile("default")) {
|
||||||
|
Log.fatal("Could not load default fallback profile! No tasks will be started.");
|
||||||
|
m_jsonMap.loadDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,24 +144,24 @@ ConfigService::overrideProfile(const char* profileName)
|
|||||||
m_overrideProfile = profileName;
|
m_overrideProfile = profileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
ConfigService::loadMap(const String& mapName)
|
ConfigService::loadMap(const String& mapName)
|
||||||
{
|
{
|
||||||
String fname = String("/maps/") + mapName + ".json";
|
String fname = String("/maps/") + mapName + ".json";
|
||||||
if (LittleFS.exists(fname)) {
|
if (LittleFS.exists(fname)) {
|
||||||
File configFile = LittleFS.open(fname, "r");
|
File configFile = LittleFS.open(fname, "r");
|
||||||
Log.notice("config: Loading coordinate map %s", mapName.c_str());
|
Log.notice("config: Loading coordinate map from %s", fname.c_str());
|
||||||
deserializeJson(jsonConfig, configFile);
|
deserializeJson(jsonConfig, configFile);
|
||||||
configFile.close();
|
configFile.close();
|
||||||
JsonArray strideList = jsonConfig["strides"];
|
JsonArray strideList = jsonConfig["strides"];
|
||||||
m_jsonMap.load(strideList);
|
m_jsonMap.load(strideList);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str());
|
return false;
|
||||||
m_jsonMap.loadDefault();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
ConfigService::loadProfile(const char* profileName)
|
ConfigService::loadProfile(const char* profileName)
|
||||||
{
|
{
|
||||||
Log.notice("config: Loading profile %s...", profileName);
|
Log.notice("config: Loading profile %s...", profileName);
|
||||||
@ -119,11 +169,16 @@ ConfigService::loadProfile(const char* profileName)
|
|||||||
|
|
||||||
LittleFS.begin();
|
LittleFS.begin();
|
||||||
if (LittleFS.exists(fname)) {
|
if (LittleFS.exists(fname)) {
|
||||||
|
strncpy(m_config.data.loadedProfile, fname.c_str(), sizeof(m_config.data.loadedProfile));
|
||||||
File configFile = LittleFS.open(fname, "r");
|
File configFile = LittleFS.open(fname, "r");
|
||||||
jsonConfig.clear();
|
jsonConfig.clear();
|
||||||
deserializeJson(jsonConfig, configFile);
|
deserializeJson(jsonConfig, configFile);
|
||||||
configFile.close();
|
configFile.close();
|
||||||
|
|
||||||
|
//int profileLogLevel = max(0, min(6, jsonConfig["logLevel"]));
|
||||||
|
//Log.setLevel(profileLogLevel);
|
||||||
|
//Log.trace("config: \t %d logging level");
|
||||||
|
|
||||||
JsonObject sceneList = jsonConfig["scenes"];
|
JsonObject sceneList = jsonConfig["scenes"];
|
||||||
std::vector<Sequencer::Scene> scenes;
|
std::vector<Sequencer::Scene> scenes;
|
||||||
for(JsonPair pair : sceneList) {
|
for(JsonPair pair : sceneList) {
|
||||||
@ -139,25 +194,37 @@ ConfigService::loadProfile(const char* profileName)
|
|||||||
JsonArray taskList = jsonConfig["tasks"];
|
JsonArray taskList = jsonConfig["tasks"];
|
||||||
Log.notice("config: Starting %d tasks", taskList.size());
|
Log.notice("config: Starting %d tasks", taskList.size());
|
||||||
for(int i = 0; i < taskList.size();i++) {
|
for(int i = 0; i < taskList.size();i++) {
|
||||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, taskList[i].as<const char*>()});
|
MainLoop::instance()->dispatchSync(InputEvent{InputEvent::StartThing, taskList[i].as<const char*>()});
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject defaults = jsonConfig["defaults"];
|
||||||
|
Log.notice("config: Loading %d app configurations", defaults.size());
|
||||||
|
MainLoop::instance()->dispatchSync(InputEvent{InputEvent::ConfigurationChanged, &defaults});
|
||||||
|
|
||||||
|
String mapName = jsonConfig["surfaceMap"];
|
||||||
|
|
||||||
|
jsonConfig.clear();
|
||||||
|
|
||||||
|
if (mapName.isEmpty()) {
|
||||||
|
Log.warning("config: No coordinate map defined! Defaulting to linear mapping.", mapName.c_str());
|
||||||
|
m_jsonMap.loadDefault();
|
||||||
|
} else if (!loadMap(mapName)) {
|
||||||
|
Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str());
|
||||||
|
m_jsonMap.loadDefault();
|
||||||
}
|
}
|
||||||
Log.notice("config: Loaded!");
|
Log.notice("config: Loaded!");
|
||||||
} else {
|
} else {
|
||||||
Log.warning("config: Could not load profile %s!", profileName);
|
Log.warning("config: Could not load profile %s!", profileName);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject defaults = jsonConfig["defaults"];
|
|
||||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::ConfigurationChanged, &defaults});
|
|
||||||
|
|
||||||
String configName = jsonConfig["surfaceMap"];
|
|
||||||
jsonConfig.clear();
|
|
||||||
loadMap(configName);
|
|
||||||
LittleFS.end();
|
LittleFS.end();
|
||||||
Log.notice("config: Configured to use %d pixels", m_jsonMap.physicalPixelCount());
|
Log.notice("config: Configured to use %d pixels", m_jsonMap.physicalPixelCount());
|
||||||
PhysicalCoordinates topLeft = m_jsonMap.virtualToPhysicalCoords({0, 0});
|
PhysicalCoordinates topLeft = m_jsonMap.virtualToPhysicalCoords({0, 0});
|
||||||
PhysicalCoordinates bottomRight = m_jsonMap.virtualToPhysicalCoords({255, 255});
|
PhysicalCoordinates bottomRight = m_jsonMap.virtualToPhysicalCoords({255, 255});
|
||||||
Log.verbose(" (0,0) -> (%d, %d) -> %d", topLeft.x, topLeft.y, m_jsonMap.physicalCoordsToIndex(topLeft));
|
Log.verbose(" (0,0) -> (%d, %d) -> %d", topLeft.x, topLeft.y, m_jsonMap.physicalCoordsToIndex(topLeft));
|
||||||
Log.verbose(" (255,255) -> (%d, %d) -> %d", bottomRight.x, bottomRight.y, m_jsonMap.physicalCoordsToIndex(bottomRight));
|
Log.verbose(" (255,255) -> (%d, %d) -> %d", bottomRight.x, bottomRight.y, m_jsonMap.physicalCoordsToIndex(bottomRight));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -177,7 +244,6 @@ ConfigService::handleEvent(const InputEvent &evt)
|
|||||||
switch(evt.intent) {
|
switch(evt.intent) {
|
||||||
case InputEvent::LoadConfigurationByName:
|
case InputEvent::LoadConfigurationByName:
|
||||||
Log.notice("Reloading configuration %s", evt.asString());
|
Log.notice("Reloading configuration %s", evt.asString());
|
||||||
strcpy(m_config.data.loadedProfile, evt.asString());
|
|
||||||
loadProfile(evt.asString());
|
loadProfile(evt.asString());
|
||||||
case InputEvent::SaveConfigurationRequest:
|
case InputEvent::SaveConfigurationRequest:
|
||||||
Log.notice("Saving configuration");
|
Log.notice("Saving configuration");
|
||||||
|
25
src/Config.h
25
src/Config.h
@ -1,6 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Figments.h>
|
#include <Figments.h>
|
||||||
#include "JsonCoordinateMapping.h"
|
#include "JsonCoordinateMapping.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
class Configuration {
|
||||||
|
public:
|
||||||
|
Configuration(const JsonObject& data);
|
||||||
|
const char* get(const char* key, const char* defaultVal) const;
|
||||||
|
int get(const char* key, int defaultVal) const;
|
||||||
|
bool get(const char* key, bool defaultVal) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const JsonObject& m_json;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigTaskMixin : public virtual Loopable {
|
||||||
|
public:
|
||||||
|
void handleEvent(const InputEvent &evt) override;
|
||||||
|
|
||||||
|
void loop() override {}
|
||||||
|
|
||||||
|
virtual void handleConfigChange(const Configuration& config) {}
|
||||||
|
};
|
||||||
|
|
||||||
struct HardwareConfig {
|
struct HardwareConfig {
|
||||||
uint8_t version = 3;
|
uint8_t version = 3;
|
||||||
@ -44,6 +65,6 @@ private:
|
|||||||
JsonCoordinateMapping m_jsonMap;
|
JsonCoordinateMapping m_jsonMap;
|
||||||
const char* m_overrideProfile = nullptr;
|
const char* m_overrideProfile = nullptr;
|
||||||
|
|
||||||
void loadProfile(const char* name);
|
bool loadProfile(const char* name);
|
||||||
void loadMap(const String& mapName);
|
bool loadMap(const String& mapName);
|
||||||
};
|
};
|
||||||
|
@ -48,6 +48,8 @@ LogService::intentName(InputEvent::Intent intent)
|
|||||||
return "set-display-length";
|
return "set-display-length";
|
||||||
case InputEvent::SaveConfigurationRequest:
|
case InputEvent::SaveConfigurationRequest:
|
||||||
return "save-configuration";
|
return "save-configuration";
|
||||||
|
case InputEvent::ConfigurationChanged:
|
||||||
|
return "configuration-changed";
|
||||||
default:
|
default:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -67,6 +69,10 @@ LogService::eventValue(const InputEvent& evt)
|
|||||||
snprintf(s_valueBuf, sizeof(s_valueBuf), "\"%s\"", evt.asString());break;
|
snprintf(s_valueBuf, sizeof(s_valueBuf), "\"%s\"", evt.asString());break;
|
||||||
case InputEvent::Color:
|
case InputEvent::Color:
|
||||||
snprintf(s_valueBuf, sizeof(s_valueBuf), "[%d, %d, %d]", evt.asRGB().r, evt.asRGB().g, evt.asRGB().b);break;
|
snprintf(s_valueBuf, sizeof(s_valueBuf), "[%d, %d, %d]", evt.asRGB().r, evt.asRGB().g, evt.asRGB().b);break;
|
||||||
|
case InputEvent::Pointer:
|
||||||
|
snprintf(s_valueBuf, sizeof(s_valueBuf), "*%p", evt.as<void*>());break;
|
||||||
|
default:
|
||||||
|
snprintf(s_valueBuf, sizeof(s_valueBuf), "CORRUPTED");break;
|
||||||
}
|
}
|
||||||
return s_valueBuf;
|
return s_valueBuf;
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,9 @@ Platform::freeRam()
|
|||||||
#ifdef BOARD_ESP8266
|
#ifdef BOARD_ESP8266
|
||||||
return ESP.getFreeHeap();
|
return ESP.getFreeHeap();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef BOARD_ESP32
|
||||||
|
return ESP.getFreeHeap();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -206,6 +209,28 @@ Platform::deviceID()
|
|||||||
return s_deviceID;
|
return s_deviceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Platform::addLEDs(CRGB* leds, unsigned int ledCount) {
|
||||||
|
FastLED.addLeds<WS2812B, RENDERBUG_LED_PIN, RENDERBUG_LED_PACKING>(leds, ledCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
const String
|
||||||
|
Platform::model()
|
||||||
|
{
|
||||||
|
static String modelName = String("Renderbug " ) + Platform::name();
|
||||||
|
return modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Platform::restart() {
|
||||||
|
#ifdef BOARD_ESP8266
|
||||||
|
ESP.wdtDisable();
|
||||||
|
ESP.restart();
|
||||||
|
#elif defined(BOARD_ESP32)
|
||||||
|
ESP.restart();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
BootOptions
|
BootOptions
|
||||||
Platform::bootopts;
|
Platform::bootopts;
|
||||||
|
|
||||||
|
@ -11,17 +11,11 @@ class Platform : public Task {
|
|||||||
static BootOptions bootopts;
|
static BootOptions bootopts;
|
||||||
static void setTimezone(int tz) { s_timezone = tz; }
|
static void setTimezone(int tz) { s_timezone = tz; }
|
||||||
static int getTimezone() { return s_timezone; }
|
static int getTimezone() { return s_timezone; }
|
||||||
|
static void addLEDs(CRGB* leds, unsigned int ledCount);
|
||||||
static void addLEDs(CRGB* leds, unsigned int ledCount) {
|
|
||||||
FastLED.addLeds<WS2812B, RENDERBUG_LED_PIN, RENDERBUG_LED_PACKING>(leds, ledCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* name();
|
static const char* name();
|
||||||
static const char* version();
|
static const char* version();
|
||||||
static const String model() {
|
static const String model();
|
||||||
static String modelName = String("Renderbug " ) + Platform::name();
|
|
||||||
return modelName;
|
|
||||||
}
|
|
||||||
static const String deviceName() {
|
static const String deviceName() {
|
||||||
static String devName = model() + " " + Platform::deviceID();
|
static String devName = model() + " " + Platform::deviceID();
|
||||||
return devName;
|
return devName;
|
||||||
@ -110,12 +104,5 @@ class Platform : public Task {
|
|||||||
return task_iterator(NULL);
|
return task_iterator(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void restart() {
|
static void restart();
|
||||||
#ifdef BOARD_ESP8266
|
|
||||||
ESP.wdtDisable();
|
|
||||||
ESP.restart();
|
|
||||||
#elif defined(BOARD_ESP32)
|
|
||||||
ESP.restart();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
@ -50,7 +50,6 @@ Sequencer::onStart()
|
|||||||
void
|
void
|
||||||
Sequencer::setScenes(std::vector<Scene> &&scenes)
|
Sequencer::setScenes(std::vector<Scene> &&scenes)
|
||||||
{
|
{
|
||||||
Log.notice("Updated scenes");
|
|
||||||
m_idx = 0;
|
m_idx = 0;
|
||||||
m_scenes = scenes;
|
m_scenes = scenes;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
#include "./Power.h"
|
||||||
|
#include "../Static.h"
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
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);
|
@ -1,8 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Figments.h>
|
#include <Figments.h>
|
||||||
|
#include "../Config.h"
|
||||||
|
|
||||||
template<uint8_t MaxBrightness = 255, uint32_t MaxMilliAmps = 500, uint32_t Voltage = 5>
|
class Power: public Figment, ConfigTaskMixin {
|
||||||
class Power: public Figment {
|
|
||||||
public:
|
public:
|
||||||
Power() : Figment("Power") {state = Task::Running;}
|
Power() : Figment("Power") {state = Task::Running;}
|
||||||
|
|
||||||
@ -24,11 +24,14 @@ public:
|
|||||||
m_beatDecay.set(0, 255);
|
m_beatDecay.set(0, 255);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
ConfigTaskMixin::handleEvent(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleConfigChange(const Configuration& config) override;
|
||||||
|
|
||||||
void loop() override {
|
void loop() override {
|
||||||
|
ConfigTaskMixin::loop();
|
||||||
m_powerState.update();
|
m_powerState.update();
|
||||||
m_brightness.update();
|
m_brightness.update();
|
||||||
EVERY_N_MILLISECONDS(20) {
|
EVERY_N_MILLISECONDS(20) {
|
||||||
@ -37,18 +40,23 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void render(Display* dpy) const override {
|
void render(Display* dpy) const override {
|
||||||
const uint8_t decayedBrightness = scale8((uint8_t)m_brightness, ease8InOutCubic((uint8_t)m_beatDecay));
|
if (F_LIKELY(m_valid)) {
|
||||||
const uint8_t clippedBrightness = std::min(decayedBrightness, MaxBrightness);
|
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 scaledBrightness = scale8(m_powerState, clippedBrightness);
|
||||||
const uint8_t videoBrightness = brighten8_video(scaledBrightness);
|
const uint8_t videoBrightness = brighten8_video(scaledBrightness);
|
||||||
const uint8_t powerBrightness = calculate_max_brightness_for_power_mW(videoBrightness, Watts);
|
const uint8_t powerBrightness = calculate_max_brightness_for_power_mW(videoBrightness, m_voltage * m_milliamps);
|
||||||
FastLED.setBrightness(powerBrightness);
|
FastLED.setBrightness(powerBrightness);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr uint32_t Watts = Voltage * MaxMilliAmps;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AnimatedNumber m_powerState = 255;
|
AnimatedNumber m_powerState = 255;
|
||||||
AnimatedNumber m_brightness = MaxBrightness;
|
AnimatedNumber m_brightness = 255;
|
||||||
AnimatedNumber m_beatDecay = 255;
|
AnimatedNumber m_beatDecay = 255;
|
||||||
|
uint8_t m_voltage = 5;
|
||||||
|
uint16_t m_milliamps = 500;
|
||||||
|
bool m_valid = true;
|
||||||
|
bool m_useBPM = false;
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,7 @@ void SolidAnimation::randomize() {
|
|||||||
m_blobs.forEach([](Blob& blob) {
|
m_blobs.forEach([](Blob& blob) {
|
||||||
blob.setPos(random(140));
|
blob.setPos(random(140));
|
||||||
blob.setBrightness(random(255));
|
blob.setBrightness(random(255));
|
||||||
if (random(255) % 2) {
|
if (random(254) % 2) {
|
||||||
blob.setVelocity(-1);
|
blob.setVelocity(-1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -26,7 +26,7 @@ void SolidAnimation::handleEvent(const InputEvent& evt) {
|
|||||||
m_curColor = nextColor;
|
m_curColor = nextColor;
|
||||||
m_horizontal = !m_horizontal;
|
m_horizontal = !m_horizontal;
|
||||||
} else if (evt.intent == InputEvent::Beat) {
|
} else if (evt.intent == InputEvent::Beat) {
|
||||||
m_isRandom = false;
|
//m_isRandom = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,13 +34,11 @@ void SolidAnimation::loop() {
|
|||||||
if (!m_isRandom) {
|
if (!m_isRandom) {
|
||||||
randomize();
|
randomize();
|
||||||
}
|
}
|
||||||
m_red.update(15);
|
EVERY_N_MILLIS(20) {
|
||||||
m_green.update(15);
|
m_changePct.update(1);
|
||||||
m_blue.update(15);
|
m_red.update(1);
|
||||||
EVERY_N_MILLIS(16) {
|
m_green.update(1);
|
||||||
m_changePct.update(12);
|
m_blue.update(1);
|
||||||
}
|
|
||||||
EVERY_N_MILLIS(6) {
|
|
||||||
CRGB rgb{m_red, m_green, m_blue};
|
CRGB rgb{m_red, m_green, m_blue};
|
||||||
CHSV hsv = rgb2hsv_approximate(rgb);
|
CHSV hsv = rgb2hsv_approximate(rgb);
|
||||||
m_blobs.forEach([=](Blob& blob) {
|
m_blobs.forEach([=](Blob& blob) {
|
||||||
@ -56,15 +54,17 @@ void SolidAnimation::loop() {
|
|||||||
void SolidAnimation::render(Display* dpy) const {
|
void SolidAnimation::render(Display* dpy) const {
|
||||||
PerfCounter _("solidRender");
|
PerfCounter _("solidRender");
|
||||||
CRGB color(m_red.value(), m_green.value(), m_blue.value());
|
CRGB color(m_red.value(), m_green.value(), m_blue.value());
|
||||||
|
CRGB scaledPrev = m_prevColor;
|
||||||
|
scaledPrev = color.nscale8(30);
|
||||||
uint8_t frame = ease8InOutApprox(m_changePct);
|
uint8_t frame = ease8InOutApprox(m_changePct);
|
||||||
if (frame == 255) {
|
if (F_LIKELY(frame == 255)) {
|
||||||
Surface(dpy, {0, 0}, {255, 255}) = color;
|
Surface(dpy, {0, 0}, {255, 255}) = color.nscale8(10);
|
||||||
} else {
|
} else {
|
||||||
uint8_t cutoff = (frame / 2);
|
uint8_t cutoff = (frame / 2);
|
||||||
uint8_t rotation = m_horizontal ? 0 : 128;
|
uint8_t rotation = m_horizontal ? 0 : 128;
|
||||||
Surface(dpy, {0, 0}, {128 - cutoff, 255}, rotation) = m_prevColor;
|
Surface(dpy, {0, 0}, {128 - cutoff, 255}, rotation) = scaledPrev;
|
||||||
Surface(dpy, {128 - cutoff, 0}, {128 + cutoff, 255}, rotation) = color;
|
Surface(dpy, {128 - cutoff, 0}, {128 + cutoff, 255}, rotation) = scaledPrev;
|
||||||
Surface(dpy, {128 + cutoff, 0}, {255, 255}, rotation) = m_prevColor;
|
Surface(dpy, {128 + cutoff, 0}, {255, 255}, rotation) = scaledPrev;
|
||||||
}
|
}
|
||||||
m_blobs.render(dpy);
|
m_blobs.render(dpy);
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,40 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <Figments.h>
|
#include <Figments.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "../Config.h"
|
||||||
|
|
||||||
class BPM : public InputSource {
|
class BPM : public InputSource, ConfigTaskMixin {
|
||||||
public:
|
public:
|
||||||
BPM() : InputSource("BPM") {}
|
BPM() : InputSource("BPM") {}
|
||||||
void handleEvent(const InputEvent& evt) override {
|
void handleEvent(const InputEvent& evt) override {
|
||||||
if (evt.intent == InputEvent::BeatDetect) {
|
if (evt.intent == InputEvent::BeatDetect) {
|
||||||
m_nextBpm = millis();
|
m_nextBpm = millis();
|
||||||
m_timings.insert(millis());
|
m_timings.insert(millis());
|
||||||
Log.notice("%d timings", m_timings.size());
|
Log.trace("bpm: %d timings", m_timings.size());
|
||||||
if (m_timings.size() >= 5) {
|
if (m_timings.size() >= 5) {
|
||||||
updateBPM();
|
updateBPM();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ConfigTaskMixin::handleEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
InputSource::loop();
|
||||||
|
ConfigTaskMixin::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleConfigChange(const Configuration& cfg) override {
|
||||||
|
double requestedBPM = cfg.get("bpm.idle", msToBPM(m_msPerBeat));
|
||||||
|
m_msPerBeat = 60000.0 / (double)requestedBPM;
|
||||||
|
Log.notice("bpm: idle BPM set to %d (requested %d)", (int)msToBPM(m_msPerBeat), (int)requestedBPM);
|
||||||
}
|
}
|
||||||
|
|
||||||
InputEvent read() override {
|
InputEvent read() override {
|
||||||
if (m_bpm > 0) {
|
if (m_msPerBeat > 0) {
|
||||||
uint16_t now = millis();
|
uint16_t now = millis();
|
||||||
if (now >= m_nextBpm) {
|
if (now >= m_nextBpm) {
|
||||||
m_nextBpm += m_bpm;
|
m_nextBpm += m_msPerBeat;
|
||||||
return InputEvent{InputEvent::Beat, m_bpm};
|
return InputEvent{InputEvent::Beat, msToBPM(m_msPerBeat)};
|
||||||
}
|
}
|
||||||
if (now >= m_nextLearn && m_nextLearn != 0) {
|
if (now >= m_nextLearn && m_nextLearn != 0) {
|
||||||
m_timings.clear();
|
m_timings.clear();
|
||||||
@ -31,21 +45,28 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t m_bpm = 0;
|
uint16_t m_msPerBeat = 60000;
|
||||||
uint16_t m_nextBpm = 0;
|
uint16_t m_nextBpm = 0;
|
||||||
uint16_t m_nextLearn = 0;
|
uint16_t m_nextLearn = 0;
|
||||||
Ringbuf<uint16_t, 7> m_timings;
|
Ringbuf<uint16_t, 7> m_timings;
|
||||||
|
|
||||||
|
constexpr uint16_t msToBPM(float msPerBeat) const {
|
||||||
|
if (msPerBeat == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 60000.0 / msPerBeat;
|
||||||
|
}
|
||||||
|
|
||||||
void updateBPM() {
|
void updateBPM() {
|
||||||
uint16_t avgDelta = 0;
|
uint16_t avgDelta = 0;
|
||||||
for(uint8_t i = 0; i < m_timings.size() - 1; i++) {
|
for(uint8_t i = 0; i < m_timings.size() - 1; i++) {
|
||||||
uint16_t delta = m_timings.peek(i+1) - m_timings.peek(i);
|
uint16_t delta = m_timings.peek(i+1) - m_timings.peek(i);
|
||||||
Log.notice("Timing %d Delta %d", m_timings.peek(i), delta);
|
Log.trace("bpm: Timing %d Delta %d", m_timings.peek(i), delta);
|
||||||
avgDelta += delta;
|
avgDelta += delta;
|
||||||
}
|
}
|
||||||
m_bpm = avgDelta / 4;
|
m_msPerBeat = avgDelta / 4;
|
||||||
m_nextLearn = m_bpm * 5 + millis();
|
m_nextLearn = m_msPerBeat * 5 + millis();
|
||||||
Log.notice("BPM is now %d", m_bpm);
|
Log.notice("bpm: BPM is now %d", msToBPM(m_msPerBeat));
|
||||||
uint16_t trash;
|
uint16_t trash;
|
||||||
m_timings.take(trash);
|
m_timings.take(trash);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ public:
|
|||||||
}
|
}
|
||||||
if (m_reset) {
|
if (m_reset) {
|
||||||
m_reset = false;
|
m_reset = false;
|
||||||
Log.notice("Cycling %s color to %d [%d, %d, %d]", name, m_idx, m_colors[m_idx].r, m_colors[m_idx].g, m_colors[m_idx].b);
|
|
||||||
return InputEvent{InputEvent::SetColor, m_colors[m_idx]};
|
return InputEvent{InputEvent::SetColor, m_colors[m_idx]};
|
||||||
}
|
}
|
||||||
return InputEvent{};
|
return InputEvent{};
|
||||||
|
53
src/main.cpp
53
src/main.cpp
@ -13,30 +13,14 @@
|
|||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "animations/Power.h"
|
|
||||||
|
|
||||||
#include "inputs/ColorCycle.h"
|
#include "inputs/ColorCycle.h"
|
||||||
#include "inputs/Buttons.h"
|
#include "inputs/Buttons.h"
|
||||||
#include "SafeMode.h"
|
#include "SafeMode.h"
|
||||||
|
|
||||||
#define MAX_BRIGHTNESS 255
|
|
||||||
//#define PSU_MILLIAMPS 4800
|
|
||||||
//#define PSU_MILLIAMPS 500
|
|
||||||
//#define PSU_MILLIAMPS 1000
|
|
||||||
#define PSU_MILLIAMPS 1000
|
|
||||||
|
|
||||||
// Enable system thread, so rendering happens while booting
|
|
||||||
//SYSTEM_THREAD(ENABLED);
|
|
||||||
|
|
||||||
|
|
||||||
// Setup FastLED and the display
|
// Setup FastLED and the display
|
||||||
CRGB leds[HardwareConfig::MAX_LED_NUM];
|
CRGB leds[HardwareConfig::MAX_LED_NUM];
|
||||||
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
|
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
|
||||||
|
|
||||||
// Setup power management
|
|
||||||
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
|
|
||||||
REGISTER_TASK(power);
|
|
||||||
|
|
||||||
// FIXME: rewrite as static task
|
// FIXME: rewrite as static task
|
||||||
/*FigmentFunc configDisplay([](Display* dpy) {
|
/*FigmentFunc configDisplay([](Display* dpy) {
|
||||||
uint8_t brightness = brighten8_video(beatsin8(60));
|
uint8_t brightness = brighten8_video(beatsin8(60));
|
||||||
@ -50,24 +34,6 @@ REGISTER_TASK(power);
|
|||||||
}
|
}
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
InputFunc randomPulse([]() {
|
|
||||||
static unsigned int pulse = 0;
|
|
||||||
EVERY_N_MILLISECONDS(25) {
|
|
||||||
if (pulse == 0) {
|
|
||||||
pulse = random(25) + 25;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pulse > 0) {
|
|
||||||
if (random(255) >= 25) {
|
|
||||||
pulse--;
|
|
||||||
return InputEvent{InputEvent::Acceleration, beatsin16(60 * 8, 0, 4972)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return InputEvent{};
|
|
||||||
}, "Pulse");
|
|
||||||
|
|
||||||
REGISTER_TASK(randomPulse);
|
|
||||||
|
|
||||||
InputMapper keyMap([](const InputEvent& evt) {
|
InputMapper keyMap([](const InputEvent& evt) {
|
||||||
if (evt.intent == InputEvent::UserInput) {
|
if (evt.intent == InputEvent::UserInput) {
|
||||||
Buttons::Chord chord = (Buttons::Chord)evt.asInt();
|
Buttons::Chord chord = (Buttons::Chord)evt.asInt();
|
||||||
@ -90,7 +56,6 @@ InputMapper keyMap([](const InputEvent& evt) {
|
|||||||
|
|
||||||
REGISTER_TASK(keyMap);
|
REGISTER_TASK(keyMap);
|
||||||
|
|
||||||
|
|
||||||
// Cycle some random colors
|
// Cycle some random colors
|
||||||
ColorSequenceInput<9> idleCycle{{
|
ColorSequenceInput<9> idleCycle{{
|
||||||
CRGB(0, 123, 167), // Cerulean
|
CRGB(0, 123, 167), // Cerulean
|
||||||
@ -119,13 +84,23 @@ void setup() {
|
|||||||
// Turn on,
|
// Turn on,
|
||||||
Platform::preSetup();
|
Platform::preSetup();
|
||||||
Log.notice(u8"🐛 Booting Renderbug!");
|
Log.notice(u8"🐛 Booting Renderbug!");
|
||||||
Log.notice(u8"🐞 I am built for %d LEDs running on %dmA", HardwareConfig::MAX_LED_NUM, PSU_MILLIAMPS);
|
Log.notice(u8"🐞 I am built for %d LEDs on pin %d", HardwareConfig::MAX_LED_NUM, RENDERBUG_LED_PIN);
|
||||||
Log.notice(u8"📡 Platform %s version %s", Platform::name(), Platform::version());
|
Log.notice(u8"📡 Platform %s version %s", Platform::name(), Platform::version());
|
||||||
|
|
||||||
|
if (Platform::bootopts.crashCount > 0) {
|
||||||
|
Log.warning(u8"Previous crash detected!!!! We're on attempt %d", Platform::bootopts.crashCount);
|
||||||
|
char lastTaskBuf[15];
|
||||||
|
strncpy(lastTaskBuf, MainLoop::lastTaskName(), sizeof(lastTaskBuf));
|
||||||
|
lastTaskBuf[15] = 0;
|
||||||
|
Log.error(u8"Crash occurred in task %s", lastTaskBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.trace("Startup reason: %d", Platform::bootopts.resetReason);
|
||||||
|
|
||||||
Log.notice(u8"Setting timezone to +2 (CEST)");
|
Log.notice(u8"Setting timezone to +2 (CEST)");
|
||||||
Platform::setTimezone(+2);
|
Platform::setTimezone(+2);
|
||||||
|
|
||||||
Log.notice(u8"Setting up platform...");
|
Log.trace(u8"Setting up platform...");
|
||||||
|
|
||||||
Platform::setup();
|
Platform::setup();
|
||||||
Platform::bootSplash();
|
Platform::bootSplash();
|
||||||
@ -135,7 +110,7 @@ void setup() {
|
|||||||
|
|
||||||
// Tune in,
|
// Tune in,
|
||||||
if (Platform::bootopts.isSafeMode) {
|
if (Platform::bootopts.isSafeMode) {
|
||||||
Log.notice(u8"⚠️ Starting Figment in safe mode!!!");
|
Log.error(u8"⚠️ Starting Figment in safe mode!!!");
|
||||||
runner = &SafeMode::safeModeApp;
|
runner = &SafeMode::safeModeApp;
|
||||||
FastLED.showColor(CRGB(5, 0, 0));
|
FastLED.showColor(CRGB(5, 0, 0));
|
||||||
FastLED.show();
|
FastLED.show();
|
||||||
@ -143,7 +118,7 @@ void setup() {
|
|||||||
Log.notice(u8"🌌 Starting Figment...");
|
Log.notice(u8"🌌 Starting Figment...");
|
||||||
|
|
||||||
if (Platform::bootopts.isSetup) {
|
if (Platform::bootopts.isSetup) {
|
||||||
Log.notice(u8"🔧 Booting up into setup profile!!!");
|
Log.warning(u8"🔧 Booting up into setup profile!!!");
|
||||||
Static<ConfigService>::instance()->overrideProfile("setup");
|
Static<ConfigService>::instance()->overrideProfile("setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,7 @@ MQTTTelemetry::MQTTTelemetry() : BufferedInputSource("MQTT"),
|
|||||||
m_logPrinter(this)
|
m_logPrinter(this)
|
||||||
{
|
{
|
||||||
m_debugTopic = String("renderbug/") + Platform::deviceID();
|
m_debugTopic = String("renderbug/") + Platform::deviceID();
|
||||||
|
memset(m_hostBuf, 0, sizeof(m_hostBuf));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -199,10 +200,13 @@ MQTTTelemetry::handleEventOnline(const InputEvent& evt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MQTTTelemetry::handleConfigChange(const InputEvent& event)
|
MQTTTelemetry::handleConfigChange(const Configuration& cfg)
|
||||||
{
|
{
|
||||||
const JsonObject& obj = event.as<JsonObject>();
|
Log.notice("Config change in mqtt");
|
||||||
strncpy(m_hostBuf, obj["mqtt.ip"].as<JsonString>().c_str(), sizeof(m_hostBuf));
|
const char* newIP = cfg.get("mqtt.ip", m_hostBuf);
|
||||||
|
Log.notice("ip: %s", newIP);
|
||||||
|
strncpy(m_hostBuf, newIP, sizeof(m_hostBuf));
|
||||||
|
m_hostBuf[sizeof(m_hostBuf)-1] = 0;
|
||||||
m_mqtt.disconnect();
|
m_mqtt.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +221,7 @@ MQTTTelemetry::loop()
|
|||||||
void
|
void
|
||||||
MQTTTelemetry::handleEvent(const InputEvent& evt)
|
MQTTTelemetry::handleEvent(const InputEvent& evt)
|
||||||
{
|
{
|
||||||
|
BufferedInputSource::handleEvent(evt);
|
||||||
OnlineTaskMixin::handleEvent(evt);
|
OnlineTaskMixin::handleEvent(evt);
|
||||||
ConfigTaskMixin::handleEvent(evt);
|
ConfigTaskMixin::handleEvent(evt);
|
||||||
}
|
}
|
||||||
@ -225,7 +230,11 @@ void
|
|||||||
MQTTTelemetry::onOnline()
|
MQTTTelemetry::onOnline()
|
||||||
{
|
{
|
||||||
m_needHeartbeat = true;
|
m_needHeartbeat = true;
|
||||||
|
if (strlen(m_hostBuf) > 0) {
|
||||||
m_mqtt.setServer(m_hostBuf, 1883);
|
m_mqtt.setServer(m_hostBuf, 1883);
|
||||||
|
} else {
|
||||||
|
Log.warning("mqtt.ip is not configured, no connection possible");
|
||||||
|
}
|
||||||
m_mqtt.setBufferSize(1024);
|
m_mqtt.setBufferSize(1024);
|
||||||
m_mqtt.setCallback(&MQTTTelemetry::s_callback);
|
m_mqtt.setCallback(&MQTTTelemetry::s_callback);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <PubSubClient.h>
|
#include <PubSubClient.h>
|
||||||
#include <ArduinoLog.h>
|
#include <ArduinoLog.h>
|
||||||
|
#include "../../Config.h"
|
||||||
|
|
||||||
#include "../../Sequencer.h"
|
#include "../../Sequencer.h"
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin, ConfigTaskMix
|
|||||||
|
|
||||||
void handleEvent(const InputEvent& evt) override;
|
void handleEvent(const InputEvent& evt) override;
|
||||||
void handleEventOnline(const InputEvent& evt) override;
|
void handleEventOnline(const InputEvent& evt) override;
|
||||||
void handleConfigChange(const InputEvent& evt) override;
|
void handleConfigChange(const Configuration& cfg) override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
void loopOnline() override;
|
void loopOnline() override;
|
||||||
|
@ -18,6 +18,7 @@ class U8Display : public Task {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void onStart() {
|
void onStart() {
|
||||||
|
Log.trace("display: starting redraw thread");
|
||||||
xTaskCreatePinnedToCore(
|
xTaskCreatePinnedToCore(
|
||||||
&U8Display::redrawTask,
|
&U8Display::redrawTask,
|
||||||
name,
|
name,
|
||||||
@ -28,6 +29,11 @@ class U8Display : public Task {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onStop() {
|
||||||
|
Log.trace("display: stopping redraw thread");
|
||||||
|
vTaskDelete(m_renderTask);
|
||||||
|
}
|
||||||
|
|
||||||
void handleEvent(const InputEvent& evt) {
|
void handleEvent(const InputEvent& evt) {
|
||||||
m_lastEvent = evt;
|
m_lastEvent = evt;
|
||||||
if (m_state == Idle) {
|
if (m_state == Idle) {
|
||||||
|
@ -70,21 +70,5 @@ public:
|
|||||||
|
|
||||||
pixel += blend(CRGB(blobColor), pixel, 80);
|
pixel += blend(CRGB(blobColor), pixel, 80);
|
||||||
});
|
});
|
||||||
/*uint8_t scaledWidth = std::abs(endPos.x - startPos.x);
|
|
||||||
|
|
||||||
//Log.notice("blob w=%d x=%d", scaledWidth, startPos.x);
|
|
||||||
for(uint8_t i = 0;i < scaledWidth; i++) {
|
|
||||||
// Blobs desaturate towards their tail
|
|
||||||
//Log.notice("blob i=%d w=%d x=%d", i, scaledWidth, startPos.x);
|
|
||||||
uint8_t scalePct = map8(i, 0, scaledWidth);
|
|
||||||
uint8_t val = lerp8by8(0, m_brightness, scalePct);
|
|
||||||
//CHSV blobColor(m_hue, m_saturation, quadwave8((i / (double)scaledWidth) * m_brightness));
|
|
||||||
CHSV blobColor(m_hue, m_saturation, quadwave8(val));
|
|
||||||
|
|
||||||
PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), startPos.y};
|
|
||||||
|
|
||||||
CRGB src(display->pixelAt(pos));
|
|
||||||
display->pixelAt(pos) = blend(CRGB(blobColor), src, 140);
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user