port to platformio

This commit is contained in:
Torrie Fischer 2021-03-29 01:10:55 -07:00
parent 9a3bf84214
commit a6534bcb20
131 changed files with 1537 additions and 1148 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
bin/* bin/*
*.bin *.bin
.pio

View File

@ -1,164 +0,0 @@
#include "./Config.h"
#include "./Static.h"
HardwareConfig
HardwareConfig::load() {
HardwareConfig ret;
EEPROM.get(0, ret);
return ret;
}
void
HardwareConfig::save() {
HardwareConfig dataCopy{*this};
dataCopy.checksum = getCRC();
EEPROM.put(0, dataCopy);
}
LinearCoordinateMapping
HardwareConfig::toCoordMap() const
{
auto pixelCount = min(HardwareConfig::MAX_LED_NUM, max(1, data.pixelCount));
auto startPixel = min(pixelCount, max(1, data.startPixel));
return LinearCoordinateMapping{pixelCount, startPixel};
}
bool
HardwareConfig::isValid() const
{
return version == 1 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM;
}
uint8_t
HardwareConfig::getCRC() const
{
const unsigned char* message = reinterpret_cast<const unsigned char*>(&data);
constexpr uint8_t length = sizeof(data);
unsigned char i, j, crc = 0;
for(i = 0; i < length; i++) {
crc ^= message[i];
for(j = 0; j < 8; j++) {
if (crc & 1) {
crc ^= CRC7_POLY;
}
crc >>= 1;
}
}
return crc;
}
void
ConfigService::onStart()
{
Log.info("Starting configuration service...");
m_config = HardwareConfig::load();
if (m_config.isValid()) {
Log.info("Configuration found!");
} else {
Log.info("No configuration found. Writing defaults...");
m_config = HardwareConfig{};
m_config.save();
}
m_coordMap = m_config.toCoordMap();
m_pixelCount = AnimatedNumber{max(1, m_coordMap.pixelCount)};
m_startPixel = AnimatedNumber{max(0, m_coordMap.startPixel)};
Log.info("Configured to use %d pixels, starting at %d", m_pixelCount.value(), m_startPixel.value());
Log.info("Loading task states...");
for(int i = 0; i < 32; i++) {
auto svc = m_config.data.serviceStates[i];
if (strlen(svc.name) > 0) {
Log.info("* %s: %s", svc.name, svc.isRunning ? "RUNNING" : "STOPPED");
}
}
}
void
ConfigService::onConnected()
{
Log.info("Connecting photon configuration...");
Particle.function("pixelCount", &ConfigService::setPixelCount, this);
Particle.function("startPixel", &ConfigService::setStartPixel, this);
Particle.function("save", &ConfigService::photonSave, this);
Particle.variable("pixelCount", m_pixelCountInt);
Particle.variable("startPixel", m_startPixelInt);
publishConfig();
}
void
ConfigService::loop()
{
//m_startPixel.update();
//m_pixelCount.update();
//m_coordMap.pixelCount = max(1, m_pixelCount.value());
//m_coordMap.startPixel = max(0, m_startPixel.value());
}
void
ConfigService::handleEvent(const InputEvent &evt)
{
switch(evt.intent) {
case InputEvent::NetworkStatus:
onConnected();
break;
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();
m_pixelCountInt = evt.asInt();
//m_pixelCount = m_config.data.pixelCount;
//m_coordMap.pixelCount = evt.asInt();
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();
m_startPixelInt = evt.asInt();
Log.info("Offset is now %d", m_coordMap.startPixel);
//m_coordMap.startPixel = evt.asInt();
//m_startPixel = m_config.data.startPixel;
break;
case InputEvent::SaveConfigurationRequest:
Log.info("Saving configuration");
m_config.save();
break;
}
}
void
ConfigService::publishConfig() const
{
char buf[255];
snprintf(buf, sizeof(buf), "{\"pixels\": \"%d\", \"offset\": \"%d\"}", m_config.data.pixelCount, m_config.data.startPixel);
Particle.publish("renderbug/config", buf, PRIVATE);
}
int
ConfigService::photonSave(String command)
{
m_config.save();
return 0;
}
int
ConfigService::setPixelCount(String command)
{
m_config.data.pixelCount = command.toInt();
m_pixelCount = m_config.data.pixelCount;
publishConfig();
return 0;
}
int
ConfigService::setStartPixel(String command)
{
m_config.data.startPixel = command.toInt();
m_startPixel = m_config.data.startPixel;
publishConfig();
return 0;
}
STATIC_ALLOC(ConfigService);

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/FastSPI_LED2.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/LICENSE

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/PORTING.md

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/README.md

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/bitswap.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/chipsets.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/clockless_arm_stm32.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/color.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/controller.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/delay.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/dmx.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/docs

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/examples

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_arm_stm32.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_config.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin_arm_stm32.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_bitbang.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_dma.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_nop.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_ref.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_types.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/keywords.txt

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs_arm_stm32.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/noise.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/noise.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/pixeltypes.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/platforms

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/platforms.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/preview_changes.txt

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/release_notes.md

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/FastLED/firmware/wiring.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/Buffer.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/Buffer.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/Label.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/Label.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/MDNS

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/MDNS.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/MDNS.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/Record.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MDNS/src/Record.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MQTT/src/MQTT

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MQTT/src/MQTT.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/MQTT/src/MQTT.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.h

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino.cpp

View File

@ -1 +0,0 @@
/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino.h

View File

@ -1,780 +0,0 @@
#define RENDERBUG_VERSION 1
#define PLATFORM_PHOTON
//#define PLATFORM_ESP2800
#include "FastLED/FastLED.h"
#include "Figments/Figments.h"
#include "Static.h"
#include "Config.h"
#include "colors.h"
#include "WebTelemetry.cpp"
#include "MDNSService.cpp"
#include "Sequencer.h"
#include "animations/Power.cpp"
#include "animations/SolidAnimation.cpp"
#include "animations/Chimes.cpp"
#include "animations/Flashlight.cpp"
#include "animations/Drain.cpp"
#include "animations/UpdateStatus.h"
#include "inputs/ColorCycle.h"
#include "inputs/Buttons.h"
#include "inputs/MPU6050.h"
#ifdef PLATFORM_PHOTON
#include "platform/particle/inputs/Photon.h"
#include "platform/particle/inputs/CloudStatus.h"
#include "platform/particle/PhotonTelemetry.h"
#endif
SerialLogHandler logHandler;
using namespace NSFastLED;
#define MAX_BRIGHTNESS 255
//#define PSU_MILLIAMPS 4800
//#define PSU_MILLIAMPS 500
//#define PSU_MILLIAMPS 1000
#define PSU_MILLIAMPS 2000
// Enable system thread, so rendering happens while booting
SYSTEM_THREAD(ENABLED);
// Setup FastLED and the display
CRGB leds[HardwareConfig::MAX_LED_NUM];
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
LinearCoordinateMapping neckMap{60, 0};
Display neckDisplay(leds, HardwareConfig::MAX_LED_NUM, &neckMap);
// Setup power management
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
// Clip the display at whatever is configured while still showing over-paints
FigmentFunc displayClip([](Display* dpy) {
auto coords = Static<ConfigService>::instance()->coordMap();
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
if (i < coords->startPixel || i > coords->pixelCount + coords->startPixel) {
dpy->pixelAt(i) %= 40;
}
}
});
FigmentFunc configDisplay([](Display* dpy) {
uint8_t brightness = brighten8_video(beatsin8(60));
auto coords = Static<ConfigService>::instance()->coordMap();
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
if (i < coords->startPixel || i > coords->startPixel + coords->pixelCount) {
dpy->pixelAt(i) += CRGB(brightness, 0, 0);
} else {
dpy->pixelAt(i) += CRGB(255 - brightness, 255 - brightness, 255 - brightness);
}
}
});
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;
InputFunc randomPulse([]() {
static unsigned int pulse = 0;
EVERY_N_MILLISECONDS(25) {
if (pulse == 0) {
pulse = random(25) + 25;
}
}
if (pulse > 0) {
if (random(255) >= 25) {
pulse--;
return InputEvent{InputEvent::Acceleration, beatsin16(60 * 8, 0, 4972)};
}
}
return InputEvent{};
}, "Pulse", Task::Running);
InputMapper keyMap([](const InputEvent& evt) {
if (evt.intent == InputEvent::UserInput) {
Buttons::Chord chord = (Buttons::Chord)evt.asInt();
switch(chord) {
case Buttons::Circle:
return InputEvent::PowerToggle;
break;
case Buttons::Triangle:
return InputEvent::NextPattern;
break;
case Buttons::Cross:
return InputEvent::UserInput;
break;
}
}
return InputEvent::None;
});
ChimesAnimation chimes{Task::Stopped};
SolidAnimation solid{Task::Running};
DrainAnimation drain{Task::Stopped};
Flashlight flashlight{Task::Stopped};
Sequencer sequencer{{
{"Idle", {"Solid", "MPU5060", "Pulse", "Hackerbots", "Kieryn", "CircadianRhythm"}},
{"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}},
{"Interactive", {"Drain", "CircadianRhythm"}},
{"Flashlight", {"Flashlight"}},
{"Nightlight", {"Drain", "Pulse", "Noisebridge"}},
{"Gay", {"Solid", "Pulse", "Rainbow", "Hackerbots", "Kieryn"}},
{"Acid", {"Chimes", "Pulse", "MPU5060", "Hackerbots", "Rainbow"}},
}};
// Render all layers to the displays
Renderer renderer{
//{&dpy, &neckDisplay},
{&dpy},
{
&chimes,
&drain,
&solid,
&flashlight,
Static<UpdateStatus>::instance(),
&displayClip,
&inputBlip,
&power,
}
};
Renderer configRenderer{
{&dpy},
{&drain, &configDisplay, &inputBlip, &power}
};
MDNSService mdnsService;
class OnlineTaskMixin : public virtual Loopable {
public:
void handleEvent(const InputEvent &evt) override {
if (evt.intent == InputEvent::NetworkStatus) {
m_online = evt.asInt();
}
if (m_online) {
handleEventOnline(evt);
}
}
virtual void handleEventOnline(const InputEvent &evt) = 0;
void loop() override {
if (m_online) {
loopOnline();
}
}
virtual void loopOnline() = 0;
private:
bool m_online = false;
};
#include "MQTT/MQTT.h"
class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin {
public:
MQTTTelemetry() : BufferedInputSource("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) {
strcpy(m_deviceName, System.deviceID().c_str());
}
void loop() override {
BufferedInputSource::loop();
OnlineTaskMixin::loop();
}
void handleEventOnline(const InputEvent& event) override {
char response[255];
if (event.intent == InputEvent::SetPower) {
JSONBufferWriter writer(response, sizeof(response));
writer.beginObject();
writer.name("state").value(event.asInt() == 0 ? "OFF" : "ON");
writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
m_client.publish(m_statTopic, response, MQTT::QOS1);
} else if (event.intent == InputEvent::SetBrightness) {
JSONBufferWriter writer(response, sizeof(response));
writer.beginObject();
writer.name("brightness").value(event.asInt());
writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
m_client.publish(m_statTopic, response, MQTT::QOS1);
} else if (event.intent == InputEvent::SetColor) {
JSONBufferWriter writer(response, sizeof(response));
writer.beginObject();
writer.name("color").beginObject();
CRGB rgb = event.asRGB();
writer.name("r").value(rgb.r);
writer.name("g").value(rgb.g);
writer.name("b").value(rgb.b);
writer.endObject();
writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
m_client.publish(m_statTopic, response, MQTT::QOS1);
} else if (event.intent == InputEvent::SetPattern) {
JSONBufferWriter writer(response, sizeof(response));
writer.beginObject();
writer.name("effect").value(event.asString());
writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
m_client.publish(m_statTopic, response, MQTT::QOS1);
} else {
if (m_lastIntent != event.intent) {
m_lastIntent = event.intent;
JSONBufferWriter writer(response, sizeof(response));
writer.beginObject();
writer.name("intent").value(event.intent);
writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
m_client.publish("renderbug/events", response, MQTT::QOS1);
}
}
}
void loopOnline() override {
if (m_client.isConnected()) {
m_client.loop();
EVERY_N_SECONDS(10) {
char heartbeatBuf[255];
JSONBufferWriter writer(heartbeatBuf, sizeof(heartbeatBuf));
writer.beginObject();
writer.name("fps").value(FastLED.getFPS());
writer.name("os_version").value(System.version());
writer.name("free_ram").value((unsigned int)System.freeMemory());
//writer.name("uptime").value(System.uptime());
/*WiFiSignal sig = WiFi.RSSI();
writer.name("strength").value(sig.getStrength());
writer.name("quality").value(sig.getQuality());*/
writer.name("RSSI").value(WiFi.RSSI());
writer.name("SSID").value(WiFi.SSID());
//writer.name("MAC").value(WiFi.macAddress());
writer.name("localip").value(WiFi.localIP().toString());
writer.name("startPixel").value(Static<ConfigService>::instance()->coordMap()->startPixel);
writer.name("pixelCount").value(Static<ConfigService>::instance()->coordMap()->pixelCount);
writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
m_client.publish(m_attrTopic, heartbeatBuf);
}
} else {
m_client.connect("renderbug_" + String(m_deviceName) + "_" + String(Time.now()));
char response[512];
JSONBufferWriter writer(response, sizeof(response));
String devTopic = String("homeassistant/light/renderbug/") + m_deviceName;
writer.beginObject();
writer.name("~").value(devTopic.c_str());
writer.name("name").value("Renderbug");
writer.name("unique_id").value(m_deviceName);
writer.name("cmd_t").value("~/set");
writer.name("stat_t").value("~/set");
writer.name("json_attr_t").value("~/attributes");
writer.name("schema").value("json");
writer.name("brightness").value(true);
writer.name("rgb").value(true);
writer.name("ret").value(true);
writer.name("dev").beginObject();
writer.name("name").value("Renderbug");
writer.name("mdl").value("Renderbug");
writer.name("sw").value(RENDERBUG_VERSION);
writer.name("mf").value("Phong Robotics");
writer.name("ids").beginArray();
writer.value(m_deviceName);
writer.endArray();
writer.endObject();
writer.name("fx_list").beginArray();
for(const Sequencer::Scene& scene : sequencer.scenes()) {
writer.value(scene.name);
}
writer.endArray();
writer.endObject();
writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0;
String configTopic = devTopic + "/config";
m_client.publish(configTopic, response, true);
String statTopic = devTopic + "/state";
String cmdTopic = devTopic + "/set";
String attrTopic = devTopic + "/attributes";
strcpy(m_statTopic, statTopic.c_str());
strcpy(m_attrTopic, attrTopic.c_str());
strcpy(m_cmdTopic, cmdTopic.c_str());
m_client.subscribe(m_cmdTopic, MQTT::QOS1);
}
}
private:
void callback(char* topic, byte* payload, unsigned int length) {
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
if (!strcmp(topic, m_cmdTopic)) {
JSONValue cmd = JSONValue::parseCopy((char*)payload, length);
JSONObjectIterator cmdIter(cmd);
while(cmdIter.next()) {
if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") {
setEvent({InputEvent::SetPower, 1});
} else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") {
setEvent({InputEvent::SetPower, 0});
}
if (cmdIter.name() == "color") {
JSONObjectIterator colorIter(cmdIter.value());
uint8_t r, g, b;
while(colorIter.next()) {
if (colorIter.name() == "r") {
r = colorIter.value().toInt();
} else if (colorIter.name() == "g") {
g = colorIter.value().toInt();
} else if (colorIter.name() == "b") {
b = colorIter.value().toInt();
}
}
setEvent(InputEvent{InputEvent::SetColor, CRGB{r, g, b}});
}
if (cmdIter.name() == "brightness") {
uint8_t brightness = cmdIter.value().toInt();
setEvent(InputEvent{InputEvent::SetBrightness, brightness});
}
if (cmdIter.name() == "effect") {
strcpy(m_patternBuf, (const char*) cmdIter.value().toString());
setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf});
}
if (cmdIter.name() == "pixelCount") {
setEvent(InputEvent{InputEvent::SetDisplayLength, cmdIter.value().toInt()});
}
if (cmdIter.name() == "startPixel") {
setEvent(InputEvent{InputEvent::SetDisplayOffset, cmdIter.value().toInt()});
}
if (cmdIter.name() == "save") {
setEvent(InputEvent{InputEvent::SaveConfigurationRequest});
}
}
}
}
static void s_callback(char* topic, byte* payload, unsigned int length) {
Static<MQTTTelemetry>::instance()->callback(topic, payload, length);
};
MQTT m_client;
InputEvent::Intent m_lastIntent;
char m_deviceName[100];
char m_statTopic[100];
char m_attrTopic[100];
char m_cmdTopic[100];
char m_patternBuf[48];
};
STATIC_ALLOC(MQTTTelemetry);
WebTelemetry webTelemetry(sequencer);
// Cycle some random colors
ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped};
ColorSequenceInput<13> kierynCycle{{
colorForName("Cerulean").rgb,
colorForName("Electric Purple").rgb,
colorForName("Emerald").rgb,
colorForName("Sky Magenta").rgb
}, "Kieryn", Task::Stopped};
ColorSequenceInput<7> rainbowCycle{{
colorForName("Red").rgb,
colorForName("Orange").rgb,
colorForName("Yellow").rgb,
colorForName("Green").rgb,
colorForName("Blue").rgb,
colorForName("Purple").rgb,
colorForName("White").rgb,
}, "Rainbow", Task::Stopped};
ColorSequenceInput<7> hackerbotsCycle{{
colorForName("Purple").rgb,
colorForName("White").rgb,
colorForName("Cyan").rgb,
}, "Hackerbots", Task::Stopped};
struct ConfigInputTask: public BufferedInputSource {
public:
ConfigInputTask() : BufferedInputSource("ConfigInput") {}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::UserInput) {
Buttons::Chord chord = (Buttons::Chord) evt.asInt();
switch (chord) {
case Buttons::Circle:
m_currentIntent = nextIntent();
Log.info("Next setting... (%d)", m_currentIntent);
break;
case Buttons::CircleTriangle:
Log.info("Increment...");
increment();
break;
case Buttons::CircleCross:
Log.info("Decrement...");
decrement();
break;
case Buttons::Triangle:
Log.info("Save...");
setEvent(InputEvent::SaveConfigurationRequest);
break;
}
}
}
private:
InputEvent::Intent m_currentIntent = InputEvent::SetDisplayLength;
void decrement() {
int current = 0;
switch (m_currentIntent) {
case InputEvent::SetDisplayLength:
current = Static<ConfigService>::instance()->coordMap()->pixelCount;
break;
case InputEvent::SetDisplayOffset:
current = Static<ConfigService>::instance()->coordMap()->startPixel;
break;
}
setEvent(InputEvent{m_currentIntent, current - 1});
}
void increment() {
int current = 0;
switch (m_currentIntent) {
case InputEvent::SetDisplayLength:
current = Static<ConfigService>::instance()->coordMap()->pixelCount;
break;
case InputEvent::SetDisplayOffset:
current = Static<ConfigService>::instance()->coordMap()->startPixel;
break;
}
setEvent(InputEvent{m_currentIntent, current + 1});
}
InputEvent::Intent nextIntent() {
switch (m_currentIntent) {
case InputEvent::SetDisplayLength:
return InputEvent::SetDisplayOffset;
case InputEvent::SetDisplayOffset:
return InputEvent::SetDisplayLength;
}
}
};
enum Phase {
Null,
AstronomicalDay,
NauticalDay,
CivilDay,
CivilNight,
NauticalNight,
AstronomicalNight,
Evening,
Bedtime,
};
struct ScheduleEntry {
uint8_t hour;
uint8_t brightness;
};
std::array<ScheduleEntry, 10> schedule{{
{0, 0},
{5, 0},
{6, 0},
{7, 10},
{8, 80},
{11, 120},
{18, 200},
{19, 255},
{22, 120},
{23, 20}
}};
uint8_t brightnessForTime(uint8_t hour, uint8_t minute) {
ScheduleEntry start = schedule.back();
ScheduleEntry end = schedule.front();
for(ScheduleEntry cur : schedule) {
// Find the last hour that is prior to or equal to now
if (cur.hour <= hour) {
start = cur;
} else {
break;
}
}
for(ScheduleEntry cur : schedule) {
// Find the first hour that is after now
// If no such hour exists, we should automatically wrap back to hour 0
if (cur.hour > hour) {
end = cur;
break;
}
}
if (start.hour > end.hour) {
end.hour += 24;
}
uint16_t startTime = start.hour * 60;
uint16_t endTime = end.hour * 60;
uint16_t nowTime = hour * 60 + minute;
uint16_t duration = endTime - startTime;
uint16_t curDuration = nowTime - startTime;
uint8_t frac = ((double)curDuration / (double)duration) * 255.0;
return lerp8by8(start.brightness, end.brightness, frac);
}
InputFunc circadianRhythm([]() {
static Phase lastPhase = Null;
static bool needsUpdate = true;
if (Time.isValid()) {
EVERY_N_SECONDS(60) {
needsUpdate = true;
}
if (needsUpdate) {
needsUpdate = false;
return InputEvent{InputEvent::SetBrightness, brightnessForTime(Time.hour(), Time.minute())};
}
}
return InputEvent{};
}, "CircadianRhythm", Task::Running);
// A special mainloop app for configuring hardware settings that reboots the
// device when the user is finished.
MainLoop configApp{{
// Manage read/write of configuration data
Static<ConfigService>::instance(),
#ifdef PLATFORM_PHOTON
// Update photon telemetry
Static<PhotonTelemetry>::instance(),
#endif
// Read hardware inputs
new Buttons(),
// Map input buttons to configuration commands
new ConfigInputTask(),
// Fill the entire display with a color, to see size
&configDisplay,
// Render some basic input feedback
&inputBlip,
// Render it all
&configRenderer,
}};
// Turn on,
MainLoop renderbugApp{{
// Load/update graphics configuration from EEPROM and Particle
Static<ConfigService>::instance(),
// Platform inputs
#ifdef PLATFORM_PHOTON
// Particle cloud status
Static<CloudStatus>::instance(),
// Monitor network state and provide particle API events
Static<PhotonInput>::instance(),
#endif
// Hardware drivers
new MPU5060(),
new Buttons(),
// Map buttons to events
&keyMap,
// Pattern sequencer
&sequencer,
// Daily rhythm activities
&circadianRhythm,
// Periodic motion input
&randomPulse,
// Periodic color inputs
&noisebridgeCycle,
&kierynCycle,
&rainbowCycle,
&hackerbotsCycle,
// Animations
&chimes,
&drain,
&solid,
&flashlight,
// Update UI layer
&power,
&displayClip,
Static<UpdateStatus>::instance(),
&inputBlip,
// Render everything
&renderer,
// Platform telemetry
#ifdef PLATFORM_PHOTON
// Update photon telemetry
Static<PhotonTelemetry>::instance(),
// Web telemetry UI
&webTelemetry,
// MQTT telemetry
Static<MQTTTelemetry>::instance(),
// Network discovery
&mdnsService,
#endif
}};
MainLoop &runner = renderbugApp;
retained bool LAST_BOOT_WAS_FLASH;
retained bool LAST_BOOT_WAS_SERIAL;
struct BootOptions {
static void initPins() {
pinMode(2, INPUT_PULLDOWN);
pinMode(3, INPUT_PULLDOWN);
pinMode(4, INPUT_PULLDOWN);
}
BootOptions() {
isSetup = digitalRead(2) == HIGH;
isSerial = digitalRead(3) == HIGH || LAST_BOOT_WAS_SERIAL;
isFlash = digitalRead(4) == HIGH;
LAST_BOOT_WAS_FLASH = isFlash;
LAST_BOOT_WAS_SERIAL |= isSerial;
configStatus.setActive(isSetup);
serialStatus.setActive(isSerial);
}
void waitForRelease() {
while(digitalRead(2) == HIGH || digitalRead(3) == HIGH) {};
}
bool isSetup = false;
bool isSerial = false;
bool isFlash = false;
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT);
};
STARTUP(BootOptions::initPins());
retained BootOptions bootopts;
ApplicationWatchdog *wd;
void watchdogHandler() {
for(int i = 0; i < 8; i++) {
leds[i] = CRGB(i % 3 ? 35 : 255, 0, 0);
}
FastLED.show();
if (LAST_BOOT_WAS_FLASH) {
System.dfu();
} else {
System.enterSafeMode();
}
}
// Tune in,
void setup() {
System.enableFeature(FEATURE_RETAINED_MEMORY);
wd = new ApplicationWatchdog(5000, watchdogHandler, 1536);
Serial.begin(115200);
if (bootopts.isFlash) {
System.dfu();
}
if (bootopts.isSerial) {
bootopts.waitForRelease();
while(!Serial.isConnected()) {
#ifdef PLATFORM_PHOTON
Particle.process();
#endif
}
Log.info("\xf0\x9f\x94\x8c Serial connected");
}
Log.info(u8"🐛 Booting Renderbug %s!", System.deviceID().c_str());
Log.info(u8"🐞 I am built for %d LEDs running on %dmA", HardwareConfig::MAX_LED_NUM, PSU_MILLIAMPS);
#ifdef PLATFORM_PHOTON
Log.info(u8"📡 Particle version %s", System.version().c_str());
#endif
Log.info(u8" Boot pin configuration:");
Log.info(u8" 2: Setup - %d", bootopts.isSetup);
Log.info(u8" 3: Serial - %d", bootopts.isSerial);
Log.info(u8" 4: Flash - %d", bootopts.isFlash);
Log.info(u8" Setting timezone to UTC-7");
Time.zone(-7);
Log.info(u8"💡 Starting FastLED...");
FastLED.addLeds<NEOPIXEL, 6>(leds, HardwareConfig::MAX_LED_NUM);
if (bootopts.isSetup) {
Log.info(u8"🌌 Starting Figment in configuration mode...");
runner = configApp;
} else {
Log.info(u8"🌌 Starting Figment...");
}
Serial.flush();
runner.start();
Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory());
Log.info(u8"🚀 Setup complete! Ready to rock and roll.");
Serial.flush();
}
// Drop out.
void loop() {
runner.loop();
wd->checkin();
}

