Compare commits

..

No commits in common. "f224bba2a0841292b64e62bfd4ca5d5514b3a88c" and "3b32d72d5e84a7f1c6a42b5893cd412ad42c5dba" have entirely different histories.

153 changed files with 1499 additions and 4532 deletions

54
.atom-build.yml Normal file
View File

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

2
.gitignore vendored
View File

@ -1,4 +1,2 @@
bin/*
*.bin
.pio
*.swp

10
.travis.yml Normal file
View File

@ -0,0 +1,10 @@
dist: trusty
sudo: required
language: generic
script:
- ci/travis.sh
cache:
directories:
- $HOME/.po-util

View File

@ -1,22 +0,0 @@
pipeline:
bike:
group: build
image: alpine:3.13
commands:
- pip install -U platformio
- pio run -e ${VARIANT}
matrix:
VARIANT:
- bike
- bike_ble
- esp32_wifi
- esp32_bluetooth
- home_lighting-12f
- home_lighting_grb
- prototype
esp32:
group: build
image: alpine:3.13
commands:
- pip install -U platformio
- pio run -e esp32_wifi -e esp32_bluetooth

12
Pipfile
View File

@ -1,12 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
platformio = "*"
[dev-packages]
[requires]
python_version = "3.10"

5
ci/travis.sh Executable file
View File

@ -0,0 +1,5 @@
#!/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

130
firmware/Config.cpp Normal file
View File

@ -0,0 +1,130 @@
#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);
}
bool
HardwareConfig::isValid() const
{
return version == 1 && checksum == getCRC();
}
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...");
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_pixelCount = AnimatedNumber{m_config.data.pixelCount};
m_startPixel = AnimatedNumber{m_config.data.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.startPixel = m_startPixel;
m_coordMap.pixelCount = m_pixelCount;
}
void
ConfigService::handleEvent(const InputEvent &evt)
{
if (evt.intent == InputEvent::NetworkStatus) {
onConnected();
}
}
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);

53
firmware/Config.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
#include "Particle.h"
#include "./Figments/Figments.h"
struct HardwareConfig {
uint8_t version = 1;
uint8_t checksum = 0;
struct TaskState {
char name[16] = {0};
bool isRunning = false;
};
struct Data {
uint16_t pixelCount = 255;
uint16_t startPixel = 0;
TaskState serviceStates[32];
};
Data data;
static HardwareConfig load();
void save();
bool isValid() const;
private:
uint8_t getCRC() const;
static constexpr uint8_t CRC7_POLY = 0x91;
};
// A task that manages the EEPROM settings and coord mapping when modified via
// Particle. This allows for multiple devices with wildly different displays to
// run the same code
struct ConfigService: public Task {
ConfigService() : Task("Configuration") {}
void onStart();
void loop() override;
void handleEvent(const InputEvent &evt) override;
const LinearCoordinateMapping* coordMap() const { return &m_coordMap; }
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;
LinearCoordinateMapping m_coordMap;
};

View File

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

1
firmware/FastLED/FastLED.h Symbolic link
View File

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

View File

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

1
firmware/FastLED/LICENSE Symbolic link
View File

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

1
firmware/FastLED/PORTING.md Symbolic link
View File

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

1
firmware/FastLED/README.md Symbolic link
View File

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

1
firmware/FastLED/bitswap.h Symbolic link
View File

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

1
firmware/FastLED/chipsets.h Symbolic link
View File

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

View File

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

1
firmware/FastLED/color.h Symbolic link
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
firmware/FastLED/delay.h Symbolic link
View File

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

1
firmware/FastLED/dmx.h Symbolic link
View File

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

1
firmware/FastLED/docs Symbolic link
View File

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

1
firmware/FastLED/examples Symbolic link
View File

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

View File

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

View File

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

1
firmware/FastLED/fastpin.h Symbolic link
View File

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

View File

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

1
firmware/FastLED/fastspi.h Symbolic link
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
firmware/FastLED/hsv2rgb.h Symbolic link
View File

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

View File

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

View File

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

View File

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

View File

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

1
firmware/FastLED/lib8tion.h Symbolic link
View File

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

1
firmware/FastLED/noise.cpp Symbolic link
View File

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

1
firmware/FastLED/noise.h Symbolic link
View File

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

View File

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

1
firmware/FastLED/platforms Symbolic link
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

1
firmware/FastLED/wiring.cpp Symbolic link
View File

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

View File

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

View File

@ -1,6 +1,7 @@
#pragma once
#include <FastLED.h>
#include "FastLED/FastLED.h"
#include "./Figment.h"
#include <vector>
class Display;
@ -23,18 +24,6 @@ struct AnimatedNumber {
}
}
void update(uint8_t speed) {
if (255 - speed >= m_idx) {
m_idx += speed;
} else {
m_idx = 255;
}
}
bool isFinished() const {
return m_idx == 255;
}
AnimatedNumber() {}
AnimatedNumber(uint8_t v) : m_end(v) {}
@ -79,16 +68,16 @@ private:
};
struct AnimatedRGB {
CRGB start;
CRGB end;
NSFastLED::CRGB start;
NSFastLED::CRGB end;
AnimatedNumber pos;
AnimatedRGB(const CRGB& color)
AnimatedRGB(const NSFastLED::CRGB& color)
: start(color), end(color) {}
AnimatedRGB() {}
AnimatedRGB& operator=(const CRGB& rgb) {
AnimatedRGB& operator=(const NSFastLED::CRGB& rgb) {
start = *this;
end = rgb;
pos.set(0, 255);
@ -99,11 +88,11 @@ struct AnimatedRGB {
pos.update();
}
operator CRGB() const {
uint8_t red = lerp8by8(start.red, end.red, pos);
uint8_t green = lerp8by8(start.green, end.green, pos);
uint8_t blue = lerp8by8(start.blue, end.blue, pos);
return CRGB(red, green, blue);
operator NSFastLED::CRGB() const {
uint8_t red = NSFastLED::lerp8by8(start.red, end.red, pos);
uint8_t green = NSFastLED::lerp8by8(start.green, end.green, pos);
uint8_t blue = NSFastLED::lerp8by8(start.blue, end.blue, pos);
return NSFastLED::CRGB(red, green, blue);
}
};

View File

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

View File

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

View File

@ -1,18 +1,14 @@
#pragma once
#include <Arduino.h>
#include "application.h"
#include <functional>
#include <ArduinoLog.h>
class Display;
class InputEvent;
class InputSource;
struct Loopable {
struct Task {
virtual void handleEvent(const InputEvent& event) {}
virtual void loop() = 0;
};
struct Task : public virtual Loopable {
virtual void onStart() {};
virtual void onStop() {};
@ -22,31 +18,23 @@ struct Task : public virtual Loopable {
};
Task() {}
explicit Task(State initialState) : Task(0, initialState) {}
explicit Task(const char* name) : Task(name, Running) {}
Task(State initialState) : Task(0, initialState) {}
Task(const char* name) : Task(name, Running) {}
Task(const char* name, State initialState) : name(name), state(initialState) {}
void start() { state = Running; onStart(); }
void stop() { onStop(); state = Stopped; }
virtual bool isFigment() const { return false; }
void start() { Log.info("* Starting %s...", name); state = Running; onStart(); }
void stop() { Log.info("* Stopping %s...", name); onStop(); state = Stopped; }
const char* name = "";
const char* name = 0;
State state = Running;
};
struct TaskFunc: public Task {
TaskFunc(std::function<void()> func) : Task("lambda"), func(func) {}
void loop() override {func();}
std::function<void()> func;
};
struct Figment: public Task {
Figment() : Task() {}
explicit Figment(State initialState) : Task(initialState) {}
explicit Figment(const char* name) : Task(name) {}
Figment(State initialState) : Task(initialState) {}
Figment(const char* name) : Task(name) {}
Figment(const char* name, State initialState) : Task(name, initialState) {}
virtual void render(Display* dpy) const = 0;
bool isFigment() const override { return true; }
};
struct FigmentFunc: public Figment {

View File

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

View File

@ -0,0 +1,41 @@
#include "application.h"
#include "./Input.h"
#include "./MainLoop.h"
NSFastLED::CRGB
Variant::asRGB() const
{
return NSFastLED::CRGB(m_value.asRGB[0], m_value.asRGB[1], m_value.asRGB[2]);
}
const char*
Variant::asString() const
{
return m_value.asString;
}
int
Variant::asInt() const
{
return m_value.asInt;
}
void
InputSource::loop()
{
MainLoop::instance()->dispatch(read());
}
InputEvent
BufferedInputSource::read()
{
InputEvent ret = m_lastEvent;
m_lastEvent = InputEvent{};
return ret;
}
void
BufferedInputSource::setEvent(InputEvent &&evt)
{
m_lastEvent = std::move(evt);
}

109
firmware/Figments/Input.h Normal file
View File

@ -0,0 +1,109 @@
#pragma once
#include "application.h"
#include "./Geometry.h"
#include "./Figment.h"
#include "FastLED/FastLED.h"
typedef Vector3d<int> MotionVec;
struct Variant {
enum Type {
Null,
Integer,
String,
Color,
};
Variant(int v)
: type(Integer), m_value{.asInt=v} {}
Variant(const char* v)
: type(String), m_value{.asString=v} {}
Variant(const NSFastLED::CRGB &v)
: type(Color), m_value{.asRGB={v.r, v.g, v.b}} {}
Variant()
: type(Null) {}
Type type;
const char* asString() const;
NSFastLED::CRGB asRGB() const;
int asInt() const;
private:
union {
int asInt;
const char* asString;
uint8_t asRGB[3];
} m_value;
};
struct InputEvent: public Variant {
enum Intent {
None,
PowerToggle,
SetPower,
SetBrightness,
PreviousPattern,
NextPattern,
SetPattern,
SetColor,
Acceleration,
FirmwareUpdate,
NetworkStatus,
StartThing,
StopThing,
UserInput,
};
template<typename Value>
InputEvent(Intent s, Value v)
: Variant(v), intent(s) {}
InputEvent(Intent s)
: Variant(), intent(s) {}
InputEvent()
: Variant(), intent(None) {}
Intent intent;
};
class InputSource: public Task {
public:
InputSource() : Task() {}
InputSource(const char* name) : Task(name) {}
InputSource(Task::State initialState) : Task(initialState) {}
InputSource(const char* name, Task::State initialState) : Task(name, initialState) {}
void loop() override;
virtual InputEvent read() = 0;
};
class InputFunc : public InputSource {
public:
InputFunc(std::function<InputEvent(void)> f) : InputSource(), m_func(f) {}
InputFunc(std::function<InputEvent(void)> f, const char* name) : InputSource(name), m_func(f) {}
InputFunc(std::function<InputEvent(void)> f, const char* name, Task::State initialState) : InputSource(name, initialState), m_func(f) {}
InputEvent read() override {
return m_func();
}
private:
std::function<InputEvent(void)> m_func;
};
class BufferedInputSource: public InputSource {
public:
BufferedInputSource() : InputSource() {}
BufferedInputSource(const char* name) : InputSource(name) {}
InputEvent read() override;
protected:
void setEvent(InputEvent &&evt);
private:
InputEvent m_lastEvent;
};

View File

@ -0,0 +1,51 @@
#include "./MainLoop.h"
#include "./Input.h"
#include "./Figment.h"
void
MainLoop::dispatch(const InputEvent& evt)
{
if (evt.intent == InputEvent::None) {
return;
}
m_eventBuf.insert(evt);
}
void
MainLoop::loop()
{
InputEvent evt;
while (m_eventBuf.take(evt)) {
if (evt.intent == InputEvent::StartThing || evt.intent == InputEvent::StopThing) {
const bool jobState = (evt.intent == InputEvent::StartThing);
for(auto figmentJob: scheduler.tasks) {
if (strcmp(figmentJob->name, evt.asString()) == 0) {
if (jobState) {
figmentJob->start();
} else {
figmentJob->stop();
}
}
}
}
for(Task* task : scheduler) {
task->handleEvent(evt);
}
}
for(Task* task : scheduler) {
task->loop();
}
}
void
MainLoop::start()
{
Log.info("*** Starting %d tasks...", scheduler.tasks.size());
Serial.flush();
for(auto task: scheduler) {
task->start();
}
}
MainLoop* MainLoop::s_instance;

View File

@ -3,7 +3,6 @@
#include <vector>
#include <algorithm>
#include "./Input.h"
#include "./Ringbuf.h"
class Task;
class InputSource;
@ -51,11 +50,48 @@ struct Scheduler {
iterator end() { return iterator(*this, tasks.size()); }
};
template<typename T, int Size>
struct Ringbuf {
Ringbuf() : m_head(0), m_tail(0) {}
void clear() {
m_head = 0;
m_tail = 0;
}
bool take(T& dest) {
if (m_head == m_tail) {
return false;
}
const int cur = m_head;
const int nextHead = (m_head + 1) % Size;
m_head = nextHead;
dest = m_items[cur];
return true;
}
void insert(const T& src) {
const int cur = m_tail;
const int nextTail = (m_tail + 1) % Size;
if (nextTail == m_head) {
return;
} else {
m_tail = nextTail;
}
m_items[cur] = src;
}
private:
int m_head = 0;
int m_tail = 0;
std::array<T, Size> m_items;
};
struct MainLoop {
Scheduler scheduler;
MainLoop(std::vector<Task*> &&tasks)
: scheduler(std::move(tasks)) {}
: scheduler(std::move(tasks)) {s_instance = this;}
void start();
void loop();
@ -63,7 +99,7 @@ struct MainLoop {
static MainLoop* instance() { return s_instance; }
private:
Ringbuf<InputEvent, 32> m_eventBuf;
Ringbuf<InputEvent, 10> m_eventBuf;
static MainLoop* s_instance;
};

View File

@ -0,0 +1,24 @@
#include "./Renderer.h"
#include "./Display.h"
void
Renderer::loop()
{
for(Display* dpy : m_displays) {
for(Figment* figment : m_figments) {
if (figment->state == Task::Running) {
figment->render(dpy);
}
};
}
NSFastLED::FastLED.show();
}
void
Renderer::onStart()
{
for(Display* dpy : m_displays) {
dpy->clear();
}
NSFastLED::FastLED.show();
}

View File

@ -5,7 +5,7 @@ class Display;
struct Renderer: public Task {
public:
Renderer(std::vector<Display*>&& displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(std::move(displays)) {}
Renderer(std::vector<Display*> displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(displays) {}
void loop() override;
void onStart() override;

View File

@ -0,0 +1,37 @@
#include "./Surface.h"
#include "./Display.h"
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
m_display(dpy)
{
}
Surface&
Surface::operator=(const NSFastLED::CRGB& color)
{
paintWith([&](NSFastLED::CRGB& pixel) {
pixel = color;
});
return *this;
}
Surface&
Surface::operator+=(const NSFastLED::CRGB& color)
{
paintWith([&](NSFastLED::CRGB& pixel) {
pixel += color;
});
return *this;
}
void
Surface::paintWith(std::function<void(NSFastLED::CRGB&)> func)
{
for(uint8_t x = start.x; x <= end.x; x++) {
for(uint8_t y = start.y; y <= end.y; y++) {
func(m_display->pixelAt(PhysicalCoordinates{x, y}));
}
}
}

View File

@ -0,0 +1,20 @@
#include "FastLED/FastLED.h"
#include "./Geometry.h"
class Display;
class Surface {
public:
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end);
Surface& operator=(const NSFastLED::CRGB& color);
Surface& operator+=(const NSFastLED::CRGB& color);
void paintWith(std::function<void(NSFastLED::CRGB&)> func);
const PhysicalCoordinates start;
const PhysicalCoordinates end;
private:
Display* m_display;
};

View File

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

View File

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

View File

@ -0,0 +1,143 @@
#include "PhotonTelemetry.h"
using namespace NSFastLED;
PhotonTelemetry::PhotonTelemetry() : Task("PhotonTelemetry") {}
void
PhotonTelemetry::onConnected()
{
Log.info("Connecting photon telemetry...");
Particle.variable("frame", m_frameIdx);
Particle.variable("brightness", m_currentBrightness);
Particle.variable("fps", m_fps);
Particle.variable("services", m_serviceList);
m_online = true;
}
void
PhotonTelemetry::loop()
{
m_frameIdx++;
if (m_rgbPulseFrame == 1) {
m_ledStatus.setActive(false);
} else if (m_rgbPulseFrame > 0) {
m_rgbPulseFrame--;
}
m_currentBrightness = NSFastLED::FastLED.getBrightness();
NSFastLED::FastLED.countFPS();
m_fps = NSFastLED::FastLED.getFPS();
if (m_online) {
EVERY_N_SECONDS(30) {
char valueBuf[255];
snprintf(valueBuf, sizeof(valueBuf), "{\"fps\": %lu}", m_fps);
Log.info("Heartbeat: %s", valueBuf);
Particle.publish("renderbug/heartbeat", valueBuf);
auto sched = MainLoop::instance()->scheduler;
m_serviceList = String{};
for(auto task : sched.tasks) {
m_serviceList.concat(task->name);
m_serviceList.concat(':');
if (task->state == Task::Running) {
m_serviceList.concat(1);
} else {
m_serviceList.concat(0);
}
m_serviceList.concat(',');
}
}
}
}
void
PhotonTelemetry::handleEvent(const InputEvent &evt)
{
if (evt.intent == InputEvent::NetworkStatus) {
onConnected();
}
if (evt.intent != InputEvent::None) {
const char* sourceName;
switch(evt.intent) {
case InputEvent::PowerToggle:
sourceName = "power-toggle";
break;
case InputEvent::SetPower:
sourceName = "set-power";
break;
case InputEvent::PreviousPattern:
sourceName = "previous-pattern";
break;
case InputEvent::NextPattern:
sourceName = "next-pattern";
break;
case InputEvent::SetPattern:
sourceName = "set-pattern";
break;
case InputEvent::SetColor:
sourceName = "set-color";
break;
case InputEvent::Acceleration:
sourceName = "acceleration";
break;
case InputEvent::UserInput:
sourceName = "user";
break;
case InputEvent::SetBrightness:
sourceName = "set-brightness";
break;
case InputEvent::FirmwareUpdate:
sourceName = "firmware-update";
break;
case InputEvent::NetworkStatus:
sourceName = "network-status";
break;
case InputEvent::StartThing:
sourceName = "start-thing";
break;
case InputEvent::StopThing:
sourceName = "stop-thing";
break;
default:
sourceName = "unknown";
break;
}
char valueBuf[255];
switch(evt.type) {
case InputEvent::Null:
snprintf(valueBuf, sizeof(valueBuf), "null");break;
case InputEvent::Integer:
snprintf(valueBuf, sizeof(valueBuf), "%d", evt.asInt());break;
case InputEvent::String:
snprintf(valueBuf, sizeof(valueBuf), "\"%s\"", evt.asString());break;
case InputEvent::Color:
snprintf(valueBuf, sizeof(valueBuf), "[%d, %d, %d]", evt.asRGB().r, evt.asRGB().g, evt.asRGB().b);break;
}
char buf[255];
snprintf(buf, sizeof(buf), "{\"intent\": \"%s\", \"value\": %s}", sourceName, valueBuf);
if (m_online) {
if (evt.intent != m_lastEvent.intent) {
if (m_duplicateEvents > 0) {
Log.info("Suppressed reporting %d duplicate events.", m_duplicateEvents);
}
Log.info("Event: %s", buf);
m_duplicateEvents = 0;
m_lastEvent = evt;
Particle.publish("renderbug/event", buf, PRIVATE);
} else {
m_duplicateEvents++;
}
} else {
Log.info("[offline] Event: %s", buf);
}
if (evt.intent == InputEvent::SetColor) {
NSFastLED::CRGB rgb {evt.asRGB()};
uint32_t color = (rgb.r << 16) + (rgb.g << 8) + (rgb.b << 0);
m_ledStatus.setColor(color);
m_ledStatus.setActive(true);
m_rgbPulseFrame = 1000;
}
}
}

View File

@ -12,7 +12,6 @@ private:
int m_frameIdx;
String m_serviceList;
String m_localIP;
uint32_t m_currentBrightness;
LEDStatus m_ledStatus = LEDStatus(0, LED_PATTERN_FADE, LED_SPEED_FAST);
uint32_t m_rgbPulseFrame = 0;

16
firmware/Static.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
// Utility class mostly for when certain inputs need singleton callback handlers
template<typename T> class Static {
public:
static T* instance() {
return s_instance;
}
private:
static T* s_instance;
};
#define NAMED_STATIC_ALLOC(Cls, StaticName) static Cls _staticAlloc__ ## StaticName;\
template<> Cls* Static<Cls>::s_instance=&_staticAlloc__ ## StaticName;
#define STATIC_ALLOC(Cls) NAMED_STATIC_ALLOC(Cls, Cls)

View File

@ -0,0 +1,77 @@
#include "../Figments/Figments.h"
#include "../sprites/Chime.h"
#include "../sprites/Blob.h"
using namespace NSFastLED;
#define CHIME_LENGTH 23
#define CHIME_COUNT 4
#define BLOB_COUNT 10
class ChimesAnimation: public Figment {
public:
ChimesAnimation(Task::State initialState) : Figment("Chimes", initialState) {
m_chimes.forEach([](Chime<CHIME_LENGTH> &chime) {
chime.setPos(random(Chime<CHIME_LENGTH>::Length * 5));
chime.setHue(random(255));
chime.setSpeed(random(90) + 138);
chime.setBrightness(200);
chime.setOffset(random(1024));
});
m_blobs.forEach([](Blob &blob) {
blob.setPos(random(255));
blob.setHue(random(255));
blob.setBrightness(random(255));
if (random(255) % 2) {
blob.setVelocity(-1);
} else {
blob.setVelocity(1);
}
});
}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::UserInput) {
if (strcmp(evt.asString(), "blobs") == 0) {
m_blobs.toggle();
} else if (strcmp(evt.asString(), "chimes") == 0) {
m_chimes.toggle();
}
} else if (evt.intent == InputEvent::SetColor) {
m_flashBrightness.set(255, 0);
m_flashColor = evt.asRGB();
uint8_t flashHue = rgb2hsv_approximate(m_flashColor).hue;
m_blobs.forEach([&](Blob& blob) {
blob.setHue(flashHue);
});
m_chimes.forEach([&](Chime<CHIME_LENGTH>& chime) {
chime.setHue(flashHue);
});
}
}
void loop() override {
m_chimes.update();
m_blobs.update();
m_flashColor.update();
EVERY_N_MILLISECONDS(5) {
m_flashBrightness.update();
}
}
void render(Display* dpy) const override {
m_chimes.render(dpy);
m_blobs.render(dpy);
Surface fullSurface(dpy, {0, 0}, {255, 0});
NSFastLED::CRGB scaledColor = NSFastLED::CRGB(m_flashColor).nscale8_video(max(10, NSFastLED::ease8InOutCubic(m_flashBrightness)));
fullSurface.paintWith([&](NSFastLED::CRGB& pixel) {
pixel = NSFastLED::blend(scaledColor, pixel, 200);
//pixel = scaledColor;
});
}
SpriteList<Chime<CHIME_LENGTH>, CHIME_COUNT> m_chimes;
SpriteList<Blob, BLOB_COUNT> m_blobs;
AnimatedRGB m_flashColor;
AnimatedNumber m_flashBrightness;
};

View File

@ -0,0 +1,49 @@
#pragma once
#include "../Figments/Figments.h"
#include "../sprites/Blob.h"
using NSFastLED::CHSV;
class Flashlight: public Figment {
public:
Flashlight(Task::State initialState) : Figment("Flashlight", initialState) {
m_blobs.forEach([](Blob &blob) {
blob.setHue(random(255));
blob.setSaturation(10);
blob.setPos(random(255));
});
}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::Acceleration) {
if (evt.asInt() > 10) {
m_blobs.forEach([](Blob& blob) {blob.update();});
}
}
/*if (evt.intent() == InputEvent::UserInput) {
if (evt.asInt() == 1) {
m_blobs.forEach([](Blob& blob) {blob.setPos(random(255));});
}
if (evt.asInt() == 2) {
m_blobs.forEach([](Blob& chime) {blob.setPos(0);});
}
}*/
}
void loop() override {
m_blobs.update();
}
void render(Display* dpy) const override {
m_blobs.render(dpy);
for(int i = 0; i < dpy->pixelCount();i++) {
dpy->pixelAt(i) |= 100;
}
}
private:
static constexpr int blobCount = 30;
SpriteList<Blob, blobCount> m_blobs;
};

