Compare commits

..

No commits in common. "f0abdc05676037770b81a17fc54b1d843293f458" and "6831dfdfca60074f9eaef32a172f432b0eb8d8b1" have entirely different histories.

10 changed files with 177 additions and 250 deletions

View File

@ -42,7 +42,7 @@ BootOptions::BootOptions()
EEPROM.begin(sizeof(crashCount)); EEPROM.begin(sizeof(crashCount));
EEPROM.get(sizeof(HardwareConfig) + 32, crashCount); EEPROM.get(sizeof(HardwareConfig) + 32, crashCount);
EEPROM.end(); EEPROM.end();
if (resetInfo.reason == REASON_WDT_RST || resetInfo.reason == REASON_EXCEPTION_RST) { if (resetInfo.reason == REASON_WDT_RST) {
if (crashCount++ >= 3) { if (crashCount++ >= 3) {
// Boot into safe mode if the watchdog reset us three times in a row. // Boot into safe mode if the watchdog reset us three times in a row.
isSafeMode = true; isSafeMode = true;

View File

@ -1,16 +1,8 @@
#include "./Config.h" #include "./Config.h"
#include "./Static.h" #include "./Static.h"
#include "./Sequencer.h"
#include <ArduinoLog.h> #include <ArduinoLog.h>
#include <ArduinoJson.h>
#include <EEPROM.h> #include <EEPROM.h>
#include <LittleFS.h>
#include <vector>
StaticJsonDocument<256> jsonConfig;
constexpr uint16_t HardwareConfig::MAX_LED_NUM; constexpr uint16_t HardwareConfig::MAX_LED_NUM;
HardwareConfig HardwareConfig
@ -23,7 +15,7 @@ HardwareConfig::load() {
#ifndef BOARD_TEENSY #ifndef BOARD_TEENSY
EEPROM.end(); EEPROM.end();
#endif #endif
Log.notice("config: Loaded SRAM config version %d, CRC %d", ret.version, ret.checksum); Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum);
return ret; return ret;
} }
@ -41,10 +33,18 @@ HardwareConfig::save() {
#endif #endif
} }
LinearCoordinateMapping
HardwareConfig::toCoordMap() const
{
auto pixelCount = min(HardwareConfig::MAX_LED_NUM, std::max((uint16_t)1, data.pixelCount));
auto startPixel = min(pixelCount, std::max((uint16_t)1, data.startPixel));
return LinearCoordinateMapping{pixelCount, startPixel};
}
bool bool
HardwareConfig::isValid() const HardwareConfig::isValid() const
{ {
return version == 3 && checksum == getCRC(); return version == 2 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM;
} }
uint8_t uint8_t
@ -68,82 +68,18 @@ HardwareConfig::getCRC() const
void void
ConfigService::onStart() ConfigService::onStart()
{ {
Log.notice("config: Starting configuration service..."); Log.notice("Starting configuration service...");
m_config = HardwareConfig::load(); m_config = HardwareConfig::load();
if (m_config.isValid()) { if (m_config.isValid()) {
Log.notice("config: Configuration found!"); Log.notice("Configuration found!");
} else { } else {
Log.notice("config: No configuration found. Writing defaults..."); Log.notice("No configuration found. Writing defaults...");
m_config = HardwareConfig{}; m_config = HardwareConfig{};
m_config.save(); m_config.save();
} }
if (strlen(m_config.data.loadedProfile) == 0) { m_coordMap = m_config.toCoordMap();
strcpy(m_config.data.loadedProfile, "default");
}
loadProfile(m_config.data.loadedProfile);
}
void Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel);
ConfigService::loadMap(const String& mapName)
{
String fname = String("/maps/") + mapName + ".json";
if (LittleFS.exists(fname)) {
File configFile = LittleFS.open(fname, "r");
Log.notice("config: Loading coordinate map %s", mapName.c_str());
deserializeJson(jsonConfig, configFile);
configFile.close();
JsonArray strideList = jsonConfig["strides"];
m_jsonMap.load(strideList);
} else {
Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str());
m_jsonMap.loadDefault();
}
}
void
ConfigService::loadProfile(const char* profileName)
{
Log.notice("config: Loading profile %s...", profileName);
String fname = String("/profiles/") + profileName + ".json";
LittleFS.begin();
if (LittleFS.exists(fname)) {
File configFile = LittleFS.open(fname, "r");
jsonConfig.clear();
deserializeJson(jsonConfig, configFile);
configFile.close();
JsonObject sceneList = jsonConfig["scenes"];
std::vector<Sequencer::Scene> scenes;
for(JsonPair pair : sceneList) {
Log.notice("config: \tFound scene %s", pair.key().c_str());
std::vector<const char*> patterns;
for(const char* taskName : pair.value().as<JsonArray>()) {
patterns.push_back(taskName);
}
scenes.push_back(Sequencer::Scene{pair.key().c_str(), patterns});
}
Static<Sequencer>::instance()->setScenes(std::move(scenes));
JsonArray taskList = jsonConfig["tasks"];
Log.notice("config: Starting %d tasks", taskList.size());
for(int i = 0; i < taskList.size();i++) {
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, taskList[i].as<const char*>()});
}
Log.notice("config: Loaded!");
} else {
Log.warning("config: Could not load profile %s!", profileName);
}
String configName = jsonConfig["surfaceMap"];
jsonConfig.clear();
loadMap(configName);
LittleFS.end();
Log.notice("config: Configured to use %d pixels", m_jsonMap.physicalPixelCount());
PhysicalCoordinates topLeft = m_jsonMap.virtualToPhysicalCoords({0, 0});
PhysicalCoordinates bottomRight = m_jsonMap.virtualToPhysicalCoords({255, 255});
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));
} }
void void
@ -151,22 +87,24 @@ ConfigService::loop()
{ {
} }
const char*
ConfigService::loadedProfile() const
{
return m_config.data.loadedProfile;
}
void void
ConfigService::handleEvent(const InputEvent &evt) ConfigService::handleEvent(const InputEvent &evt)
{ {
switch(evt.intent) { switch(evt.intent) {
case InputEvent::LoadConfigurationByName: case InputEvent::SetDisplayLength:
Log.notice("Reloading configuration %s", evt.asString()); //Log.info("Updating pixel count from %d to %d", m_coordMap.pixelCount, evt.asInt());
strcpy(m_config.data.loadedProfile, evt.asString()); m_config.data.pixelCount = evt.asInt();
loadProfile(evt.asString()); m_coordMap = m_config.toCoordMap();
//Log.info("Count is now %d", m_coordMap.pixelCount);
break;
case InputEvent::SetDisplayOffset:
//Log.info("Updating pixel offset from %d to %d", m_coordMap.startPixel, evt.asInt());
m_config.data.startPixel = evt.asInt();
m_coordMap = m_config.toCoordMap();
//Log.info("Offset is now %d", m_coordMap.startPixel);
break;
case InputEvent::SaveConfigurationRequest: case InputEvent::SaveConfigurationRequest:
Log.notice("Saving configuration"); //Log.info("Saving configuration");
m_config.save(); m_config.save();
break; break;
default: default:

View File

@ -1,12 +1,78 @@
#pragma once #pragma once
#include <Figments.h> #include <Figments.h>
#include "JsonCoordinateMapping.h"
struct MaskCoordinateMapping : CoordinateMapping {
struct Span {
int length = 0;
int x = 0;
int y = 0;
Span(int length, int x, int y) : length(length), x(x), y(y) {}
};
Span displayMap[13] = {
{6, 0, 6},
{6, 1, 6},
{7, 2, 6},
{9, 3, 4},
{14, 4, 4},
{17, 5, 0},
{12, 6, 2},
{18, 7, 0},
{14, 8, 4},
{9, 9, 5},
{7, 10, 4},
{6, 11, 5},
{6, 12, 5}
};
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override {
int offset = localCoords.x;
for(int i = 0; i < 12; i++) {
if (offset > displayMap[i].length) {
offset -= displayMap[i].length;
} else {
return VirtualCoordinates{i, offset};
}
}
}
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override {
const uint8_t spanIdx = scale8(12, virtualCoords.x);
const uint8_t spanOffset = scale8(17, virtualCoords.y);
return PhysicalCoordinates{spanIdx, spanOffset};
}
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override {
uint8_t idx = 0;
bool inverse = false;
for(int i = 0; i < localCoords.x; i++) {
idx += displayMap[i].length;
inverse = !inverse;
}
if (inverse) {
idx += std::max(0, displayMap[localCoords.x].length - 1 - std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
} else {
idx += std::min((int)displayMap[localCoords.x].length - 1, std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
}
return idx;
}
unsigned int physicalPixelCount() const override {
int total = 0;
for(int i = 0; i < 13; i++) {
total += displayMap[i].length;
}
return total;
}
};
struct HardwareConfig { struct HardwareConfig {
uint8_t version = 3; uint8_t version = 3;
uint8_t checksum = 0; uint8_t checksum = 0;
struct Data { struct Data {
char loadedProfile[16] = {0}; uint16_t pixelCount = 255;
uint16_t startPixel = 0;
uint8_t lastRed = 255; uint8_t lastRed = 255;
uint8_t lastGreen = 255; uint8_t lastGreen = 255;
uint8_t lastBlue = 255; uint8_t lastBlue = 255;
@ -18,6 +84,7 @@ struct HardwareConfig {
void save(); void save();
bool isValid() const; bool isValid() const;
LinearCoordinateMapping toCoordMap() const;
static constexpr uint16_t MAX_LED_NUM = 255; static constexpr uint16_t MAX_LED_NUM = 255;
private: private:
@ -34,13 +101,10 @@ struct ConfigService: public Task {
void onStart(); void onStart();
void loop() override; void loop() override;
void handleEvent(const InputEvent &evt) override; void handleEvent(const InputEvent &evt) override;
const CoordinateMapping* coordMap() const { return &m_jsonMap; } const CoordinateMapping* coordMap() const { return /*&m_maskMap;*/ &m_coordMap; }
const char* loadedProfile() const;
private: private:
HardwareConfig m_config; HardwareConfig m_config;
JsonCoordinateMapping m_jsonMap; MaskCoordinateMapping m_maskMap;
LinearCoordinateMapping m_coordMap;
void loadProfile(const char* name);
void loadMap(const String& mapName);
}; };

View File

@ -1,70 +0,0 @@
#include "./JsonCoordinateMapping.h"
unsigned int
JsonCoordinateMapping::physicalPixelCount() const {
int total = 0;
for(int i = 0; i < strideCount; i++) {
total += displayMap[i].length;
}
return total;
}
int
JsonCoordinateMapping::physicalCoordsToIndex(const PhysicalCoordinates localCoords) const {
uint8_t idx = 0;
bool inverse = false;
for(int i = 0; i < localCoords.x; i++) {
idx += displayMap[i].length;
inverse = !inverse;
}
if (inverse) {
idx += std::max(0, displayMap[localCoords.x].length - 1 - std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
} else {
idx += std::min((int)displayMap[localCoords.x].length - 1, std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
}
return idx;
}
PhysicalCoordinates
JsonCoordinateMapping::virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
const uint8_t spanIdx = scale8(strideCount-1, virtualCoords.x);
const uint8_t spanOffset = scale8(maxStrideSize - 1, virtualCoords.y);
return PhysicalCoordinates{spanIdx, spanOffset};
}
VirtualCoordinates
JsonCoordinateMapping::physicalToVirtualCoords(const PhysicalCoordinates localCoords) const {
int offset = localCoords.x;
for(int i = 0; i < strideCount; i++) {
if (offset > displayMap[i].length) {
offset -= displayMap[i].length;
} else {
return VirtualCoordinates{i, offset};
}
}
return VirtualCoordinates{0, 0};
}
void
JsonCoordinateMapping::load(const JsonArray& strideList)
{
maxStrideSize = 0;
strideCount = strideList.size();
for(int i = 0; i < strideList.size();i++) {
JsonObject strideObj = strideList[i];
int length = strideObj["pixels"];
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);
maxStrideSize = length > maxStrideSize ? length : maxStrideSize;
}
}
void
JsonCoordinateMapping::loadDefault()
{
displayMap[0] = Span{255, 0, 0};
maxStrideSize = 255;
strideCount = 1;
}

View File

@ -1,26 +0,0 @@
#pragma once
#include <Figments.h>
#include <ArduinoJson.h>
struct JsonCoordinateMapping : CoordinateMapping {
struct Span {
int length = 0;
int x = 0;
int y = 0;
Span() {}
Span(int length, int x, int y) : length(length), x(x), y(y) {}
};
Span displayMap[32];
int strideCount = 0;
int maxStrideSize = 0;
void load(const JsonArray& strides);
void loadDefault();
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override;
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override;
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override;
unsigned int physicalPixelCount() const override;
};

View File

@ -68,14 +68,6 @@ Platform::version()
#endif #endif
} }
int
Platform::freeRam()
{
#ifdef BOARD_ESP8266
return ESP.getFreeHeap();
#endif
}
void void
Platform::preSetup() Platform::preSetup()
{ {

View File

@ -32,7 +32,6 @@ class Platform : public Task {
void loop() override; void loop() override;
static bool getLocalTime(struct tm* timedata); static bool getLocalTime(struct tm* timedata);
static const char* deviceID(); static const char* deviceID();
static int freeRam();
struct TaskRegistration { struct TaskRegistration {
Task* task = 0; Task* task = 0;

View File

@ -1,29 +1,18 @@
#include "Sequencer.h" #include "Sequencer.h"
#include <MainLoop.h> #include <MainLoop.h>
#include "Static.h"
Sequencer::Sequencer() : Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) :
Task("SceneSequencer"), Task("SceneSequencer"),
m_idx(0) m_idx(0),
m_scenes(std::move(scenes))
{ {
} }
void Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes, int startIndex) :
Sequencer::Scene::start() Task("SceneSequencer"),
m_idx(startIndex),
m_scenes(std::move(scenes))
{ {
for(const char* pattern : patterns) {
Log.verbose("Starting pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
}
}
void
Sequencer::Scene::stop()
{
for(const char* pattern : patterns) {
Log.verbose("Stopping pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
}
} }
void void
@ -46,35 +35,32 @@ Sequencer::onStart()
{ {
} }
void
Sequencer::setScenes(std::vector<Scene> &&scenes)
{
Log.notice("Updated scenes");
m_idx = 0;
m_scenes = scenes;
}
void void
Sequencer::handleEvent(const InputEvent& evt) Sequencer::handleEvent(const InputEvent& evt)
{ {
if (evt.intent == InputEvent::ReadyToRoll && !m_scenes.empty()) { if (evt.intent == InputEvent::ReadyToRoll) {
Log.notice("Starting pattern %s!", m_scenes[m_idx].name); Log.notice("Starting pattern %s!", m_scenes[m_idx].name);
for(const char* pattern : m_scenes[m_idx].patterns) { for(const char* pattern : m_scenes[m_idx].patterns) {
Log.verbose("Starting pattern task %s", pattern); Log.verbose("Starting pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
} }
} else if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) { }
if (evt.intent == InputEvent::SetPattern && evt.asString() == m_scenes[m_idx].name) {
return; return;
} else if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) { }
if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
Log.notice("Switching pattern!"); Log.notice("Switching pattern!");
for(const char* pattern : m_scenes[m_idx].patterns) {
m_scenes[m_idx].stop(); Log.verbose("Stopping pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
}
if (evt.intent == InputEvent::NextPattern) { if (evt.intent == InputEvent::NextPattern) {
m_idx++; m_idx++;
} else if (evt.intent == InputEvent::PreviousPattern) { } else if (evt.intent == InputEvent::PreviousPattern) {
m_idx--; m_idx--;
} else { } else {
//m_idx = evt.asInt();
for(m_idx = 0; m_idx < m_scenes.size(); m_idx++) { for(m_idx = 0; m_idx < m_scenes.size(); m_idx++) {
if (!strcmp(evt.asString(), m_scenes[m_idx].name)) { if (!strcmp(evt.asString(), m_scenes[m_idx].name)) {
break; break;
@ -90,9 +76,9 @@ Sequencer::handleEvent(const InputEvent& evt)
m_idx = 0; m_idx = 0;
} }
m_scenes[m_idx].start(); for(const char* pattern : m_scenes[m_idx].patterns) {
Log.verbose("Starting pattern task %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
}
} }
} }
STATIC_ALLOC(Sequencer);
STATIC_TASK(Sequencer);

View File

@ -10,16 +10,14 @@ public:
public: public:
const char* name; const char* name;
std::vector<const char*> patterns; std::vector<const char*> patterns;
void start();
void stop();
}; };
Sequencer(); Sequencer(std::vector<Scene> &&scenes);
Sequencer(std::vector<Scene> &&scenes, int startingIndex);
void loop() override; void loop() override;
void onStart() override; void onStart() override;
void handleEvent(const InputEvent& evt) override; void handleEvent(const InputEvent& evt) override;
void setScenes(std::vector<Scene> &&scenes);
const char* currentSceneName(); const char* currentSceneName();
const std::vector<Scene> scenes() const; const std::vector<Scene> scenes() const;

View File

@ -12,13 +12,17 @@
#include "Static.h" #include "Static.h"
#include "Config.h" #include "Config.h"
#include "Sequencer.h"
#include "LogService.h" #include "LogService.h"
#include <time.h> #include <time.h>
#include "animations/Power.h" #include "animations/Power.h"
#include "animations/SolidAnimation.h"
#include "animations/Chimes.h"
#include "animations/Flashlight.h"
#include "animations/Drain.h" #include "animations/Drain.h"
#include "animations/InputBlip.h" #include "animations/UpdateStatus.h"
#include "inputs/ColorCycle.h" #include "inputs/ColorCycle.h"
#include "inputs/Buttons.h" #include "inputs/Buttons.h"
@ -61,6 +65,32 @@ REGISTER_TASK(power);
} }
});*/ });*/
class InputBlip: public Figment {
public:
InputBlip() : Figment("InputBlip", Task::Stopped) {}
void handleEvent(const InputEvent& evt) override {
if (evt.intent != InputEvent::None) {
m_time = qadd8(m_time, 5);
}
}
void loop() override {
if (m_time > 0) {
m_time--;
}
}
void render(Display* dpy) const override {
if (m_time > 0) {
dpy->pixelAt(0) = CRGB(0, brighten8_video(ease8InOutApprox(m_time)), 0);
}
}
private:
uint8_t m_time = 0;
};
InputBlip inputBlip;
REGISTER_TASK(inputBlip);
InputFunc randomPulse([]() { InputFunc randomPulse([]() {
static unsigned int pulse = 0; static unsigned int pulse = 0;
EVERY_N_MILLISECONDS(25) { EVERY_N_MILLISECONDS(25) {
@ -101,6 +131,22 @@ InputMapper keyMap([](const InputEvent& evt) {
REGISTER_TASK(keyMap); REGISTER_TASK(keyMap);
#ifndef DEFAULT_PATTERN_INDEX
#define DEFAULT_PATTERN_INDEX 0
#endif
Sequencer sequencer{{
{"Idle", {"Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"}},
{"Acid", {"Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"}},
{"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
{"Interactive", {"Drain", "MPU5060", "CircadianRhythm"}},
{"Flashlight", {"Flashlight"}},
{"Gay", {"Solid", "Pulse", "Rainbow"}},
}, DEFAULT_PATTERN_INDEX};
REGISTER_TASK(sequencer);
class BPM : public InputSource { class BPM : public InputSource {
public: public:
BPM() : InputSource("BPM") {} BPM() : InputSource("BPM") {}
@ -172,7 +218,7 @@ REGISTER_TASK(renderer);
Renderer configRenderer{ Renderer configRenderer{
{&dpy}, {&dpy},
{Static<DrainAnimation>::instance(), /*&configDisplay,*/ Static<InputBlip>::instance(), &power} {Static<DrainAnimation>::instance(), /*&configDisplay,*/ &inputBlip, &power}
}; };
// Cycle some random colors // Cycle some random colors