39
include/README Normal file
View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@ -6,5 +6,5 @@
uint8_t uint8_t
AnimatedNumber::value() const AnimatedNumber::value() const
{ {
return NSFastLED::lerp8by8(m_start, m_end, m_idx); return lerp8by8(m_start, m_end, m_idx);
} }

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "FastLED/FastLED.h" #include <FastLED.h>
#include "./Figment.h" #include "./Figment.h"
#include <vector> #include <vector>
@ -68,16 +68,16 @@ private:
}; };
struct AnimatedRGB { struct AnimatedRGB {
NSFastLED::CRGB start; CRGB start;
NSFastLED::CRGB end; CRGB end;
AnimatedNumber pos; AnimatedNumber pos;
AnimatedRGB(const NSFastLED::CRGB& color) AnimatedRGB(const CRGB& color)
: start(color), end(color) {} : start(color), end(color) {}
AnimatedRGB() {} AnimatedRGB() {}
AnimatedRGB& operator=(const NSFastLED::CRGB& rgb) { AnimatedRGB& operator=(const CRGB& rgb) {
start = *this; start = *this;
end = rgb; end = rgb;
pos.set(0, 255); pos.set(0, 255);
@ -88,11 +88,11 @@ struct AnimatedRGB {
pos.update(); pos.update();
} }
operator NSFastLED::CRGB() const { operator CRGB() const {
uint8_t red = NSFastLED::lerp8by8(start.red, end.red, pos); uint8_t red = lerp8by8(start.red, end.red, pos);
uint8_t green = NSFastLED::lerp8by8(start.green, end.green, pos); uint8_t green = lerp8by8(start.green, end.green, pos);
uint8_t blue = NSFastLED::lerp8by8(start.blue, end.blue, pos); uint8_t blue = lerp8by8(start.blue, end.blue, pos);
return NSFastLED::CRGB(red, green, blue); return CRGB(red, green, blue);
} }
}; };

View File

@ -1,8 +1,6 @@
#include "Display.h" #include "Display.h"
#include <math.h> #include <math.h>
using namespace NSFastLED;
int int
Display::pixelCount() const Display::pixelCount() const
{ {

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "FastLED/FastLED.h" #include <FastLED.h>
#include "Geometry.h" #include "Geometry.h"
@ -20,7 +20,7 @@ struct LinearCoordinateMapping: CoordinateMapping {
} }
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const { PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
return PhysicalCoordinates{NSFastLED::scale8(pixelCount, virtualCoords.x), 0}; return PhysicalCoordinates{scale8(pixelCount, virtualCoords.x), 0};
} }
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override { int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override {
@ -35,22 +35,22 @@ struct LinearCoordinateMapping: CoordinateMapping {
class Display { class Display {
public: public:
Display(NSFastLED::CRGB* pixels, int pixelCount, const CoordinateMapping* map) Display(CRGB* pixels, int pixelCount, const CoordinateMapping* map)
: m_pixels(pixels), m_pixelCount(pixelCount), m_coordMap(map) {} : m_pixels(pixels), m_pixelCount(pixelCount), m_coordMap(map) {}
NSFastLED::CRGB& pixelAt(const PhysicalCoordinates coords); CRGB& pixelAt(const PhysicalCoordinates coords);
NSFastLED::CRGB& pixelAt(const VirtualCoordinates coords); CRGB& pixelAt(const VirtualCoordinates coords);
NSFastLED::CRGB& pixelAt(int idx); CRGB& pixelAt(int idx);
int pixelCount() const; int pixelCount() const;
NSFastLED::CRGB* pixelBacking() const; CRGB* pixelBacking() const;
const CoordinateMapping* coordinateMapping() const; const CoordinateMapping* coordinateMapping() const;
void clear(); void clear();
void clear(const NSFastLED::CRGB& color); void clear(const CRGB& color);
void clear(VirtualCoordinates& start, VirtualCoordinates& end, const NSFastLED::CRGB& color); void clear(VirtualCoordinates& start, VirtualCoordinates& end, const CRGB& color);
private: private:
NSFastLED::CRGB* m_pixels; CRGB* m_pixels;
int m_pixelCount; int m_pixelCount;
const CoordinateMapping* m_coordMap; const CoordinateMapping* m_coordMap;
}; };

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "application.h" #include <Arduino.h>
#include <functional> #include <functional>
#include <ArduinoLog.h>
class Display; class Display;
class InputEvent; class InputEvent;
@ -25,8 +26,8 @@ struct Task : public virtual Loopable {
Task(const char* name) : Task(name, Running) {} Task(const char* name) : Task(name, Running) {}
Task(const char* name, State initialState) : name(name), state(initialState) {} Task(const char* name, State initialState) : name(name), state(initialState) {}
void start() { Log.info("* Starting %s...", name); state = Running; onStart(); } void start() { state = Running; onStart(); }
void stop() { Log.info("* Stopping %s...", name); onStop(); state = Stopped; } void stop() { onStop(); state = Stopped; }
virtual bool isFigment() const { return false; } virtual bool isFigment() const { return false; }
const char* name = 0; const char* name = 0;

View File

@ -8,12 +8,12 @@ template<typename T> struct Coordinates {
T y; T y;
}; };
struct VirtualCoordinates: Coordinates<uint8_t> { struct VirtualCoordinates: Coordinates<uint16_t> {
VirtualCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {} VirtualCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {}
}; };
struct PhysicalCoordinates: Coordinates<uint8_t> { struct PhysicalCoordinates: Coordinates<uint16_t> {
PhysicalCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {} PhysicalCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {}
}; };
template<typename T> struct Vector3d { template<typename T> struct Vector3d {

View File

@ -1,11 +1,11 @@
#include "application.h" #include <Arduino.h>
#include "./Input.h" #include "./Input.h"
#include "./MainLoop.h" #include "./MainLoop.h"
NSFastLED::CRGB CRGB
Variant::asRGB() const Variant::asRGB() const
{ {
return NSFastLED::CRGB(m_value.asRGB[0], m_value.asRGB[1], m_value.asRGB[2]); return CRGB(m_value.asRGB[0], m_value.asRGB[1], m_value.asRGB[2]);
} }
const char* const char*

View File

@ -1,9 +1,9 @@
#pragma once #pragma once
#include "application.h" #include <Arduino.h>
#include "./Geometry.h" #include "./Geometry.h"
#include "./Figment.h" #include "./Figment.h"
#include "./Ringbuf.h" #include "./Ringbuf.h"
#include "FastLED/FastLED.h" #include <FastLED.h>
typedef Vector3d<int> MotionVec; typedef Vector3d<int> MotionVec;
@ -21,7 +21,7 @@ struct Variant {
Variant(const char* v) Variant(const char* v)
: type(String), m_value{.asString=v} {} : type(String), m_value{.asString=v} {}
Variant(const NSFastLED::CRGB &v) Variant(const CRGB &v)
: type(Color), m_value{.asRGB={v.r, v.g, v.b}} {} : type(Color), m_value{.asRGB={v.r, v.g, v.b}} {}
Variant() Variant()
@ -30,7 +30,7 @@ struct Variant {
Type type; Type type;
const char* asString() const; const char* asString() const;
NSFastLED::CRGB asRGB() const; CRGB asRGB() const;
int asInt() const; int asInt() const;
private: private:
@ -142,7 +142,7 @@ private:
class InputMapper: public BufferedInputSource { class InputMapper: public BufferedInputSource {
public: public:
InputMapper(std::function<InputEvent(const InputEvent)> f) : BufferedInputSource(), m_func(f) {} InputMapper(std::function<InputEvent(const InputEvent)> f, const char* name) : BufferedInputSource(name), m_func(f) {}
void handleEvent(const InputEvent& evt) override { void handleEvent(const InputEvent& evt) override {
setEvent(m_func(evt)); setEvent(m_func(evt));
@ -151,3 +151,28 @@ public:
private: private:
std::function<InputEvent(const InputEvent)> m_func; std::function<InputEvent(const InputEvent)> m_func;
}; };
class OnlineTaskMixin : public virtual Loopable {
public:
void handleEvent(const InputEvent &evt) override {
if (evt.intent == InputEvent::NetworkStatus) {
m_online = evt.asInt();
}
if (m_online) {
handleEventOnline(evt);
}
}
virtual void handleEventOnline(const InputEvent &evt) = 0;
void loop() override {
if (m_online) {
loopOnline();
}
}
virtual void loopOnline() = 0;
private:
bool m_online = false;
};

View File

@ -2,6 +2,8 @@
#include "./Input.h" #include "./Input.h"
#include "./Figment.h" #include "./Figment.h"
#include <ArduinoLog.h>
void void
MainLoop::dispatch(const InputEvent& evt) MainLoop::dispatch(const InputEvent& evt)
{ {
@ -19,10 +21,12 @@ MainLoop::loop()
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) { if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
const bool jobState = (evt.intent == InputEvent::StartThing); const bool jobState = (evt.intent == InputEvent::StartThing);
for(auto figmentJob: scheduler.tasks) { for(auto figmentJob: scheduler.tasks) {
if (strcmp(figmentJob->name, evt.asString()) == 0) { if (!strcmp(figmentJob->name, evt.asString())) {
if (jobState) { if (jobState) {
//Log.notice("Starting %s", figmentJob->name);
figmentJob->start(); figmentJob->start();
} else { } else {
//Log.notice("Stopping %s", figmentJob->name);
figmentJob->stop(); figmentJob->stop();
} }
} }
@ -34,18 +38,19 @@ MainLoop::loop()
} }
} }
for(Task* task : scheduler) { for(Task* task : scheduler) {
//Log.info("Running %s", task->name); //Log.notice("Running %s", task->name);
task->loop(); task->loop();
//Log.info("next"); //Log.notice("next");
} }
} }
void void
MainLoop::start() MainLoop::start()
{ {
Log.info("*** Starting %d tasks...", scheduler.tasks.size()); Log.notice("*** Starting %d tasks...", scheduler.tasks.size());
Serial.flush(); Serial.flush();
for(auto task: scheduler) { for(auto task: scheduler) {
Log.notice("** Starting %s", task->name);
task->start(); task->start();
} }
} }

View File

@ -1,21 +1,24 @@
#include "./Renderer.h" #include "./Renderer.h"
#include "./Display.h" #include "./Display.h"
#include <ArduinoLog.h>
void void
Renderer::loop() Renderer::loop()
{ {
for(Display* dpy : m_displays) { for(Display* dpy : m_displays) {
for(Figment* figment : m_figments) { for(Figment* figment : m_figments) {
if (figment->state == Task::Running) { if (figment->state == Task::Running) {
//Log.info("Rendering %s", figment->name); //Log.notice("Rendering %s", figment->name);
figment->render(dpy); figment->render(dpy);
//Log.info("next"); //Log.notice("next");
} else { } else {
//Log.info("Not rendering %s", figment->name); //Log.notice("Not rendering %s", figment->name);
} }
}; };
} }
NSFastLED::FastLED.show(); FastLED.show();
FastLED.countFPS();
} }
void void
@ -24,5 +27,5 @@ Renderer::onStart()
for(Display* dpy : m_displays) { for(Display* dpy : m_displays) {
dpy->clear(); dpy->clear();
} }
NSFastLED::FastLED.show(); FastLED.show();
} }

View File

@ -1,5 +1,6 @@
#include "./Surface.h" #include "./Surface.h"
#include "./Display.h" #include "./Display.h"
#include <ArduinoLog.h>
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end) Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)), : start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
@ -9,28 +10,30 @@ Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoo
} }
Surface& Surface&
Surface::operator=(const NSFastLED::CRGB& color) Surface::operator=(const CRGB& color)
{ {
paintWith([&](NSFastLED::CRGB& pixel) { paintWith([&](CRGB& pixel) {
pixel = color; pixel = color;
}); });
return *this; return *this;
} }
Surface& Surface&
Surface::operator+=(const NSFastLED::CRGB& color) Surface::operator+=(const CRGB& color)
{ {
paintWith([&](NSFastLED::CRGB& pixel) { paintWith([&](CRGB& pixel) {
pixel += color; pixel += color;
}); });
return *this; return *this;
} }
void void
Surface::paintWith(std::function<void(NSFastLED::CRGB&)> func) Surface::paintWith(std::function<void(CRGB&)> func)
{ {
for(uint8_t x = start.x; x <= end.x; x++) { //Log.verbose("Painting startx=%d endx=%d starty=%d endy=%d", start.x, end.x, start.y, end.y);
for(uint8_t y = start.y; y <= end.y; y++) { for(auto x = start.x; x <= end.x; x++) {
for(auto y = start.y; y <= end.y; y++) {
//Log.verbose("x=%d y=%d", x, y);
func(m_display->pixelAt(PhysicalCoordinates{x, y})); func(m_display->pixelAt(PhysicalCoordinates{x, y}));
} }
} }

View File

@ -1,5 +1,6 @@
#include "FastLED/FastLED.h" #include <FastLED.h>
#include "./Geometry.h" #include "./Geometry.h"
#include <functional>
class Display; class Display;
@ -7,10 +8,10 @@ class Surface {
public: public:
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end); Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end);
Surface& operator=(const NSFastLED::CRGB& color); Surface& operator=(const CRGB& color);
Surface& operator+=(const NSFastLED::CRGB& color); Surface& operator+=(const CRGB& color);
void paintWith(std::function<void(NSFastLED::CRGB&)> func); void paintWith(std::function<void(CRGB&)> func);
const PhysicalCoordinates start; const PhysicalCoordinates start;
const PhysicalCoordinates end; const PhysicalCoordinates end;

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

33
out.log Normal file
View File

@ -0,0 +1,33 @@
ets Jun 8 2016 00:22:57
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:10124
load:0x40080400,len:5828
entry 0x400806a8
N: 🐛 Booting Renderbug!
N: 🐞 I am built for 255 LEDs running on 2000mA
N: Boot pin configuration:
N: 2: Setup - 0
N: 3: Serial - 0
N: 4: Flash - 0
N: 💡 Starting FastLED...
N: 🌌 Starting Figment...
N: *** Starting 20 tasks...
N: * Starting Configuration...
N: * Starting MPU5060...
N: * Starting Buttons...
N: * Starting ...
N: * Starting SceneSequencer...
N: * Starting CircadianRhythm...
N: * Starting Pulse...
N: * Starting Solid...
N: * Starting Power...
N: * Starting lambda...
N: * Starting UpdateStatusAnimation...
N: * Starting Renderer...
N: 🚀 Setup complete! Ready to rock and roll.

24
platformio.ini Normal file
View File

@ -0,0 +1,24 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:featheresp32]
platform = espressif32
board = featheresp32
framework = arduino
lib_deps =
fastled/FastLED@^3.4.0
thijse/ArduinoLog@^1.0.3
knolleary/PubSubClient@^2.8.0
bblanchon/ArduinoJson@^6.17.3
ricaun/ArduinoUniqueID@^1.1.0
sstaub/NTP@^1.4.0
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> +<platform/arduino/>"
; upload_protocol = espota
; upload_port = 10.0.0.171

42
src/BootOptions.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "BootOptions.h"
#ifdef PLATFORM_PHOTON
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT);
retained bool LAST_BOOT_WAS_FLASH;
retained bool LAST_BOOT_WAS_SERIAL;
#endif
void
BootOptions::initPins()
{
#ifdef PLATFORM_PHOTON
pinMode(2, INPUT_PULLDOWN);
pinMode(3, INPUT_PULLDOWN);
pinMode(4, INPUT_PULLDOWN);
#endif
}
BootOptions::BootOptions()
{
#ifdef PLATFORM_PHOTON
isSetup = digitalRead(2) == HIGH;
isSerial = digitalRead(3) == HIGH || LAST_BOOT_WAS_SERIAL;
isFlash = digitalRead(4) == HIGH;
LAST_BOOT_WAS_FLASH = isFlash;
LAST_BOOT_WAS_SERIAL |= isSerial;
lastBootWasFlash = LAST_BOOT_WAS_FLASH;
configStatus.setActive(isSetup);
serialStatus.setActive(isSerial);
#endif
}
void
BootOptions::waitForRelease()
{
#ifdef PLATFORM_PHOTON
while(digitalRead(2) == HIGH || digitalRead(3) == HIGH) {};
#endif
}