View File

@ -0,0 +1,40 @@
#include "../Figments/Figments.h"
template<uint8_t MaxBrightness = 255, uint32_t MaxMilliAmps = 500, uint32_t Voltage = 5>
class Power: public Figment {
public:
Power() : Figment("Power") {}
void handleEvent(const InputEvent& evt) override {
switch (evt.intent) {
case InputEvent::PowerToggle:
m_powerState = m_powerState.value() <= 128 ? 255 : 0;
Log.info("POWER TOGGLE %d", m_powerState.value());
break;
case InputEvent::SetPower:
m_powerState = evt.asInt() == 0 ? 0 : 255;
break;
case InputEvent::SetBrightness:
m_brightness = evt.asInt();
}
}
void loop() override {
m_powerState.update();
m_brightness.update();
}
void render(Display* dpy) const override {
const uint8_t clippedBrightness = min((uint8_t)m_brightness, MaxBrightness);
const uint8_t scaledBrightness = NSFastLED::scale8(m_powerState, clippedBrightness);
const uint8_t videoBrightness = NSFastLED::brighten8_video(scaledBrightness);
const uint8_t powerBrightness = NSFastLED::calculate_max_brightness_for_power_mW(videoBrightness, Watts);
NSFastLED::FastLED.setBrightness(powerBrightness);
}
static constexpr uint32_t Watts = Voltage * MaxMilliAmps;
private:
AnimatedNumber m_powerState = 255;
AnimatedNumber m_brightness = MaxBrightness;
};

