update
This commit is contained in:
parent
3b32d72d5e
commit
cadfd40b61
@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
cmd: po
|
|
||||||
args:
|
|
||||||
- photon
|
|
||||||
- build
|
|
||||||
sh: false
|
|
||||||
|
|
||||||
targets:
|
|
||||||
Build:
|
|
||||||
atomCommandName: po:Build Particle Firmware Locally
|
|
||||||
sh: false
|
|
||||||
args:
|
|
||||||
- photon
|
|
||||||
- build
|
|
||||||
cmd: po
|
|
||||||
keymap: ctrl-alt-1
|
|
||||||
name: Build
|
|
||||||
Flash:
|
|
||||||
atomCommandName: po:Flash Particle Firmware Locally
|
|
||||||
sh: false
|
|
||||||
args:
|
|
||||||
- photon
|
|
||||||
- flash
|
|
||||||
cmd: po
|
|
||||||
keymap: ctrl-alt-2
|
|
||||||
name: Flash
|
|
||||||
Clean:
|
|
||||||
atomCommandName: po:Clean Particle Firmware Locally
|
|
||||||
sh: false
|
|
||||||
args:
|
|
||||||
- photon
|
|
||||||
- clean
|
|
||||||
cmd: po
|
|
||||||
keymap: ctrl-alt-3
|
|
||||||
name: Clean
|
|
||||||
DFU:
|
|
||||||
atomCommandName: po:Upload Particle Firmware Locally with DFU
|
|
||||||
sh: false
|
|
||||||
args:
|
|
||||||
- photon
|
|
||||||
- dfu
|
|
||||||
cmd: po
|
|
||||||
keymap: ctrl-alt-4
|
|
||||||
name: DFU
|
|
||||||
OTA:
|
|
||||||
atomCommandName: po:Upload Particle Firmware Locally with OTA
|
|
||||||
sh: false
|
|
||||||
args:
|
|
||||||
- photon
|
|
||||||
- ota
|
|
||||||
- --multi
|
|
||||||
cmd: po
|
|
||||||
keymap: ctrl-alt-5
|
|
||||||
name: OTA
|
|
10
.travis.yml
10
.travis.yml
@ -1,10 +0,0 @@
|
|||||||
dist: trusty
|
|
||||||
sudo: required
|
|
||||||
language: generic
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ci/travis.sh
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.po-util
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
bash <(curl -sL https://raw.githubusercontent.com/nrobinson2000/po/master/ci/ci-install)
|
|
||||||
po lib clean . -f &> /dev/null
|
|
||||||
yes "no" | po lib setup # change to "yes" to prefer libraries from GitHub
|
|
||||||
po photon build
|
|
@ -15,10 +15,18 @@ HardwareConfig::save() {
|
|||||||
EEPROM.put(0, dataCopy);
|
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
|
bool
|
||||||
HardwareConfig::isValid() const
|
HardwareConfig::isValid() const
|
||||||
{
|
{
|
||||||
return version == 1 && checksum == getCRC();
|
return version == 1 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t
|
uint8_t
|
||||||
@ -42,7 +50,7 @@ HardwareConfig::getCRC() const
|
|||||||
void
|
void
|
||||||
ConfigService::onStart()
|
ConfigService::onStart()
|
||||||
{
|
{
|
||||||
Log.info("Starting configuration...");
|
Log.info("Starting configuration service...");
|
||||||
m_config = HardwareConfig::load();
|
m_config = HardwareConfig::load();
|
||||||
if (m_config.isValid()) {
|
if (m_config.isValid()) {
|
||||||
Log.info("Configuration found!");
|
Log.info("Configuration found!");
|
||||||
@ -51,8 +59,12 @@ ConfigService::onStart()
|
|||||||
m_config = HardwareConfig{};
|
m_config = HardwareConfig{};
|
||||||
m_config.save();
|
m_config.save();
|
||||||
}
|
}
|
||||||
m_pixelCount = AnimatedNumber{m_config.data.pixelCount};
|
m_coordMap = m_config.toCoordMap();
|
||||||
m_startPixel = AnimatedNumber{m_config.data.startPixel};
|
//m_coordMap.pixelCount = min(HardwareConfig::MAX_LED_NUM, max(1, m_config.data.pixelCount));
|
||||||
|
//m_coordMap.startPixel = min(m_coordMap.pixelCount, max(0, m_config.data.startPixel));
|
||||||
|
|
||||||
|
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("Configured to use %d pixels, starting at %d", m_pixelCount.value(), m_startPixel.value());
|
||||||
Log.info("Loading task states...");
|
Log.info("Loading task states...");
|
||||||
for(int i = 0; i < 32; i++) {
|
for(int i = 0; i < 32; i++) {
|
||||||
@ -80,17 +92,41 @@ ConfigService::onConnected()
|
|||||||
void
|
void
|
||||||
ConfigService::loop()
|
ConfigService::loop()
|
||||||
{
|
{
|
||||||
m_startPixel.update();
|
//m_startPixel.update();
|
||||||
m_pixelCount.update();
|
//m_pixelCount.update();
|
||||||
m_coordMap.startPixel = m_startPixel;
|
//m_coordMap.pixelCount = max(1, m_pixelCount.value());
|
||||||
m_coordMap.pixelCount = m_pixelCount;
|
//m_coordMap.startPixel = max(0, m_startPixel.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ConfigService::handleEvent(const InputEvent &evt)
|
ConfigService::handleEvent(const InputEvent &evt)
|
||||||
{
|
{
|
||||||
if (evt.intent == InputEvent::NetworkStatus) {
|
switch(evt.intent) {
|
||||||
|
case InputEvent::NetworkStatus:
|
||||||
onConnected();
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,11 @@ struct HardwareConfig {
|
|||||||
void save();
|
void save();
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
|
|
||||||
|
LinearCoordinateMapping toCoordMap() const;
|
||||||
|
|
||||||
|
static constexpr uint16_t MAX_LED_NUM = 255;
|
||||||
|
static constexpr bool HAS_MPU_6050 = true;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t getCRC() const;
|
uint8_t getCRC() const;
|
||||||
|
|
||||||
|
@ -6,9 +6,12 @@ class Display;
|
|||||||
class InputEvent;
|
class InputEvent;
|
||||||
class InputSource;
|
class InputSource;
|
||||||
|
|
||||||
struct Task {
|
struct Loopable {
|
||||||
virtual void handleEvent(const InputEvent& event) {}
|
virtual void handleEvent(const InputEvent& event) {}
|
||||||
virtual void loop() = 0;
|
virtual void loop() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Task : public virtual Loopable {
|
||||||
virtual void onStart() {};
|
virtual void onStart() {};
|
||||||
virtual void onStop() {};
|
virtual void onStop() {};
|
||||||
|
|
||||||
@ -24,6 +27,7 @@ struct Task {
|
|||||||
|
|
||||||
void start() { Log.info("* Starting %s...", name); state = Running; onStart(); }
|
void start() { Log.info("* Starting %s...", name); state = Running; onStart(); }
|
||||||
void stop() { Log.info("* Stopping %s...", name); onStop(); state = Stopped; }
|
void stop() { Log.info("* Stopping %s...", name); onStop(); state = Stopped; }
|
||||||
|
virtual bool isFigment() const { return false; }
|
||||||
|
|
||||||
const char* name = 0;
|
const char* name = 0;
|
||||||
State state = Running;
|
State state = Running;
|
||||||
@ -35,6 +39,7 @@ struct Figment: public Task {
|
|||||||
Figment(const char* name) : Task(name) {}
|
Figment(const char* name) : Task(name) {}
|
||||||
Figment(const char* name, State initialState) : Task(name, initialState) {}
|
Figment(const char* name, State initialState) : Task(name, initialState) {}
|
||||||
virtual void render(Display* dpy) const = 0;
|
virtual void render(Display* dpy) const = 0;
|
||||||
|
bool isFigment() const override { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FigmentFunc: public Figment {
|
struct FigmentFunc: public Figment {
|
||||||
|
@ -39,3 +39,9 @@ BufferedInputSource::setEvent(InputEvent &&evt)
|
|||||||
{
|
{
|
||||||
m_lastEvent = std::move(evt);
|
m_lastEvent = std::move(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BufferedInputSource::setEvent(InputEvent::Intent intent, Variant &&v)
|
||||||
|
{
|
||||||
|
m_lastEvent = InputEvent{intent, std::move(v)};
|
||||||
|
}
|
||||||
|
@ -42,20 +42,50 @@ private:
|
|||||||
|
|
||||||
struct InputEvent: public Variant {
|
struct InputEvent: public Variant {
|
||||||
enum Intent {
|
enum Intent {
|
||||||
|
// An empty non-event
|
||||||
None,
|
None,
|
||||||
|
|
||||||
|
// An input from the user, for other tasks to translate into canonical
|
||||||
|
// types. Makes for easy button re-mapping on the fly.
|
||||||
|
UserInput,
|
||||||
|
|
||||||
|
//
|
||||||
|
// The canonical types
|
||||||
|
//
|
||||||
|
// Hardware inputs
|
||||||
|
ButtonPress,
|
||||||
|
Acceleration,
|
||||||
|
NetworkStatus,
|
||||||
|
NetworkActivity,
|
||||||
|
|
||||||
|
// Power management
|
||||||
PowerToggle,
|
PowerToggle,
|
||||||
SetPower,
|
SetPower,
|
||||||
SetBrightness,
|
SetBrightness,
|
||||||
|
|
||||||
|
// Animation sequencing
|
||||||
PreviousPattern,
|
PreviousPattern,
|
||||||
NextPattern,
|
NextPattern,
|
||||||
SetPattern,
|
SetPattern,
|
||||||
SetColor,
|
PreviousScene,
|
||||||
Acceleration,
|
NextScene,
|
||||||
FirmwareUpdate,
|
SetScene,
|
||||||
NetworkStatus,
|
|
||||||
|
// Timekeeping
|
||||||
|
ScheduleChange,
|
||||||
|
|
||||||
|
// Task management
|
||||||
StartThing,
|
StartThing,
|
||||||
StopThing,
|
StopThing,
|
||||||
UserInput,
|
|
||||||
|
// Configuration
|
||||||
|
SetDisplayOffset,
|
||||||
|
SetDisplayLength,
|
||||||
|
SetColor,
|
||||||
|
SaveConfigurationRequest,
|
||||||
|
|
||||||
|
// Firmware events
|
||||||
|
FirmwareUpdate,
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Value>
|
template<typename Value>
|
||||||
@ -103,7 +133,20 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setEvent(InputEvent &&evt);
|
void setEvent(InputEvent &&evt);
|
||||||
|
void setEvent(InputEvent::Intent intent, Variant &&v);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
InputEvent m_lastEvent;
|
InputEvent m_lastEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class InputMapper: public BufferedInputSource {
|
||||||
|
public:
|
||||||
|
InputMapper(std::function<InputEvent(const InputEvent)> f) : BufferedInputSource(), m_func(f) {}
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent& evt) override {
|
||||||
|
setEvent(m_func(evt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<InputEvent(const InputEvent)> m_func;
|
||||||
|
};
|
||||||
|
@ -34,7 +34,9 @@ MainLoop::loop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(Task* task : scheduler) {
|
for(Task* task : scheduler) {
|
||||||
|
//Log.info("Running %s", task->name);
|
||||||
task->loop();
|
task->loop();
|
||||||
|
//Log.info("next");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,6 @@ private:
|
|||||||
std::array<T, Size> m_items;
|
std::array<T, Size> m_items;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct MainLoop {
|
struct MainLoop {
|
||||||
Scheduler scheduler;
|
Scheduler scheduler;
|
||||||
|
|
||||||
|
@ -7,7 +7,11 @@ 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);
|
||||||
figment->render(dpy);
|
figment->render(dpy);
|
||||||
|
//Log.info("next");
|
||||||
|
} else {
|
||||||
|
//Log.info("Not rendering %s", figment->name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
1
firmware/MDNS/Buffer.cpp
Symbolic link
1
firmware/MDNS/Buffer.cpp
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/Buffer.cpp
|
1
firmware/MDNS/Buffer.h
Symbolic link
1
firmware/MDNS/Buffer.h
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/Buffer.h
|
1
firmware/MDNS/Label.cpp
Symbolic link
1
firmware/MDNS/Label.cpp
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/Label.cpp
|
1
firmware/MDNS/Label.h
Symbolic link
1
firmware/MDNS/Label.h
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/Label.h
|
1
firmware/MDNS/MDNS
Symbolic link
1
firmware/MDNS/MDNS
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/MDNS
|
1
firmware/MDNS/MDNS.cpp
Symbolic link
1
firmware/MDNS/MDNS.cpp
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/MDNS.cpp
|
1
firmware/MDNS/MDNS.h
Symbolic link
1
firmware/MDNS/MDNS.h
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/MDNS.h
|
1
firmware/MDNS/Record.cpp
Symbolic link
1
firmware/MDNS/Record.cpp
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/Record.cpp
|
1
firmware/MDNS/Record.h
Symbolic link
1
firmware/MDNS/Record.h
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MDNS/src/Record.h
|
33
firmware/MDNSService.cpp
Normal file
33
firmware/MDNSService.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "MDNS/MDNS.h"
|
||||||
|
#include "Figments/Figments.h"
|
||||||
|
#include "Figments/MainLoop.h"
|
||||||
|
#include "Figments/Input.h"
|
||||||
|
#include "colors.h"
|
||||||
|
|
||||||
|
class MDNSService : public Task {
|
||||||
|
private:
|
||||||
|
mdns::MDNS m_mdns;
|
||||||
|
bool m_online = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MDNSService() : Task("MDNS") {
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent &evt) {
|
||||||
|
if (evt.intent == InputEvent::NetworkStatus) {
|
||||||
|
m_mdns.setHostname("renderbug");
|
||||||
|
m_mdns.addService("tcp", "http", 80, "Renderbug");
|
||||||
|
m_mdns.begin(true);
|
||||||
|
m_online = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
if (m_online) {
|
||||||
|
// Returns true when it reads at least one byte
|
||||||
|
if (m_mdns.processQueries()) {
|
||||||
|
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
1
firmware/MQTT/MQTT
Symbolic link
1
firmware/MQTT/MQTT
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MQTT/src/MQTT
|
1
firmware/MQTT/MQTT.cpp
Symbolic link
1
firmware/MQTT/MQTT.cpp
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MQTT/src/MQTT.cpp
|
1
firmware/MQTT/MQTT.h
Symbolic link
1
firmware/MQTT/MQTT.h
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/MQTT/src/MQTT.h
|
54
firmware/Sequencer.cpp
Normal file
54
firmware/Sequencer.cpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#include "Sequencer.h"
|
||||||
|
#include "Figments/MainLoop.h"
|
||||||
|
|
||||||
|
Sequencer::Sequencer(std::vector<Sequencer::Scene> &&scenes) :
|
||||||
|
Task("SceneSequencer"),
|
||||||
|
m_scenes(std::move(scenes))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Sequencer::loop() {}
|
||||||
|
|
||||||
|
const char*
|
||||||
|
Sequencer::currentSceneName()
|
||||||
|
{
|
||||||
|
return m_scenes[m_idx].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Sequencer::Scene>
|
||||||
|
Sequencer::scenes() const
|
||||||
|
{
|
||||||
|
return m_scenes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Sequencer::handleEvent(const InputEvent& evt)
|
||||||
|
{
|
||||||
|
if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) {
|
||||||
|
Log.info("Switching pattern!");
|
||||||
|
for(const char* pattern : m_scenes[m_idx].patterns) {
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evt.intent == InputEvent::NextPattern) {
|
||||||
|
m_idx++;
|
||||||
|
} else if (evt.intent == InputEvent::PreviousPattern) {
|
||||||
|
m_idx--;
|
||||||
|
} else {
|
||||||
|
m_idx = evt.asInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_idx < 0) {
|
||||||
|
m_idx = m_scenes.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_idx >= m_scenes.size()) {
|
||||||
|
m_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const char* pattern : m_scenes[m_idx].patterns) {
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
firmware/Sequencer.h
Normal file
26
firmware/Sequencer.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Figments/Figment.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Sequencer: public Task {
|
||||||
|
public:
|
||||||
|
|
||||||
|
class Scene {
|
||||||
|
public:
|
||||||
|
const char* name;
|
||||||
|
std::vector<const char*> patterns;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sequencer(std::vector<Scene> &&scenes);
|
||||||
|
|
||||||
|
void loop() override;
|
||||||
|
void handleEvent(const InputEvent& evt) override;
|
||||||
|
|
||||||
|
const char* currentSceneName();
|
||||||
|
const std::vector<Scene> scenes() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_idx = 0;
|
||||||
|
std::vector<Scene> m_scenes;
|
||||||
|
};
|
1
firmware/WebDuino/WebDuino
Symbolic link
1
firmware/WebDuino/WebDuino
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino
|
1
firmware/WebDuino/WebDuino.cpp
Symbolic link
1
firmware/WebDuino/WebDuino.cpp
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino.cpp
|
1
firmware/WebDuino/WebDuino.h
Symbolic link
1
firmware/WebDuino/WebDuino.h
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino.h
|
157
firmware/WebTelemetry.cpp
Normal file
157
firmware/WebTelemetry.cpp
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
#include "WebDuino/WebDuino.h"
|
||||||
|
#include "Figments/Figments.h"
|
||||||
|
#include "Figments/Input.h"
|
||||||
|
#include "colors.h"
|
||||||
|
#include "Sequencer.h"
|
||||||
|
|
||||||
|
class WebTelemetry : public Task {
|
||||||
|
private:
|
||||||
|
TCPServer m_server;
|
||||||
|
TCPClient m_client;
|
||||||
|
Sequencer& m_sequencer;
|
||||||
|
|
||||||
|
void onConnected() {
|
||||||
|
m_server.begin();
|
||||||
|
Log.info("HTTP server started on %s:80", WiFi.localIP().toString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void redirectToRoot() {
|
||||||
|
m_server.write("HTTP/1.1 303 Redirect\n");
|
||||||
|
m_server.write("Location: /\n");
|
||||||
|
m_server.write("Connection: close\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
WebTelemetry(Sequencer& sequencer) : Task("WebTelemetry"), m_server(80), m_sequencer(sequencer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent &evt) {
|
||||||
|
if (evt.intent == InputEvent::NetworkStatus) {
|
||||||
|
onConnected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
static String taskName;
|
||||||
|
if (m_client.connected()) {
|
||||||
|
if (m_client.available()) {
|
||||||
|
MainLoop::instance()->dispatch(InputEvent::NetworkActivity);
|
||||||
|
String requestLine = m_client.readStringUntil('\n');
|
||||||
|
Log.info("%s %s", m_client.remoteIP().toString().c_str(), requestLine.c_str());
|
||||||
|
if (requestLine.startsWith("GET")) {
|
||||||
|
int httpVersionIdx = requestLine.lastIndexOf(" ");
|
||||||
|
String uri = requestLine.substring(4, httpVersionIdx);
|
||||||
|
if (uri.equals("/")) {
|
||||||
|
m_server.write("HTTP/1.1 200 Renderbug is here!\n");
|
||||||
|
m_server.write("Connection: close\n\n");
|
||||||
|
m_server.write("<!DOCTYPE HTML><html><body>");
|
||||||
|
m_server.write("<p>Scenes</p><table>");
|
||||||
|
auto curScene = m_sequencer.currentSceneName();
|
||||||
|
for(auto scene : m_sequencer.scenes()) {
|
||||||
|
bool isEnabled = strcmp(curScene, scene.name) == 0;
|
||||||
|
m_server.write("<tr><td>");
|
||||||
|
if (isEnabled) {
|
||||||
|
m_server.write("<strong>");
|
||||||
|
}
|
||||||
|
m_server.write(scene.name);
|
||||||
|
if (isEnabled) {
|
||||||
|
m_server.write("</strong>");
|
||||||
|
}
|
||||||
|
m_server.write("</td><td><ul>");
|
||||||
|
for(auto patternName : scene.patterns) {
|
||||||
|
m_server.write("<li>");
|
||||||
|
m_server.write(patternName);
|
||||||
|
m_server.write("</li>");
|
||||||
|
}
|
||||||
|
m_server.write("</ul></td></tr>");
|
||||||
|
}
|
||||||
|
m_server.write("</table>");
|
||||||
|
m_server.write("<form><button name=\"pattern\" value=\"prev\">Previous pattern</button><button name=\"pattern\" value=\"next\">Next pattern</button></form>");
|
||||||
|
m_server.write("<form><select name=\"color\">");
|
||||||
|
const ColorInfo* colors = allColors();
|
||||||
|
for(int i = 0; colors[i].name != 0;i++) {
|
||||||
|
m_server.write("<option>");
|
||||||
|
m_server.write(colors[i].name);
|
||||||
|
m_server.write("</option>");
|
||||||
|
}
|
||||||
|
m_server.write("</select>");
|
||||||
|
m_server.write("<button>Set Color</button>");
|
||||||
|
m_server.write("</form>");
|
||||||
|
m_server.write("<p>Tasks</p><table>");
|
||||||
|
auto sched = MainLoop::instance()->scheduler;
|
||||||
|
for(auto task : sched.tasks) {
|
||||||
|
bool isFigment = task->isFigment();
|
||||||
|
|
||||||
|
m_server.write("<tr><td>");
|
||||||
|
if (isFigment) {
|
||||||
|
m_server.write("<strong>");
|
||||||
|
}
|
||||||
|
m_server.write(task->name);
|
||||||
|
if (isFigment) {
|
||||||
|
m_server.write("</strong>");
|
||||||
|
}
|
||||||
|
m_server.write("</td><td>");
|
||||||
|
if (task->state == Task::Running) {
|
||||||
|
m_server.write("Running");
|
||||||
|
} else {
|
||||||
|
m_server.write("Paused");
|
||||||
|
}
|
||||||
|
m_server.write("</td><td>");
|
||||||
|
m_server.write("<a href=\"/?stop=");
|
||||||
|
m_server.write(task->name);
|
||||||
|
m_server.write("\">Stop</a></td><td>");
|
||||||
|
m_server.write("<a href=\"/?start=");
|
||||||
|
m_server.write(task->name);
|
||||||
|
m_server.write("\">Start</a></td></tr>");
|
||||||
|
}
|
||||||
|
m_server.write("</table>");
|
||||||
|
m_server.write("<a href='/reboot'>Reboot Renderbug</a>");
|
||||||
|
m_server.write("<a href='/save'>Save configuration</a>");
|
||||||
|
} else if (uri.startsWith("/save")) {
|
||||||
|
MainLoop::instance()->dispatch(InputEvent::SaveConfigurationRequest);
|
||||||
|
redirectToRoot();
|
||||||
|
} else if (uri.startsWith("/?color=")) {
|
||||||
|
int varStart = uri.indexOf("=");
|
||||||
|
String colorName = uri.substring(varStart + 1);
|
||||||
|
colorName.replace('+', ' ');
|
||||||
|
colorName.replace("%20", " ");
|
||||||
|
ColorInfo nextColor = colorForName(colorName);
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetColor, nextColor.rgb});
|
||||||
|
redirectToRoot();
|
||||||
|
} else if (uri.startsWith("/?start=")) {
|
||||||
|
int varStart = uri.indexOf("=");
|
||||||
|
taskName = uri.substring(varStart + 1);
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, taskName.c_str()});
|
||||||
|
redirectToRoot();
|
||||||
|
} else if (uri.startsWith("/?stop=")) {
|
||||||
|
int varStart = uri.indexOf("=");
|
||||||
|
taskName = uri.substring(varStart + 1);
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, taskName.c_str()});
|
||||||
|
redirectToRoot();
|
||||||
|
} else if (uri.equals("/?pattern=prev")) {
|
||||||
|
redirectToRoot();
|
||||||
|
MainLoop::instance()->dispatch(InputEvent::PreviousPattern);
|
||||||
|
} else if (uri.equals("/?pattern=next")) {
|
||||||
|
redirectToRoot();
|
||||||
|
MainLoop::instance()->dispatch(InputEvent::NextPattern);
|
||||||
|
} else if (uri.equals("/reboot")) {
|
||||||
|
m_server.write("HTTP/1.1 200 Ok\n");
|
||||||
|
m_server.write("Connection: close\n\n");
|
||||||
|
m_server.write("Rebooting!");
|
||||||
|
} else {
|
||||||
|
m_server.write("HTTP/1.1 404 Not found\n");
|
||||||
|
m_server.write("Connection: close\n\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_server.write("HTTP/1.1 501 Not Implemented\n");
|
||||||
|
m_server.write("Connection: close\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_client.stop();
|
||||||
|
} else {
|
||||||
|
m_client = m_server.available();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
66
firmware/animations/Drain.cpp
Normal file
66
firmware/animations/Drain.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include "../Figments/Figments.h"
|
||||||
|
|
||||||
|
using namespace NSFastLED;
|
||||||
|
class DrainAnimation: public Figment {
|
||||||
|
public:
|
||||||
|
|
||||||
|
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
EVERY_N_MILLISECONDS(8) {
|
||||||
|
m_pos++;
|
||||||
|
m_fillColor.update();
|
||||||
|
}
|
||||||
|
EVERY_N_MILLISECONDS(50) {
|
||||||
|
if (random(255) >= 10) {
|
||||||
|
m_burst -= m_burst / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent& event) override {
|
||||||
|
if (event.intent == InputEvent::SetColor) {
|
||||||
|
m_fillColor = event.asRGB();
|
||||||
|
} else if (event.intent == InputEvent::Acceleration) {
|
||||||
|
m_pos += log10(event.asInt());
|
||||||
|
uint16_t burstInc = event.asInt() / 6;
|
||||||
|
m_burst = (m_burst > 0xFFFF - burstInc) ? 0xFFFF : m_burst + burstInc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedRGB m_fillColor;
|
||||||
|
|
||||||
|
void render(Display* dpy) const override {
|
||||||
|
dpy->clear();
|
||||||
|
Surface leftPanel{dpy, {0, 0}, {128, 0}};
|
||||||
|
Surface rightPanel{dpy, {128, 0}, {255, 0}};
|
||||||
|
fillRange(dpy, leftPanel.start, leftPanel.end, rgb2hsv_approximate(m_fillColor));
|
||||||
|
fillRange(dpy, rightPanel.end, rightPanel.start, rgb2hsv_approximate(m_fillColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const {
|
||||||
|
int length = end.x - start.x;
|
||||||
|
int direction = 1;
|
||||||
|
if (length < 0) {
|
||||||
|
direction = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t frac = 255 / std::abs(length);
|
||||||
|
for(int i = 0; i < std::abs(length); i++) {
|
||||||
|
auto coords = PhysicalCoordinates((start.x + (i * direction)), 0);
|
||||||
|
|
||||||
|
const uint8_t localScale = inoise8(i * 80, m_pos * 3);
|
||||||
|
const uint8_t dimPosition = lerp8by8(50, 190, scale8(sin8((frac * i) / 2), localScale));
|
||||||
|
const uint8_t withBurst = ease8InOutCubic(lerp16by16(dimPosition, 255, m_burst));
|
||||||
|
auto scaledColor = CHSV(baseColor.hue, lerp8by8(100, 255, localScale), withBurst);
|
||||||
|
|
||||||
|
CRGB src(dpy->pixelAt(coords));
|
||||||
|
dpy->pixelAt(coords) = blend(scaledColor, src, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t m_pos;
|
||||||
|
uint16_t m_burst;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
63
firmware/animations/Drain.h
Normal file
63
firmware/animations/Drain.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
class DrainAnimation: public Figment {
|
||||||
|
public:
|
||||||
|
|
||||||
|
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
EVERY_N_MILLISECONDS(8) {
|
||||||
|
m_pos++;
|
||||||
|
m_fillColor.update();
|
||||||
|
}
|
||||||
|
EVERY_N_MILLISECONDS(50) {
|
||||||
|
if (random(255) >= 10) {
|
||||||
|
m_burst -= m_burst / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent& event) override {
|
||||||
|
if (event.intent == InputEvent::SetColor) {
|
||||||
|
m_fillColor = event.asRGB();
|
||||||
|
} else if (event.intent == InputEvent::Acceleration) {
|
||||||
|
m_pos += log10(event.asInt());
|
||||||
|
uint16_t burstInc = event.asInt() / 6;
|
||||||
|
m_burst = (m_burst > 0xFFFF - burstInc) ? 0xFFFF : m_burst + burstInc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedRGB m_fillColor;
|
||||||
|
|
||||||
|
void render(Display* dpy) const override {
|
||||||
|
dpy->clear();
|
||||||
|
Surface leftPanel{dpy, {0, 0}, {128, 0}};
|
||||||
|
Surface rightPanel{dpy, {128, 0}, {255, 0}};
|
||||||
|
fillRange(dpy, leftPanel.start, leftPanel.end, rgb2hsv_approximate(m_fillColor));
|
||||||
|
fillRange(dpy, rightPanel.end, rightPanel.start, rgb2hsv_approximate(m_fillColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
void fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const {
|
||||||
|
int length = end.x - start.x;
|
||||||
|
int direction = 1;
|
||||||
|
if (length < 0) {
|
||||||
|
direction = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t frac = 255 / std::abs(length);
|
||||||
|
for(int i = 0; i < std::abs(length); i++) {
|
||||||
|
auto coords = PhysicalCoordinates((start.x + (i * direction)), 0);
|
||||||
|
|
||||||
|
const uint8_t localScale = inoise8(i * 80, m_pos * 3);
|
||||||
|
const uint8_t dimPosition = lerp8by8(50, 190, scale8(sin8((frac * i) / 2), localScale));
|
||||||
|
const uint8_t withBurst = ease8InOutCubic(lerp16by16(dimPosition, 255, m_burst));
|
||||||
|
auto scaledColor = CHSV(baseColor.hue, lerp8by8(100, 255, localScale), withBurst);
|
||||||
|
|
||||||
|
CRGB src(dpy->pixelAt(coords));
|
||||||
|
dpy->pixelAt(coords) = blend(scaledColor, src, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t m_pos;
|
||||||
|
uint16_t m_burst;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -807,3 +807,7 @@ ColorInfo colorForName(const char *name) {
|
|||||||
}
|
}
|
||||||
return ColorInfo();
|
return ColorInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ColorInfo* allColors() {
|
||||||
|
return color_data;
|
||||||
|
}
|
||||||
|
@ -7,3 +7,4 @@ typedef struct ColorInfo {
|
|||||||
} ColorInfo;
|
} ColorInfo;
|
||||||
|
|
||||||
ColorInfo colorForName(const char *name);
|
ColorInfo colorForName(const char *name);
|
||||||
|
const ColorInfo* allColors();
|
||||||
|
Binary file not shown.
@ -5,6 +5,7 @@ void
|
|||||||
Buttons::onStart()
|
Buttons::onStart()
|
||||||
{
|
{
|
||||||
for(int i = 0; i < 3; i++) {
|
for(int i = 0; i < 3; i++) {
|
||||||
|
Log.info("Bound pin %d to button %d", 2 + i, i);
|
||||||
m_buttons[i].attach(2 + i, INPUT_PULLDOWN);
|
m_buttons[i].attach(2 + i, INPUT_PULLDOWN);
|
||||||
m_buttons[i].interval(15);
|
m_buttons[i].interval(15);
|
||||||
}
|
}
|
||||||
@ -15,8 +16,22 @@ Buttons::read()
|
|||||||
{
|
{
|
||||||
for(int i = 0; i < 3; i++) {
|
for(int i = 0; i < 3; i++) {
|
||||||
m_buttons[i].update();
|
m_buttons[i].update();
|
||||||
if (m_buttons[i].fell()) {
|
if (m_buttons[i].rose()) {
|
||||||
return InputEvent{m_buttonMap[i]};
|
//Log.info("Read press on %d", i);
|
||||||
|
int buttonID = m_buttonMap[i];
|
||||||
|
for(int j = 0; j < 3; j++ ) {
|
||||||
|
if (j != i && m_buttons[j].held()) {
|
||||||
|
buttonID |= m_buttonMap[j];
|
||||||
|
Log.info("Chord with %d", j);
|
||||||
|
m_wasChord[j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_wasChord[i]) {
|
||||||
|
//Log.info("Not emitting release from previous chord");
|
||||||
|
m_wasChord[i] = false;
|
||||||
|
} else {
|
||||||
|
return InputEvent{InputEvent::UserInput, buttonID};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return InputEvent{};
|
return InputEvent{};
|
||||||
|
@ -15,24 +15,27 @@ public:
|
|||||||
if (readResult == HIGH) {
|
if (readResult == HIGH) {
|
||||||
m_state = Started;
|
m_state = Started;
|
||||||
m_downStart = millis();
|
m_downStart = millis();
|
||||||
Log.info("Button %d is started!", m_pin);
|
//Log.info("Button %d is started!", m_pin);
|
||||||
}
|
}
|
||||||
} else if (m_state == Started && millis() - m_downStart >= m_interval) {
|
} else if (m_state == Started && millis() - m_downStart >= m_interval) {
|
||||||
if (readResult == HIGH) {
|
if (readResult == HIGH) {
|
||||||
m_state = Confirmed;
|
m_state = Confirmed;
|
||||||
Log.info("Button %d is CONFIRMED!", m_pin);
|
//Log.info("Button %d is CONFIRMED!", m_pin);
|
||||||
} else {
|
} else {
|
||||||
m_state = Ready;
|
m_state = Ready;
|
||||||
Log.info("Button %d bounced back to ready!", m_pin);
|
//Log.info("Button %d bounced back to ready!", m_pin);
|
||||||
}
|
}
|
||||||
} else if (m_state == Confirmed || m_state == Held) {
|
} else if (m_state == Confirmed || m_state == Held) {
|
||||||
if (readResult == LOW) {
|
if (readResult == LOW) {
|
||||||
m_state = Ready;
|
//Log.info("Button %d is released", m_pin);
|
||||||
Log.info("Button %d is released and back to ready!", m_pin);
|
m_state = Released;
|
||||||
} else if (m_state == Confirmed) {
|
} else if (m_state == Confirmed) {
|
||||||
m_state = Held;
|
m_state = Held;
|
||||||
Log.info("Button %d is being held down!", m_pin);
|
//Log.info("Button %d is being held down!", m_pin);
|
||||||
}
|
}
|
||||||
|
} else if (m_state == Released) {
|
||||||
|
//Log.info("Button %d is ready!", m_pin);
|
||||||
|
m_state = Ready;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,6 +47,10 @@ public:
|
|||||||
return m_state == Confirmed;
|
return m_state == Confirmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool rose() const {
|
||||||
|
return m_state == Released;
|
||||||
|
}
|
||||||
|
|
||||||
bool held() const {
|
bool held() const {
|
||||||
return m_state == Held;
|
return m_state == Held;
|
||||||
}
|
}
|
||||||
@ -53,7 +60,8 @@ private:
|
|||||||
Ready,
|
Ready,
|
||||||
Started,
|
Started,
|
||||||
Confirmed,
|
Confirmed,
|
||||||
Held
|
Held,
|
||||||
|
Released
|
||||||
};
|
};
|
||||||
|
|
||||||
State m_state = Ready;
|
State m_state = Ready;
|
||||||
@ -64,10 +72,23 @@ private:
|
|||||||
|
|
||||||
class Buttons: public InputSource {
|
class Buttons: public InputSource {
|
||||||
public:
|
public:
|
||||||
|
Buttons() : InputSource("Buttons") {}
|
||||||
void onStart() override;
|
void onStart() override;
|
||||||
InputEvent read() override;
|
InputEvent read() override;
|
||||||
|
|
||||||
|
enum Chord {
|
||||||
|
None = 0,
|
||||||
|
Circle = 1,
|
||||||
|
Triangle = 2,
|
||||||
|
Cross = 4,
|
||||||
|
CircleTriangle = Circle | Triangle,
|
||||||
|
CircleCross = Circle | Cross,
|
||||||
|
TriangleCross = Triangle | Cross,
|
||||||
|
CircleTriangleCross = Circle | Triangle | Cross
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Bounce m_buttons[3];
|
Bounce m_buttons[3];
|
||||||
InputEvent::Intent m_buttonMap[3] = {InputEvent::PowerToggle, InputEvent::NextPattern, InputEvent::UserInput};
|
Chord m_buttonMap[3] = {Circle, Triangle, Cross};
|
||||||
|
bool m_wasChord[3] = {false, false, false};
|
||||||
};
|
};
|
||||||
|
@ -7,14 +7,33 @@ public:
|
|||||||
: InputSource(name, initialState), m_colors(colors) {}
|
: InputSource(name, initialState), m_colors(colors) {}
|
||||||
|
|
||||||
InputEvent read() override {
|
InputEvent read() override {
|
||||||
|
if (m_reset) {
|
||||||
|
m_reset = false;
|
||||||
|
m_override = false;
|
||||||
|
return InputEvent{InputEvent::SetColor, m_colors[m_idx]};
|
||||||
|
}
|
||||||
|
if (!m_override) {
|
||||||
EVERY_N_SECONDS(Period) {
|
EVERY_N_SECONDS(Period) {
|
||||||
m_idx %= m_colors.size();
|
m_idx %= m_colors.size();
|
||||||
return InputEvent{InputEvent::SetColor, m_colors[m_idx++]};
|
return InputEvent{InputEvent::SetColor, m_colors[m_idx++]};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return InputEvent{};
|
return InputEvent{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent& event) {
|
||||||
|
if (event.intent == InputEvent::SetColor) {
|
||||||
|
m_override = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onStart() override {
|
||||||
|
m_reset = true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<NSFastLED::CRGB> m_colors;
|
std::vector<NSFastLED::CRGB> m_colors;
|
||||||
int m_idx = 0;
|
int m_idx = 0;
|
||||||
|
bool m_reset = true;
|
||||||
|
bool m_override = false;
|
||||||
};
|
};
|
||||||
|
@ -13,6 +13,7 @@ class MPU5060: public InputSource {
|
|||||||
const int ACCEL_CONFIG_REG = 0x1C;
|
const int ACCEL_CONFIG_REG = 0x1C;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
MPU5060() : InputSource("MPU5060", HardwareConfig::HAS_MPU_6050 ? Task::Running : Task::Stopped) {}
|
||||||
void onStart() override {
|
void onStart() override {
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
|
|
||||||
|
@ -1,120 +1,153 @@
|
|||||||
|
#define RENDERBUG_VERSION 1
|
||||||
|
#define PLATFORM_PHOTON
|
||||||
|
//#define PLATFORM_ESP2800
|
||||||
|
|
||||||
#include "FastLED/FastLED.h"
|
#include "FastLED/FastLED.h"
|
||||||
#include "Figments/Figments.h"
|
#include "Figments/Figments.h"
|
||||||
|
|
||||||
#include "PhotonTelemetry.h"
|
|
||||||
#include "Static.h"
|
#include "Static.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "colors.h"
|
#include "colors.h"
|
||||||
|
|
||||||
|
#include "WebTelemetry.cpp"
|
||||||
|
#include "MDNSService.cpp"
|
||||||
|
#include "Sequencer.h"
|
||||||
|
|
||||||
#include "animations/Power.cpp"
|
#include "animations/Power.cpp"
|
||||||
#include "animations/SolidAnimation.cpp"
|
#include "animations/SolidAnimation.cpp"
|
||||||
#include "animations/Chimes.cpp"
|
#include "animations/Chimes.cpp"
|
||||||
#include "animations/Flashlight.cpp"
|
#include "animations/Flashlight.cpp"
|
||||||
|
#include "animations/Drain.cpp"
|
||||||
#include "animations/UpdateStatus.h"
|
#include "animations/UpdateStatus.h"
|
||||||
|
|
||||||
#include "inputs/Photon.h"
|
|
||||||
#include "inputs/ColorCycle.h"
|
#include "inputs/ColorCycle.h"
|
||||||
#include "inputs/CloudStatus.h"
|
|
||||||
#include "inputs/MPU6050.h"
|
|
||||||
#include "inputs/Buttons.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;
|
SerialLogHandler logHandler;
|
||||||
|
|
||||||
using namespace NSFastLED;
|
using namespace NSFastLED;
|
||||||
|
|
||||||
#define LED_NUM 256
|
|
||||||
#define MAX_BRIGHTNESS 255
|
#define MAX_BRIGHTNESS 255
|
||||||
#define PSU_MILLIAMPS 4800
|
//#define PSU_MILLIAMPS 4800
|
||||||
|
//#define PSU_MILLIAMPS 500
|
||||||
|
#define PSU_MILLIAMPS 1000
|
||||||
|
|
||||||
// Enable system thread, so rendering happens while booting
|
// Enable system thread, so rendering happens while booting
|
||||||
SYSTEM_THREAD(ENABLED);
|
SYSTEM_THREAD(ENABLED);
|
||||||
|
|
||||||
// Setup FastLED and the display
|
// Setup FastLED and the display
|
||||||
CRGB leds[LED_NUM];
|
CRGB leds[HardwareConfig::MAX_LED_NUM];
|
||||||
Display dpy(leds, LED_NUM, Static<ConfigService>::instance()->coordMap());
|
Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static<ConfigService>::instance()->coordMap());
|
||||||
|
|
||||||
LinearCoordinateMapping neckMap{60, 0};
|
LinearCoordinateMapping neckMap{60, 0};
|
||||||
Display neckDisplay(leds, LED_NUM, &neckMap);
|
Display neckDisplay(leds, HardwareConfig::MAX_LED_NUM, &neckMap);
|
||||||
|
|
||||||
// Setup power management
|
// Setup power management
|
||||||
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
|
Power<MAX_BRIGHTNESS, PSU_MILLIAMPS> power;
|
||||||
|
|
||||||
class DrainAnimation: public Figment {
|
|
||||||
public:
|
|
||||||
|
|
||||||
DrainAnimation(Task::State initialState) : Figment("Drain", initialState) {}
|
|
||||||
|
|
||||||
void loop() override {
|
|
||||||
EVERY_N_MILLISECONDS(15) {
|
|
||||||
m_pos++;
|
|
||||||
m_fillColor.update();
|
|
||||||
}
|
|
||||||
EVERY_N_MILLISECONDS(50) {
|
|
||||||
m_burst -= m_burst / 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleEvent(const InputEvent& event) override {
|
|
||||||
if (event.intent == InputEvent::SetColor) {
|
|
||||||
m_fillColor = event.asRGB();
|
|
||||||
} else if (event.intent == InputEvent::Acceleration) {
|
|
||||||
m_pos += log10(event.asInt());
|
|
||||||
uint16_t burstInc = event.asInt() / 6;
|
|
||||||
m_burst = (m_burst > 0xFFFF - burstInc) ? 0xFFFF : m_burst + burstInc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedRGB m_fillColor;
|
|
||||||
|
|
||||||
void render(Display* dpy) const override {
|
|
||||||
dpy->clear();
|
|
||||||
Surface leftPanel{dpy, {0, 0}, {128, 0}};
|
|
||||||
Surface rightPanel{dpy, {128, 0}, {255, 0}};
|
|
||||||
fillRange(dpy, leftPanel.start, leftPanel.end, rgb2hsv_approximate(m_fillColor));
|
|
||||||
fillRange(dpy, rightPanel.end, rightPanel.start, rgb2hsv_approximate(m_fillColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
void fillRange(Display* dpy, const PhysicalCoordinates &start, const PhysicalCoordinates& end, const CHSV &baseColor) const {
|
|
||||||
int length = end.x - start.x;
|
|
||||||
int direction = 1;
|
|
||||||
if (length < 0) {
|
|
||||||
direction = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t frac = 255 / std::abs(length);
|
|
||||||
for(int i = 0; i < std::abs(length); i++) {
|
|
||||||
auto coords = PhysicalCoordinates((start.x + (i * direction)), 0);
|
|
||||||
|
|
||||||
const uint8_t localScale = inoise8(i * 80, m_pos * 3);
|
|
||||||
const uint8_t dimPosition = lerp8by8(50, 190, scale8(sin8((frac * i) / 2), localScale));
|
|
||||||
const uint8_t withBurst = ease8InOutCubic(lerp16by16(dimPosition, 255, m_burst));
|
|
||||||
auto scaledColor = CHSV(baseColor.hue, lerp8by8(100, 255, localScale), withBurst);
|
|
||||||
|
|
||||||
CRGB src(dpy->pixelAt(coords));
|
|
||||||
dpy->pixelAt(coords) = blend(scaledColor, src, 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t m_pos;
|
|
||||||
uint16_t m_burst;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Clip the display at whatever is configured while still showing over-paints
|
// Clip the display at whatever is configured while still showing over-paints
|
||||||
FigmentFunc displayClip([](Display* dpy) {
|
FigmentFunc displayClip([](Display* dpy) {
|
||||||
auto coords = Static<ConfigService>::instance()->coordMap();
|
auto coords = Static<ConfigService>::instance()->coordMap();
|
||||||
for(int i = 0; i < coords->startPixel; i++) {
|
for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) {
|
||||||
|
if (i < coords->startPixel || i > coords->pixelCount + coords->startPixel) {
|
||||||
dpy->pixelAt(i) %= 40;
|
dpy->pixelAt(i) %= 40;
|
||||||
}
|
}
|
||||||
for(int i = LED_NUM; i > coords->pixelCount + coords->startPixel; i--) {
|
|
||||||
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") {}
|
||||||
|
|
||||||
|
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};
|
ChimesAnimation chimes{Task::Stopped};
|
||||||
SolidAnimation solid{Task::Stopped};
|
SolidAnimation solid{Task::Running};
|
||||||
DrainAnimation drain{Task::Running};
|
DrainAnimation drain{Task::Stopped};
|
||||||
Flashlight flashlight{Task::Stopped};
|
Flashlight flashlight{Task::Stopped};
|
||||||
|
|
||||||
|
Sequencer sequencer{{
|
||||||
|
{"Solid", {"Solid"}},
|
||||||
|
{"Drain", {"Drain"}},
|
||||||
|
{"Flashlight", {"Flashlight"}},
|
||||||
|
{"Fiercewater", {"Solid", "Kieryn", "Hackerbots"}},
|
||||||
|
{"Nightlight", {"Drain", "Noisebridge"}},
|
||||||
|
{"Gay", {"Solid", "Rainbow", "Hackerbots", "Kieryn"}},
|
||||||
|
{"Acid", {"Chimes", "Hackerbots", "Rainbow"}},
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
||||||
// Render all layers to the displays
|
// Render all layers to the displays
|
||||||
Renderer renderer{
|
Renderer renderer{
|
||||||
@ -127,17 +160,226 @@ Renderer renderer{
|
|||||||
&flashlight,
|
&flashlight,
|
||||||
Static<UpdateStatus>::instance(),
|
Static<UpdateStatus>::instance(),
|
||||||
&displayClip,
|
&displayClip,
|
||||||
|
&inputBlip,
|
||||||
&power,
|
&power,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Photon telemetry needs a reference to the selected animation's name, so we
|
Renderer configRenderer{
|
||||||
// set it up here
|
{&dpy},
|
||||||
PhotonTelemetry telemetry;
|
{&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 = true;
|
||||||
|
}
|
||||||
|
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 Task, OnlineTaskMixin {
|
||||||
|
public:
|
||||||
|
MQTTTelemetry() : Task("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) {
|
||||||
|
s_instance = this;
|
||||||
|
strcpy(m_deviceName, System.deviceID().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
/*root["intent"] = event.intent;
|
||||||
|
switch(event.type) {
|
||||||
|
case InputEvent::Null:
|
||||||
|
root["value"] = 0;break;
|
||||||
|
case InputEvent::Integer:
|
||||||
|
root["value"] = event.asInt();break;
|
||||||
|
case InputEvent::String:
|
||||||
|
root["value"] = event.asString();break;
|
||||||
|
case InputEvent::Color:
|
||||||
|
root["value"] = "RGB";break;
|
||||||
|
}
|
||||||
|
//root.printTo(response, sizeof(response));
|
||||||
|
//m_client.publish("renderbug/event", response);*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(NSFastLED::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.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::parse((char*)payload, length);
|
||||||
|
JSONObjectIterator cmdIter(cmd);
|
||||||
|
while(cmdIter.next()) {
|
||||||
|
if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") {
|
||||||
|
MainLoop::instance()->dispatch({InputEvent::SetPower, 1});
|
||||||
|
} else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") {
|
||||||
|
MainLoop::instance()->dispatch({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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetColor, CRGB{r, g, b}});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmdIter.name() == "brightness") {
|
||||||
|
uint8_t brightness = cmdIter.value().toInt();
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::SetBrightness, brightness});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void s_callback(char* topic, byte* payload, unsigned int length) {
|
||||||
|
s_instance->callback(topic, payload, length);
|
||||||
|
};
|
||||||
|
static MQTTTelemetry* s_instance;
|
||||||
|
MQTT m_client;
|
||||||
|
char m_deviceName[100];
|
||||||
|
char m_statTopic[100];
|
||||||
|
char m_attrTopic[100];
|
||||||
|
char m_cmdTopic[100];
|
||||||
|
};
|
||||||
|
|
||||||
|
MQTTTelemetry mqttTelemetry;
|
||||||
|
|
||||||
|
MQTTTelemetry* MQTTTelemetry::s_instance = 0;
|
||||||
|
|
||||||
|
WebTelemetry webTelemetry(sequencer);
|
||||||
|
|
||||||
// Cycle some random colors
|
// Cycle some random colors
|
||||||
ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped};
|
ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped};
|
||||||
ColorSequenceInput<7> hackerbotsCycle{{colorForName("Purple").rgb}, "Hackerbots", Task::Stopped};
|
|
||||||
|
|
||||||
ColorSequenceInput<13> kierynCycle{{
|
ColorSequenceInput<13> kierynCycle{{
|
||||||
colorForName("Cerulean").rgb,
|
colorForName("Cerulean").rgb,
|
||||||
@ -154,28 +396,223 @@ ColorSequenceInput<7> rainbowCycle{{
|
|||||||
colorForName("Blue").rgb,
|
colorForName("Blue").rgb,
|
||||||
colorForName("Purple").rgb,
|
colorForName("Purple").rgb,
|
||||||
colorForName("White").rgb,
|
colorForName("White").rgb,
|
||||||
}, "Rainbow", Task::Running};
|
}, "Rainbow", Task::Stopped};
|
||||||
|
|
||||||
|
ColorSequenceInput<7> hackerbotsCycle{{
|
||||||
|
colorForName("Purple").rgb,
|
||||||
|
colorForName("White").rgb,
|
||||||
|
colorForName("Cyan").rgb,
|
||||||
|
}, "Hackerbots", Task::Running};
|
||||||
|
|
||||||
|
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;
|
||||||
|
EVERY_N_SECONDS(60) {
|
||||||
|
if (Time.isValid()) {
|
||||||
|
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(),
|
||||||
|
&randomPulse,
|
||||||
|
//new MPU5060(),
|
||||||
|
|
||||||
|
// Map input buttons to configuration commands
|
||||||
|
new ConfigInputTask(),
|
||||||
|
|
||||||
|
// Fill the entire display with a color, to see size
|
||||||
|
&rainbowCycle,
|
||||||
|
&drain,
|
||||||
|
&configDisplay,
|
||||||
|
// Render some basic input feedback
|
||||||
|
&inputBlip,
|
||||||
|
// Render it all
|
||||||
|
&configRenderer,
|
||||||
|
}};
|
||||||
|
|
||||||
// Turn on,
|
// Turn on,
|
||||||
MainLoop runner{{
|
MainLoop renderbugApp{{
|
||||||
|
|
||||||
// Load/update graphics configuration from EEPROM and Particle
|
// Load/update graphics configuration from EEPROM and Particle
|
||||||
Static<ConfigService>::instance(),
|
Static<ConfigService>::instance(),
|
||||||
|
|
||||||
|
// Platform inputs
|
||||||
|
#ifdef PLATFORM_PHOTON
|
||||||
// Particle cloud status
|
// Particle cloud status
|
||||||
Static<CloudStatus>::instance(),
|
Static<CloudStatus>::instance(),
|
||||||
|
|
||||||
// Monitor network state and provide particle API events
|
// Monitor network state and provide particle API events
|
||||||
Static<PhotonInput>::instance(),
|
Static<PhotonInput>::instance(),
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Hardware drivers
|
||||||
new MPU5060(),
|
new MPU5060(),
|
||||||
new Buttons(),
|
new Buttons(),
|
||||||
|
|
||||||
|
// Map buttons to events
|
||||||
|
&keyMap,
|
||||||
|
|
||||||
|
// Pattern sequencer
|
||||||
|
&sequencer,
|
||||||
|
|
||||||
|
// Daily rhythm activities
|
||||||
|
&circadianRhythm,
|
||||||
|
|
||||||
|
// Periodic motion input
|
||||||
|
&randomPulse,
|
||||||
|
|
||||||
// Periodic color inputs
|
// Periodic color inputs
|
||||||
&noisebridgeCycle,
|
&noisebridgeCycle,
|
||||||
&hackerbotsCycle,
|
|
||||||
&kierynCycle,
|
&kierynCycle,
|
||||||
&rainbowCycle,
|
&rainbowCycle,
|
||||||
|
&hackerbotsCycle,
|
||||||
|
|
||||||
// Animations
|
// Animations
|
||||||
&chimes,
|
&chimes,
|
||||||
@ -187,36 +624,110 @@ MainLoop runner{{
|
|||||||
&power,
|
&power,
|
||||||
&displayClip,
|
&displayClip,
|
||||||
Static<UpdateStatus>::instance(),
|
Static<UpdateStatus>::instance(),
|
||||||
|
&inputBlip,
|
||||||
|
|
||||||
// Render everything
|
// Render everything
|
||||||
&renderer,
|
&renderer,
|
||||||
|
|
||||||
|
// Platform telemetry
|
||||||
|
#ifdef PLATFORM_PHOTON
|
||||||
// Update photon telemetry
|
// Update photon telemetry
|
||||||
&telemetry,
|
Static<PhotonTelemetry>::instance(),
|
||||||
|
|
||||||
|
// Web telemetry UI
|
||||||
|
&webTelemetry,
|
||||||
|
|
||||||
|
// MQTT telemetry
|
||||||
|
&mqttTelemetry,
|
||||||
|
|
||||||
|
// Network discovery
|
||||||
|
&mdnsService,
|
||||||
|
#endif
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
MainLoop &runner = renderbugApp;
|
||||||
|
|
||||||
|
struct BootOptions {
|
||||||
|
BootOptions() {
|
||||||
|
pinMode(2, INPUT_PULLDOWN);
|
||||||
|
pinMode(3, INPUT_PULLDOWN);
|
||||||
|
pinMode(4, INPUT_PULLDOWN);
|
||||||
|
isSetup = digitalRead(2) == HIGH;
|
||||||
|
isSerial = digitalRead(3) == HIGH;
|
||||||
|
isFlash = digitalRead(4) == HIGH;
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
BootOptions bootopts;
|
||||||
|
|
||||||
|
ApplicationWatchdog *wd;
|
||||||
|
|
||||||
|
void watchdogHandler() {
|
||||||
|
System.enterSafeMode();
|
||||||
|
}
|
||||||
|
|
||||||
// Tune in,
|
// Tune in,
|
||||||
void setup() {
|
void setup() {
|
||||||
|
wd = new ApplicationWatchdog(5000, watchdogHandler, 1536);
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
//while(!Serial.isConnected()) { Particle.process(); }
|
if (bootopts.isFlash) {
|
||||||
//Serial.println("Hello, there!");
|
System.dfu();
|
||||||
Log.info("🐛 Booting Renderbug %s!", System.deviceID().c_str());
|
}
|
||||||
Log.info("🐞 I am built for %d LEDs running on %dmA", LED_NUM, PSU_MILLIAMPS);
|
if (bootopts.isSerial) {
|
||||||
Log.info("📡 Particle version %s", System.version().c_str());
|
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("💡 Starting FastLED...");
|
Log.info(u8" Setting timezone to UTC-7");
|
||||||
FastLED.addLeds<NEOPIXEL, 6>(leds, LED_NUM);
|
Time.zone(-7);
|
||||||
|
|
||||||
Log.info("🌌 Starting Figment...");
|
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();
|
Serial.flush();
|
||||||
runner.start();
|
runner.start();
|
||||||
|
|
||||||
Log.info("💽 %lu bytes of free RAM", System.freeMemory());
|
Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory());
|
||||||
Log.info("🚀 Setup complete! Ready to rock and roll.");
|
Log.info(u8"🚀 Setup complete! Ready to rock and roll.");
|
||||||
Serial.flush();
|
Serial.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop out.
|
// Drop out.
|
||||||
void loop() {
|
void loop() {
|
||||||
runner.loop();
|
runner.loop();
|
||||||
|
wd->checkin();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "PhotonTelemetry.h"
|
#include "PhotonTelemetry.h"
|
||||||
|
#include "../../Static.h"
|
||||||
|
|
||||||
using namespace NSFastLED;
|
using namespace NSFastLED;
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ PhotonTelemetry::onConnected()
|
|||||||
Particle.variable("brightness", m_currentBrightness);
|
Particle.variable("brightness", m_currentBrightness);
|
||||||
Particle.variable("fps", m_fps);
|
Particle.variable("fps", m_fps);
|
||||||
Particle.variable("services", m_serviceList);
|
Particle.variable("services", m_serviceList);
|
||||||
|
Particle.variable("localip", m_localIP);
|
||||||
m_online = true;
|
m_online = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,8 +33,9 @@ PhotonTelemetry::loop()
|
|||||||
|
|
||||||
if (m_online) {
|
if (m_online) {
|
||||||
EVERY_N_SECONDS(30) {
|
EVERY_N_SECONDS(30) {
|
||||||
|
m_localIP = WiFi.localIP().toString();
|
||||||
char valueBuf[255];
|
char valueBuf[255];
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "{\"fps\": %lu}", m_fps);
|
snprintf(valueBuf, sizeof(valueBuf), "{\"fps\": %lu, \"localip\": \"%s\"}", m_fps, m_localIP.c_str());
|
||||||
Log.info("Heartbeat: %s", valueBuf);
|
Log.info("Heartbeat: %s", valueBuf);
|
||||||
Particle.publish("renderbug/heartbeat", valueBuf);
|
Particle.publish("renderbug/heartbeat", valueBuf);
|
||||||
auto sched = MainLoop::instance()->scheduler;
|
auto sched = MainLoop::instance()->scheduler;
|
||||||
@ -54,6 +57,7 @@ PhotonTelemetry::loop()
|
|||||||
void
|
void
|
||||||
PhotonTelemetry::handleEvent(const InputEvent &evt)
|
PhotonTelemetry::handleEvent(const InputEvent &evt)
|
||||||
{
|
{
|
||||||
|
Serial.flush();
|
||||||
if (evt.intent == InputEvent::NetworkStatus) {
|
if (evt.intent == InputEvent::NetworkStatus) {
|
||||||
onConnected();
|
onConnected();
|
||||||
}
|
}
|
||||||
@ -93,14 +97,26 @@ PhotonTelemetry::handleEvent(const InputEvent &evt)
|
|||||||
case InputEvent::NetworkStatus:
|
case InputEvent::NetworkStatus:
|
||||||
sourceName = "network-status";
|
sourceName = "network-status";
|
||||||
break;
|
break;
|
||||||
|
case InputEvent::NetworkActivity:
|
||||||
|
sourceName = "network-activity";
|
||||||
|
break;
|
||||||
case InputEvent::StartThing:
|
case InputEvent::StartThing:
|
||||||
sourceName = "start-thing";
|
sourceName = "start-thing";
|
||||||
break;
|
break;
|
||||||
case InputEvent::StopThing:
|
case InputEvent::StopThing:
|
||||||
sourceName = "stop-thing";
|
sourceName = "stop-thing";
|
||||||
break;
|
break;
|
||||||
|
case InputEvent::SetDisplayOffset:
|
||||||
|
sourceName = "set-display-offset";
|
||||||
|
break;
|
||||||
|
case InputEvent::SetDisplayLength:
|
||||||
|
sourceName = "set-display-length";
|
||||||
|
break;
|
||||||
|
case InputEvent::SaveConfigurationRequest:
|
||||||
|
sourceName = "save-configuration";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
sourceName = "unknown";
|
sourceName = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
char valueBuf[255];
|
char valueBuf[255];
|
||||||
@ -108,18 +124,22 @@ PhotonTelemetry::handleEvent(const InputEvent &evt)
|
|||||||
case InputEvent::Null:
|
case InputEvent::Null:
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "null");break;
|
snprintf(valueBuf, sizeof(valueBuf), "null");break;
|
||||||
case InputEvent::Integer:
|
case InputEvent::Integer:
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%d", evt.asInt());break;
|
snprintf(valueBuf, sizeof(valueBuf), "%d %02x", evt.asInt(), evt.asInt());break;
|
||||||
case InputEvent::String:
|
case InputEvent::String:
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "\"%s\"", evt.asString());break;
|
snprintf(valueBuf, sizeof(valueBuf), "\"%s\"", evt.asString());break;
|
||||||
case InputEvent::Color:
|
case InputEvent::Color:
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "[%d, %d, %d]", evt.asRGB().r, evt.asRGB().g, evt.asRGB().b);break;
|
snprintf(valueBuf, sizeof(valueBuf), "[%d, %d, %d]", evt.asRGB().r, evt.asRGB().g, evt.asRGB().b);break;
|
||||||
}
|
}
|
||||||
char buf[255];
|
char buf[255 * 2];
|
||||||
|
if (sourceName == 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "{\"intent\": %d, \"value\": %s}", evt.intent, valueBuf);
|
||||||
|
} else {
|
||||||
snprintf(buf, sizeof(buf), "{\"intent\": \"%s\", \"value\": %s}", sourceName, valueBuf);
|
snprintf(buf, sizeof(buf), "{\"intent\": \"%s\", \"value\": %s}", sourceName, valueBuf);
|
||||||
|
}
|
||||||
if (m_online) {
|
if (m_online) {
|
||||||
if (evt.intent != m_lastEvent.intent) {
|
if (evt.intent != m_lastEvent.intent) {
|
||||||
if (m_duplicateEvents > 0) {
|
if (m_duplicateEvents > 0) {
|
||||||
Log.info("Suppressed reporting %d duplicate events.", m_duplicateEvents);
|
Log.info("Suppressed reporting %ld duplicate events.", m_duplicateEvents);
|
||||||
}
|
}
|
||||||
Log.info("Event: %s", buf);
|
Log.info("Event: %s", buf);
|
||||||
m_duplicateEvents = 0;
|
m_duplicateEvents = 0;
|
||||||
@ -141,3 +161,5 @@ PhotonTelemetry::handleEvent(const InputEvent &evt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STATIC_ALLOC(PhotonTelemetry);
|
@ -12,6 +12,7 @@ private:
|
|||||||
|
|
||||||
int m_frameIdx;
|
int m_frameIdx;
|
||||||
String m_serviceList;
|
String m_serviceList;
|
||||||
|
String m_localIP;
|
||||||
uint32_t m_currentBrightness;
|
uint32_t m_currentBrightness;
|
||||||
LEDStatus m_ledStatus = LEDStatus(0, LED_PATTERN_FADE, LED_SPEED_FAST);
|
LEDStatus m_ledStatus = LEDStatus(0, LED_PATTERN_FADE, LED_SPEED_FAST);
|
||||||
uint32_t m_rgbPulseFrame = 0;
|
uint32_t m_rgbPulseFrame = 0;
|
@ -1,5 +1,5 @@
|
|||||||
#include "CloudStatus.h"
|
#include "CloudStatus.h"
|
||||||
#include "../Static.h"
|
#include "../../../Static.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
CloudStatus::onStart()
|
CloudStatus::onStart()
|
||||||
@ -19,6 +19,9 @@ CloudStatus::initNetwork(system_event_t event, int param) {
|
|||||||
if (param == cloud_status_connected) {
|
if (param == cloud_status_connected) {
|
||||||
Log.info("Connected to T H E C L O U D");
|
Log.info("Connected to T H E C L O U D");
|
||||||
MainLoop::instance()->dispatch(InputEvent{InputEvent::NetworkStatus, true});
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::NetworkStatus, true});
|
||||||
|
} else if (param == cloud_status_disconnected) {
|
||||||
|
Log.info("Lost cloud connection!!");
|
||||||
|
MainLoop::instance()->dispatch(InputEvent{InputEvent::NetworkStatus, false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
#include "../Figments/Figments.h"
|
#include "../../../Figments/Figments.h"
|
||||||
|
|
||||||
class CloudStatus: public Task {
|
class CloudStatus: public Task {
|
||||||
public:
|
public:
|
@ -1,13 +1,14 @@
|
|||||||
#include "Particle.h"
|
#include "Particle.h"
|
||||||
#include "../Figments/Figments.h"
|
#include "../../../Figments/Figments.h"
|
||||||
#include "../colors.h"
|
#include "../../../colors.h"
|
||||||
#include "../Static.h"
|
#include "../../../Static.h"
|
||||||
#include "./Photon.h"
|
#include "./Photon.h"
|
||||||
|
|
||||||
void
|
void
|
||||||
PhotonInput::onConnected()
|
PhotonInput::onConnected()
|
||||||
{
|
{
|
||||||
Log.info("Connecting photon input...");
|
Log.info("Connecting photon input...");
|
||||||
|
Particle.function("save", &PhotonInput::save, this);
|
||||||
Particle.function("power", &PhotonInput::setPower, this);
|
Particle.function("power", &PhotonInput::setPower, this);
|
||||||
Particle.function("next", &PhotonInput::nextPattern, this);
|
Particle.function("next", &PhotonInput::nextPattern, this);
|
||||||
Particle.function("input", &PhotonInput::input, this);
|
Particle.function("input", &PhotonInput::input, this);
|
||||||
@ -115,6 +116,11 @@ PhotonInput::previousPattern(String command)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PhotonInput::save(String command) {
|
||||||
|
setEvent(InputEvent::SaveConfigurationRequest);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
PhotonInput::setPower(String command)
|
PhotonInput::setPower(String command)
|
||||||
{
|
{
|
@ -1,5 +1,5 @@
|
|||||||
#include "Particle.h"
|
#include "Particle.h"
|
||||||
#include "../Figments/Figments.h"
|
#include "../../../Figments/Figments.h"
|
||||||
|
|
||||||
class PhotonInput: public BufferedInputSource {
|
class PhotonInput: public BufferedInputSource {
|
||||||
public:
|
public:
|
||||||
@ -19,6 +19,7 @@ private:
|
|||||||
int previousPattern(String command);
|
int previousPattern(String command);
|
||||||
int setPower(String command);
|
int setPower(String command);
|
||||||
int setBrightness(String command);
|
int setBrightness(String command);
|
||||||
|
int save(String command);
|
||||||
|
|
||||||
int startThing(String command);
|
int startThing(String command);
|
||||||
int stopThing(String command);
|
int stopThing(String command);
|
3
libs.txt
3
libs.txt
@ -1,2 +1,5 @@
|
|||||||
https://github.com/focalintent/fastled-sparkcore.git FastLED
|
https://github.com/focalintent/fastled-sparkcore.git FastLED
|
||||||
https://github.com/geeksville/ParticleWebLog ParticleWebLog
|
https://github.com/geeksville/ParticleWebLog ParticleWebLog
|
||||||
|
MDNS
|
||||||
|
https://github.com/kasperkamperman/webduino WebDuino
|
||||||
|
https://github.com/hirotakaster/MQTT/ MQTT
|
||||||
|
Loading…
Reference in New Issue
Block a user