14
src/BootOptions.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
struct BootOptions {
static void initPins();
BootOptions();
void waitForRelease();
bool isSetup = false;
bool isSerial = false;
bool isFlash = false;
bool lastBootWasFlash = false;
};

109
src/Config.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "./Config.h"
#include "./Static.h"
#include <ArduinoLog.h>
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
HardwareConfig
HardwareConfig::load() {
HardwareConfig ret;
#ifdef PLATFORM_PHOTON
EEPROM.get(0, ret);
#endif
return ret;
}
void
HardwareConfig::save() {
HardwareConfig dataCopy{*this};
dataCopy.checksum = getCRC();
#ifdef PLATFORM_PHOTON
EEPROM.put(0, dataCopy);
#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;
}
uint8_t
HardwareConfig::getCRC() const
{
const unsigned char* message = reinterpret_cast<const unsigned char*>(&data);
constexpr uint8_t length = sizeof(data);
unsigned char i, j, crc = 0;
for(i = 0; i < length; i++) {
crc ^= message[i];
for(j = 0; j < 8; j++) {
if (crc & 1) {
crc ^= CRC7_POLY;
}
crc >>= 1;
}
}
return crc;
}
void
ConfigService::onStart()
{
Log.notice("Starting configuration service...");
m_config = HardwareConfig::load();
if (m_config.isValid()) {
Log.notice("Configuration found!");
} else {
Log.notice("No configuration found. Writing defaults...");
m_config = HardwareConfig{};
m_config.save();
}
m_coordMap = m_config.toCoordMap();
Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel);
Log.notice("Loading task states...");
for(int i = 0; i < 32; i++) {
auto svc = m_config.data.serviceStates[i];
if (strlen(svc.name) > 0) {
Log.notice("* %s: %s", svc.name, svc.isDisabled? "DISABLED" : "ENABLED");
}
}
}
void
ConfigService::loop()
{
}
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::SaveConfigurationRequest:
//Log.info("Saving configuration");
m_config.save();
break;
}
}
STATIC_ALLOC(ConfigService);

