Compare commits

...

4 Commits

10 changed files with 250 additions and 177 deletions

View File

@ -42,7 +42,7 @@ BootOptions::BootOptions()
EEPROM.begin(sizeof(crashCount));
EEPROM.get(sizeof(HardwareConfig) + 32, crashCount);
EEPROM.end();
if (resetInfo.reason == REASON_WDT_RST) {
if (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;

View File

@ -1,8 +1,16 @@
#include "./Config.h"
#include "./Static.h"
#include "./Sequencer.h"
#include <ArduinoLog.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <LittleFS.h>
#include <vector>
StaticJsonDocument<256> jsonConfig;
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
HardwareConfig
@ -15,7 +23,7 @@ HardwareConfig::load() {
#ifndef BOARD_TEENSY
EEPROM.end();
#endif
Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum);
Log.notice("config: Loaded SRAM config version %d, CRC %d", ret.version, ret.checksum);
return ret;
}
@ -33,18 +41,10 @@ HardwareConfig::save() {
#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
HardwareConfig::isValid() const
{
return version == 2 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM;
return version == 3 && checksum == getCRC();
}
uint8_t
@ -68,18 +68,82 @@ HardwareConfig::getCRC() const
void
ConfigService::onStart()
{
Log.notice("Starting configuration service...");
Log.notice("config: Starting configuration service...");
m_config = HardwareConfig::load();
if (m_config.isValid()) {
Log.notice("Configuration found!");
Log.notice("config: Configuration found!");
} else {
Log.notice("No configuration found. Writing defaults...");
Log.notice("config: No configuration found. Writing defaults...");
m_config = HardwareConfig{};
m_config.save();
}
m_coordMap = m_config.toCoordMap();
if (strlen(m_config.data.loadedProfile) == 0) {
strcpy(m_config.data.loadedProfile, "default");
}
loadProfile(m_config.data.loadedProfile);
}
Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel);
void
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
@ -87,24 +151,22 @@ ConfigService::loop()
{
}
const char*
ConfigService::loadedProfile() const
{
return m_config.data.loadedProfile;
}
void
ConfigService::handleEvent(const InputEvent &evt)
{
switch(evt.intent) {
case InputEvent::SetDisplayLength:
//Log.info("Updating pixel count from %d to %d", m_coordMap.pixelCount, evt.asInt());
m_config.data.pixelCount = evt.asInt();
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::LoadConfigurationByName:
Log.notice("Reloading configuration %s", evt.asString());
strcpy(m_config.data.loadedProfile, evt.asString());
loadProfile(evt.asString());
case InputEvent::SaveConfigurationRequest:
//Log.info("Saving configuration");
Log.notice("Saving configuration");
m_config.save();
break;
default:

View File

@ -1,78 +1,12 @@
#pragma once
#include <Figments.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;
}
};
#include "JsonCoordinateMapping.h"
struct HardwareConfig {
uint8_t version = 3;
uint8_t checksum = 0;
struct Data {
uint16_t pixelCount = 255;
uint16_t startPixel = 0;
char loadedProfile[16] = {0};
uint8_t lastRed = 255;
uint8_t lastGreen = 255;
uint8_t lastBlue = 255;
@ -84,7 +18,6 @@ struct HardwareConfig {
void save();
bool isValid() const;
LinearCoordinateMapping toCoordMap() const;
static constexpr uint16_t MAX_LED_NUM = 255;
private:
@ -101,10 +34,13 @@ struct ConfigService: public Task {
void onStart();
void loop() override;
void handleEvent(const InputEvent &evt) override;
const CoordinateMapping* coordMap() const { return /*&m_maskMap;*/ &m_coordMap; }
const CoordinateMapping* coordMap() const { return &m_jsonMap; }
const char* loadedProfile() const;
private:
HardwareConfig m_config;
MaskCoordinateMapping m_maskMap;
LinearCoordinateMapping m_coordMap;
JsonCoordinateMapping m_jsonMap;
void loadProfile(const char* name);
void loadMap(const String& mapName);
};

View File

@ -0,0 +1,70 @@
#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

@ -0,0 +1,26 @@
#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,6 +68,14 @@ Platform::version()
#endif
}
int
Platform::freeRam()
{
#ifdef BOARD_ESP8266
return ESP.getFreeHeap();
#endif
}
void
Platform::preSetup()
{

View File

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

View File

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

View File

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

View File

@ -12,17 +12,13 @@
#include "Static.h"
#include "Config.h"
#include "Sequencer.h"
#include "LogService.h"
#include <time.h>
#include "animations/Power.h"
#include "animations/SolidAnimation.h"
#include "animations/Chimes.h"
#include "animations/Flashlight.h"
#include "animations/Drain.h"
#include "animations/UpdateStatus.h"
#include "animations/InputBlip.h"
#include "inputs/ColorCycle.h"
#include "inputs/Buttons.h"
@ -65,32 +61,6 @@ 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([]() {
static unsigned int pulse = 0;
EVERY_N_MILLISECONDS(25) {
@ -131,22 +101,6 @@ InputMapper keyMap([](const InputEvent& evt) {
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 {
public:
BPM() : InputSource("BPM") {}
@ -218,7 +172,7 @@ REGISTER_TASK(renderer);
Renderer configRenderer{
{&dpy},
{Static<DrainAnimation>::instance(), /*&configDisplay,*/ &inputBlip, &power}
{Static<DrainAnimation>::instance(), /*&configDisplay,*/ Static<InputBlip>::instance(), &power}
};
// Cycle some random colors