Compare commits
10 Commits
3b32d72d5e
...
f224bba2a0
Author | SHA1 | Date | |
---|---|---|---|
f224bba2a0 | |||
d14fa7fde1 | |||
0c9eb831dd | |||
75bf48756b | |||
439a456d1a | |||
10bbcd6786 | |||
a6534bcb20 | |||
9a3bf84214 | |||
92d5e73bd8 | |||
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
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
|||||||
bin/*
|
bin/*
|
||||||
*.bin
|
*.bin
|
||||||
|
.pio
|
||||||
|
*.swp
|
||||||
|
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
|
|
22
.woodpecker.yml
Normal file
22
.woodpecker.yml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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
Normal file
12
Pipfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
platformio = "*"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.10"
|
@ -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
|
|
@ -1,130 +0,0 @@
|
|||||||
#include "./Config.h"
|
|
||||||
#include "./Static.h"
|
|
||||||
|
|
||||||
HardwareConfig
|
|
||||||
HardwareConfig::load() {
|
|
||||||
HardwareConfig ret;
|
|
||||||
EEPROM.get(0, ret);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
HardwareConfig::save() {
|
|
||||||
HardwareConfig dataCopy{*this};
|
|
||||||
dataCopy.checksum = getCRC();
|
|
||||||
EEPROM.put(0, dataCopy);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
@ -1,53 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/FastSPI_LED2.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/LICENSE
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/PORTING.md
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/README.md
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/bitswap.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/chipsets.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/clockless_arm_stm32.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/color.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/controller.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/delay.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/dmx.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/docs
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/examples
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_arm_stm32.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_config.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin_arm_stm32.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_bitbang.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_dma.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_nop.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_ref.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_types.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/keywords.txt
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs_arm_stm32.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/noise.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/noise.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/pixeltypes.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/platforms
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/platforms.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.h
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/preview_changes.txt
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/release_notes.md
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/FastLED/firmware/wiring.cpp
|
|
@ -1,41 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
@ -1,51 +0,0 @@
|
|||||||
#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;
|
|
@ -1,24 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
#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}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.cpp
|
|
@ -1 +0,0 @@
|
|||||||
/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.h
|
|
@ -1,143 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
#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)
|
|
@ -1,77 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
@ -1,49 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
@ -1,40 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
@ -1,54 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
};
|
|
Binary file not shown.
@ -1,23 +0,0 @@
|
|||||||
#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{};
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
@ -1,94 +0,0 @@
|
|||||||
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;
|
|
||||||
};
|
|
@ -1,222 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -6,5 +6,5 @@
|
|||||||
uint8_t
|
uint8_t
|
||||||
AnimatedNumber::value() const
|
AnimatedNumber::value() const
|
||||||
{
|
{
|
||||||
return NSFastLED::lerp8by8(m_start, m_end, m_idx);
|
return lerp8by8(m_start, m_end, m_idx);
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FastLED/FastLED.h"
|
#include <FastLED.h>
|
||||||
#include "./Figment.h"
|
#include "./Figment.h"
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class Display;
|
class Display;
|
||||||
|
|
||||||
@ -24,6 +23,18 @@ 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() {}
|
||||||
AnimatedNumber(uint8_t v) : m_end(v) {}
|
AnimatedNumber(uint8_t v) : m_end(v) {}
|
||||||
|
|
||||||
@ -68,16 +79,16 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AnimatedRGB {
|
struct AnimatedRGB {
|
||||||
NSFastLED::CRGB start;
|
CRGB start;
|
||||||
NSFastLED::CRGB end;
|
CRGB end;
|
||||||
AnimatedNumber pos;
|
AnimatedNumber pos;
|
||||||
|
|
||||||
AnimatedRGB(const NSFastLED::CRGB& color)
|
AnimatedRGB(const CRGB& color)
|
||||||
: start(color), end(color) {}
|
: start(color), end(color) {}
|
||||||
|
|
||||||
AnimatedRGB() {}
|
AnimatedRGB() {}
|
||||||
|
|
||||||
AnimatedRGB& operator=(const NSFastLED::CRGB& rgb) {
|
AnimatedRGB& operator=(const CRGB& rgb) {
|
||||||
start = *this;
|
start = *this;
|
||||||
end = rgb;
|
end = rgb;
|
||||||
pos.set(0, 255);
|
pos.set(0, 255);
|
||||||
@ -88,11 +99,11 @@ struct AnimatedRGB {
|
|||||||
pos.update();
|
pos.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
operator NSFastLED::CRGB() const {
|
operator CRGB() const {
|
||||||
uint8_t red = NSFastLED::lerp8by8(start.red, end.red, pos);
|
uint8_t red = lerp8by8(start.red, end.red, pos);
|
||||||
uint8_t green = NSFastLED::lerp8by8(start.green, end.green, pos);
|
uint8_t green = lerp8by8(start.green, end.green, pos);
|
||||||
uint8_t blue = NSFastLED::lerp8by8(start.blue, end.blue, pos);
|
uint8_t blue = lerp8by8(start.blue, end.blue, pos);
|
||||||
return NSFastLED::CRGB(red, green, blue);
|
return CRGB(red, green, blue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1,8 +1,6 @@
|
|||||||
#include "Display.h"
|
#include "Display.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
using namespace NSFastLED;
|
|
||||||
|
|
||||||
int
|
int
|
||||||
Display::pixelCount() const
|
Display::pixelCount() const
|
||||||
{
|
{
|
@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "FastLED/FastLED.h"
|
#include <FastLED.h>
|
||||||
|
|
||||||
#include "Geometry.h"
|
#include "Geometry.h"
|
||||||
|
|
||||||
@ -15,19 +15,19 @@ struct LinearCoordinateMapping: CoordinateMapping {
|
|||||||
unsigned int startPixel = 0;
|
unsigned int startPixel = 0;
|
||||||
LinearCoordinateMapping() {}
|
LinearCoordinateMapping() {}
|
||||||
LinearCoordinateMapping(unsigned int count, unsigned int start) : pixelCount(count), startPixel(start) {}
|
LinearCoordinateMapping(unsigned int count, unsigned int start) : pixelCount(count), startPixel(start) {}
|
||||||
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const {
|
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override {
|
||||||
return VirtualCoordinates{(uint8_t)((localCoords.x) / pixelCount), 0};
|
return VirtualCoordinates{map8(localCoords.x, 0, pixelCount), 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const {
|
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override {
|
||||||
return PhysicalCoordinates{NSFastLED::scale8(pixelCount, virtualCoords.x), 0};
|
return PhysicalCoordinates{scale8(pixelCount, virtualCoords.x), 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override {
|
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override {
|
||||||
return localCoords.x + startPixel;
|
return localCoords.x + startPixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int physicalPixelCount() const {
|
unsigned int physicalPixelCount() const override {
|
||||||
return pixelCount;
|
return pixelCount;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -35,22 +35,22 @@ struct LinearCoordinateMapping: CoordinateMapping {
|
|||||||
class Display {
|
class Display {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Display(NSFastLED::CRGB* pixels, int pixelCount, const CoordinateMapping* map)
|
Display(CRGB* pixels, int pixelCount, const CoordinateMapping* map)
|
||||||
: m_pixels(pixels), m_pixelCount(pixelCount), m_coordMap(map) {}
|
: m_pixels(pixels), m_pixelCount(pixelCount), m_coordMap(map) {}
|
||||||
|
|
||||||
NSFastLED::CRGB& pixelAt(const PhysicalCoordinates coords);
|
CRGB& pixelAt(const PhysicalCoordinates coords);
|
||||||
NSFastLED::CRGB& pixelAt(const VirtualCoordinates coords);
|
CRGB& pixelAt(const VirtualCoordinates coords);
|
||||||
NSFastLED::CRGB& pixelAt(int idx);
|
CRGB& pixelAt(int idx);
|
||||||
|
|
||||||
int pixelCount() const;
|
int pixelCount() const;
|
||||||
NSFastLED::CRGB* pixelBacking() const;
|
CRGB* pixelBacking() const;
|
||||||
const CoordinateMapping* coordinateMapping() const;
|
const CoordinateMapping* coordinateMapping() const;
|
||||||
void clear();
|
void clear();
|
||||||
void clear(const NSFastLED::CRGB& color);
|
void clear(const CRGB& color);
|
||||||
void clear(VirtualCoordinates& start, VirtualCoordinates& end, const NSFastLED::CRGB& color);
|
void clear(VirtualCoordinates& start, VirtualCoordinates& end, const CRGB& color);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NSFastLED::CRGB* m_pixels;
|
CRGB* m_pixels;
|
||||||
int m_pixelCount;
|
int m_pixelCount;
|
||||||
const CoordinateMapping* m_coordMap;
|
const CoordinateMapping* m_coordMap;
|
||||||
};
|
};
|
@ -1,14 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "application.h"
|
#include <Arduino.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
class Display;
|
class Display;
|
||||||
class InputEvent;
|
class InputEvent;
|
||||||
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() {};
|
||||||
|
|
||||||
@ -18,23 +22,31 @@ struct Task {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Task() {}
|
Task() {}
|
||||||
Task(State initialState) : Task(0, initialState) {}
|
explicit Task(State initialState) : Task(0, initialState) {}
|
||||||
Task(const char* name) : Task(name, Running) {}
|
explicit Task(const char* name) : Task(name, Running) {}
|
||||||
Task(const char* name, State initialState) : name(name), state(initialState) {}
|
Task(const char* name, State initialState) : name(name), state(initialState) {}
|
||||||
|
|
||||||
void start() { Log.info("* Starting %s...", name); state = Running; onStart(); }
|
void start() { state = Running; onStart(); }
|
||||||
void stop() { Log.info("* Stopping %s...", name); onStop(); state = Stopped; }
|
void stop() { onStop(); state = Stopped; }
|
||||||
|
virtual bool isFigment() const { return false; }
|
||||||
|
|
||||||
const char* name = 0;
|
const char* name = "";
|
||||||
State state = Running;
|
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 {
|
struct Figment: public Task {
|
||||||
Figment() : Task() {}
|
Figment() : Task() {}
|
||||||
Figment(State initialState) : Task(initialState) {}
|
explicit Figment(State initialState) : Task(initialState) {}
|
||||||
Figment(const char* name) : Task(name) {}
|
explicit 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 {
|
@ -12,8 +12,8 @@ struct VirtualCoordinates: Coordinates<uint8_t> {
|
|||||||
VirtualCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {}
|
VirtualCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PhysicalCoordinates: Coordinates<uint8_t> {
|
struct PhysicalCoordinates: Coordinates<uint16_t> {
|
||||||
PhysicalCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {}
|
PhysicalCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T> struct Vector3d {
|
template<typename T> struct Vector3d {
|
100
lib/Figments/Input.cpp
Normal file
100
lib/Figments/Input.cpp
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "./Input.h"
|
||||||
|
#include "./MainLoop.h"
|
||||||
|
|
||||||
|
CRGB
|
||||||
|
Variant::asRGB() const
|
||||||
|
{
|
||||||
|
return 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Variant::asBool() const
|
||||||
|
{
|
||||||
|
return (bool)m_value.asInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputSource::init()
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
m_queue = xQueueCreate(32, sizeof(InputEvent));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
void
|
||||||
|
InputSource::readThread(void* data)
|
||||||
|
{
|
||||||
|
InputSource* self = static_cast<InputSource*>(data);
|
||||||
|
while(true) {
|
||||||
|
InputEvent evt = self->read();
|
||||||
|
if (evt.intent != InputEvent::None) {
|
||||||
|
xQueueSend(m_queue, &evt, 0)
|
||||||
|
}
|
||||||
|
taskYIELD();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
InputSource::onStart()
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
m_threadLoop = MainLoop::instance();
|
||||||
|
xTaskCreate(
|
||||||
|
&InputSource::readThread,
|
||||||
|
name,
|
||||||
|
1000,
|
||||||
|
this,
|
||||||
|
1,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputSource::loop()
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_THREADED_INPUTS
|
||||||
|
MainLoop::instance()->dispatch(read());
|
||||||
|
#else
|
||||||
|
InputEvent evt;
|
||||||
|
xQueueReceive(m_queue, &evt, 0);
|
||||||
|
if (evt.intent != InputEvent::None) {
|
||||||
|
MainLoop::instance()->dispatch(evt);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
InputEvent
|
||||||
|
BufferedInputSource::read()
|
||||||
|
{
|
||||||
|
InputEvent ret;
|
||||||
|
m_eventQueue.take(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BufferedInputSource::setEvent(InputEvent &&evt)
|
||||||
|
{
|
||||||
|
m_eventQueue.insert(std::move(evt));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BufferedInputSource::setEvent(InputEvent::Intent intent, Variant &&v)
|
||||||
|
{
|
||||||
|
m_eventQueue.insert(InputEvent{intent, std::move(v)});
|
||||||
|
}
|
202
lib/Figments/Input.h
Normal file
202
lib/Figments/Input.h
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "./Geometry.h"
|
||||||
|
#include "./Figment.h"
|
||||||
|
#include "./Ringbuf.h"
|
||||||
|
#include <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 CRGB &v)
|
||||||
|
: type(Color), m_value{.asRGB={v.r, v.g, v.b}} {}
|
||||||
|
|
||||||
|
Variant()
|
||||||
|
: type(Null) {}
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
|
||||||
|
const char* asString() const;
|
||||||
|
CRGB asRGB() const;
|
||||||
|
int asInt() const;
|
||||||
|
bool asBool() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
union {
|
||||||
|
int asInt;
|
||||||
|
const char* asString;
|
||||||
|
uint8_t asRGB[3];
|
||||||
|
} m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InputEvent: public Variant {
|
||||||
|
enum Intent {
|
||||||
|
// An empty non-event
|
||||||
|
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,
|
||||||
|
SetPower,
|
||||||
|
SetBrightness,
|
||||||
|
|
||||||
|
// Animation sequencing
|
||||||
|
PreviousPattern,
|
||||||
|
NextPattern,
|
||||||
|
SetPattern,
|
||||||
|
PreviousScene,
|
||||||
|
NextScene,
|
||||||
|
SetScene,
|
||||||
|
|
||||||
|
// Timekeeping
|
||||||
|
ScheduleChange,
|
||||||
|
Beat,
|
||||||
|
BeatDetect,
|
||||||
|
|
||||||
|
// Task management
|
||||||
|
StartThing,
|
||||||
|
StopThing,
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
SetDisplayOffset,
|
||||||
|
SetDisplayLength,
|
||||||
|
SetColor,
|
||||||
|
SaveConfigurationRequest,
|
||||||
|
|
||||||
|
// Firmware events
|
||||||
|
FirmwareUpdate,
|
||||||
|
|
||||||
|
ReadyToRoll,
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Value>
|
||||||
|
InputEvent(Intent s, Value v)
|
||||||
|
: Variant(v), intent(s) {}
|
||||||
|
|
||||||
|
InputEvent(Intent s)
|
||||||
|
: Variant(), intent(s) {}
|
||||||
|
|
||||||
|
InputEvent()
|
||||||
|
: Variant(), intent(None) {}
|
||||||
|
|
||||||
|
Intent intent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MainLoop;
|
||||||
|
|
||||||
|
class InputSource: public Task {
|
||||||
|
public:
|
||||||
|
InputSource() : Task() {init();}
|
||||||
|
explicit InputSource(const char* name) : Task(name) {init();}
|
||||||
|
explicit InputSource(Task::State initialState) : Task(initialState) {init();}
|
||||||
|
InputSource(const char* name, Task::State initialState) : Task(name, initialState) {init();}
|
||||||
|
void loop() override;
|
||||||
|
void onStart() override;
|
||||||
|
virtual InputEvent read() = 0;
|
||||||
|
private:
|
||||||
|
void init();
|
||||||
|
#ifdef CONFIG_THREADED_INPUTS
|
||||||
|
static void readThread(void* data);
|
||||||
|
MainLoop* m_threadLoop;
|
||||||
|
static uint8_t m_threadBuf[sizeof(InputEvent) * 32];
|
||||||
|
QueueHandle_t m_queue;
|
||||||
|
StaticQueue_t m_threadQueue;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
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(const char* name) : InputSource(name) {}
|
||||||
|
InputEvent read() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setEvent(InputEvent &&evt);
|
||||||
|
void setEvent(InputEvent::Intent intent, Variant &&v);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ringbuf<InputEvent, 12> m_eventQueue;
|
||||||
|
};
|
||||||
|
|
||||||
|
class InputMapper: public BufferedInputSource {
|
||||||
|
public:
|
||||||
|
InputMapper(std::function<InputEvent(const InputEvent)> f, const char* name) : BufferedInputSource(name), m_func(f) {}
|
||||||
|
|
||||||
|
void handleEvent(const InputEvent& evt) override {
|
||||||
|
setEvent(m_func(evt));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<InputEvent(const InputEvent)> m_func;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OnlineTaskMixin : public virtual Loopable {
|
||||||
|
public:
|
||||||
|
void handleEvent(const InputEvent &evt) override {
|
||||||
|
if (evt.intent == InputEvent::NetworkStatus) {
|
||||||
|
m_online = evt.asInt();
|
||||||
|
if (m_online) {
|
||||||
|
onOnline();
|
||||||
|
} else {
|
||||||
|
onOffline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_online) {
|
||||||
|
handleEventOnline(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void onOnline() {}
|
||||||
|
virtual void onOffline() {}
|
||||||
|
|
||||||
|
virtual void handleEventOnline(const InputEvent &evt) {}
|
||||||
|
|
||||||
|
void loop() override {
|
||||||
|
if (m_online) {
|
||||||
|
loopOnline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void loopOnline() {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_online = false;
|
||||||
|
};
|
90
lib/Figments/MainLoop.cpp
Normal file
90
lib/Figments/MainLoop.cpp
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#include "./MainLoop.h"
|
||||||
|
#include "./Input.h"
|
||||||
|
#include "./Figment.h"
|
||||||
|
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
MainLoop::dispatch(const InputEvent& evt)
|
||||||
|
{
|
||||||
|
if (evt.intent == InputEvent::None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_eventBuf.insert(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MainLoop::loop()
|
||||||
|
{
|
||||||
|
s_instance = this;
|
||||||
|
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())) {
|
||||||
|
if (jobState) {
|
||||||
|
Log.trace("Starting task %s", figmentJob->name);
|
||||||
|
figmentJob->start();
|
||||||
|
} else {
|
||||||
|
Log.trace("Stopping task %s", figmentJob->name);
|
||||||
|
figmentJob->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(Task* task : scheduler) {
|
||||||
|
if (evt.intent == InputEvent::SetPower) {
|
||||||
|
Log.notice("Event %s", task->name);
|
||||||
|
}
|
||||||
|
task->handleEvent(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsigned int slowest = 0;
|
||||||
|
unsigned int frameSpeed = 0;
|
||||||
|
unsigned int frameStart = millis();
|
||||||
|
unsigned int taskCount = 0;
|
||||||
|
Task* slowestTask = NULL;
|
||||||
|
for(Task* task : scheduler) {
|
||||||
|
//unsigned int start = millis();
|
||||||
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
|
unsigned int start = ESP.getCycleCount();
|
||||||
|
#else
|
||||||
|
unsigned int start = millis();
|
||||||
|
#endif
|
||||||
|
Log.verbose("Running %s", task->name);
|
||||||
|
task->loop();
|
||||||
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
|
unsigned int runtime = (ESP.getCycleCount() - start) / 160000;
|
||||||
|
#else
|
||||||
|
unsigned int runtime = millis() - start;
|
||||||
|
#endif
|
||||||
|
frameSpeed += runtime;
|
||||||
|
taskCount++;
|
||||||
|
if (runtime > slowest) {
|
||||||
|
slowest = runtime;
|
||||||
|
slowestTask = task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameSpeed = millis() - frameStart;
|
||||||
|
if (frameSpeed >= 23) {
|
||||||
|
const char* slowestName = (slowestTask->name ? slowestTask->name : "(Unnamed)");
|
||||||
|
Log.warning("Slow frame: %dms, %d tasks, longest task %s was %dms", frameSpeed, taskCount, slowestTask->name, slowest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MainLoop::start()
|
||||||
|
{
|
||||||
|
s_instance = this;
|
||||||
|
Log.notice("*** Starting %d tasks...", scheduler.tasks.size());
|
||||||
|
Serial.flush();
|
||||||
|
for(auto task: scheduler) {
|
||||||
|
Log.notice("** Starting %s", task->name);
|
||||||
|
task->start();
|
||||||
|
}
|
||||||
|
dispatch(InputEvent::ReadyToRoll);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainLoop* MainLoop::s_instance;
|
@ -3,6 +3,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "./Input.h"
|
#include "./Input.h"
|
||||||
|
#include "./Ringbuf.h"
|
||||||
|
|
||||||
class Task;
|
class Task;
|
||||||
class InputSource;
|
class InputSource;
|
||||||
@ -50,48 +51,11 @@ struct Scheduler {
|
|||||||
iterator end() { return iterator(*this, tasks.size()); }
|
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 {
|
struct MainLoop {
|
||||||
Scheduler scheduler;
|
Scheduler scheduler;
|
||||||
|
|
||||||
MainLoop(std::vector<Task*> &&tasks)
|
MainLoop(std::vector<Task*> &&tasks)
|
||||||
: scheduler(std::move(tasks)) {s_instance = this;}
|
: scheduler(std::move(tasks)) {}
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void loop();
|
void loop();
|
||||||
@ -99,7 +63,7 @@ struct MainLoop {
|
|||||||
static MainLoop* instance() { return s_instance; }
|
static MainLoop* instance() { return s_instance; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ringbuf<InputEvent, 10> m_eventBuf;
|
Ringbuf<InputEvent, 32> m_eventBuf;
|
||||||
|
|
||||||
static MainLoop* s_instance;
|
static MainLoop* s_instance;
|
||||||
};
|
};
|
12
lib/Figments/Perfcounter.h
Normal file
12
lib/Figments/Perfcounter.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
|
struct PerfCounter {
|
||||||
|
PerfCounter(const char* name) {}
|
||||||
|
~PerfCounter() {}
|
||||||
|
/*PerfCounter(const char* name) : start(millis()), name(name) {}
|
||||||
|
~PerfCounter() {Log.notice("%s: %d", name, millis() - start);}*/
|
||||||
|
uint16_t start;
|
||||||
|
const char* name;
|
||||||
|
};
|
39
lib/Figments/Renderer.cpp
Normal file
39
lib/Figments/Renderer.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#include "./Renderer.h"
|
||||||
|
#include "./Display.h"
|
||||||
|
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
Renderer::loop()
|
||||||
|
{
|
||||||
|
for(Display* dpy : m_displays) {
|
||||||
|
for(Figment* figment : m_figments) {
|
||||||
|
if (figment->state == Task::Running) {
|
||||||
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
|
unsigned int frameStart = ESP.getCycleCount();
|
||||||
|
#endif
|
||||||
|
Log.verbose("Render %s", figment->name);
|
||||||
|
figment->render(dpy);
|
||||||
|
#if defined(BOARD_ESP32) or defined(BOARD_ESP8266)
|
||||||
|
unsigned int runtime = (ESP.getCycleCount() - frameStart) / 160000;
|
||||||
|
#else
|
||||||
|
unsigned int runtime = 0;
|
||||||
|
#endif
|
||||||
|
if (runtime >= 8) {
|
||||||
|
Log.warning("SLOW RENDER: %s took %dms!", figment->name, runtime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
FastLED.show();
|
||||||
|
FastLED.countFPS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Renderer::onStart()
|
||||||
|
{
|
||||||
|
for(Display* dpy : m_displays) {
|
||||||
|
dpy->clear();
|
||||||
|
}
|
||||||
|
FastLED.show();
|
||||||
|
}
|
@ -5,7 +5,7 @@ class Display;
|
|||||||
|
|
||||||
struct Renderer: public Task {
|
struct Renderer: public Task {
|
||||||
public:
|
public:
|
||||||
Renderer(std::vector<Display*> displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(displays) {}
|
Renderer(std::vector<Display*>&& displays, const std::vector<Figment*> &figments) : Task("Renderer"), m_figments(figments), m_displays(std::move(displays)) {}
|
||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void onStart() override;
|
void onStart() override;
|
70
lib/Figments/Ringbuf.h
Normal file
70
lib/Figments/Ringbuf.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
template<typename T, int Size>
|
||||||
|
struct Ringbuf {
|
||||||
|
Ringbuf() : m_head(0), m_tail(0) {}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
m_head = 0;
|
||||||
|
m_tail = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
T peek(int offset) const {
|
||||||
|
const int nextHead = (m_head + offset) % Size;
|
||||||
|
return m_items[nextHead];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(T(&dest)[Size]) {
|
||||||
|
int i = 0;
|
||||||
|
size_t ret = 0;
|
||||||
|
while(take(dest[i])) {
|
||||||
|
i++;
|
||||||
|
ret += sizeof(T);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(Print& stream) {
|
||||||
|
T val;
|
||||||
|
size_t ret = 0;
|
||||||
|
while(take(val)) {
|
||||||
|
stream.write(val);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() {
|
||||||
|
if (m_tail > m_head) {
|
||||||
|
return m_tail - m_head;
|
||||||
|
}
|
||||||
|
return m_tail + (Size - m_head);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int m_head = 0;
|
||||||
|
int m_tail = 0;
|
||||||
|
std::array<T, Size> m_items;
|
||||||
|
};
|
||||||
|
|
73
lib/Figments/Surface.cpp
Normal file
73
lib/Figments/Surface.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#include "./Surface.h"
|
||||||
|
#include "./Display.h"
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
#include "Perfcounter.h"
|
||||||
|
|
||||||
|
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end)
|
||||||
|
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
|
||||||
|
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
|
||||||
|
virtStart(start),
|
||||||
|
virtEnd(end),
|
||||||
|
m_display(dpy)
|
||||||
|
{
|
||||||
|
//assert(start.x <= end.x);
|
||||||
|
//assert(start.y <= end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end, uint8_t rotation)
|
||||||
|
: start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)),
|
||||||
|
end(dpy->coordinateMapping()->virtualToPhysicalCoords(end)),
|
||||||
|
virtStart(start),
|
||||||
|
virtEnd(end),
|
||||||
|
m_display(dpy),
|
||||||
|
m_rotation(rotation)
|
||||||
|
{
|
||||||
|
//assert(start.x <= end.x);
|
||||||
|
//assert(start.y <= end.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface&
|
||||||
|
Surface::operator=(const CRGB& color)
|
||||||
|
{
|
||||||
|
paintWith([&](CRGB& pixel) {
|
||||||
|
pixel = color;
|
||||||
|
});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface&
|
||||||
|
Surface::operator+=(const CRGB& color)
|
||||||
|
{
|
||||||
|
paintWith([&](CRGB& pixel) {
|
||||||
|
pixel += color;
|
||||||
|
});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::paintWith(std::function<void(CRGB&)> func)
|
||||||
|
{
|
||||||
|
paintShader([=](CRGB& pixel, const VirtualCoordinates&, const PhysicalCoordinates&, const VirtualCoordinates&){ func(pixel); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::paintShader(Surface::Shader shader)
|
||||||
|
{
|
||||||
|
PerfCounter _("paintShader");
|
||||||
|
const uint16_t width = end.x - start.x + 1;
|
||||||
|
const uint16_t height = end.y - start.y + 1;
|
||||||
|
const uint8_t xMod = 255 / width;
|
||||||
|
const uint8_t yMod = 255 / height;
|
||||||
|
for(auto x = 0; x < width; x++) {
|
||||||
|
for(auto y = 0; y < height; y++) {
|
||||||
|
PhysicalCoordinates coords{x + start.x, y + start.y};
|
||||||
|
VirtualCoordinates virtCoords{m_display->coordinateMapping()->physicalToVirtualCoords(coords)};
|
||||||
|
VirtualCoordinates surfaceCoords{xMod * x, yMod * y};
|
||||||
|
//Log.notice("width=%d height=%d vx=%d vy=%d sx=%d sy=%d x=%d y=%d px=%d py=%d", width, height, start.x, start.y, x, y, coords.x, coords.y);
|
||||||
|
// 256 = 1.0
|
||||||
|
// 128 = 0.0
|
||||||
|
// 0 = 1.0
|
||||||
|
shader(m_display->pixelAt(coords), virtCoords, coords, surfaceCoords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
lib/Figments/Surface.h
Normal file
38
lib/Figments/Surface.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <FastLED.h>
|
||||||
|
#include "./Geometry.h"
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class Display;
|
||||||
|
|
||||||
|
class Surface {
|
||||||
|
public:
|
||||||
|
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end);
|
||||||
|
Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end, uint8_t rotation);
|
||||||
|
|
||||||
|
Surface& operator=(const CRGB& color);
|
||||||
|
Surface& operator+=(const CRGB& color);
|
||||||
|
template<typename T>
|
||||||
|
Surface& operator|=(const T& val) {
|
||||||
|
paintWith([&](CRGB& pixel) {
|
||||||
|
pixel |= val;
|
||||||
|
});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
using Shader = std::function<void(CRGB&, const VirtualCoordinates& virtPos, const PhysicalCoordinates& realPos, const VirtualCoordinates& surfacePos)>;
|
||||||
|
using BrushFunc = std::function<void(CRGB&)>;
|
||||||
|
|
||||||
|
void paintWith(BrushFunc func);
|
||||||
|
void paintShader(Shader shader);
|
||||||
|
|
||||||
|
const PhysicalCoordinates start;
|
||||||
|
const PhysicalCoordinates end;
|
||||||
|
const VirtualCoordinates virtStart;
|
||||||
|
const VirtualCoordinates virtEnd;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Display* m_display;
|
||||||
|
uint8_t m_rotation = 0;
|
||||||
|
};
|
8
lib/Figments/library.json
Normal file
8
lib/Figments/library.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "Figments",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "An embedded graphics rendering engine",
|
||||||
|
"keywords": ["FastLED", "esp32", "esp8266"],
|
||||||
|
"frameworks": ["arduino"],
|
||||||
|
"platforms": ["espressif32", "espressif8266"]
|
||||||
|
}
|
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
|
||||||
|
4
no_ota.csv
Normal file
4
no_ota.csv
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
nvs, data, nvs, 0x9000, 0x5000,
|
||||||
|
otadata, data, ota, 0xe000, 0x1000,
|
||||||
|
app, app, factory, 0x10000, 2M,
|
|
204
platformio.ini
Normal file
204
platformio.ini
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[common_env_data]
|
||||||
|
src_filter = "+<*> -<.git/> -<.svn/> -<platform/> -<inputs/>"
|
||||||
|
lib_ldf_mode = chain+
|
||||||
|
src_build_flags =
|
||||||
|
-DRENDERBUG_VERSION=3
|
||||||
|
-DRENDERBUG_LED_PIN=14
|
||||||
|
-DRENDERBUG_LED_PACKING=RGB
|
||||||
|
-DDEFAULT_PATTERN_INDEX=0
|
||||||
|
lib_deps_external =
|
||||||
|
fastled/FastLED@^3.4.0
|
||||||
|
thijse/ArduinoLog@1.0.3
|
||||||
|
bblanchon/ArduinoJson@^6.17.3
|
||||||
|
|
||||||
|
[config_u8display]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_U8DISPLAY
|
||||||
|
lib_deps =
|
||||||
|
olikraus/U8g2@2.28.8
|
||||||
|
src_filter = "+<platform/arduino/U8Display.cpp>"
|
||||||
|
|
||||||
|
[config_mqtt]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_MQTT
|
||||||
|
lib_deps =
|
||||||
|
knolleary/PubSubClient@^2.8.0
|
||||||
|
src_filter = "+<platform/arduino/MQTTTelemetry.cpp>"
|
||||||
|
|
||||||
|
[config_wifi]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_WIFI
|
||||||
|
src_filter = "+<platform/arduino/WiFiTask.cpp>"
|
||||||
|
|
||||||
|
[config_bluetooth]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_BLUETOOTH
|
||||||
|
src_filter = "+<platform/arduino/BluetoothSerialTelemetry.cpp>"
|
||||||
|
lib_deps =
|
||||||
|
BluetoothSerial
|
||||||
|
|
||||||
|
[config_ota]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_OTA
|
||||||
|
src_filter = "+<platform/arduino/OTA.cpp>"
|
||||||
|
lib_deps =
|
||||||
|
ArduinoOTA
|
||||||
|
ESP8266mDNS
|
||||||
|
|
||||||
|
[config_nocolor]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_NO_COLORDATA
|
||||||
|
|
||||||
|
[config_buttons]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_BUTTONS
|
||||||
|
src_filter = "+<inputs/Buttons.cpp>"
|
||||||
|
|
||||||
|
[config_mpu5060]
|
||||||
|
src_build_flags =
|
||||||
|
-DCONFIG_MPU5060
|
||||||
|
src_filter = "+<inputs/MPU6050.cpp>"
|
||||||
|
|
||||||
|
[env:teensy]
|
||||||
|
extends = config_nocolor
|
||||||
|
platform = teensy
|
||||||
|
board = teensy31
|
||||||
|
framework = arduino
|
||||||
|
src_build_flags =
|
||||||
|
${common_env_data.src_build_flags}
|
||||||
|
${config_nocolor.src_build_flags}
|
||||||
|
-DPLATFORM_ARDUINO
|
||||||
|
-DBOARD_TEENSY
|
||||||
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_external}
|
||||||
|
src_filter = "${common_env_data.src_filter}"
|
||||||
|
|
||||||
|
[env:bike_teensy]
|
||||||
|
extends = env:teensy
|
||||||
|
src_build_flags=
|
||||||
|
${env:teensy.src_build_flags}
|
||||||
|
-DRENDERBUG_LED_PIN=11
|
||||||
|
-DRENDERBUG_LED_PACKING=GRB
|
||||||
|
-DDEFAULT_PATTERN_INDEX=1
|
||||||
|
|
||||||
|
[env:bike]
|
||||||
|
extends = env:esp32, config_u8display
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_u8display.src_filter}"
|
||||||
|
lib_deps =
|
||||||
|
${env:esp32.lib_deps}
|
||||||
|
${config_u8display.lib_deps}
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp32.src_build_flags}
|
||||||
|
${config_u8display.src_build_flags}
|
||||||
|
build_type = debug
|
||||||
|
|
||||||
|
[env:bike_ble]
|
||||||
|
extends = env:bike
|
||||||
|
lib_deps =
|
||||||
|
${env:bike.lib_deps}
|
||||||
|
nkolban/ESP32 BLE Arduino@1.0.1
|
||||||
|
src_build_flags =
|
||||||
|
${env:bike.src_build_flags}
|
||||||
|
|
||||||
|
[env:esp32]
|
||||||
|
extends = config_nocolor
|
||||||
|
platform = espressif32
|
||||||
|
board = featheresp32
|
||||||
|
framework = arduino
|
||||||
|
board_build.filesystem = littlefs
|
||||||
|
src_build_flags =
|
||||||
|
${common_env_data.src_build_flags}
|
||||||
|
${config_nocolor.src_build_flags}
|
||||||
|
-DPLATFORM_ARDUINO
|
||||||
|
-DBOARD_ESP32
|
||||||
|
; -DCONFIG_THREADED_INPUTS
|
||||||
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_external}
|
||||||
|
src_filter = "${common_env_data.src_filter}"
|
||||||
|
board_build.partitions = no_ota.csv
|
||||||
|
|
||||||
|
[env:esp8266-12f]
|
||||||
|
extends = env:esp8266
|
||||||
|
board = esp12e
|
||||||
|
|
||||||
|
[env:esp8266]
|
||||||
|
platform = espressif8266
|
||||||
|
board = huzzah
|
||||||
|
framework = arduino
|
||||||
|
booard_build.filesystem = littlefs
|
||||||
|
src_build_flags =
|
||||||
|
${common_env_data.src_build_flags}
|
||||||
|
-DPLATFORM_ARDUINO
|
||||||
|
-DBOARD_ESP8266
|
||||||
|
-DCORE_DEBUG_LEVEL=5
|
||||||
|
-fstack-protector
|
||||||
|
lib_deps =
|
||||||
|
${common_env_data.lib_deps_external}
|
||||||
|
arduino-libraries/NTPClient@^3.1.0
|
||||||
|
src_filter = "${common_env_data.src_filter}"
|
||||||
|
|
||||||
|
[env:cyberplague]
|
||||||
|
extends = env:esp32, config_bluetooth
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_bluetooth.src_filter}"
|
||||||
|
lib_deps =
|
||||||
|
${env:esp32.lib_deps}
|
||||||
|
${config_bluetooth.lib_deps}
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp32.src_build_flags}
|
||||||
|
${config_bluetooth.src_build_flags}
|
||||||
|
-DRENDERBUG_LED_PIN=13
|
||||||
|
-DDEFAULT_PATTERN_INDEX=1
|
||||||
|
|
||||||
|
[env:cyberplague_wifi]
|
||||||
|
extends = env:esp32, config_wifi, config_mqtt
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
|
||||||
|
lib_deps =
|
||||||
|
${env:esp32.lib_deps}
|
||||||
|
${config_mqtt.lib_deps}
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp32.src_build_flags}
|
||||||
|
${config_mqtt.src_build_flags}
|
||||||
|
${config_wifi.src_build_flags}
|
||||||
|
|
||||||
|
[env:prototype]
|
||||||
|
extends = env:esp32, config_buttons, config_mpu5060
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_buttons.src_filter} ${config_mpu5060.src_filter}"
|
||||||
|
|
||||||
|
[env:home_lighting]
|
||||||
|
extends = env:esp8266, config_wifi, config_mqtt, config_ota
|
||||||
|
src_filter = "${env:esp32.src_filter} ${config_ota.src_filter} ${config_wifi.src_filter} ${config_mqtt.src_filter}"
|
||||||
|
src_build_flags =
|
||||||
|
${env:esp8266.src_build_flags}
|
||||||
|
${config_mqtt.src_build_flags}
|
||||||
|
${config_wifi.src_build_flags}
|
||||||
|
${config_ota.src_build_flags}
|
||||||
|
lib_deps =
|
||||||
|
${env:esp8266.lib_deps}
|
||||||
|
${config_mqtt.lib_deps}
|
||||||
|
ESP8266WiFi
|
||||||
|
${config_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:home_lighting_grb]
|
||||||
|
extends = env:home_lighting
|
||||||
|
src_build_flags =
|
||||||
|
${env:home_lighting.src_build_flags}
|
||||||
|
-DRENDERBUG_LED_PACKING=GRB
|
||||||
|
|
||||||
|
[env:home_lighting-12f]
|
||||||
|
extends = env:home_lighting
|
||||||
|
board = esp12e
|
||||||
|
|
||||||
|
;[env:photon]
|
||||||
|
;platform = particlephoton
|
||||||
|
;board = photon
|
||||||
|
;framework = arduino
|
69
src/BootOptions.cpp
Normal file
69
src/BootOptions.cpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#include "BootOptions.h"
|
||||||
|
#ifdef BOARD_ESP8266
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#endif
|
||||||
|
#include <EEPROM.h>
|
||||||
|
#include "Config.h"
|
||||||
|
|
||||||
|
#ifdef PLATFORM_PHOTON
|
||||||
|
LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND);
|
||||||
|
LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT);
|
||||||
|
retained bool LAST_BOOT_WAS_FLASH;
|
||||||
|
retained bool LAST_BOOT_WAS_SERIAL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
BootOptions::initPins()
|
||||||
|
{
|
||||||
|
#ifdef PLATFORM_PHOTON
|
||||||
|
pinMode(2, INPUT_PULLDOWN);
|
||||||
|
pinMode(3, INPUT_PULLDOWN);
|
||||||
|
pinMode(4, INPUT_PULLDOWN);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
BootOptions::BootOptions()
|
||||||
|
{
|
||||||
|
#ifdef PLATFORM_PHOTON
|
||||||
|
isSetup = digitalRead(2) == HIGH;
|
||||||
|
isSerial = digitalRead(3) == HIGH || LAST_BOOT_WAS_SERIAL;
|
||||||
|
isFlash = digitalRead(4) == HIGH;
|
||||||
|
|
||||||
|
LAST_BOOT_WAS_FLASH = isFlash;
|
||||||
|
LAST_BOOT_WAS_SERIAL |= isSerial;
|
||||||
|
lastBootWasFlash = LAST_BOOT_WAS_FLASH;
|
||||||
|
|
||||||
|
configStatus.setActive(isSetup);
|
||||||
|
serialStatus.setActive(isSerial);
|
||||||
|
#endif
|
||||||
|
#ifdef BOARD_ESP8266
|
||||||
|
struct rst_info resetInfo = *ESP.getResetInfoPtr();
|
||||||
|
uint8_t crashCount;
|
||||||
|
EEPROM.begin(sizeof(crashCount));
|
||||||
|
EEPROM.get(sizeof(HardwareConfig) + 32, crashCount);
|
||||||
|
EEPROM.end();
|
||||||
|
if (resetInfo.reason == REASON_WDT_RST) {
|
||||||
|
if (crashCount++ >= 3) {
|
||||||
|
// Boot into safe mode if the watchdog reset us three times in a row.
|
||||||
|
isSafeMode = true;
|
||||||
|
} else {
|
||||||
|
EEPROM.begin(sizeof(crashCount));
|
||||||
|
EEPROM.put(sizeof(HardwareConfig) + 32, crashCount);
|
||||||
|
EEPROM.end();
|
||||||
|
}
|
||||||
|
} else if (crashCount != 0) {
|
||||||
|
crashCount = 0;
|
||||||
|
EEPROM.begin(sizeof(crashCount));
|
||||||
|
EEPROM.put(sizeof(HardwareConfig) + 32, crashCount);
|
||||||
|
EEPROM.end();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BootOptions::waitForRelease()
|
||||||
|
{
|
||||||
|
#ifdef PLATFORM_PHOTON
|
||||||
|
while(digitalRead(2) == HIGH || digitalRead(3) == HIGH) {};
|
||||||
|
#endif
|
||||||
|
}
|
15
src/BootOptions.h
Normal file
15
src/BootOptions.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct BootOptions {
|
||||||
|
static void initPins();
|
||||||
|
|
||||||
|
BootOptions();
|
||||||
|
|
||||||
|
void waitForRelease();
|
||||||
|
|
||||||
|
bool isSetup = false;
|
||||||
|
bool isSerial = false;
|
||||||
|
bool isFlash = false;
|
||||||
|
bool lastBootWasFlash = false;
|
||||||
|
bool isSafeMode = false;
|
||||||
|
};
|
116
src/Config.cpp
Normal file
116
src/Config.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include "./Config.h"
|
||||||
|
#include "./Static.h"
|
||||||
|
#include <ArduinoLog.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
constexpr uint16_t HardwareConfig::MAX_LED_NUM;
|
||||||
|
|
||||||
|
HardwareConfig
|
||||||
|
HardwareConfig::load() {
|
||||||
|
HardwareConfig ret;
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
|
EEPROM.begin(sizeof(ret));
|
||||||
|
#endif
|
||||||
|
EEPROM.get(0, ret);
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
|
EEPROM.end();
|
||||||
|
#endif
|
||||||
|
Log.notice("Loaded config version %d, CRC %d", ret.version, ret.checksum);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
HardwareConfig::save() {
|
||||||
|
HardwareConfig dataCopy{*this};
|
||||||
|
dataCopy.checksum = getCRC();
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
|
EEPROM.begin(sizeof(dataCopy));
|
||||||
|
#endif
|
||||||
|
EEPROM.put(0, dataCopy);
|
||||||
|
#ifndef BOARD_TEENSY
|
||||||
|
EEPROM.commit();
|
||||||
|
EEPROM.end();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearCoordinateMapping
|
||||||
|
HardwareConfig::toCoordMap() const
|
||||||
|
{
|
||||||
|
auto pixelCount = min(HardwareConfig::MAX_LED_NUM, std::max((uint16_t)1, data.pixelCount));
|
||||||
|
auto startPixel = min(pixelCount, std::max((uint16_t)1, data.startPixel));
|
||||||
|
return LinearCoordinateMapping{pixelCount, startPixel};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
HardwareConfig::isValid() const
|
||||||
|
{
|
||||||
|
return version == 2 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t
|
||||||
|
HardwareConfig::getCRC() const
|
||||||
|
{
|
||||||
|
const unsigned char* message = reinterpret_cast<const unsigned char*>(&data);
|
||||||
|
constexpr uint8_t length = sizeof(data);
|
||||||
|
unsigned char i, j, crc = 0;
|
||||||
|
for(i = 0; i < length; i++) {
|
||||||
|
crc ^= message[i];
|
||||||
|
for(j = 0; j < 8; j++) {
|
||||||
|
if (crc & 1) {
|
||||||
|
crc ^= CRC7_POLY;
|
||||||
|
}
|
||||||
|
crc >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ConfigService::onStart()
|
||||||
|
{
|
||||||
|
Log.notice("Starting configuration service...");
|
||||||
|
m_config = HardwareConfig::load();
|
||||||
|
if (m_config.isValid()) {
|
||||||
|
Log.notice("Configuration found!");
|
||||||
|
} else {
|
||||||
|
Log.notice("No configuration found. Writing defaults...");
|
||||||
|
m_config = HardwareConfig{};
|
||||||
|
m_config.save();
|
||||||
|
}
|
||||||
|
m_coordMap = m_config.toCoordMap();
|
||||||
|
|
||||||
|
Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ConfigService::loop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ConfigService::handleEvent(const InputEvent &evt)
|
||||||
|
{
|
||||||
|
switch(evt.intent) {
|
||||||
|
case InputEvent::SetDisplayLength:
|
||||||
|
//Log.info("Updating pixel count from %d to %d", m_coordMap.pixelCount, evt.asInt());
|
||||||
|
m_config.data.pixelCount = evt.asInt();
|
||||||
|
m_coordMap = m_config.toCoordMap();
|
||||||
|
//Log.info("Count is now %d", m_coordMap.pixelCount);
|
||||||
|
break;
|
||||||
|
case InputEvent::SetDisplayOffset:
|
||||||
|
//Log.info("Updating pixel offset from %d to %d", m_coordMap.startPixel, evt.asInt());
|
||||||
|
m_config.data.startPixel = evt.asInt();
|
||||||
|
m_coordMap = m_config.toCoordMap();
|
||||||
|
//Log.info("Offset is now %d", m_coordMap.startPixel);
|
||||||
|
break;
|
||||||
|
case InputEvent::SaveConfigurationRequest:
|
||||||
|
//Log.info("Saving configuration");
|
||||||
|
m_config.save();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STATIC_ALLOC(ConfigService);
|
||||||
|
STATIC_TASK(ConfigService);
|
110
src/Config.h
Normal file
110
src/Config.h
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Figments.h>
|
||||||
|
|
||||||
|
struct MaskCoordinateMapping : CoordinateMapping {
|
||||||
|
struct Span {
|
||||||
|
int length = 0;
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
|
||||||
|
Span(int length, int x, int y) : length(length), x(x), y(y) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Span displayMap[13] = {
|
||||||
|
{6, 0, 6},
|
||||||
|
{6, 1, 6},
|
||||||
|
{7, 2, 6},
|
||||||
|
{9, 3, 4},
|
||||||
|
{14, 4, 4},
|
||||||
|
{17, 5, 0},
|
||||||
|
{12, 6, 2},
|
||||||
|
{18, 7, 0},
|
||||||
|
{14, 8, 4},
|
||||||
|
{9, 9, 5},
|
||||||
|
{7, 10, 4},
|
||||||
|
{6, 11, 5},
|
||||||
|
{6, 12, 5}
|
||||||
|
};
|
||||||
|
|
||||||
|
VirtualCoordinates physicalToVirtualCoords(const PhysicalCoordinates localCoords) const override {
|
||||||
|
int offset = localCoords.x;
|
||||||
|
for(int i = 0; i < 12; i++) {
|
||||||
|
if (offset > displayMap[i].length) {
|
||||||
|
offset -= displayMap[i].length;
|
||||||
|
} else {
|
||||||
|
return VirtualCoordinates{i, offset};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const override {
|
||||||
|
const uint8_t spanIdx = scale8(12, virtualCoords.x);
|
||||||
|
const uint8_t spanOffset = scale8(17, virtualCoords.y);
|
||||||
|
return PhysicalCoordinates{spanIdx, spanOffset};
|
||||||
|
}
|
||||||
|
|
||||||
|
int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override {
|
||||||
|
uint8_t idx = 0;
|
||||||
|
bool inverse = false;
|
||||||
|
for(int i = 0; i < localCoords.x; i++) {
|
||||||
|
idx += displayMap[i].length;
|
||||||
|
inverse = !inverse;
|
||||||
|
}
|
||||||
|
if (inverse) {
|
||||||
|
idx += std::max(0, displayMap[localCoords.x].length - 1 - std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
|
||||||
|
} else {
|
||||||
|
idx += std::min((int)displayMap[localCoords.x].length - 1, std::max(0, (int)localCoords.y - displayMap[localCoords.x].y));
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int physicalPixelCount() const override {
|
||||||
|
int total = 0;
|
||||||
|
for(int i = 0; i < 13; i++) {
|
||||||
|
total += displayMap[i].length;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HardwareConfig {
|
||||||
|
uint8_t version = 3;
|
||||||
|
uint8_t checksum = 0;
|
||||||
|
struct Data {
|
||||||
|
uint16_t pixelCount = 255;
|
||||||
|
uint16_t startPixel = 0;
|
||||||
|
uint8_t lastRed = 255;
|
||||||
|
uint8_t lastGreen = 255;
|
||||||
|
uint8_t lastBlue = 255;
|
||||||
|
char lastScene[16] = {0};
|
||||||
|
};
|
||||||
|
Data data;
|
||||||
|
|
||||||
|
static HardwareConfig load();
|
||||||
|
void save();
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
LinearCoordinateMapping toCoordMap() const;
|
||||||
|
static constexpr uint16_t MAX_LED_NUM = 255;
|
||||||
|
|
||||||
|
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 CoordinateMapping* coordMap() const { return /*&m_maskMap;*/ &m_coordMap; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
HardwareConfig m_config;
|
||||||
|
MaskCoordinateMapping m_maskMap;
|
||||||
|
LinearCoordinateMapping m_coordMap;
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user