View File

@ -1,6 +1,8 @@
#pragma once #pragma once
#include "Particle.h" #include <Figments.h>
#include "./Figments/Figments.h"
//#define PLATFORM_PHOTON
#define PLATFORM_ARDUINO
struct HardwareConfig { struct HardwareConfig {
uint8_t version = 2; uint8_t version = 2;
@ -27,7 +29,7 @@ struct HardwareConfig {
LinearCoordinateMapping toCoordMap() const; LinearCoordinateMapping toCoordMap() const;
static constexpr uint16_t MAX_LED_NUM = 255; static constexpr uint16_t MAX_LED_NUM = 255;
static constexpr bool HAS_MPU_6050 = true; static constexpr bool HAS_MPU_6050 = false;
private: private:
uint8_t getCRC() const; uint8_t getCRC() const;
@ -46,17 +48,6 @@ struct ConfigService: public Task {
const LinearCoordinateMapping* coordMap() const { return &m_coordMap; } const LinearCoordinateMapping* coordMap() const { return &m_coordMap; }
private: private:
void onConnected();
void publishConfig() const;
int photonSave(String command);
int setPixelCount(String command);
int setStartPixel(String command);
AnimatedNumber m_pixelCount;
AnimatedNumber m_startPixel;
int m_pixelCountInt;
int m_startPixelInt;
HardwareConfig m_config; HardwareConfig m_config;
LinearCoordinateMapping m_coordMap; LinearCoordinateMapping m_coordMap;
}; };

View File

@ -1,5 +1,5 @@
#include "Sequencer.h" #include "Sequencer.h"
#include "Figments/MainLoop.h" #include <MainLoop.h>
Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) : Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) :
Task("SceneSequencer"), Task("SceneSequencer"),
@ -26,8 +26,9 @@ void
Sequencer::handleEvent(const InputEvent& evt) Sequencer::handleEvent(const InputEvent& evt)
{ {
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.info("Switching pattern!"); Log.notice("Switching pattern!");
for(const char* pattern : m_scenes[m_idx].patterns) { for(const char* pattern : m_scenes[m_idx].patterns) {
Log.notice("Stopping %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern}); MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
} }
@ -53,6 +54,7 @@ Sequencer::handleEvent(const InputEvent& evt)
} }
for(const char* pattern : m_scenes[m_idx].patterns) { for(const char* pattern : m_scenes[m_idx].patterns) {
Log.notice("Starting %s", pattern);
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
} }
} }

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "Figments/Figment.h" #include <Figment.h>
#include <vector> #include <vector>
class Sequencer: public Task { class Sequencer: public Task {

49
src/WiFiTask.cpp Normal file
View File

@ -0,0 +1,49 @@
#include <Input.h>
#include <ArduinoLog.h>
#include <WiFi.h>
#include "Static.h"
#include "WiFiTask.h"
WiFiTask::WiFiTask() : InputSource("WiFi") {}
void
WiFiTask::onStart()
{
Log.notice("Starting wifi...");
WiFi.mode(WIFI_STA);
int n = WiFi.scanNetworks();
if (n == 0) {
Log.notice("No wifi found");
} else {
for(int i = 0; i < n; ++i) {
Serial.print("WiFi: ");
Serial.println(WiFi.SSID(i));
}
}
WiFi.mode(WIFI_STA);
WiFi.begin("The Frequency", "thepasswordkenneth");
while(WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
}
InputEvent
WiFiTask::read()
{
uint8_t curStatus = WiFi.status();
if (m_lastStatus != curStatus) {
m_lastStatus = curStatus;
Log.verbose("WiFi Status: %d", curStatus);
if (curStatus == WL_CONNECTED) {
Log.notice("Connected!");
return InputEvent{InputEvent::NetworkStatus, true};
} else if (curStatus == WL_CONNECTION_LOST || curStatus == WL_DISCONNECTED) {
Log.notice("Lost wifi connection!");
return InputEvent{InputEvent::NetworkStatus, false};
}
}
return InputEvent{};
}
STATIC_ALLOC(WiFiTask);

12
src/WiFiTask.h Normal file
View File

@ -0,0 +1,12 @@
#include <Input.h>
#include <WiFi.h>
#include "Static.h"
class WiFiTask : public InputSource {
public:
WiFiTask();
void onStart() override;
InputEvent read() override;
private:
uint8_t m_lastStatus = WL_IDLE_STATUS;
};

View File

@ -2,8 +2,6 @@
#include "../sprites/Chime.h" #include "../sprites/Chime.h"
#include "../sprites/Blob.h" #include "../sprites/Blob.h"
using namespace NSFastLED;
#define CHIME_LENGTH 23 #define CHIME_LENGTH 23
#define CHIME_COUNT 4 #define CHIME_COUNT 4
#define BLOB_COUNT 10 #define BLOB_COUNT 10
@ -63,9 +61,9 @@ public:
m_chimes.render(dpy); m_chimes.render(dpy);
m_blobs.render(dpy); m_blobs.render(dpy);
Surface fullSurface(dpy, {0, 0}, {255, 0}); Surface fullSurface(dpy, {0, 0}, {255, 0});
NSFastLED::CRGB scaledColor = NSFastLED::CRGB(m_flashColor).nscale8_video(max(10, NSFastLED::ease8InOutCubic(m_flashBrightness))); CRGB scaledColor = CRGB(m_flashColor).nscale8_video(std::max((uint8_t)10, ease8InOutCubic(m_flashBrightness)));
fullSurface.paintWith([&](NSFastLED::CRGB& pixel) { fullSurface.paintWith([&](CRGB& pixel) {
pixel = NSFastLED::blend(scaledColor, pixel, 200); pixel = blend(scaledColor, pixel, 200);
//pixel = scaledColor; //pixel = scaledColor;
}); });
} }

View File

@ -1,6 +1,6 @@
#include "../Figments/Figments.h" #include <Figments.h>
#include <ArduinoLog.h>
using namespace NSFastLED;
class DrainAnimation: public Figment { class DrainAnimation: public Figment {
public: public:
@ -62,5 +62,3 @@ public:
uint16_t m_pos; uint16_t m_pos;
uint16_t m_burst; uint16_t m_burst;
}; };

View File

@ -1,10 +1,8 @@
#pragma once #pragma once
#include "../Figments/Figments.h" #include <Figments.h>
#include "../sprites/Blob.h" #include "../sprites/Blob.h"
using NSFastLED::CHSV;
class Flashlight: public Figment { class Flashlight: public Figment {
public: public:
Flashlight(Task::State initialState) : Figment("Flashlight", initialState) { Flashlight(Task::State initialState) : Figment("Flashlight", initialState) {

Some files were not shown because too many files have changed in this diff Show More