wip commit
This commit is contained in:
parent
6582bae0f2
commit
ebbf433cdf
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"rotation": 3,
|
"rotation": 2,
|
||||||
"strides": [
|
"strides": [
|
||||||
{"x": 0, "y": 0, "pixels": 17},
|
{"x": 0, "y": 0, "pixels": 17},
|
||||||
{"x": 0, "y": 1, "pixels": 17},
|
{"x": 0, "y": 1, "pixels": 17},
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"tasks": [
|
"tasks": [
|
||||||
"Renderer",
|
"Renderer",
|
||||||
"U8Display",
|
|
||||||
"WiFi",
|
"WiFi",
|
||||||
"MQTT",
|
"MQTT",
|
||||||
"ArduinoOTA",
|
"ArduinoOTA",
|
||||||
@ -10,7 +9,7 @@
|
|||||||
"Serial"
|
"Serial"
|
||||||
],
|
],
|
||||||
"scenes": {
|
"scenes": {
|
||||||
"Idle": ["Solid", "MPU5060", "IdleColors", "CircadianRhythm"],
|
"Idle": ["Solid", "IdleColors", "CircadianRhythm"],
|
||||||
"Flashlight": ["Flashlight"]
|
"Flashlight": ["Flashlight"]
|
||||||
},
|
},
|
||||||
"surfaceMap": "default",
|
"surfaceMap": "default",
|
||||||
|
@ -5,20 +5,20 @@
|
|||||||
"Renderer",
|
"Renderer",
|
||||||
"Serial"
|
"Serial"
|
||||||
],
|
],
|
||||||
"scenes": {
|
|
||||||
"Rain": ["Rain", "Rainbow"],
|
|
||||||
"Test": ["Test"],
|
|
||||||
"Idle": ["Solid", "Pulse", "Rainbow", "CircadianRhythm"],
|
|
||||||
"Acid": ["Chimes", "Pulse", "IdleColors", "Rainbow"],
|
|
||||||
"Flashlight": ["Flashlight"]
|
|
||||||
},
|
|
||||||
"surfaceMap": "djstrip",
|
"surfaceMap": "djstrip",
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"mqtt.ip": "10.0.0.2",
|
"mqtt.ip": "10.0.0.2",
|
||||||
"power.volts": 0,
|
"power.volts": 0,
|
||||||
"power.milliamps": 0,
|
"power.milliamps": 0,
|
||||||
"power.useBPM": false,
|
"power.useBPM": false,
|
||||||
"bpm.idle": 45
|
"bpm.idle": 45,
|
||||||
|
"scenes": {
|
||||||
|
"Rain": ["Rain", "Rainbow"],
|
||||||
|
"Test": ["Test"],
|
||||||
|
"Idle": ["Solid", "Pulse", "Rainbow", "CircadianRhythm"],
|
||||||
|
"Acid": ["Chimes", "Pulse", "IdleColors", "Rainbow"],
|
||||||
|
"Flashlight": ["Flashlight"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,12 @@
|
|||||||
"MQTT",
|
"MQTT",
|
||||||
"ArduinoOTA",
|
"ArduinoOTA",
|
||||||
"UpdateStatusAnimation",
|
"UpdateStatusAnimation",
|
||||||
"Serial"
|
"Serial",
|
||||||
|
"U8Display"
|
||||||
],
|
],
|
||||||
"scenes": {
|
"scenes": {
|
||||||
"Idle": ["Solid", "MPU5060", "Pulse", "IdleColors", "CircadianRhythm"],
|
"Idle": ["Solid", "Rainbow", "CircadianRhythm"],
|
||||||
"Acid": ["Chimes", "Pulse", "MPU5060", "IdleColors", "Rainbow"],
|
"Acid": ["Chimes", "Rainbow"],
|
||||||
"Flashlight": ["Flashlight"]
|
"Flashlight": ["Flashlight"]
|
||||||
},
|
},
|
||||||
"surfaceMap": "default",
|
"surfaceMap": "default",
|
||||||
|
@ -8,24 +8,40 @@
|
|||||||
"UpdateStatusAnimation",
|
"UpdateStatusAnimation",
|
||||||
"Serial",
|
"Serial",
|
||||||
"Weather",
|
"Weather",
|
||||||
"CircadianRhythm"
|
"Test",
|
||||||
|
"CircadianRhythm",
|
||||||
|
"IdleTimer"
|
||||||
],
|
],
|
||||||
"scenes": {
|
|
||||||
"Clear": ["Solid", "Rainbow"],
|
|
||||||
"Rain": ["Rain"],
|
|
||||||
"Drizzle": ["Rain"],
|
|
||||||
"Mist": ["Rain"],
|
|
||||||
"Snow": ["Solid"],
|
|
||||||
"UnknownWeather": ["Solid", "IdleColors"],
|
|
||||||
"Test": ["Test"],
|
|
||||||
"Idle": ["Solid", "Rainbow"],
|
|
||||||
"Acid": ["Chimes", "IdleColors", "Rainbow"],
|
|
||||||
"Flashlight": ["Flashlight"]
|
|
||||||
},
|
|
||||||
"surfaceMap": "ponder",
|
"surfaceMap": "ponder",
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"mqtt.ip": "10.0.0.2",
|
"mqtt.ip": "10.0.0.2",
|
||||||
"power.volts": 0,
|
"colors.sequences": {
|
||||||
"power.milliamps": 0
|
"Rainbow": [[255, 0, 0], [255, 127, 0], [0, 255, 0], [0, 0, 255], [128, 0, 128]],
|
||||||
|
"Idle": [[0, 123, 167], [80, 200, 120], [207, 113, 175], [128, 0, 128], [255, 255, 255], [0, 255, 255]],
|
||||||
|
"Wet": [[3, 177, 252], [3, 78, 252], [126, 141, 242]],
|
||||||
|
"Snowy": [[215, 219, 245], [255, 255, 255], [192, 250, 244]],
|
||||||
|
"Sunny": [[213, 237, 95], [237, 180, 95], [245, 178, 10]]
|
||||||
|
},
|
||||||
|
"colors.scenes": {
|
||||||
|
"Clear": "Sunny",
|
||||||
|
"Idle": "Rainbow",
|
||||||
|
"Rain": "Wet",
|
||||||
|
"Drizzle": "Wet",
|
||||||
|
"Mist": "Wet",
|
||||||
|
"Snow": "Snowy"
|
||||||
|
},
|
||||||
|
"colors.secondsPerColor": 9,
|
||||||
|
"scenes": {
|
||||||
|
"Clear": ["Solid"],
|
||||||
|
"Cloudy": ["Cloudy", "Solid"],
|
||||||
|
"Rain": ["Rain", "Solid"],
|
||||||
|
"Drizzle": ["Rain", "Cloudy", "Solid"],
|
||||||
|
"Mist": ["Cloudy", "Solid"],
|
||||||
|
"Snow": ["Solid"],
|
||||||
|
"UnknownWeather": ["Thinking"],
|
||||||
|
"Start": ["Thinking"],
|
||||||
|
"Idle": ["Solid"],
|
||||||
|
"Test": ["Test"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
data/profiles/pondertest.json
Normal file
7
data/profiles/pondertest.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"tasks": [
|
||||||
|
"Renderer",
|
||||||
|
"Serial"
|
||||||
|
]
|
||||||
|
}
|
@ -6,6 +6,9 @@ class Args {
|
|||||||
String *str;
|
String *str;
|
||||||
public:
|
public:
|
||||||
Args(String *str) : str(str) {}
|
Args(String *str) : str(str) {}
|
||||||
|
operator const char*() const {
|
||||||
|
return str->c_str();
|
||||||
|
}
|
||||||
String operator[](int pos) {
|
String operator[](int pos) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
strncpy(buf, str->c_str(), sizeof(buf));
|
strncpy(buf, str->c_str(), sizeof(buf));
|
||||||
|
@ -108,7 +108,7 @@ struct InputEvent: public Variant {
|
|||||||
InputEvent()
|
InputEvent()
|
||||||
: Variant(), intent(None) {}
|
: Variant(), intent(None) {}
|
||||||
|
|
||||||
bool operator!=(const InputEvent::Intent& otherIntent) {
|
bool operator!=(const InputEvent::Intent& otherIntent) const {
|
||||||
return intent != otherIntent;
|
return intent != otherIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ Renderer::loop()
|
|||||||
{
|
{
|
||||||
uint16_t totalPower = 0;
|
uint16_t totalPower = 0;
|
||||||
for(Display* dpy : m_displays) {
|
for(Display* dpy : m_displays) {
|
||||||
|
dpy->clear();
|
||||||
totalPower += calculate_unscaled_power_mW(dpy->pixelBacking(), dpy->pixelCount());
|
totalPower += calculate_unscaled_power_mW(dpy->pixelBacking(), dpy->pixelCount());
|
||||||
for(Figment* figment : m_figments) {
|
for(Figment* figment : m_figments) {
|
||||||
if (figment->state == Task::Running) {
|
if (figment->state == Task::Running) {
|
||||||
|
0
lib/Figments/Scheduler.h
Normal file
0
lib/Figments/Scheduler.h
Normal file
@ -43,6 +43,14 @@ Surface::operator+=(const CRGB& color)
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::blend(const CRGB& color, uint8_t pct)
|
||||||
|
{
|
||||||
|
paintWith([&](CRGB& pixel) {
|
||||||
|
nblend(pixel, color, pct);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Surface::paintWith(std::function<void(CRGB&)> func)
|
Surface::paintWith(std::function<void(CRGB&)> func)
|
||||||
{
|
{
|
||||||
|
@ -31,6 +31,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
Surface& operator+=(const CRGB& color);
|
Surface& operator+=(const CRGB& color);
|
||||||
|
|
||||||
|
void blend(const CRGB& color, uint8_t pct);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OR operation that applies the given color to every pixel on the surface
|
* OR operation that applies the given color to every pixel on the surface
|
||||||
*/
|
*/
|
||||||
|
@ -11,21 +11,18 @@
|
|||||||
[env]
|
[env]
|
||||||
src_filter = +<*>, -<.git/>, -<.svn/>, -<platform/>
|
src_filter = +<*>, -<.git/>, -<.svn/>, -<platform/>
|
||||||
lib_ldf_mode = chain+
|
lib_ldf_mode = chain+
|
||||||
extra_scripts = pre:verify-configs.py, pre:build-hal.py
|
extra_scripts = post:verify-configs.py, pre:build-hal.py
|
||||||
check_flags =
|
check_flags =
|
||||||
cppcheck: --inline-suppr --suppress=*:*/.pio/*
|
cppcheck: --inline-suppr --suppress=*:*/.pio/*
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
build_type = debug
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-DFASTLED_ALL_PINS_HARDWARE_SPI
|
-DFASTLED_ALL_PINS_HARDWARE_SPI
|
||||||
src_build_flags =
|
src_build_flags =
|
||||||
-DFASTLED_ALL_PINS_HARDWARE_SPI
|
-DFASTLED_ALL_PINS_HARDWARE_SPI
|
||||||
-DRENDERBUG_VERSION=3
|
-DRENDERBUG_VERSION=3
|
||||||
-DRENDERBUG_LED_PIN=14
|
-DRENDERBUG_LED_PIN=14
|
||||||
-DRENDERBUG_LED_PACKING=RGB
|
|
||||||
-DDEFAULT_PATTERN_INDEX=0
|
|
||||||
-fstack-protector
|
-fstack-protector
|
||||||
-Wall
|
-Wall
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
153
src/Colors.cpp
Normal file
153
src/Colors.cpp
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#include "Colors.h"
|
||||||
|
#include "Static.h"
|
||||||
|
|
||||||
|
Colors::Colors()
|
||||||
|
: BufferedInputSource("Colors"),
|
||||||
|
m_colorTimer(0),
|
||||||
|
m_secondsPerColor(9)
|
||||||
|
{
|
||||||
|
state = Task::Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::loop()
|
||||||
|
{
|
||||||
|
EVERY_N_SECONDS(1) {
|
||||||
|
if (m_colorTimer > 0) {
|
||||||
|
Log.verbose("colors: tick");
|
||||||
|
m_colorTimer -= 1;
|
||||||
|
|
||||||
|
if (m_colorTimer == 0) {
|
||||||
|
rotate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigTaskMixin::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::handleEvent(const InputEvent& evt)
|
||||||
|
{
|
||||||
|
if (evt.intent == InputEvent::SetScene) {
|
||||||
|
setScene(evt.asString());
|
||||||
|
}
|
||||||
|
ConfigTaskMixin::handleEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::setSequence(const char* seqName)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
int idx = 0;
|
||||||
|
for(Sequence seq : m_sequences) {
|
||||||
|
if (strcmp(seq.name.c_str(), seqName) == 0) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
Log.info("colors: Setting sequence to %s", seqName);
|
||||||
|
m_idx = idx;
|
||||||
|
rotate();
|
||||||
|
} else {
|
||||||
|
Log.warning("colors: Unknown sequence %s", seqName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::rotate()
|
||||||
|
{
|
||||||
|
if (!m_sequences.empty()) {
|
||||||
|
Log.info("colors: rotate!") ;
|
||||||
|
m_colorIdx += 1;
|
||||||
|
m_colorIdx %= m_sequences[m_idx].colors.size();
|
||||||
|
m_colorTimer = m_secondsPerColor;
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetColor, m_sequences[m_idx].colors[m_colorIdx]});
|
||||||
|
//setEvent(InputEvent{InputEvent::SetColor, m_sequences[m_idx].colors[m_colorIdx]});
|
||||||
|
} else {
|
||||||
|
Log.info("colors: No colors to rotate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::setScene(const char* sceneName)
|
||||||
|
{
|
||||||
|
auto found = m_sceneMap.find(sceneName);
|
||||||
|
if (found != m_sceneMap.end()) {
|
||||||
|
Log.info("colors: Running scene %s", sceneName);
|
||||||
|
setSequence(found->second);
|
||||||
|
} else {
|
||||||
|
Log.warning("colors: Unknown scene %s", sceneName);
|
||||||
|
m_colorTimer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::handleConfigChange(const Configuration& cfg)
|
||||||
|
{
|
||||||
|
Log.info("colors: reloading config");
|
||||||
|
m_sequences.clear();
|
||||||
|
m_colorIdx = 0;
|
||||||
|
m_idx = 0;
|
||||||
|
m_secondsPerColor = cfg.get("colors.secondsPerColor", 9);
|
||||||
|
m_colorTimer = 0;
|
||||||
|
JsonObject seqJson = cfg.get("colors.sequences");
|
||||||
|
for(const auto seq : seqJson) {
|
||||||
|
Log.info("colors: Found color sequence %s", seq.key().c_str());
|
||||||
|
std::vector<CRGB> colors;
|
||||||
|
for(JsonArray rgb : seq.value().as<JsonArray>()) {
|
||||||
|
colors.emplace_back(CRGB(rgb[0], rgb[1], rgb[2]));
|
||||||
|
}
|
||||||
|
m_sequences.emplace_back(Sequence{
|
||||||
|
seq.key().c_str(),
|
||||||
|
std::move(colors)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sceneMap.clear();
|
||||||
|
JsonObject sceneJson = cfg.get("colors.scenes");
|
||||||
|
for(const auto scene : sceneJson) {
|
||||||
|
Log.info("colors: Found scene %s", scene.key().c_str());
|
||||||
|
m_sceneMap[scene.key().c_str()] = scene.value().as<JsonString>().c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::doSequences(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
out.println("Color sequences:");
|
||||||
|
for(const Sequence& seq : m_sequences) {
|
||||||
|
out.print("\t");
|
||||||
|
out.println(seq.name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::doSequence(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
setSequence(args[1].c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Colors::doRotate(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
rotate();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
Colors::commands() const
|
||||||
|
{
|
||||||
|
static const std::vector<Command> _commands = {
|
||||||
|
{"sequences", &Colors::doSequences},
|
||||||
|
{"sequence", &Colors::doSequence},
|
||||||
|
{"rotate", &Colors::doRotate}
|
||||||
|
};
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC_TASK(Colors);
|
||||||
|
STATIC_ALLOC(Colors);
|
36
src/Colors.h
Normal file
36
src/Colors.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Figments.h>
|
||||||
|
#include "Config.h"
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
class Colors: public BufferedInputSource, ConfigTaskMixin {
|
||||||
|
public:
|
||||||
|
struct Sequence {
|
||||||
|
String name;
|
||||||
|
const std::vector<CRGB> colors;
|
||||||
|
};
|
||||||
|
|
||||||
|
Colors();
|
||||||
|
void loop() override;
|
||||||
|
void handleEvent(const InputEvent& evt) override;
|
||||||
|
void handleConfigChange(const Configuration& cfg) override;
|
||||||
|
const std::vector<Command>& commands() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t m_idx;
|
||||||
|
uint16_t m_colorIdx;
|
||||||
|
uint16_t m_colorTimer;
|
||||||
|
uint16_t m_secondsPerColor;
|
||||||
|
std::vector<Sequence> m_sequences;
|
||||||
|
std::map<const char*, const char*> m_sceneMap;
|
||||||
|
|
||||||
|
void setScene(const char* sceneName);
|
||||||
|
void setSequence(const char* seqName);
|
||||||
|
|
||||||
|
void doSequences(Args& args, Print& out);
|
||||||
|
void doSequence(Args& args, Print& out);
|
||||||
|
void doRotate(Args& args, Print& out);
|
||||||
|
|
||||||
|
void rotate();
|
||||||
|
};
|
@ -1,6 +1,5 @@
|
|||||||
#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 <ArduinoJson.h>
|
||||||
@ -23,6 +22,12 @@ Configuration::Configuration(const JsonObject& data)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonVariant
|
||||||
|
Configuration::get(const char* key) const
|
||||||
|
{
|
||||||
|
return m_json[key];
|
||||||
|
}
|
||||||
|
|
||||||
const char*
|
const char*
|
||||||
Configuration::get(const char* key, const char* defaultVal) const
|
Configuration::get(const char* key, const char* defaultVal) const
|
||||||
{
|
{
|
||||||
@ -53,8 +58,6 @@ Configuration::get(const char* key, bool defaultVal) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StaticJsonDocument<2048> jsonConfig;
|
|
||||||
|
|
||||||
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
||||||
|
|
||||||
HardwareConfig
|
HardwareConfig
|
||||||
@ -149,12 +152,15 @@ 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)) {
|
||||||
|
DynamicJsonDocument jsonConfig(2048);
|
||||||
File configFile = LittleFS.open(fname, "r");
|
File configFile = LittleFS.open(fname, "r");
|
||||||
Log.notice("config: Loading coordinate map from %s", fname.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"];
|
||||||
|
uint8_t rotation = jsonConfig["rotation"];
|
||||||
m_jsonMap.load(strideList);
|
m_jsonMap.load(strideList);
|
||||||
|
m_jsonMap.rotation = rotation;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
@ -171,25 +177,13 @@ ConfigService::loadProfile(const char* profileName)
|
|||||||
if (LittleFS.exists(fname)) {
|
if (LittleFS.exists(fname)) {
|
||||||
strncpy(m_config.data.loadedProfile, fname.c_str(), sizeof(m_config.data.loadedProfile));
|
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();
|
DynamicJsonDocument jsonConfig(2048*2);
|
||||||
deserializeJson(jsonConfig, configFile);
|
auto err = deserializeJson(jsonConfig, configFile);
|
||||||
configFile.close();
|
configFile.close();
|
||||||
|
|
||||||
//int profileLogLevel = max(0, min(6, jsonConfig["logLevel"]));
|
if (err) {
|
||||||
//Log.setLevel(profileLogLevel);
|
Log.error("JSON failure: %s", err.c_str());
|
||||||
//Log.trace("config: \t %d logging level");
|
|
||||||
|
|
||||||
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"];
|
JsonArray taskList = jsonConfig["tasks"];
|
||||||
Log.notice("config: Starting %d tasks", taskList.size());
|
Log.notice("config: Starting %d tasks", taskList.size());
|
||||||
@ -197,14 +191,8 @@ ConfigService::loadProfile(const char* profileName)
|
|||||||
MainLoop::instance()->dispatchSync(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"];
|
String mapName = jsonConfig["surfaceMap"];
|
||||||
|
|
||||||
jsonConfig.clear();
|
|
||||||
|
|
||||||
if (mapName.isEmpty()) {
|
if (mapName.isEmpty()) {
|
||||||
Log.warning("config: No coordinate map defined! Defaulting to linear mapping.", mapName.c_str());
|
Log.warning("config: No coordinate map defined! Defaulting to linear mapping.", mapName.c_str());
|
||||||
m_jsonMap.loadDefault();
|
m_jsonMap.loadDefault();
|
||||||
@ -212,6 +200,11 @@ ConfigService::loadProfile(const char* profileName)
|
|||||||
Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str());
|
Log.warning("config: Couldn't load coordinate map %s!!! Defaulting to linear mapping.", mapName.c_str());
|
||||||
m_jsonMap.loadDefault();
|
m_jsonMap.loadDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonObject defaults = jsonConfig["defaults"];
|
||||||
|
Log.notice("config: Loading %d app configurations", defaults.size());
|
||||||
|
MainLoop::instance()->dispatchSync(InputEvent{InputEvent::ConfigurationChanged, &defaults});
|
||||||
|
|
||||||
Log.notice("config: Loaded!");
|
Log.notice("config: Loaded!");
|
||||||
strcpy(m_config.data.loadedProfile, profileName);
|
strcpy(m_config.data.loadedProfile, profileName);
|
||||||
} else {
|
} else {
|
||||||
@ -223,8 +216,8 @@ ConfigService::loadProfile(const char* profileName)
|
|||||||
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.info(" (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.info(" (255,255) -> (%d, %d) -> %d", bottomRight.x, bottomRight.y, m_jsonMap.physicalCoordsToIndex(bottomRight));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,6 +286,16 @@ ConfigService::doCoordMap(Args& args, Print& out)
|
|||||||
out.println(buf);
|
out.println(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ConfigService::doRotate(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
out.print("Rotation: ");
|
||||||
|
out.print(m_jsonMap.rotation);
|
||||||
|
out.print(" ");
|
||||||
|
m_jsonMap.rotation = atoi(args[1].c_str());
|
||||||
|
out.println(m_jsonMap.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<Command>&
|
const std::vector<Command>&
|
||||||
ConfigService::commands() const
|
ConfigService::commands() const
|
||||||
{
|
{
|
||||||
@ -300,7 +303,8 @@ ConfigService::commands() const
|
|||||||
{"save", &ConfigService::doSave},
|
{"save", &ConfigService::doSave},
|
||||||
{"profile", &ConfigService::doSetProfile},
|
{"profile", &ConfigService::doSetProfile},
|
||||||
{"maps", &ConfigService::doMapList},
|
{"maps", &ConfigService::doMapList},
|
||||||
{"coordmap", &ConfigService::doCoordMap}
|
{"coordmap", &ConfigService::doCoordMap},
|
||||||
|
{"rotate", &ConfigService::doRotate}
|
||||||
};
|
};
|
||||||
return _commands;
|
return _commands;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ class Configuration {
|
|||||||
const char* get(const char* key, const char* defaultVal) const;
|
const char* get(const char* key, const char* defaultVal) const;
|
||||||
int get(const char* key, int defaultVal) const;
|
int get(const char* key, int defaultVal) const;
|
||||||
bool get(const char* key, bool defaultVal) const;
|
bool get(const char* key, bool defaultVal) const;
|
||||||
|
JsonVariant get(const char* name) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const JsonObject& m_json;
|
const JsonObject& m_json;
|
||||||
@ -79,4 +80,5 @@ private:
|
|||||||
void doSetProfile(Args& args, Print& print);
|
void doSetProfile(Args& args, Print& print);
|
||||||
void doCoordMap(Args& args, Print& print);
|
void doCoordMap(Args& args, Print& print);
|
||||||
void doMapList(Args& args, Print& print);
|
void doMapList(Args& args, Print& print);
|
||||||
|
void doRotate(Args& args, Print& print);
|
||||||
};
|
};
|
||||||
|
@ -11,24 +11,33 @@ JsonCoordinateMapping::physicalPixelCount() const {
|
|||||||
|
|
||||||
int
|
int
|
||||||
JsonCoordinateMapping::physicalCoordsToIndex(const PhysicalCoordinates localCoords) const {
|
JsonCoordinateMapping::physicalCoordsToIndex(const PhysicalCoordinates localCoords) const {
|
||||||
uint8_t idx = 0;
|
return displayMap[localCoords.x].physicalIdx + localCoords.y;
|
||||||
bool inverse = false;
|
}
|
||||||
for(int i = 0; i < localCoords.x; i++) {
|
|
||||||
idx += displayMap[i].length;
|
VirtualCoordinates
|
||||||
inverse = !inverse;
|
JsonCoordinateMapping::rotate(const VirtualCoordinates coords, uint8_t rot) const
|
||||||
}
|
{
|
||||||
if (inverse) {
|
if (rot == 0) {
|
||||||
idx += std::max(0, displayMap[localCoords.x].length - 1 - std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
|
return coords;
|
||||||
|
} else if (rot == 1) {
|
||||||
|
return VirtualCoordinates{coords.y, coords.x};
|
||||||
|
} else if (rot == 2) {
|
||||||
|
return VirtualCoordinates{255 - coords.y, 255 - coords.x};
|
||||||
|
} else if (rot == 3) {
|
||||||
|
return VirtualCoordinates{255 - coords.x, 255 - coords.y};
|
||||||
} else {
|
} else {
|
||||||
idx += std::min((int)displayMap[localCoords.x].length - 1, std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
|
return coords;
|
||||||
}
|
}
|
||||||
return idx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalCoordinates
|
PhysicalCoordinates
|
||||||
JsonCoordinateMapping::virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
|
JsonCoordinateMapping::virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
|
||||||
const uint8_t spanIdx = scale8(strideCount-1, virtualCoords.x);
|
// 0, 0 -> first pixel, first strand
|
||||||
const uint8_t spanOffset = scale8(maxStrideSize - 1, virtualCoords.y);
|
// 0, 255 -> first pixel, last strand
|
||||||
|
// 255, 255 -> last pixel, last strand
|
||||||
|
const VirtualCoordinates rotated = rotate(virtualCoords, rotation);
|
||||||
|
const uint8_t spanIdx = scale8(strideCount-1, rotated.x);
|
||||||
|
const uint8_t spanOffset = scale8(displayMap[spanIdx].length, rotated.y);
|
||||||
return PhysicalCoordinates{spanIdx, spanOffset};
|
return PhysicalCoordinates{spanIdx, spanOffset};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,23 +57,23 @@ JsonCoordinateMapping::physicalToVirtualCoords(const PhysicalCoordinates localCo
|
|||||||
void
|
void
|
||||||
JsonCoordinateMapping::load(const JsonArray& strideList)
|
JsonCoordinateMapping::load(const JsonArray& strideList)
|
||||||
{
|
{
|
||||||
maxStrideSize = 0;
|
|
||||||
strideCount = strideList.size();
|
strideCount = strideList.size();
|
||||||
|
uint16_t idx = 0;
|
||||||
for(int i = 0; i < strideList.size();i++) {
|
for(int i = 0; i < strideList.size();i++) {
|
||||||
JsonObject strideObj = strideList[i];
|
JsonObject strideObj = strideList[i];
|
||||||
int length = strideObj["pixels"];
|
int length = strideObj["pixels"];
|
||||||
int x = strideObj["x"];
|
int x = strideObj["x"];
|
||||||
int y = strideObj["y"];
|
int y = strideObj["y"];
|
||||||
displayMap[i] = Span{length, x, y};
|
bool reverse = strideObj["reverse"];
|
||||||
|
displayMap[i] = Span{length, x, y, reverse, idx};
|
||||||
|
idx += length;
|
||||||
Log.info("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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
JsonCoordinateMapping::loadDefault()
|
JsonCoordinateMapping::loadDefault()
|
||||||
{
|
{
|
||||||
displayMap[0] = Span{255, 0, 0};
|
displayMap[0] = Span{255, 0, 0, false, 0};
|
||||||
maxStrideSize = 255;
|
|
||||||
strideCount = 1;
|
strideCount = 1;
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,18 @@ struct JsonCoordinateMapping : CoordinateMapping {
|
|||||||
int length = 0;
|
int length = 0;
|
||||||
int x = 0;
|
int x = 0;
|
||||||
int y = 0;
|
int y = 0;
|
||||||
|
bool reverse = false;
|
||||||
|
|
||||||
|
uint16_t physicalIdx = 0;
|
||||||
|
|
||||||
Span() {}
|
Span() {}
|
||||||
Span(int length, int x, int y) : length(length), x(x), y(y) {}
|
Span(int length, int x, int y, bool rev, uint16_t idx) : length(length), x(x), y(y), reverse(rev), physicalIdx(idx) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
uint8_t rotation = 0;
|
||||||
|
|
||||||
Span displayMap[32];
|
Span displayMap[32];
|
||||||
int strideCount = 0;
|
int strideCount = 0;
|
||||||
int maxStrideSize = 0;
|
|
||||||
|
|
||||||
void load(const JsonArray& strides);
|
void load(const JsonArray& strides);
|
||||||
void loadDefault();
|
void loadDefault();
|
||||||
@ -23,4 +27,6 @@ struct JsonCoordinateMapping : CoordinateMapping {
|
|||||||
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override;
|
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override;
|
||||||
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override;
|
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override;
|
||||||
unsigned int physicalPixelCount() const override;
|
unsigned int physicalPixelCount() const override;
|
||||||
|
|
||||||
|
VirtualCoordinates rotate(const VirtualCoordinates coords, uint8_t rotation) const;
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,7 @@ LogService::eventValue(const InputEvent& evt)
|
|||||||
void
|
void
|
||||||
LogService::handleEvent(const InputEvent& evt) {
|
LogService::handleEvent(const InputEvent& evt) {
|
||||||
if (evt.intent != InputEvent::None) {
|
if (evt.intent != InputEvent::None) {
|
||||||
if (evt.intent != m_lastEvent.intent) {
|
if (true || evt.intent != m_lastEvent.intent) {
|
||||||
if (m_duplicateEvents > 0) {
|
if (m_duplicateEvents > 0) {
|
||||||
Log.trace("Suppressed reporting %d duplicate events.", m_duplicateEvents);
|
Log.trace("Suppressed reporting %d duplicate events.", m_duplicateEvents);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ void printNewline(Print* logOutput, int logLevel)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int Platform::s_timezone = 0;
|
int Platform::s_timezone = 1;
|
||||||
Platform::TaskRegistration* Platform::firstTask = NULL;
|
Platform::TaskRegistration* Platform::firstTask = NULL;
|
||||||
Platform::TaskRegistration* Platform::lastTask = NULL;
|
Platform::TaskRegistration* Platform::lastTask = NULL;
|
||||||
|
|
||||||
|
33
src/Scripts.cpp
Normal file
33
src/Scripts.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "Scripts.h"
|
||||||
|
#include "Static.h"
|
||||||
|
|
||||||
|
Scripts::Scripts()
|
||||||
|
: Task("Scripts")
|
||||||
|
{
|
||||||
|
state = Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Scripts::loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Scripts::doEval(Args& args, Print& out)
|
||||||
|
{
|
||||||
|
String fullCmd((const char*)args);
|
||||||
|
String fullScript(fullCmd.substring(fullCmd.indexOf(" ") + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
Scripts::commands() const
|
||||||
|
{
|
||||||
|
static const std::vector<Command> _commands{
|
||||||
|
{"eval", &Scripts::doEval}
|
||||||
|
};
|
||||||
|
|
||||||
|
return _commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC_ALLOC(Scripts);
|
||||||
|
STATIC_TASK(Scripts);
|
12
src/Scripts.h
Normal file
12
src/Scripts.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Figments.h>
|
||||||
|
|
||||||
|
class Scripts : public Task {
|
||||||
|
public:
|
||||||
|
Scripts();
|
||||||
|
void loop() override;
|
||||||
|
const std::vector<Command>& commands() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void doEval(Args& args, Print& out);
|
||||||
|
};
|
@ -3,8 +3,9 @@
|
|||||||
#include "Static.h"
|
#include "Static.h"
|
||||||
|
|
||||||
Sequencer::Sequencer() :
|
Sequencer::Sequencer() :
|
||||||
Task("SceneSequencer"),
|
Task("Sequencer"),
|
||||||
m_idx(0)
|
m_idx(0),
|
||||||
|
m_sceneBeforeIdle(-1)
|
||||||
{
|
{
|
||||||
state = Task::Running;
|
state = Task::Running;
|
||||||
}
|
}
|
||||||
@ -12,28 +13,32 @@ Sequencer::Sequencer() :
|
|||||||
void
|
void
|
||||||
Sequencer::Scene::start()
|
Sequencer::Scene::start()
|
||||||
{
|
{
|
||||||
for(const char* pattern : patterns) {
|
for(const auto& pattern : patterns) {
|
||||||
Log.verbose("Starting pattern task %s", pattern);
|
//pattern.getBytes(namebuf, sizeof(namebuf));
|
||||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
|
Log.info("sequencer: Starting pattern task %s", pattern.c_str());
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern.c_str()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Sequencer::Scene::stop()
|
Sequencer::Scene::stop()
|
||||||
{
|
{
|
||||||
for(const char* pattern : patterns) {
|
for(const auto& pattern : patterns) {
|
||||||
Log.verbose("Stopping pattern task %s", pattern);
|
Log.info("sequencer: Stopping pattern task %s", pattern.c_str());
|
||||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern.c_str()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Sequencer::loop() {}
|
Sequencer::loop()
|
||||||
|
{
|
||||||
|
ConfigTaskMixin::loop();
|
||||||
|
}
|
||||||
|
|
||||||
const char*
|
const char*
|
||||||
Sequencer::currentSceneName()
|
Sequencer::currentSceneName()
|
||||||
{
|
{
|
||||||
return m_scenes[m_idx].name;
|
return m_scenes[m_idx].name.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Sequencer::Scene>
|
const std::vector<Sequencer::Scene>
|
||||||
@ -48,30 +53,64 @@ Sequencer::onStart()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Sequencer::setScenes(std::vector<Scene> &&scenes)
|
Sequencer::handleEvent(const InputEvent& evt)
|
||||||
{
|
{
|
||||||
m_idx = 0;
|
if (!m_scenes.empty()) {
|
||||||
m_scenes = scenes;
|
if (evt.intent == InputEvent::ReadyToRoll) {
|
||||||
|
Log.info("sequencer: Activating startup scene");
|
||||||
|
if (!changeToScene("Start")) {
|
||||||
|
Log.info("sequencer: Start scene not found, falling back to first scene");
|
||||||
|
changeToScene(m_scenes[0].name.c_str());
|
||||||
|
}
|
||||||
|
} else if (evt.intent == InputEvent::SetScene && m_scenes[m_idx].name == evt.asString()) {
|
||||||
|
return;
|
||||||
|
} else if (evt.intent == InputEvent::SetScene) {
|
||||||
|
changeToScene(evt.asString());
|
||||||
|
} else if (evt.intent == InputEvent::IdleStart) {
|
||||||
|
Log.info("sequencer: Activating idle scene");
|
||||||
|
m_sceneBeforeIdle = m_idx;
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, "Idle"});
|
||||||
|
} else if (evt.intent == InputEvent::IdleStop && m_sceneBeforeIdle >= 0) {
|
||||||
|
Log.info("sequencer: Resuming pre-idle scene");
|
||||||
|
//changeToScene(m_scenes[m_sceneBeforeIdle].name.c_str());
|
||||||
|
String sceneName = m_scenes[m_sceneBeforeIdle].name;
|
||||||
|
m_sceneBeforeIdle = -1;
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, sceneName.c_str()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfigTaskMixin::handleEvent(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Sequencer::handleEvent(const InputEvent& evt)
|
Sequencer::handleConfigChange(const Configuration& cfg)
|
||||||
{
|
{
|
||||||
if (evt.intent == InputEvent::ReadyToRoll && !m_scenes.empty()) {
|
Log.notice("sequencer: Loading scenes");
|
||||||
Log.notice("Starting pattern %s!", m_scenes[m_idx].name);
|
|
||||||
for(const char* pattern : m_scenes[m_idx].patterns) {
|
JsonObject sceneList = cfg.get("scenes");
|
||||||
Log.verbose("Starting pattern task %s", pattern);
|
m_idx = 0;
|
||||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
|
m_scenes.clear();
|
||||||
|
for(JsonPair pair : sceneList) {
|
||||||
|
Log.notice("sequencer: Found scene %s", pair.key().c_str());
|
||||||
|
std::vector<String> patterns;
|
||||||
|
for(const char* taskName : pair.value().as<JsonArray>()) {
|
||||||
|
patterns.push_back(taskName);
|
||||||
}
|
}
|
||||||
} else if (evt.intent == InputEvent::SetScene && evt.asString() == m_scenes[m_idx].name) {
|
m_scenes.push_back(Scene{pair.key().c_str(), patterns});
|
||||||
return;
|
}
|
||||||
} else if (evt.intent == InputEvent::SetScene) {
|
if (m_scenes.empty()) {
|
||||||
Log.notice("Switching scene to %s!", evt.asString());
|
Log.warning("sequencer: Zero scenes loaded!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Sequencer::changeToScene(const char* name)
|
||||||
|
{
|
||||||
|
Log.notice("sequencer: Switching scene to %s!", name);
|
||||||
|
|
||||||
int newIdx = 0;
|
int newIdx = 0;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for(newIdx = 0; newIdx < m_scenes.size(); newIdx++) {
|
for(newIdx = 0; newIdx < m_scenes.size(); newIdx++) {
|
||||||
if (!strcmp(evt.asString(), m_scenes[newIdx].name)) {
|
if (!strcmp(name, m_scenes[newIdx].name.c_str())) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -81,9 +120,10 @@ Sequencer::handleEvent(const InputEvent& evt)
|
|||||||
m_scenes[m_idx].stop();
|
m_scenes[m_idx].stop();
|
||||||
m_idx = newIdx;
|
m_idx = newIdx;
|
||||||
m_scenes[m_idx].start();
|
m_scenes[m_idx].start();
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Log.notice("Scene not found!");
|
Log.notice("sequencer: Scene not found!");
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +132,7 @@ Sequencer::doScenes(Args& args, Print& out)
|
|||||||
{
|
{
|
||||||
out.println("Available scenes: ");
|
out.println("Available scenes: ");
|
||||||
for (auto scene : scenes()) {
|
for (auto scene : scenes()) {
|
||||||
out.println(scene.name);
|
out.println(scene.name.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,8 +141,8 @@ static String s;
|
|||||||
void
|
void
|
||||||
Sequencer::doScene(Args& args, Print& out)
|
Sequencer::doScene(Args& args, Print& out)
|
||||||
{
|
{
|
||||||
s = args[1];
|
String sceneName = args[1];
|
||||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, s.c_str()});
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetScene, sceneName.c_str()});
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Command>&
|
const std::vector<Command>&
|
||||||
|
@ -2,14 +2,15 @@
|
|||||||
|
|
||||||
#include <Figment.h>
|
#include <Figment.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
class Sequencer: public Task {
|
class Sequencer: public Task, ConfigTaskMixin {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
class Scene {
|
class Scene {
|
||||||
public:
|
public:
|
||||||
const char* name;
|
String name;
|
||||||
std::vector<const char*> patterns;
|
std::vector<String> patterns;
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
};
|
};
|
||||||
@ -19,7 +20,7 @@ public:
|
|||||||
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);
|
void handleConfigChange(const Configuration& cfg) override;
|
||||||
|
|
||||||
const char* currentSceneName();
|
const char* currentSceneName();
|
||||||
const std::vector<Scene> scenes() const;
|
const std::vector<Scene> scenes() const;
|
||||||
@ -27,8 +28,12 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
int m_idx;
|
int m_idx;
|
||||||
|
int m_sceneBeforeIdle;
|
||||||
|
bool m_hasStartupScene;
|
||||||
std::vector<Scene> m_scenes;
|
std::vector<Scene> m_scenes;
|
||||||
|
|
||||||
|
bool changeToScene(const char* name);
|
||||||
|
|
||||||
void doScene(Args& args, Print& out);
|
void doScene(Args& args, Print& out);
|
||||||
void doScenes(Args& args, Print& out);
|
void doScenes(Args& args, Print& out);
|
||||||
};
|
};
|
||||||
|
46
src/animations/Cloudy.cpp
Normal file
46
src/animations/Cloudy.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#include "Cloudy.h"
|
||||||
|
#include "../Static.h"
|
||||||
|
|
||||||
|
Cloudy::Cloudy()
|
||||||
|
: Figment("Cloudy")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cloudy::loop()
|
||||||
|
{
|
||||||
|
m_noiseOffset += 1;
|
||||||
|
EVERY_N_MILLISECONDS(60) {
|
||||||
|
m_hue.update(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cloudy::render(Display* dpy) const
|
||||||
|
{
|
||||||
|
Surface sfc = Surface(dpy, {0, 0}, {255, 128});
|
||||||
|
uint8_t noiseY = sin8(m_noiseOffset % 255);
|
||||||
|
uint8_t noiseX = cos8(m_noiseOffset % 255);
|
||||||
|
sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
||||||
|
uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y);
|
||||||
|
uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x);
|
||||||
|
nblend(pixel, CHSV(m_hue, scale8(saturation, 128), brightness), 255 - surfaceCoords.y);
|
||||||
|
});
|
||||||
|
Surface horizon = Surface(dpy, {0, 64}, {255, 128});
|
||||||
|
horizon.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
||||||
|
uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y);
|
||||||
|
uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x);
|
||||||
|
nblend(pixel, CHSV(m_hue + (saturation % 45), std::max((uint8_t)64, (uint8_t)saturation), brightness), surfaceCoords.y);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cloudy::handleEvent(const InputEvent& evt) {
|
||||||
|
if (evt.intent == InputEvent::SetColor) {
|
||||||
|
CHSV next = rgb2hsv_approximate(evt.asRGB());
|
||||||
|
m_hue.set(next.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC_ALLOC(Cloudy);
|
||||||
|
STATIC_TASK(Cloudy);
|
13
src/animations/Cloudy.h
Normal file
13
src/animations/Cloudy.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Figments.h>
|
||||||
|
|
||||||
|
class Cloudy: public Figment {
|
||||||
|
private:
|
||||||
|
uint16_t m_noiseOffset;
|
||||||
|
AnimatedNumber m_hue;
|
||||||
|
public:
|
||||||
|
Cloudy();
|
||||||
|
void render(Display* dpy) const override;
|
||||||
|
void loop() override;
|
||||||
|
void handleEvent(const InputEvent& evt) override;
|
||||||
|
};
|
@ -8,11 +8,13 @@ RainAnimation::RainAnimation() : Figment("Rain")
|
|||||||
void
|
void
|
||||||
RainAnimation::render(Display* dpy) const
|
RainAnimation::render(Display* dpy) const
|
||||||
{
|
{
|
||||||
Surface sfc = Surface(dpy, {0, 0}, {255, 255});
|
Surface ground = Surface(dpy, {0, 64}, {255, 255});
|
||||||
uint8_t noiseY = sin8(m_noiseOffset % 255);
|
uint8_t noiseY = sin8(m_noiseOffset % 255);
|
||||||
uint8_t noiseX = cos8(m_noiseOffset % 255);
|
uint8_t noiseX = cos8(m_noiseOffset % 255);
|
||||||
sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
ground.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
||||||
pixel = CHSV(m_hue, inoise8(noiseX + coords.x, coords.y), inoise8(m_noiseOffset + coords.x, noiseY + coords.y));
|
uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y);
|
||||||
|
uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x);
|
||||||
|
nblend(pixel, CHSV(m_hue, saturation, brightness), 255 - surfaceCoords.y);
|
||||||
});
|
});
|
||||||
m_drops.render(dpy);
|
m_drops.render(dpy);
|
||||||
}
|
}
|
||||||
@ -20,7 +22,7 @@ RainAnimation::render(Display* dpy) const
|
|||||||
void
|
void
|
||||||
RainAnimation::loop()
|
RainAnimation::loop()
|
||||||
{
|
{
|
||||||
EVERY_N_MILLISECONDS(250) {
|
EVERY_N_MILLISECONDS(120) {
|
||||||
m_drops.update();
|
m_drops.update();
|
||||||
}
|
}
|
||||||
EVERY_N_MILLISECONDS(60) {
|
EVERY_N_MILLISECONDS(60) {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
class RainAnimation: public Figment {
|
class RainAnimation: public Figment {
|
||||||
private:
|
private:
|
||||||
struct Raindrop {
|
struct Raindrop {
|
||||||
int size = 20;
|
int size = 10;
|
||||||
int x = random(255);
|
int x = random(255);
|
||||||
int y = random(255);
|
int y = random(255);
|
||||||
CHSV fg{180, 255, 255};
|
CHSV fg{180, 255, 255};
|
||||||
|
@ -47,22 +47,20 @@ void SolidAnimation::loop() {
|
|||||||
});
|
});
|
||||||
m_blobs.update();
|
m_blobs.update();
|
||||||
}
|
}
|
||||||
|
m_noiseOffset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SolidAnimation::render(Display* dpy) const {
|
void SolidAnimation::render(Display* dpy) const {
|
||||||
CRGB color(m_red.value(), m_green.value(), m_blue.value());
|
Surface sfc = Surface(dpy, {0, 0}, {255, 255});
|
||||||
CRGB scaledPrev = m_prevColor;
|
uint8_t noiseY = sin8(m_noiseOffset % 255);
|
||||||
scaledPrev = color.nscale8(30);
|
uint8_t noiseX = cos8(m_noiseOffset % 255);
|
||||||
uint8_t frame = ease8InOutApprox(m_changePct);
|
CHSV color(rgb2hsv_approximate(CRGB(m_red, m_green, m_blue)));
|
||||||
if (F_LIKELY(frame == 255)) {
|
|
||||||
Surface(dpy, {0, 0}, {255, 255}) = color.nscale8(10);
|
sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
||||||
} else {
|
uint8_t brightness = inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y);
|
||||||
uint8_t cutoff = (frame / 2);
|
uint8_t saturation = inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x);
|
||||||
uint8_t rotation = m_horizontal ? 0 : 128;
|
nblend(pixel, CHSV(color.h, std::max((uint8_t)128, saturation), scale8(color.v, brightness)), 128);
|
||||||
Surface(dpy, {0, 0}, {128 - cutoff, 255}, rotation) = scaledPrev;
|
});
|
||||||
Surface(dpy, {128 - cutoff, 0}, {128 + cutoff, 255}, rotation) = scaledPrev;
|
|
||||||
Surface(dpy, {128 + cutoff, 0}, {255, 255}, rotation) = scaledPrev;
|
|
||||||
}
|
|
||||||
m_blobs.render(dpy);
|
m_blobs.render(dpy);
|
||||||
}
|
}
|
||||||
STATIC_ALLOC(SolidAnimation);
|
STATIC_ALLOC(SolidAnimation);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
class SolidAnimation: public Figment {
|
class SolidAnimation: public Figment {
|
||||||
private:
|
private:
|
||||||
AnimatedNumber m_red, m_green, m_blue, m_changePct;
|
AnimatedNumber m_red, m_green, m_blue, m_changePct;
|
||||||
|
uint16_t m_noiseOffset;
|
||||||
CRGB m_curColor;
|
CRGB m_curColor;
|
||||||
CRGB m_prevColor;
|
CRGB m_prevColor;
|
||||||
static constexpr int blobCount = 20;
|
static constexpr int blobCount = 20;
|
||||||
|
@ -7,25 +7,104 @@ TestAnimation::loop()
|
|||||||
{
|
{
|
||||||
m_x += 1;
|
m_x += 1;
|
||||||
m_y += 1;
|
m_y += 1;
|
||||||
|
m_hue += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TestAnimation::handleEvent(const InputEvent& evt)
|
||||||
|
{
|
||||||
|
if (evt.intent == InputEvent::SetColor) {
|
||||||
|
m_color = evt.asRGB();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TestAnimation::render(Display* dpy) const
|
TestAnimation::render(Display* dpy) const
|
||||||
{
|
{
|
||||||
|
CRGB drawColor = blend(m_color, CHSV(m_hue, 255, 255), 128);
|
||||||
|
if (m_mode == Raw) {
|
||||||
for(unsigned int i = 0; i < dpy->pixelCount(); i++) {
|
for(unsigned int i = 0; i < dpy->pixelCount(); i++) {
|
||||||
dpy->pixelAt(i) = CRGB{255, 255, 255};
|
dpy->pixelAt(i) = drawColor;
|
||||||
}
|
}
|
||||||
return;
|
} else if (m_mode == Top) {
|
||||||
// Blank the canvas to white
|
Surface{dpy, {0, 0}, {255, 0}} = drawColor;
|
||||||
Surface{dpy, {0, 0}, {255, 255}} = CRGB{255, 255, 255};
|
} else if (m_mode == Left) {
|
||||||
|
Surface{dpy, {0, 0}, {0, 255}} = drawColor;
|
||||||
|
} else if (m_mode == Right) {
|
||||||
|
Surface{dpy, {255, 0}, {255, 255}} = drawColor;
|
||||||
|
} else if (m_mode == Bottom) {
|
||||||
|
Surface{dpy, {0, 255}, {255, 255}} = drawColor;
|
||||||
|
} else if (m_mode == Outline) {
|
||||||
// Draw red line on top row
|
// Draw red line on top row
|
||||||
Surface{dpy, {0, 0}, {255, 0}} = CRGB{255, 0, 0};
|
Surface{dpy, {0, 0}, {255, 0}} = CRGB{255, 0, 0};
|
||||||
// Green line on first column
|
// Green line on first column
|
||||||
Surface{dpy, {0, 0}, {0, 255}} = CRGB{0, 255, 0};
|
Surface{dpy, {0, 0}, {0, 255}} = CRGB{0, 255, 0};
|
||||||
|
} else if (m_mode == White) {
|
||||||
|
Surface{dpy, {0, 0}, {255, 255}} = CRGB{255, 255, 255};
|
||||||
|
} else if (m_mode == Red) {
|
||||||
|
Surface{dpy, {0, 0}, {255, 255}} = CRGB{255, 0, 0};
|
||||||
|
} else if (m_mode == Green) {
|
||||||
|
Surface{dpy, {0, 0}, {255, 255}} = CRGB{0, 255, 0};
|
||||||
|
} else if (m_mode == Blue) {
|
||||||
|
Surface{dpy, {0, 0}, {255, 255}} = CRGB{0, 0, 255};
|
||||||
|
} else if (m_mode == HSV) {
|
||||||
|
Surface{dpy, {0, 0}, {255, 255}}.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates&, const VirtualCoordinates& surfaceCoords) {
|
||||||
|
pixel = CHSV(coords.x, coords.y, 255);
|
||||||
|
});
|
||||||
|
} else if (m_mode == RGB) {
|
||||||
|
Surface{dpy, {0, 0}, {255, 255}}.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates&, const VirtualCoordinates& surfaceCoords) {
|
||||||
|
pixel = CRGB(coords.x, coords.y, (coords.x/2) + (coords.y/2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Surface{dpy, {m_x, 0}, {m_x, 255}} = CRGB{255, 0, 0};
|
void
|
||||||
///Surface{dpy, {0, m_y}, {255, m_y}} = CRGB{255, 0, 0};
|
TestAnimation::doMode(Args& args, Print& out)
|
||||||
//dpy->pixelAt(VirtualCoordinates{m_x, m_y}) = CRGB{255, 0, 255};
|
{
|
||||||
|
String s = args[1];
|
||||||
|
if (s == "") {
|
||||||
|
out.println("Available modes: raw outline white red green blue hsv rgb none top left right bottom");
|
||||||
|
out.print("Current mode: ");
|
||||||
|
out.println((int)m_mode);
|
||||||
|
} else if (s == "raw") {
|
||||||
|
m_mode = Raw;
|
||||||
|
} else if (s == "outline") {
|
||||||
|
m_mode = Outline;
|
||||||
|
} else if (s == "white") {
|
||||||
|
m_mode = White;
|
||||||
|
} else if (s == "red") {
|
||||||
|
m_mode = Red;
|
||||||
|
} else if (s == "green") {
|
||||||
|
m_mode = Green;
|
||||||
|
} else if (s == "blue") {
|
||||||
|
m_mode = Blue;
|
||||||
|
} else if (s == "hsv") {
|
||||||
|
m_mode = HSV;
|
||||||
|
} else if (s == "rgb") {
|
||||||
|
m_mode = RGB;
|
||||||
|
} else if (s == "top") {
|
||||||
|
m_mode = Top;
|
||||||
|
} else if (s == "left") {
|
||||||
|
m_mode = Left;
|
||||||
|
} else if (s == "right") {
|
||||||
|
m_mode = Right;
|
||||||
|
} else if (s == "bottom") {
|
||||||
|
m_mode = Bottom;
|
||||||
|
} else if (s == "none") {
|
||||||
|
m_mode = None;
|
||||||
|
} else {
|
||||||
|
out.println("Unknown test mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Command>&
|
||||||
|
TestAnimation::commands() const
|
||||||
|
{
|
||||||
|
static const std::vector<Command> _commands{
|
||||||
|
{"test", &TestAnimation::doMode}
|
||||||
|
};
|
||||||
|
return _commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
STATIC_ALLOC(TestAnimation);
|
STATIC_ALLOC(TestAnimation);
|
||||||
|
@ -5,8 +5,31 @@ public:
|
|||||||
TestAnimation() : Figment("Test") {}
|
TestAnimation() : Figment("Test") {}
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void render(Display* dpy) const override;
|
void render(Display* dpy) const override;
|
||||||
|
void handleEvent(const InputEvent& evt) override;
|
||||||
|
const std::vector<Command>& commands() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t m_x;
|
uint8_t m_x;
|
||||||
uint8_t m_y;
|
uint8_t m_y;
|
||||||
|
CRGB m_color;
|
||||||
|
uint8_t m_hue;
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
None,
|
||||||
|
Raw,
|
||||||
|
Outline,
|
||||||
|
White,
|
||||||
|
Red,
|
||||||
|
Green,
|
||||||
|
Blue,
|
||||||
|
HSV,
|
||||||
|
RGB,
|
||||||
|
Top,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Bottom
|
||||||
|
};
|
||||||
|
|
||||||
|
Mode m_mode = None;
|
||||||
|
void doMode(Args&, Print&);
|
||||||
};
|
};
|
||||||
|
26
src/animations/Thinking.cpp
Normal file
26
src/animations/Thinking.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#include <Figments.h>
|
||||||
|
#include "../Static.h"
|
||||||
|
|
||||||
|
class Thinking: public Figment {
|
||||||
|
public:
|
||||||
|
Thinking() : Figment("Thinking") {}
|
||||||
|
void render(Display* dpy) const override {
|
||||||
|
Surface sfc = Surface(dpy, {0, 0}, {255, 255});
|
||||||
|
uint8_t noiseY = sin8(m_noiseOffset % 255);
|
||||||
|
uint8_t noiseX = cos8(m_noiseOffset % 255);
|
||||||
|
sfc.paintShader([=](CRGB& pixel, const VirtualCoordinates& coords, const PhysicalCoordinates, const VirtualCoordinates& surfaceCoords) {
|
||||||
|
pixel = CHSV(inoise8(noiseX + surfaceCoords.x, noiseY + surfaceCoords.y), 255, inoise8(noiseY + surfaceCoords.y, noiseX + surfaceCoords.x));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
EVERY_N_MILLISECONDS(15) {
|
||||||
|
m_noiseOffset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int m_noiseOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
STATIC_ALLOC(Thinking);
|
||||||
|
STATIC_TASK(Thinking);
|
@ -131,10 +131,17 @@ SerialInput::doHelp(Args& args, Print& out)
|
|||||||
out.println("Available commands:");
|
out.println("Available commands:");
|
||||||
auto sched = MainLoop::instance()->scheduler;
|
auto sched = MainLoop::instance()->scheduler;
|
||||||
for(auto task : sched.tasks) {
|
for(auto task : sched.tasks) {
|
||||||
|
const auto taskCommands = task->commands();
|
||||||
|
if (!taskCommands.empty()) {
|
||||||
|
out.print(task->name);
|
||||||
|
out.println(":");
|
||||||
|
out.print("\t");
|
||||||
for(auto &command : task->commands()) {
|
for(auto &command : task->commands()) {
|
||||||
out.print(command.name);
|
out.print(command.name);
|
||||||
out.print(" ");
|
out.print(" ");
|
||||||
}
|
}
|
||||||
|
out.println();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out.println();
|
out.println();
|
||||||
}
|
}
|
||||||
|
147
src/inputs/Weather.cpp
Normal file
147
src/inputs/Weather.cpp
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#include "Weather.h"
|
||||||
|
#include "../Static.h"
|
||||||
|
|
||||||
|
Weather::Weather()
|
||||||
|
: BufferedInputSource("Weather")
|
||||||
|
{
|
||||||
|
m_parser.setListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::handleEvent(const InputEvent& evt)
|
||||||
|
{
|
||||||
|
BufferedInputSource::handleEvent(evt);
|
||||||
|
OnlineTaskMixin::handleEvent(evt);
|
||||||
|
if (evt.intent == InputEvent::IdleStart) {
|
||||||
|
doWeatherEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::loop()
|
||||||
|
{
|
||||||
|
BufferedInputSource::loop();
|
||||||
|
OnlineTaskMixin::loop();
|
||||||
|
EVERY_N_SECONDS(3600) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
if (m_fetching) {
|
||||||
|
parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::update()
|
||||||
|
{
|
||||||
|
if (!m_fetching) {
|
||||||
|
Log.info("Connecting to weather service");
|
||||||
|
m_parser.reset();
|
||||||
|
const String host = "api.openweathermap.org";
|
||||||
|
const uint8_t port = 80;
|
||||||
|
#ifdef ESP32
|
||||||
|
m_fetching = m_client.connect(host.c_str(), port);
|
||||||
|
#else
|
||||||
|
m_fetching = m_client.connect(host, port);
|
||||||
|
#endif
|
||||||
|
if (m_fetching) {
|
||||||
|
Log.info("Ok!");
|
||||||
|
m_foundBody = false;
|
||||||
|
m_client.print(
|
||||||
|
"GET /data/2.5/weather?id=2950159&appid=f7aea43b54bcd27f542195a809c1463f&units=metric&lang=en HTTP/1.1\r\n"
|
||||||
|
"Host: api.openweathermap.org\r\n"
|
||||||
|
"Connection: close\r\n\r\n"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Log.info("No :(");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::parse()
|
||||||
|
{
|
||||||
|
if (m_client.available()) {
|
||||||
|
char c = m_client.read();
|
||||||
|
if (c == '{') {
|
||||||
|
m_foundBody = true;
|
||||||
|
}
|
||||||
|
if (m_foundBody) {
|
||||||
|
m_parser.parse(c);
|
||||||
|
}
|
||||||
|
} else if (!m_client.connected()) {
|
||||||
|
Log.info("Disconnected");
|
||||||
|
m_client.stop();
|
||||||
|
m_fetching = false;
|
||||||
|
doWeatherEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::key(String key)
|
||||||
|
{
|
||||||
|
m_curKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::doWeatherEvent()
|
||||||
|
{
|
||||||
|
Log.info("Dispatching weather event for %d", m_weatherId);
|
||||||
|
if (m_weatherId >= 200 && m_weatherId < 300 ) {
|
||||||
|
setEvent(InputEvent::SetScene, "Thunderstorm");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(255, 187, 0));
|
||||||
|
} else if (m_weatherId >= 300 && m_weatherId < 400) {
|
||||||
|
setEvent(InputEvent::SetScene, "Drizzle");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(93, 183, 201));
|
||||||
|
} else if (m_weatherId >= 500 && m_weatherId < 600) {
|
||||||
|
setEvent(InputEvent::SetScene, "Rain");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(64, 64, 255));
|
||||||
|
} else if (m_weatherId >= 600 && m_weatherId < 700) {
|
||||||
|
setEvent(InputEvent::SetScene, "Snow");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(255, 255, 255));
|
||||||
|
} else if (m_weatherId == 701) {
|
||||||
|
setEvent(InputEvent::SetScene, "Mist");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(77, 255, 124));
|
||||||
|
} else if (m_weatherId == 800) {
|
||||||
|
setEvent(InputEvent::SetScene, "Clear");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(255, 249, 79));
|
||||||
|
} else if (m_weatherId >= 801 && m_weatherId <= 804) {
|
||||||
|
setEvent(InputEvent::SetScene, "Cloudy");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(102, 110, 110));
|
||||||
|
} else {
|
||||||
|
setEvent(InputEvent::SetScene, "UnknownWeather");
|
||||||
|
setEvent(InputEvent::SetColor, CRGB(255, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::value(String value)
|
||||||
|
{
|
||||||
|
if (m_curField == "weather") {
|
||||||
|
if (m_curKey == "id") {
|
||||||
|
m_weatherId = atoi(value.c_str());
|
||||||
|
} else if (m_curKey == "main") {
|
||||||
|
Log.info("WEATHER ENCOUNTERED: %s", value.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::startObject()
|
||||||
|
{
|
||||||
|
m_curField = m_curKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Weather::endObject()
|
||||||
|
{
|
||||||
|
m_curField = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Weather::endArray() {}
|
||||||
|
void Weather::startArray() {}
|
||||||
|
void Weather::endDocument() {}
|
||||||
|
void Weather::whitespace(char c) {}
|
||||||
|
void Weather::startDocument() {}
|
||||||
|
|
||||||
|
STATIC_ALLOC(Weather);
|
||||||
|
STATIC_TASK(Weather);
|
51
src/inputs/Weather.h
Normal file
51
src/inputs/Weather.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Figments.h>
|
||||||
|
#include <JsonListener.h>
|
||||||
|
#include <JsonStreamingParser.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
|
||||||
|
class Weather : public BufferedInputSource, OnlineTaskMixin, JsonListener {
|
||||||
|
public:
|
||||||
|
Weather();
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent& evt) override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
void onOnline() override {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onStart() override {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loopOnline() override {
|
||||||
|
}
|
||||||
|
|
||||||
|
void update();
|
||||||
|
void parse();
|
||||||
|
|
||||||
|
virtual void whitespace(char c);
|
||||||
|
virtual void startDocument();
|
||||||
|
virtual void key(String key);
|
||||||
|
virtual void value(String value);
|
||||||
|
virtual void endArray();
|
||||||
|
virtual void endObject();
|
||||||
|
virtual void endDocument();
|
||||||
|
virtual void startArray();
|
||||||
|
virtual void startObject();
|
||||||
|
|
||||||
|
private:
|
||||||
|
JsonStreamingParser m_parser;
|
||||||
|
WiFiClient m_client;
|
||||||
|
bool m_fetching = false;
|
||||||
|
String m_curKey;
|
||||||
|
String m_curField;
|
||||||
|
bool m_foundBody = false;
|
||||||
|
|
||||||
|
int m_weatherId = 0;
|
||||||
|
|
||||||
|
void doWeatherEvent();
|
||||||
|
};
|
||||||
|
|
@ -44,7 +44,7 @@ ColorSequenceInput<9> idleCycle{{
|
|||||||
CRGB(0, 255, 255), // Cyan
|
CRGB(0, 255, 255), // Cyan
|
||||||
}, "IdleColors"};
|
}, "IdleColors"};
|
||||||
|
|
||||||
REGISTER_TASK(idleCycle);
|
//REGISTER_TASK(idleCycle);
|
||||||
|
|
||||||
ColorSequenceInput<7> rainbowCycle{{
|
ColorSequenceInput<7> rainbowCycle{{
|
||||||
CRGB(255, 0, 0), // Red
|
CRGB(255, 0, 0), // Red
|
||||||
@ -54,7 +54,7 @@ ColorSequenceInput<7> rainbowCycle{{
|
|||||||
CRGB(128, 0, 128), // Purple
|
CRGB(128, 0, 128), // Purple
|
||||||
}, "Rainbow"};
|
}, "Rainbow"};
|
||||||
|
|
||||||
REGISTER_TASK(rainbowCycle);
|
//REGISTER_TASK(rainbowCycle);
|
||||||
|
|
||||||
MainLoop* runner = &SafeMode::safeModeApp;
|
MainLoop* runner = &SafeMode::safeModeApp;
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ __NOINIT_ATTR static uint8_t s_rebootCount;
|
|||||||
__NOINIT_ATTR static uint16_t s_forceSafeMode;
|
__NOINIT_ATTR static uint16_t s_forceSafeMode;
|
||||||
#define SAFE_MODE_MAGIC 6942
|
#define SAFE_MODE_MAGIC 6942
|
||||||
|
|
||||||
|
__NOINIT_ATTR static struct tm s_lastGoodTime;
|
||||||
|
|
||||||
WiFiUDP wifiUdp;
|
WiFiUDP wifiUdp;
|
||||||
NTPClient timeClient(wifiUdp, "pool.ntp.org", 0);
|
NTPClient timeClient(wifiUdp, "pool.ntp.org", 0);
|
||||||
|
|
||||||
@ -44,8 +46,12 @@ template<>
|
|||||||
bool
|
bool
|
||||||
PlatformImpl<HAL_ESP8266>::getLocalTime(struct tm* timedata, int timezone)
|
PlatformImpl<HAL_ESP8266>::getLocalTime(struct tm* timedata, int timezone)
|
||||||
{
|
{
|
||||||
timedata->tm_hour = (timeClient.getHours() + timezone) % 23;
|
if (timeClient.isTimeSet()) {
|
||||||
timedata->tm_min = timeClient.getMinutes();
|
s_lastGoodTime.tm_min = (timeClient.getMinutes() + timezone) % 23;
|
||||||
|
s_lastGoodTime.tm_hour = timeClient.getHours();
|
||||||
|
s_lastGoodTime.tm_sec = timeClient.getSeconds();
|
||||||
|
}
|
||||||
|
memcpy(timedata, &s_lastGoodTime, sizeof(s_lastGoodTime));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +60,9 @@ void
|
|||||||
PlatformImpl<HAL_ESP8266>::startNTP()
|
PlatformImpl<HAL_ESP8266>::startNTP()
|
||||||
{
|
{
|
||||||
timeClient.begin();
|
timeClient.begin();
|
||||||
|
if (s_lastGoodTime.tm_hour >= 24 || s_lastGoodTime.tm_min >= 60 || s_lastGoodTime.tm_sec >= 60) {
|
||||||
|
memset(&s_lastGoodTime, 0, sizeof(s_lastGoodTime));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
@ -64,9 +64,7 @@ public:
|
|||||||
uint8_t val = lerp8by8(0, m_brightness, pixelMod);
|
uint8_t val = lerp8by8(0, m_brightness, pixelMod);
|
||||||
CHSV blobColor(m_hue, m_saturation, val);
|
CHSV blobColor(m_hue, m_saturation, val);
|
||||||
|
|
||||||
//PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), startPos.y};
|
nblend(pixel, CRGB(blobColor), 80);
|
||||||
|
|
||||||
pixel += blend(CRGB(blobColor), pixel, 80);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user