View File

@ -0,0 +1,54 @@
#include "../Figments/Figments.h"
#include "../sprites/Blob.h"
using NSFastLED::CRGB;
using NSFastLED::CHSV;
using namespace NSFastLED;
class SolidAnimation: public Figment {
private:
AnimatedNumber m_red, m_green, m_blue;
static constexpr int blobCount = 20;
SpriteList<Blob, blobCount> m_blobs;
public:
SolidAnimation(Task::State initialState) : Figment("Solid", initialState) {
m_blobs.forEach([](Blob& blob) {
blob.setPos(random(140));
blob.setBrightness(random(255));
if (random(255) % 2) {
blob.setVelocity(-1);
}
});
}
void handleEvent(const InputEvent& evt) override {
if (evt.intent == InputEvent::SetColor) {
CRGB nextColor = evt.asRGB();
m_red.set(nextColor.red);
m_green.set(nextColor.green);
m_blue.set(nextColor.blue);
}
}
void loop() override {
m_red.update();
m_green.update();
m_blue.update();
EVERY_N_MILLIS(6) {
CRGB rgb{m_red, m_green, m_blue};
CHSV hsv = NSFastLED::rgb2hsv_approximate(rgb);
m_blobs.forEach([=](Blob& blob) {
blob.setHue(hsv.hue);
blob.setSaturation(hsv.saturation);
});
m_blobs.update();
}
}
void render(Display* dpy) const override {
CRGB color(m_red.value(), m_green.value(), m_blue.value());
Surface(dpy, {0, 0}, {255, 0}) = color;
m_blobs.render(dpy);
}
};

View File

@ -1,5 +1,8 @@
#include "./TestAnimation.h"
#include <FastLED.h>
#include "FastLED/FastLED.h"
using NSFastLED::CHSV;
using NSFastLED::scale8;
const char*
TestAnimation::name() const

View File

@ -1,4 +1,4 @@
#include <Figments.h>
#include "../Figments/Figments.h"
class TestAnimation: public Figment {
public:

View File

@ -1,14 +1,16 @@
#include "./UpdateStatus.h"
#include <FastLED.h>
#include "FastLED/FastLED.h"
#include "../Static.h"
using NSFastLED::CRGB;
void
UpdateStatus::handleEvent(const InputEvent& evt)
{
if (evt.intent == InputEvent::FirmwareUpdate) {
static int updateCount = 0;
updateCount++;
//Log.info("Update count %d", updateCount);
Log.info("Update count %d", updateCount);
m_updateReady = true;
}
}
@ -32,4 +34,3 @@ UpdateStatus::render(Display* dpy) const
}
STATIC_ALLOC(UpdateStatus);
STATIC_TASK(UpdateStatus);

View File

@ -1,5 +1,4 @@
#pragma once
#include <Figments.h>
#include "../Figments/Figments.h"
class UpdateStatus: public Figment {
public:

View File

@ -1,8 +1,6 @@
#include "colors.h"
const ColorInfo color_data[] = {
#ifdef CONFIG_NO_COLORDATA
#else
{ "Air Superiority Blue", { 114, 160, 193 } },
{ "Alabama Crimson", { 163, 38, 56 } },
{ "Alice Blue", { 240, 248, 255 } },
@ -794,7 +792,6 @@ const ColorInfo color_data[] = {
{ "Yellow Orange", { 255, 174, 66 } },
{ "Zaffre", { 0, 20, 168 } },
{ "Zinnwaldite Brown", { 44, 22, 8 } },
#endif
{0, {0, 0, 0}},
};
@ -810,7 +807,3 @@ ColorInfo colorForName(const char *name) {
}
return ColorInfo();
}
const ColorInfo* allColors() {
return color_data;
}

View File

@ -1,10 +1,9 @@
#pragma once
#include <FastLED.h>
#include "FastLED/FastLED.h"
typedef struct ColorInfo {
const char *name;
CRGB rgb;
NSFastLED::CRGB rgb;
} ColorInfo;
ColorInfo colorForName(const char *name);
const ColorInfo* allColors();

Binary file not shown.

View File

@ -0,0 +1,23 @@
#include "Particle.h"
#include "./Buttons.h"
void
Buttons::onStart()
{
for(int i = 0; i < 3; i++) {
m_buttons[i].attach(2 + i, INPUT_PULLDOWN);
m_buttons[i].interval(15);
}
}
InputEvent
Buttons::read()
{
for(int i = 0; i < 3; i++) {
m_buttons[i].update();
if (m_buttons[i].fell()) {
return InputEvent{m_buttonMap[i]};
}
}
return InputEvent{};
}

View File

@ -1,12 +1,12 @@
#pragma once
#include <Input.h>
#include "../Figments/Input.h"
class Bounce {
public:
void attach(int pin, int buttonPinMode) {
void attach(int pin, PinMode buttonPinMode) {
m_pin = pin;
pinMode(m_pin, buttonPinMode);
//Log.info("Attaching a button to %d", pin);
Log.info("Attaching a button to %d", pin);
}
void update() {
@ -15,27 +15,24 @@ public:
if (readResult == HIGH) {
m_state = Started;
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) {
if (readResult == HIGH) {
m_state = Confirmed;
//Log.info("Button %d is CONFIRMED!", m_pin);
Log.info("Button %d is CONFIRMED!", m_pin);
} else {
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) {
if (readResult == LOW) {
//Log.info("Button %d is released", m_pin);
m_state = Released;
m_state = Ready;
Log.info("Button %d is released and back to ready!", m_pin);
} else if (m_state == Confirmed) {
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;
}
}
@ -47,10 +44,6 @@ public:
return m_state == Confirmed;
}
bool rose() const {
return m_state == Released;
}
bool held() const {
return m_state == Held;
}
@ -60,8 +53,7 @@ private:
Ready,
Started,
Confirmed,
Held,
Released
Held
};
State m_state = Ready;
@ -72,23 +64,10 @@ private:
class Buttons: public InputSource {
public:
Buttons() : InputSource("Buttons") {}
void onStart() 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:
Bounce m_buttons[3];
Chord m_buttonMap[3] = {Circle, Triangle, Cross};
bool m_wasChord[3] = {false, false, false};
InputEvent::Intent m_buttonMap[3] = {InputEvent::PowerToggle, InputEvent::NextPattern, InputEvent::UserInput};
};

View File

@ -1,5 +1,5 @@
#include "CloudStatus.h"
#include "../../../Static.h"
#include "../Static.h"
void
CloudStatus::onStart()
@ -19,11 +19,7 @@ CloudStatus::initNetwork(system_event_t event, int param) {
if (param == cloud_status_connected) {
Log.info("Connected to T H E C L O U D");
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});
}
}
STATIC_ALLOC(CloudStatus);
STATIC_TASK(CloudStatus);

View File

@ -1,4 +1,4 @@
#include "../../../Figments/Figments.h"
#include "../Figments/Figments.h"
class CloudStatus: public Task {
public:

View File

@ -0,0 +1,20 @@
#include "../Figments/Figments.h"
template<int Period>
class ColorSequenceInput: public InputSource {
public:
ColorSequenceInput(const std::vector<NSFastLED::CRGB> &colors, const char* name, Task::State initialState)
: InputSource(name, initialState), m_colors(colors) {}
InputEvent read() override {
EVERY_N_SECONDS(Period) {
m_idx %= m_colors.size();
return InputEvent{InputEvent::SetColor, m_colors[m_idx++]};
}
return InputEvent{};
}
private:
std::vector<NSFastLED::CRGB> m_colors;
int m_idx = 0;
};

94
firmware/inputs/MPU6050.h Normal file
View File

@ -0,0 +1,94 @@
class MPU5060: public InputSource {
const int ACCEL_XOUT_HIGH = 0x3B;
const int ACCEL_XOUT_LOW = 0x3C;
const int ACCEL_YOUT_HIGH = 0x3D;
const int ACCEL_YOUT_LOW = 0x3E;
const int ACCEL_ZOUT_HIGH = 0x3F;
const int ACCEL_ZOUT_LOW = 0x40;
const int I2C_ADDRESS = 0x68;
const int PWR_MGMT_1 = 0x6B;
const int CONFIG_REG = 0x1A;
const int ACCEL_CONFIG_REG = 0x1C;
public:
void onStart() override {
Wire.begin();
// Turn on the sensor
Wire.beginTransmission(I2C_ADDRESS);
Wire.write(PWR_MGMT_1);
Wire.write(0);
Wire.endTransmission(true);
// Configure the filter
Wire.beginTransmission(I2C_ADDRESS);
Wire.write(CONFIG_REG);
Wire.write(3);
Wire.endTransmission(true);
// Configure the accel range
Wire.beginTransmission(I2C_ADDRESS);
Wire.write(ACCEL_CONFIG_REG);
// 4G
Wire.write(2 << 3);
Wire.endTransmission(true);
}
void onStop() override {
Wire.beginTransmission(I2C_ADDRESS);
// Turn off the sensor
Wire.write(PWR_MGMT_1);
Wire.write(1);
Wire.endTransmission(true);
Wire.end();
}
InputEvent read() override {
EVERY_N_MILLISECONDS(5) {
Wire.beginTransmission(I2C_ADDRESS);
Wire.write(ACCEL_XOUT_HIGH);
Wire.endTransmission(false);
Wire.requestFrom(I2C_ADDRESS, 6);
const int16_t accelX = Wire.read() << 8 | Wire.read();
const int16_t accelY = Wire.read() << 8 | Wire.read();
const int16_t accelZ = Wire.read() << 8 | Wire.read();
const uint16_t accelSum = abs(accelX) + abs(accelY) + abs(accelZ);
const uint16_t delta = abs(m_value.value() - accelSum);
m_value.add(accelSum);
if (delta > 32) {
return InputEvent{InputEvent::Acceleration, delta};
}
}
return InputEvent{};
}
template<typename T, uint8_t Size = 8>
struct Averager {
std::array<T, Size> buf;
unsigned int idx = 0;
unsigned int count = 0;
void add(const T &value) {
buf[idx] = value;
idx = (idx + 1) % Size;
if (count < Size) {
count += 1;
}
}
T value() const {
if (count == 0) {
return T{};
}
long long int sum = 0;
for(unsigned int i = 0; i < count; i++) {
sum += buf[i];
}
return sum / count;
}
};
Averager<int16_t, 32> m_value;
};

View File

@ -1,14 +1,13 @@
#include "Particle.h"
#include "../../../Figments/Figments.h"
#include "../../../colors.h"
#include "../../../Static.h"
#include "../Figments/Figments.h"
#include "../colors.h"
#include "../Static.h"
#include "./Photon.h"
void
PhotonInput::onConnected()
{
Log.info("Connecting photon input...");
Particle.function("save", &PhotonInput::save, this);
Particle.function("power", &PhotonInput::setPower, this);
Particle.function("next", &PhotonInput::nextPattern, this);
Particle.function("input", &PhotonInput::input, this);
@ -19,16 +18,6 @@ PhotonInput::onConnected()
Particle.function("reboot", &PhotonInput::reboot, this);
Particle.function("start", &PhotonInput::startThing, this);
Particle.function("stop", &PhotonInput::stopThing, this);
//Log.info("Connecting photon configuration...");
Particle.function("pixelCount", &PhotonInput::setPixelCount, this);
Particle.function("startPixel", &PhotonInput::setStartPixel, this);
Particle.function("save", &PhotonInput::photonSave, this);
Particle.variable("pixelCount", m_pixelCountInt);
Particle.variable("startPixel", m_startPixelInt);
publishConfig();
}
int
@ -126,11 +115,6 @@ PhotonInput::previousPattern(String command)
return 0;
}
int
PhotonInput::save(String command) {
setEvent(InputEvent::SaveConfigurationRequest);
}
int
PhotonInput::setPower(String command)
{
@ -151,36 +135,4 @@ PhotonInput::setBrightness(String command)
return 0;
}
void
PhotonInput::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
PhotonInput::photonSave(String command)
{
setEvent(InputEvent::SaveConfiguration);
return 0;
}
int
PhotonInput::setPixelCount(String command)
{
m_pixelCount = command.toInt();
setEvent(InputEvent{InputEvent::SetDisplayLength, m_pixelCount})
return 0;
}
int
PhotonInput::setStartPixel(String command)
{
m_startPixel = command.toInt();
setEvent(InputEvent{InputEvent::SetDisplayLength, m_startPixel})
return 0;
}
STATIC_ALLOC(PhotonInput);
STATIC_TASK(PhotonInput);

View File

@ -1,5 +1,5 @@
#include "Particle.h"
#include "../../../Figments/Figments.h"
#include "../Figments/Figments.h"
class PhotonInput: public BufferedInputSource {
public:
@ -19,20 +19,11 @@ private:
int previousPattern(String command);
int setPower(String command);
int setBrightness(String command);
int save(String command);
int startThing(String command);
int stopThing(String command);
void publishConfig() const;
int photonSave(String command);
int setPixelCount(String command);
int setStartPixel(String command);
static void onReset(system_event_t event, int param);
static void onButtonClick(system_event_t event, int param);
static void onFirmwareUpdate(system_event_t event, int param);
int m_startPixel;
int m_pixelCount;
};

222
firmware/main.cpp Normal file
View File

@ -0,0 +1,222 @@
#include "FastLED/FastLED.h"
#include "Figments/Figments.h"
#include "PhotonTelemetry.h"
#include "Static.h"
#include "Config.h"
#include "colors.h"
#include "animations/Power.cpp"
#include "animations/SolidAnimation.cpp"
#include "animations/Chimes.cpp"
#include "animations/Flashlight.cpp"
#include "animations/UpdateStatus.h"
#include "inputs/Photon.h"
#include "inputs/ColorCycle.h"
#include "inputs/CloudStatus.h"
#include "inputs/MPU6050.h"
#include "inputs/Buttons.h"
SerialLogHandler logHandler;
using namespace NSFastLED;
#define LED_NUM 256
#define MAX_BRIGHTNESS 255
#define PSU_MILLIAMPS 4800
// Enable system thread, so rendering happens while booting
SYSTEM_THREAD(ENABLED);
// Setup FastLED and the display
CRGB leds[LED_NUM];
Display dpy(leds, LED_NUM, Static<ConfigService>::instance()->coordMap());
LinearCoordinateMapping neckMap{60, 0};
Display neckDisplay(leds, LED_NUM, &neckMap);
// Setup power management
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
FigmentFunc displayClip([](Display* dpy) {
auto coords = Static<ConfigService>::instance()->coordMap();
for(int i = 0; i < coords->startPixel; i++) {
dpy->pixelAt(i) %= 40;
}
for(int i = LED_NUM; i > coords->pixelCount + coords->startPixel; i--) {
dpy->pixelAt(i) %= 40;
}
});
ChimesAnimation chimes{Task::Stopped};
SolidAnimation solid{Task::Stopped};
DrainAnimation drain{Task::Running};
Flashlight flashlight{Task::Stopped};
// Render all layers to the displays
Renderer renderer{
//{&dpy, &neckDisplay},
{&dpy},
{
&chimes,
&drain,
&solid,
&flashlight,
Static<UpdateStatus>::instance(),
&displayClip,
&power,
}
};
// Photon telemetry needs a reference to the selected animation's name, so we
// set it up here
PhotonTelemetry telemetry;
// Cycle some random colors
ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped};
ColorSequenceInput<7> hackerbotsCycle{{colorForName("Purple").rgb}, "Hackerbots", Task::Stopped};
ColorSequenceInput<13> kierynCycle{{
colorForName("Cerulean").rgb,
colorForName("Electric Purple").rgb,
colorForName("Emerald").rgb,
colorForName("Sky Magenta").rgb
}, "Kieryn", Task::Running};
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::Running};
// Turn on,
MainLoop runner{{
// Load/update graphics configuration from EEPROM and Particle
Static<ConfigService>::instance(),
// Particle cloud status
Static<CloudStatus>::instance(),
// Monitor network state and provide particle API events
Static<PhotonInput>::instance(),
new MPU5060(),
new Buttons(),
// Periodic color inputs
&noisebridgeCycle,
&hackerbotsCycle,
&kierynCycle,
&rainbowCycle,
// Animations
&chimes,
&drain,
&solid,
&flashlight,
// Update UI layer
&power,
&displayClip,
Static<UpdateStatus>::instance(),
// Render everything
&renderer,
// Update photon telemetry
&telemetry,
}};
// Tune in,
void setup() {
Serial.begin(115200);
//while(!Serial.isConnected()) { Particle.process(); }
//Serial.println("Hello, there!");
Log.info("🐛 Booting Renderbug %s!", System.deviceID().c_str());
Log.info("🐞 I am built for %d LEDs running on %dmA", LED_NUM, PSU_MILLIAMPS);
Log.info("📡 Particle version %s", System.version().c_str());
Log.info("💡 Starting FastLED...");
FastLED.addLeds<NEOPIXEL, 6>(leds, LED_NUM);
Log.info("🌌 Starting Figment...");
Serial.flush();
runner.start();
Log.info("💽 %lu bytes of free RAM", System.freeMemory());
Log.info("🚀 Setup complete! Ready to rock and roll.");
Serial.flush();
}
// Drop out.
void loop() {
runner.loop();
}

67
firmware/sprites/Blob.h Normal file
View File

@ -0,0 +1,67 @@
#pragma once
class Blob {
uint16_t m_pos;
int8_t m_velocity;
uint8_t m_hue;
int16_t m_brightness;
uint8_t m_saturation;
int8_t m_fadeDir;
public:
Blob()
: m_pos(0),
m_velocity(1),
m_hue(0),
m_brightness(1),
m_saturation(200),
m_fadeDir(1) {}
void setSaturation(uint8_t v) {
m_saturation = v;
}
void setPos(uint16_t p) {
m_pos = p;
m_brightness = p % 120 + 1;
}
void setHue(uint8_t p) {
m_hue = p;
}
void setBrightness(uint8_t p) {
m_brightness = p;
}
void setVelocity(int8_t v) {
m_velocity = v;
}
void update() {
m_pos += m_velocity;
m_hue += 1;
m_brightness += m_fadeDir;
if (m_brightness >= 255 || m_brightness <= 0) {
m_fadeDir *= -1;
}
}
void render(Display* display) const {
const uint8_t width = 25;
auto map = display->coordinateMapping();
// Grab the physical pixel we'll start with
PhysicalCoordinates startPos = map->virtualToPhysicalCoords({m_pos, 0});
PhysicalCoordinates endPos = map->virtualToPhysicalCoords({m_pos + width, 0});
int scaledWidth = std::abs(endPos.x - startPos.x);
for(uint8_t i = 0;i < scaledWidth; i++) {
// Blobs desaturate towards their tail
NSFastLED::CHSV blobColor(m_hue, m_saturation, NSFastLED::quadwave8((i / (double)scaledWidth) * m_brightness));
PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0};
NSFastLED::CRGB src(display->pixelAt(pos));
display->pixelAt(pos) = NSFastLED::blend(NSFastLED::CRGB(blobColor), src, 200);
}
}
};

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