diff --git a/.gitignore b/.gitignore index f73200f..4fbc9ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ bin/* *.bin +.pio diff --git a/firmware/Config.cpp b/firmware/Config.cpp deleted file mode 100644 index 5966582..0000000 --- a/firmware/Config.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "./Config.h" -#include "./Static.h" - -HardwareConfig -HardwareConfig::load() { - HardwareConfig ret; - EEPROM.get(0, ret); - return ret; -} - -void -HardwareConfig::save() { - HardwareConfig dataCopy{*this}; - dataCopy.checksum = getCRC(); - EEPROM.put(0, dataCopy); -} - -LinearCoordinateMapping -HardwareConfig::toCoordMap() const -{ - auto pixelCount = min(HardwareConfig::MAX_LED_NUM, max(1, data.pixelCount)); - auto startPixel = min(pixelCount, max(1, data.startPixel)); - return LinearCoordinateMapping{pixelCount, startPixel}; -} - -bool -HardwareConfig::isValid() const -{ - return version == 1 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM; -} - -uint8_t -HardwareConfig::getCRC() const -{ - const unsigned char* message = reinterpret_cast(&data); - constexpr uint8_t length = sizeof(data); - unsigned char i, j, crc = 0; - for(i = 0; i < length; i++) { - crc ^= message[i]; - for(j = 0; j < 8; j++) { - if (crc & 1) { - crc ^= CRC7_POLY; - } - crc >>= 1; - } - } - return crc; -} - -void -ConfigService::onStart() -{ - Log.info("Starting configuration service..."); - m_config = HardwareConfig::load(); - if (m_config.isValid()) { - Log.info("Configuration found!"); - } else { - Log.info("No configuration found. Writing defaults..."); - m_config = HardwareConfig{}; - m_config.save(); - } - m_coordMap = m_config.toCoordMap(); - - m_pixelCount = AnimatedNumber{max(1, m_coordMap.pixelCount)}; - m_startPixel = AnimatedNumber{max(0, m_coordMap.startPixel)}; - Log.info("Configured to use %d pixels, starting at %d", m_pixelCount.value(), m_startPixel.value()); - Log.info("Loading task states..."); - for(int i = 0; i < 32; i++) { - auto svc = m_config.data.serviceStates[i]; - if (strlen(svc.name) > 0) { - Log.info("* %s: %s", svc.name, svc.isRunning ? "RUNNING" : "STOPPED"); - } - } -} - -void -ConfigService::onConnected() -{ - Log.info("Connecting photon configuration..."); - Particle.function("pixelCount", &ConfigService::setPixelCount, this); - Particle.function("startPixel", &ConfigService::setStartPixel, this); - - Particle.function("save", &ConfigService::photonSave, this); - Particle.variable("pixelCount", m_pixelCountInt); - Particle.variable("startPixel", m_startPixelInt); - - publishConfig(); -} - -void -ConfigService::loop() -{ - //m_startPixel.update(); - //m_pixelCount.update(); - //m_coordMap.pixelCount = max(1, m_pixelCount.value()); - //m_coordMap.startPixel = max(0, m_startPixel.value()); -} - -void -ConfigService::handleEvent(const InputEvent &evt) -{ - switch(evt.intent) { - case InputEvent::NetworkStatus: - onConnected(); - break; - case InputEvent::SetDisplayLength: - Log.info("Updating pixel count from %d to %d", m_coordMap.pixelCount, evt.asInt()); - m_config.data.pixelCount = evt.asInt(); - m_coordMap = m_config.toCoordMap(); - m_pixelCountInt = evt.asInt(); - //m_pixelCount = m_config.data.pixelCount; - //m_coordMap.pixelCount = evt.asInt(); - Log.info("Count is now %d", m_coordMap.pixelCount); - break; - case InputEvent::SetDisplayOffset: - Log.info("Updating pixel offset from %d to %d", m_coordMap.startPixel, evt.asInt()); - m_config.data.startPixel = evt.asInt(); - m_coordMap = m_config.toCoordMap(); - m_startPixelInt = evt.asInt(); - Log.info("Offset is now %d", m_coordMap.startPixel); - //m_coordMap.startPixel = evt.asInt(); - //m_startPixel = m_config.data.startPixel; - break; - case InputEvent::SaveConfigurationRequest: - Log.info("Saving configuration"); - m_config.save(); - break; - } -} - -void -ConfigService::publishConfig() const -{ - char buf[255]; - snprintf(buf, sizeof(buf), "{\"pixels\": \"%d\", \"offset\": \"%d\"}", m_config.data.pixelCount, m_config.data.startPixel); - Particle.publish("renderbug/config", buf, PRIVATE); -} - -int -ConfigService::photonSave(String command) -{ - m_config.save(); - return 0; -} - -int -ConfigService::setPixelCount(String command) -{ - m_config.data.pixelCount = command.toInt(); - m_pixelCount = m_config.data.pixelCount; - publishConfig(); - return 0; -} - -int -ConfigService::setStartPixel(String command) -{ - m_config.data.startPixel = command.toInt(); - m_startPixel = m_config.data.startPixel; - publishConfig(); - return 0; -} - -STATIC_ALLOC(ConfigService); diff --git a/firmware/FastLED/FastLED.cpp b/firmware/FastLED/FastLED.cpp deleted file mode 120000 index e82b2a1..0000000 --- a/firmware/FastLED/FastLED.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.cpp \ No newline at end of file diff --git a/firmware/FastLED/FastLED.h b/firmware/FastLED/FastLED.h deleted file mode 120000 index 703ee95..0000000 --- a/firmware/FastLED/FastLED.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/FastLED.h \ No newline at end of file diff --git a/firmware/FastLED/FastSPI_LED2.h b/firmware/FastLED/FastSPI_LED2.h deleted file mode 120000 index c3284ca..0000000 --- a/firmware/FastLED/FastSPI_LED2.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/FastSPI_LED2.h \ No newline at end of file diff --git a/firmware/FastLED/LICENSE b/firmware/FastLED/LICENSE deleted file mode 120000 index 9f51ffe..0000000 --- a/firmware/FastLED/LICENSE +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/LICENSE \ No newline at end of file diff --git a/firmware/FastLED/PORTING.md b/firmware/FastLED/PORTING.md deleted file mode 120000 index 62a7ccf..0000000 --- a/firmware/FastLED/PORTING.md +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/PORTING.md \ No newline at end of file diff --git a/firmware/FastLED/README.md b/firmware/FastLED/README.md deleted file mode 120000 index 7cebb30..0000000 --- a/firmware/FastLED/README.md +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/README.md \ No newline at end of file diff --git a/firmware/FastLED/bitswap.h b/firmware/FastLED/bitswap.h deleted file mode 120000 index 484fbe0..0000000 --- a/firmware/FastLED/bitswap.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/bitswap.h \ No newline at end of file diff --git a/firmware/FastLED/chipsets.h b/firmware/FastLED/chipsets.h deleted file mode 120000 index 9a5ccd1..0000000 --- a/firmware/FastLED/chipsets.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/chipsets.h \ No newline at end of file diff --git a/firmware/FastLED/clockless_arm_stm32.h b/firmware/FastLED/clockless_arm_stm32.h deleted file mode 120000 index d42deb4..0000000 --- a/firmware/FastLED/clockless_arm_stm32.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/clockless_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/color.h b/firmware/FastLED/color.h deleted file mode 120000 index 4488c49..0000000 --- a/firmware/FastLED/color.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/color.h \ No newline at end of file diff --git a/firmware/FastLED/colorpalettes.cpp b/firmware/FastLED/colorpalettes.cpp deleted file mode 120000 index 75d0c46..0000000 --- a/firmware/FastLED/colorpalettes.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.cpp \ No newline at end of file diff --git a/firmware/FastLED/colorpalettes.h b/firmware/FastLED/colorpalettes.h deleted file mode 120000 index a8ca684..0000000 --- a/firmware/FastLED/colorpalettes.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/colorpalettes.h \ No newline at end of file diff --git a/firmware/FastLED/colorutils.cpp b/firmware/FastLED/colorutils.cpp deleted file mode 120000 index 5fb0ce9..0000000 --- a/firmware/FastLED/colorutils.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.cpp \ No newline at end of file diff --git a/firmware/FastLED/colorutils.h b/firmware/FastLED/colorutils.h deleted file mode 120000 index 339e209..0000000 --- a/firmware/FastLED/colorutils.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/colorutils.h \ No newline at end of file diff --git a/firmware/FastLED/controller.h b/firmware/FastLED/controller.h deleted file mode 120000 index c00e95e..0000000 --- a/firmware/FastLED/controller.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/controller.h \ No newline at end of file diff --git a/firmware/FastLED/delay.h b/firmware/FastLED/delay.h deleted file mode 120000 index 3bf3b32..0000000 --- a/firmware/FastLED/delay.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/delay.h \ No newline at end of file diff --git a/firmware/FastLED/dmx.h b/firmware/FastLED/dmx.h deleted file mode 120000 index a69a4bc..0000000 --- a/firmware/FastLED/dmx.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/dmx.h \ No newline at end of file diff --git a/firmware/FastLED/docs b/firmware/FastLED/docs deleted file mode 120000 index e7df508..0000000 --- a/firmware/FastLED/docs +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/docs \ No newline at end of file diff --git a/firmware/FastLED/examples b/firmware/FastLED/examples deleted file mode 120000 index 6a3e154..0000000 --- a/firmware/FastLED/examples +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/examples \ No newline at end of file diff --git a/firmware/FastLED/fastled_arm_stm32.h b/firmware/FastLED/fastled_arm_stm32.h deleted file mode 120000 index d7d422a..0000000 --- a/firmware/FastLED/fastled_arm_stm32.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/fastled_config.h b/firmware/FastLED/fastled_config.h deleted file mode 120000 index 22e3c58..0000000 --- a/firmware/FastLED/fastled_config.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastled_config.h \ No newline at end of file diff --git a/firmware/FastLED/fastpin.h b/firmware/FastLED/fastpin.h deleted file mode 120000 index 8f80ce2..0000000 --- a/firmware/FastLED/fastpin.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin.h \ No newline at end of file diff --git a/firmware/FastLED/fastpin_arm_stm32.h b/firmware/FastLED/fastpin_arm_stm32.h deleted file mode 120000 index 80eb7da..0000000 --- a/firmware/FastLED/fastpin_arm_stm32.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastpin_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi.h b/firmware/FastLED/fastspi.h deleted file mode 120000 index 248b025..0000000 --- a/firmware/FastLED/fastspi.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_bitbang.h b/firmware/FastLED/fastspi_bitbang.h deleted file mode 120000 index eb2b1ae..0000000 --- a/firmware/FastLED/fastspi_bitbang.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_bitbang.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_dma.h b/firmware/FastLED/fastspi_dma.h deleted file mode 120000 index 706d926..0000000 --- a/firmware/FastLED/fastspi_dma.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_dma.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_nop.h b/firmware/FastLED/fastspi_nop.h deleted file mode 120000 index e155d4a..0000000 --- a/firmware/FastLED/fastspi_nop.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_nop.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_ref.h b/firmware/FastLED/fastspi_ref.h deleted file mode 120000 index b3e57ef..0000000 --- a/firmware/FastLED/fastspi_ref.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_ref.h \ No newline at end of file diff --git a/firmware/FastLED/fastspi_types.h b/firmware/FastLED/fastspi_types.h deleted file mode 120000 index 3c63fb3..0000000 --- a/firmware/FastLED/fastspi_types.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/fastspi_types.h \ No newline at end of file diff --git a/firmware/FastLED/hsv2rgb.cpp b/firmware/FastLED/hsv2rgb.cpp deleted file mode 120000 index 859dbb2..0000000 --- a/firmware/FastLED/hsv2rgb.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.cpp \ No newline at end of file diff --git a/firmware/FastLED/hsv2rgb.h b/firmware/FastLED/hsv2rgb.h deleted file mode 120000 index 8427440..0000000 --- a/firmware/FastLED/hsv2rgb.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/hsv2rgb.h \ No newline at end of file diff --git a/firmware/FastLED/keywords.txt b/firmware/FastLED/keywords.txt deleted file mode 120000 index d466098..0000000 --- a/firmware/FastLED/keywords.txt +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/keywords.txt \ No newline at end of file diff --git a/firmware/FastLED/led_sysdefs.h b/firmware/FastLED/led_sysdefs.h deleted file mode 120000 index 2656be0..0000000 --- a/firmware/FastLED/led_sysdefs.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs.h \ No newline at end of file diff --git a/firmware/FastLED/led_sysdefs_arm_stm32.h b/firmware/FastLED/led_sysdefs_arm_stm32.h deleted file mode 120000 index e8f4e74..0000000 --- a/firmware/FastLED/led_sysdefs_arm_stm32.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/led_sysdefs_arm_stm32.h \ No newline at end of file diff --git a/firmware/FastLED/lib8tion.cpp b/firmware/FastLED/lib8tion.cpp deleted file mode 120000 index b7688bb..0000000 --- a/firmware/FastLED/lib8tion.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.cpp \ No newline at end of file diff --git a/firmware/FastLED/lib8tion.h b/firmware/FastLED/lib8tion.h deleted file mode 120000 index 1dc6fe0..0000000 --- a/firmware/FastLED/lib8tion.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/lib8tion.h \ No newline at end of file diff --git a/firmware/FastLED/noise.cpp b/firmware/FastLED/noise.cpp deleted file mode 120000 index 7b4909d..0000000 --- a/firmware/FastLED/noise.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/noise.cpp \ No newline at end of file diff --git a/firmware/FastLED/noise.h b/firmware/FastLED/noise.h deleted file mode 120000 index 16747fc..0000000 --- a/firmware/FastLED/noise.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/noise.h \ No newline at end of file diff --git a/firmware/FastLED/pixeltypes.h b/firmware/FastLED/pixeltypes.h deleted file mode 120000 index 4bb6865..0000000 --- a/firmware/FastLED/pixeltypes.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/pixeltypes.h \ No newline at end of file diff --git a/firmware/FastLED/platforms b/firmware/FastLED/platforms deleted file mode 120000 index 36ddb1e..0000000 --- a/firmware/FastLED/platforms +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/platforms \ No newline at end of file diff --git a/firmware/FastLED/platforms.h b/firmware/FastLED/platforms.h deleted file mode 120000 index 872b881..0000000 --- a/firmware/FastLED/platforms.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/platforms.h \ No newline at end of file diff --git a/firmware/FastLED/power_mgt.cpp b/firmware/FastLED/power_mgt.cpp deleted file mode 120000 index 87ed38b..0000000 --- a/firmware/FastLED/power_mgt.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.cpp \ No newline at end of file diff --git a/firmware/FastLED/power_mgt.h b/firmware/FastLED/power_mgt.h deleted file mode 120000 index d0fe179..0000000 --- a/firmware/FastLED/power_mgt.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/power_mgt.h \ No newline at end of file diff --git a/firmware/FastLED/preview_changes.txt b/firmware/FastLED/preview_changes.txt deleted file mode 120000 index f03f6c1..0000000 --- a/firmware/FastLED/preview_changes.txt +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/preview_changes.txt \ No newline at end of file diff --git a/firmware/FastLED/release_notes.md b/firmware/FastLED/release_notes.md deleted file mode 120000 index 9f66b3a..0000000 --- a/firmware/FastLED/release_notes.md +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/release_notes.md \ No newline at end of file diff --git a/firmware/FastLED/wiring.cpp b/firmware/FastLED/wiring.cpp deleted file mode 120000 index 62c12ec..0000000 --- a/firmware/FastLED/wiring.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/FastLED/firmware/wiring.cpp \ No newline at end of file diff --git a/firmware/MDNS/Buffer.cpp b/firmware/MDNS/Buffer.cpp deleted file mode 120000 index 73fccbf..0000000 --- a/firmware/MDNS/Buffer.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/Buffer.cpp \ No newline at end of file diff --git a/firmware/MDNS/Buffer.h b/firmware/MDNS/Buffer.h deleted file mode 120000 index 1c103b5..0000000 --- a/firmware/MDNS/Buffer.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/Buffer.h \ No newline at end of file diff --git a/firmware/MDNS/Label.cpp b/firmware/MDNS/Label.cpp deleted file mode 120000 index e3dd379..0000000 --- a/firmware/MDNS/Label.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/Label.cpp \ No newline at end of file diff --git a/firmware/MDNS/Label.h b/firmware/MDNS/Label.h deleted file mode 120000 index d0cda62..0000000 --- a/firmware/MDNS/Label.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/Label.h \ No newline at end of file diff --git a/firmware/MDNS/MDNS b/firmware/MDNS/MDNS deleted file mode 120000 index e22a966..0000000 --- a/firmware/MDNS/MDNS +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/MDNS \ No newline at end of file diff --git a/firmware/MDNS/MDNS.cpp b/firmware/MDNS/MDNS.cpp deleted file mode 120000 index dd3f84d..0000000 --- a/firmware/MDNS/MDNS.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/MDNS.cpp \ No newline at end of file diff --git a/firmware/MDNS/MDNS.h b/firmware/MDNS/MDNS.h deleted file mode 120000 index 7b792d6..0000000 --- a/firmware/MDNS/MDNS.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/MDNS.h \ No newline at end of file diff --git a/firmware/MDNS/Record.cpp b/firmware/MDNS/Record.cpp deleted file mode 120000 index 9c03fc6..0000000 --- a/firmware/MDNS/Record.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/Record.cpp \ No newline at end of file diff --git a/firmware/MDNS/Record.h b/firmware/MDNS/Record.h deleted file mode 120000 index c0b0ace..0000000 --- a/firmware/MDNS/Record.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MDNS/src/Record.h \ No newline at end of file diff --git a/firmware/MQTT/MQTT b/firmware/MQTT/MQTT deleted file mode 120000 index 6de4cb2..0000000 --- a/firmware/MQTT/MQTT +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MQTT/src/MQTT \ No newline at end of file diff --git a/firmware/MQTT/MQTT.cpp b/firmware/MQTT/MQTT.cpp deleted file mode 120000 index 8aaff5b..0000000 --- a/firmware/MQTT/MQTT.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MQTT/src/MQTT.cpp \ No newline at end of file diff --git a/firmware/MQTT/MQTT.h b/firmware/MQTT/MQTT.h deleted file mode 120000 index 8d834cb..0000000 --- a/firmware/MQTT/MQTT.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/MQTT/src/MQTT.h \ No newline at end of file diff --git a/firmware/ParticleWebLog/ParticleWebLog.cpp b/firmware/ParticleWebLog/ParticleWebLog.cpp deleted file mode 120000 index b6e225c..0000000 --- a/firmware/ParticleWebLog/ParticleWebLog.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.cpp \ No newline at end of file diff --git a/firmware/ParticleWebLog/ParticleWebLog.h b/firmware/ParticleWebLog/ParticleWebLog.h deleted file mode 120000 index 67b88d6..0000000 --- a/firmware/ParticleWebLog/ParticleWebLog.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/ParticleWebLog/src/ParticleWebLog.h \ No newline at end of file diff --git a/firmware/WebDuino/WebDuino b/firmware/WebDuino/WebDuino deleted file mode 120000 index d2f5bff..0000000 --- a/firmware/WebDuino/WebDuino +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino \ No newline at end of file diff --git a/firmware/WebDuino/WebDuino.cpp b/firmware/WebDuino/WebDuino.cpp deleted file mode 120000 index 416f3f5..0000000 --- a/firmware/WebDuino/WebDuino.cpp +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino.cpp \ No newline at end of file diff --git a/firmware/WebDuino/WebDuino.h b/firmware/WebDuino/WebDuino.h deleted file mode 120000 index 965b593..0000000 --- a/firmware/WebDuino/WebDuino.h +++ /dev/null @@ -1 +0,0 @@ -/home/tdfischer/.po-util/lib/WebDuino/src/WebDuino.h \ No newline at end of file diff --git a/firmware/main.cpp b/firmware/main.cpp deleted file mode 100644 index 91b2461..0000000 --- a/firmware/main.cpp +++ /dev/null @@ -1,780 +0,0 @@ -#define RENDERBUG_VERSION 1 -#define PLATFORM_PHOTON -//#define PLATFORM_ESP2800 - -#include "FastLED/FastLED.h" -#include "Figments/Figments.h" - -#include "Static.h" -#include "Config.h" -#include "colors.h" - -#include "WebTelemetry.cpp" -#include "MDNSService.cpp" -#include "Sequencer.h" - -#include "animations/Power.cpp" -#include "animations/SolidAnimation.cpp" -#include "animations/Chimes.cpp" -#include "animations/Flashlight.cpp" -#include "animations/Drain.cpp" -#include "animations/UpdateStatus.h" - -#include "inputs/ColorCycle.h" -#include "inputs/Buttons.h" -#include "inputs/MPU6050.h" - -#ifdef PLATFORM_PHOTON -#include "platform/particle/inputs/Photon.h" -#include "platform/particle/inputs/CloudStatus.h" -#include "platform/particle/PhotonTelemetry.h" -#endif - -SerialLogHandler logHandler; - -using namespace NSFastLED; - -#define MAX_BRIGHTNESS 255 -//#define PSU_MILLIAMPS 4800 -//#define PSU_MILLIAMPS 500 -//#define PSU_MILLIAMPS 1000 -#define PSU_MILLIAMPS 2000 - -// Enable system thread, so rendering happens while booting -SYSTEM_THREAD(ENABLED); - -// Setup FastLED and the display -CRGB leds[HardwareConfig::MAX_LED_NUM]; -Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static::instance()->coordMap()); - -LinearCoordinateMapping neckMap{60, 0}; -Display neckDisplay(leds, HardwareConfig::MAX_LED_NUM, &neckMap); - -// Setup power management -Power power; - -// Clip the display at whatever is configured while still showing over-paints -FigmentFunc displayClip([](Display* dpy) { - auto coords = Static::instance()->coordMap(); - for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) { - if (i < coords->startPixel || i > coords->pixelCount + coords->startPixel) { - dpy->pixelAt(i) %= 40; - } - } -}); - -FigmentFunc configDisplay([](Display* dpy) { - uint8_t brightness = brighten8_video(beatsin8(60)); - auto coords = Static::instance()->coordMap(); - for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) { - if (i < coords->startPixel || i > coords->startPixel + coords->pixelCount) { - dpy->pixelAt(i) += CRGB(brightness, 0, 0); - } else { - dpy->pixelAt(i) += CRGB(255 - brightness, 255 - brightness, 255 - brightness); - } - } -}); - -class InputBlip: public Figment { -public: - InputBlip() : Figment("InputBlip", Task::Stopped) {} - - void handleEvent(const InputEvent& evt) override { - if (evt.intent != InputEvent::None) { - m_time = qadd8(m_time, 5); - } - } - void loop() override { - if (m_time > 0) { - m_time--; - } - } - void render(Display* dpy) const override { - if (m_time > 0) { - dpy->pixelAt(0) = CRGB(0, brighten8_video(ease8InOutApprox(m_time)), 0); - } - } -private: - uint8_t m_time = 0; -}; - -InputBlip inputBlip; - -InputFunc randomPulse([]() { - static unsigned int pulse = 0; - EVERY_N_MILLISECONDS(25) { - if (pulse == 0) { - pulse = random(25) + 25; - } - } - if (pulse > 0) { - if (random(255) >= 25) { - pulse--; - return InputEvent{InputEvent::Acceleration, beatsin16(60 * 8, 0, 4972)}; - } - } - return InputEvent{}; -}, "Pulse", Task::Running); - -InputMapper keyMap([](const InputEvent& evt) { - if (evt.intent == InputEvent::UserInput) { - Buttons::Chord chord = (Buttons::Chord)evt.asInt(); - switch(chord) { - case Buttons::Circle: - return InputEvent::PowerToggle; - break; - case Buttons::Triangle: - return InputEvent::NextPattern; - break; - case Buttons::Cross: - return InputEvent::UserInput; - break; - } - } - return InputEvent::None; -}); - -ChimesAnimation chimes{Task::Stopped}; -SolidAnimation solid{Task::Running}; -DrainAnimation drain{Task::Stopped}; -Flashlight flashlight{Task::Stopped}; - -Sequencer sequencer{{ - {"Idle", {"Solid", "MPU5060", "Pulse", "Hackerbots", "Kieryn", "CircadianRhythm"}}, - {"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}}, - {"Interactive", {"Drain", "CircadianRhythm"}}, - {"Flashlight", {"Flashlight"}}, - {"Nightlight", {"Drain", "Pulse", "Noisebridge"}}, - {"Gay", {"Solid", "Pulse", "Rainbow", "Hackerbots", "Kieryn"}}, - {"Acid", {"Chimes", "Pulse", "MPU5060", "Hackerbots", "Rainbow"}}, -}}; - - -// Render all layers to the displays -Renderer renderer{ - //{&dpy, &neckDisplay}, - {&dpy}, - { - &chimes, - &drain, - &solid, - &flashlight, - Static::instance(), - &displayClip, - &inputBlip, - &power, - } -}; - -Renderer configRenderer{ - {&dpy}, - {&drain, &configDisplay, &inputBlip, &power} -}; - -MDNSService mdnsService; - -class OnlineTaskMixin : public virtual Loopable { - public: - void handleEvent(const InputEvent &evt) override { - if (evt.intent == InputEvent::NetworkStatus) { - m_online = evt.asInt(); - } - if (m_online) { - handleEventOnline(evt); - } - } - - virtual void handleEventOnline(const InputEvent &evt) = 0; - - void loop() override { - if (m_online) { - loopOnline(); - } - } - - virtual void loopOnline() = 0; - - private: - bool m_online = false; -}; - -#include "MQTT/MQTT.h" - -class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin { - public: - MQTTTelemetry() : BufferedInputSource("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) { - strcpy(m_deviceName, System.deviceID().c_str()); - } - - void loop() override { - BufferedInputSource::loop(); - OnlineTaskMixin::loop(); - } - - void handleEventOnline(const InputEvent& event) override { - char response[255]; - if (event.intent == InputEvent::SetPower) { - JSONBufferWriter writer(response, sizeof(response)); - writer.beginObject(); - writer.name("state").value(event.asInt() == 0 ? "OFF" : "ON"); - writer.endObject(); - writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; - m_client.publish(m_statTopic, response, MQTT::QOS1); - } else if (event.intent == InputEvent::SetBrightness) { - JSONBufferWriter writer(response, sizeof(response)); - writer.beginObject(); - writer.name("brightness").value(event.asInt()); - writer.endObject(); - writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; - m_client.publish(m_statTopic, response, MQTT::QOS1); - } else if (event.intent == InputEvent::SetColor) { - JSONBufferWriter writer(response, sizeof(response)); - writer.beginObject(); - writer.name("color").beginObject(); - CRGB rgb = event.asRGB(); - writer.name("r").value(rgb.r); - writer.name("g").value(rgb.g); - writer.name("b").value(rgb.b); - writer.endObject(); - writer.endObject(); - writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; - m_client.publish(m_statTopic, response, MQTT::QOS1); - } else if (event.intent == InputEvent::SetPattern) { - JSONBufferWriter writer(response, sizeof(response)); - writer.beginObject(); - writer.name("effect").value(event.asString()); - writer.endObject(); - writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; - m_client.publish(m_statTopic, response, MQTT::QOS1); - } else { - if (m_lastIntent != event.intent) { - m_lastIntent = event.intent; - JSONBufferWriter writer(response, sizeof(response)); - writer.beginObject(); - writer.name("intent").value(event.intent); - writer.endObject(); - writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; - m_client.publish("renderbug/events", response, MQTT::QOS1); - } - } - } - - void loopOnline() override { - if (m_client.isConnected()) { - m_client.loop(); - EVERY_N_SECONDS(10) { - char heartbeatBuf[255]; - JSONBufferWriter writer(heartbeatBuf, sizeof(heartbeatBuf)); - writer.beginObject(); - writer.name("fps").value(FastLED.getFPS()); - writer.name("os_version").value(System.version()); - writer.name("free_ram").value((unsigned int)System.freeMemory()); - //writer.name("uptime").value(System.uptime()); - /*WiFiSignal sig = WiFi.RSSI(); - writer.name("strength").value(sig.getStrength()); - writer.name("quality").value(sig.getQuality());*/ - writer.name("RSSI").value(WiFi.RSSI()); - writer.name("SSID").value(WiFi.SSID()); - //writer.name("MAC").value(WiFi.macAddress()); - writer.name("localip").value(WiFi.localIP().toString()); - writer.name("startPixel").value(Static::instance()->coordMap()->startPixel); - writer.name("pixelCount").value(Static::instance()->coordMap()->pixelCount); - writer.endObject(); - writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; - m_client.publish(m_attrTopic, heartbeatBuf); - } - } else { - m_client.connect("renderbug_" + String(m_deviceName) + "_" + String(Time.now())); - char response[512]; - JSONBufferWriter writer(response, sizeof(response)); - - String devTopic = String("homeassistant/light/renderbug/") + m_deviceName; - writer.beginObject(); - writer.name("~").value(devTopic.c_str()); - writer.name("name").value("Renderbug"); - writer.name("unique_id").value(m_deviceName); - writer.name("cmd_t").value("~/set"); - writer.name("stat_t").value("~/set"); - writer.name("json_attr_t").value("~/attributes"); - writer.name("schema").value("json"); - writer.name("brightness").value(true); - writer.name("rgb").value(true); - writer.name("ret").value(true); - - writer.name("dev").beginObject(); - writer.name("name").value("Renderbug"); - writer.name("mdl").value("Renderbug"); - writer.name("sw").value(RENDERBUG_VERSION); - writer.name("mf").value("Phong Robotics"); - - writer.name("ids").beginArray(); - writer.value(m_deviceName); - writer.endArray(); - - writer.endObject(); - - writer.name("fx_list").beginArray(); - for(const Sequencer::Scene& scene : sequencer.scenes()) { - writer.value(scene.name); - } - writer.endArray(); - - writer.endObject(); - writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; - - String configTopic = devTopic + "/config"; - m_client.publish(configTopic, response, true); - - String statTopic = devTopic + "/state"; - String cmdTopic = devTopic + "/set"; - String attrTopic = devTopic + "/attributes"; - strcpy(m_statTopic, statTopic.c_str()); - strcpy(m_attrTopic, attrTopic.c_str()); - strcpy(m_cmdTopic, cmdTopic.c_str()); - m_client.subscribe(m_cmdTopic, MQTT::QOS1); - } - } - - private: - void callback(char* topic, byte* payload, unsigned int length) { - MainLoop::instance()->dispatch(InputEvent::NetworkActivity); - if (!strcmp(topic, m_cmdTopic)) { - JSONValue cmd = JSONValue::parseCopy((char*)payload, length); - JSONObjectIterator cmdIter(cmd); - while(cmdIter.next()) { - if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") { - setEvent({InputEvent::SetPower, 1}); - } else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") { - setEvent({InputEvent::SetPower, 0}); - } - - if (cmdIter.name() == "color") { - JSONObjectIterator colorIter(cmdIter.value()); - uint8_t r, g, b; - while(colorIter.next()) { - if (colorIter.name() == "r") { - r = colorIter.value().toInt(); - } else if (colorIter.name() == "g") { - g = colorIter.value().toInt(); - } else if (colorIter.name() == "b") { - b = colorIter.value().toInt(); - } - } - setEvent(InputEvent{InputEvent::SetColor, CRGB{r, g, b}}); - } - - if (cmdIter.name() == "brightness") { - uint8_t brightness = cmdIter.value().toInt(); - setEvent(InputEvent{InputEvent::SetBrightness, brightness}); - } - - if (cmdIter.name() == "effect") { - strcpy(m_patternBuf, (const char*) cmdIter.value().toString()); - setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf}); - } - - if (cmdIter.name() == "pixelCount") { - setEvent(InputEvent{InputEvent::SetDisplayLength, cmdIter.value().toInt()}); - } - - if (cmdIter.name() == "startPixel") { - setEvent(InputEvent{InputEvent::SetDisplayOffset, cmdIter.value().toInt()}); - } - - if (cmdIter.name() == "save") { - setEvent(InputEvent{InputEvent::SaveConfigurationRequest}); - } - } - } - } - - static void s_callback(char* topic, byte* payload, unsigned int length) { - Static::instance()->callback(topic, payload, length); - }; - - MQTT m_client; - InputEvent::Intent m_lastIntent; - char m_deviceName[100]; - char m_statTopic[100]; - char m_attrTopic[100]; - char m_cmdTopic[100]; - char m_patternBuf[48]; -}; - -STATIC_ALLOC(MQTTTelemetry); - -WebTelemetry webTelemetry(sequencer); - -// Cycle some random colors -ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped}; - -ColorSequenceInput<13> kierynCycle{{ - colorForName("Cerulean").rgb, - colorForName("Electric Purple").rgb, - colorForName("Emerald").rgb, - colorForName("Sky Magenta").rgb -}, "Kieryn", Task::Stopped}; - -ColorSequenceInput<7> rainbowCycle{{ - colorForName("Red").rgb, - colorForName("Orange").rgb, - colorForName("Yellow").rgb, - colorForName("Green").rgb, - colorForName("Blue").rgb, - colorForName("Purple").rgb, - colorForName("White").rgb, -}, "Rainbow", Task::Stopped}; - -ColorSequenceInput<7> hackerbotsCycle{{ - colorForName("Purple").rgb, - colorForName("White").rgb, - colorForName("Cyan").rgb, -}, "Hackerbots", Task::Stopped}; - -struct ConfigInputTask: public BufferedInputSource { -public: - ConfigInputTask() : BufferedInputSource("ConfigInput") {} - - void handleEvent(const InputEvent& evt) override { - if (evt.intent == InputEvent::UserInput) { - Buttons::Chord chord = (Buttons::Chord) evt.asInt(); - switch (chord) { - case Buttons::Circle: - m_currentIntent = nextIntent(); - Log.info("Next setting... (%d)", m_currentIntent); - break; - case Buttons::CircleTriangle: - Log.info("Increment..."); - increment(); - break; - case Buttons::CircleCross: - Log.info("Decrement..."); - decrement(); - break; - case Buttons::Triangle: - Log.info("Save..."); - setEvent(InputEvent::SaveConfigurationRequest); - break; - } - } - } - -private: - InputEvent::Intent m_currentIntent = InputEvent::SetDisplayLength; - - void decrement() { - int current = 0; - switch (m_currentIntent) { - case InputEvent::SetDisplayLength: - current = Static::instance()->coordMap()->pixelCount; - break; - case InputEvent::SetDisplayOffset: - current = Static::instance()->coordMap()->startPixel; - break; - } - setEvent(InputEvent{m_currentIntent, current - 1}); - } - - void increment() { - int current = 0; - switch (m_currentIntent) { - case InputEvent::SetDisplayLength: - current = Static::instance()->coordMap()->pixelCount; - break; - case InputEvent::SetDisplayOffset: - current = Static::instance()->coordMap()->startPixel; - break; - } - setEvent(InputEvent{m_currentIntent, current + 1}); - } - - InputEvent::Intent nextIntent() { - switch (m_currentIntent) { - case InputEvent::SetDisplayLength: - return InputEvent::SetDisplayOffset; - case InputEvent::SetDisplayOffset: - return InputEvent::SetDisplayLength; - } - } -}; - -enum Phase { - Null, - AstronomicalDay, - NauticalDay, - CivilDay, - CivilNight, - NauticalNight, - AstronomicalNight, - Evening, - Bedtime, -}; - -struct ScheduleEntry { - uint8_t hour; - uint8_t brightness; -}; - -std::array schedule{{ - {0, 0}, - {5, 0}, - {6, 0}, - {7, 10}, - {8, 80}, - {11, 120}, - {18, 200}, - {19, 255}, - {22, 120}, - {23, 20} -}}; - -uint8_t brightnessForTime(uint8_t hour, uint8_t minute) { - ScheduleEntry start = schedule.back(); - ScheduleEntry end = schedule.front(); - for(ScheduleEntry cur : schedule) { - // Find the last hour that is prior to or equal to now - if (cur.hour <= hour) { - start = cur; - } else { - break; - } - } - for(ScheduleEntry cur : schedule) { - // Find the first hour that is after now - // If no such hour exists, we should automatically wrap back to hour 0 - if (cur.hour > hour) { - end = cur; - break; - } - } - - if (start.hour > end.hour) { - end.hour += 24; - } - - uint16_t startTime = start.hour * 60; - uint16_t endTime = end.hour * 60; - uint16_t nowTime = hour * 60 + minute; - - uint16_t duration = endTime - startTime; - uint16_t curDuration = nowTime - startTime; - - uint8_t frac = ((double)curDuration / (double)duration) * 255.0; - - return lerp8by8(start.brightness, end.brightness, frac); -} - -InputFunc circadianRhythm([]() { - static Phase lastPhase = Null; - static bool needsUpdate = true; - if (Time.isValid()) { - EVERY_N_SECONDS(60) { - needsUpdate = true; - } - if (needsUpdate) { - needsUpdate = false; - return InputEvent{InputEvent::SetBrightness, brightnessForTime(Time.hour(), Time.minute())}; - } - } - return InputEvent{}; -}, "CircadianRhythm", Task::Running); - -// A special mainloop app for configuring hardware settings that reboots the -// device when the user is finished. -MainLoop configApp{{ - // Manage read/write of configuration data - Static::instance(), - -#ifdef PLATFORM_PHOTON - // Update photon telemetry - Static::instance(), -#endif - - // Read hardware inputs - new Buttons(), - - // Map input buttons to configuration commands - new ConfigInputTask(), - - // Fill the entire display with a color, to see size - &configDisplay, - // Render some basic input feedback - &inputBlip, - // Render it all - &configRenderer, -}}; - -// Turn on, -MainLoop renderbugApp{{ - - // Load/update graphics configuration from EEPROM and Particle - Static::instance(), - - // Platform inputs -#ifdef PLATFORM_PHOTON - // Particle cloud status - Static::instance(), - - // Monitor network state and provide particle API events - Static::instance(), -#endif - - // Hardware drivers - new MPU5060(), - new Buttons(), - - // Map buttons to events - &keyMap, - - // Pattern sequencer - &sequencer, - - // Daily rhythm activities - &circadianRhythm, - - // Periodic motion input - &randomPulse, - - // Periodic color inputs - &noisebridgeCycle, - &kierynCycle, - &rainbowCycle, - &hackerbotsCycle, - - // Animations - &chimes, - &drain, - &solid, - &flashlight, - - // Update UI layer - &power, - &displayClip, - Static::instance(), - &inputBlip, - - // Render everything - &renderer, - - // Platform telemetry -#ifdef PLATFORM_PHOTON - // Update photon telemetry - Static::instance(), - - // Web telemetry UI - &webTelemetry, - - // MQTT telemetry - Static::instance(), - - // Network discovery - &mdnsService, -#endif -}}; - -MainLoop &runner = renderbugApp; - -retained bool LAST_BOOT_WAS_FLASH; -retained bool LAST_BOOT_WAS_SERIAL; - -struct BootOptions { - static void initPins() { - pinMode(2, INPUT_PULLDOWN); - pinMode(3, INPUT_PULLDOWN); - pinMode(4, INPUT_PULLDOWN); - } - - BootOptions() { - isSetup = digitalRead(2) == HIGH; - isSerial = digitalRead(3) == HIGH || LAST_BOOT_WAS_SERIAL; - isFlash = digitalRead(4) == HIGH; - - LAST_BOOT_WAS_FLASH = isFlash; - LAST_BOOT_WAS_SERIAL |= isSerial; - - configStatus.setActive(isSetup); - serialStatus.setActive(isSerial); - } - - void waitForRelease() { - while(digitalRead(2) == HIGH || digitalRead(3) == HIGH) {}; - } - - bool isSetup = false; - bool isSerial = false; - bool isFlash = false; - - LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND); - LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT); -}; - -STARTUP(BootOptions::initPins()); - -retained BootOptions bootopts; - -ApplicationWatchdog *wd; - -void watchdogHandler() { - for(int i = 0; i < 8; i++) { - leds[i] = CRGB(i % 3 ? 35 : 255, 0, 0); - } - FastLED.show(); - if (LAST_BOOT_WAS_FLASH) { - System.dfu(); - } else { - System.enterSafeMode(); - } -} - -// Tune in, -void setup() { - System.enableFeature(FEATURE_RETAINED_MEMORY); - wd = new ApplicationWatchdog(5000, watchdogHandler, 1536); - Serial.begin(115200); - if (bootopts.isFlash) { - System.dfu(); - } - if (bootopts.isSerial) { - bootopts.waitForRelease(); - while(!Serial.isConnected()) { -#ifdef PLATFORM_PHOTON - Particle.process(); -#endif - } - Log.info("\xf0\x9f\x94\x8c Serial connected"); - } - Log.info(u8"🐛 Booting Renderbug %s!", System.deviceID().c_str()); - Log.info(u8"🐞 I am built for %d LEDs running on %dmA", HardwareConfig::MAX_LED_NUM, PSU_MILLIAMPS); -#ifdef PLATFORM_PHOTON - Log.info(u8"📡 Particle version %s", System.version().c_str()); -#endif - Log.info(u8" Boot pin configuration:"); - Log.info(u8" 2: Setup - %d", bootopts.isSetup); - Log.info(u8" 3: Serial - %d", bootopts.isSerial); - Log.info(u8" 4: Flash - %d", bootopts.isFlash); - - Log.info(u8" Setting timezone to UTC-7"); - Time.zone(-7); - - Log.info(u8"💡 Starting FastLED..."); - FastLED.addLeds(leds, HardwareConfig::MAX_LED_NUM); - - if (bootopts.isSetup) { - Log.info(u8"🌌 Starting Figment in configuration mode..."); - runner = configApp; - } else { - Log.info(u8"🌌 Starting Figment..."); - } - Serial.flush(); - runner.start(); - - Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory()); - Log.info(u8"🚀 Setup complete! Ready to rock and roll."); - Serial.flush(); -} - -// Drop out. -void loop() { - runner.loop(); - wd->checkin(); -} diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/firmware/Figments/Animation.cpp b/lib/Figments/Animation.cpp similarity index 70% rename from firmware/Figments/Animation.cpp rename to lib/Figments/Animation.cpp index 3eeac2a..6082059 100644 --- a/firmware/Figments/Animation.cpp +++ b/lib/Figments/Animation.cpp @@ -6,5 +6,5 @@ uint8_t AnimatedNumber::value() const { - return NSFastLED::lerp8by8(m_start, m_end, m_idx); + return lerp8by8(m_start, m_end, m_idx); } diff --git a/firmware/Figments/Animation.h b/lib/Figments/Animation.h similarity index 83% rename from firmware/Figments/Animation.h rename to lib/Figments/Animation.h index 2b93258..cc04dd4 100644 --- a/firmware/Figments/Animation.h +++ b/lib/Figments/Animation.h @@ -1,5 +1,5 @@ #pragma once -#include "FastLED/FastLED.h" +#include #include "./Figment.h" #include @@ -68,16 +68,16 @@ private: }; struct AnimatedRGB { - NSFastLED::CRGB start; - NSFastLED::CRGB end; + CRGB start; + CRGB end; AnimatedNumber pos; - AnimatedRGB(const NSFastLED::CRGB& color) + AnimatedRGB(const CRGB& color) : start(color), end(color) {} AnimatedRGB() {} - AnimatedRGB& operator=(const NSFastLED::CRGB& rgb) { + AnimatedRGB& operator=(const CRGB& rgb) { start = *this; end = rgb; pos.set(0, 255); @@ -88,11 +88,11 @@ struct AnimatedRGB { pos.update(); } - operator NSFastLED::CRGB() const { - uint8_t red = NSFastLED::lerp8by8(start.red, end.red, pos); - uint8_t green = NSFastLED::lerp8by8(start.green, end.green, pos); - uint8_t blue = NSFastLED::lerp8by8(start.blue, end.blue, pos); - return NSFastLED::CRGB(red, green, blue); + operator CRGB() const { + uint8_t red = lerp8by8(start.red, end.red, pos); + uint8_t green = lerp8by8(start.green, end.green, pos); + uint8_t blue = lerp8by8(start.blue, end.blue, pos); + return CRGB(red, green, blue); } }; diff --git a/firmware/Figments/Display.cpp b/lib/Figments/Display.cpp similarity index 96% rename from firmware/Figments/Display.cpp rename to lib/Figments/Display.cpp index a2455c8..d92f283 100644 --- a/firmware/Figments/Display.cpp +++ b/lib/Figments/Display.cpp @@ -1,8 +1,6 @@ #include "Display.h" #include -using namespace NSFastLED; - int Display::pixelCount() const { diff --git a/firmware/Figments/Display.h b/lib/Figments/Display.h similarity index 74% rename from firmware/Figments/Display.h rename to lib/Figments/Display.h index 9a60a00..f8c8b04 100644 --- a/firmware/Figments/Display.h +++ b/lib/Figments/Display.h @@ -1,5 +1,5 @@ #pragma once -#include "FastLED/FastLED.h" +#include #include "Geometry.h" @@ -20,7 +20,7 @@ struct LinearCoordinateMapping: CoordinateMapping { } PhysicalCoordinates virtualToPhysicalCoords(const VirtualCoordinates virtualCoords) const { - return PhysicalCoordinates{NSFastLED::scale8(pixelCount, virtualCoords.x), 0}; + return PhysicalCoordinates{scale8(pixelCount, virtualCoords.x), 0}; } int physicalCoordsToIndex(const PhysicalCoordinates localCoords) const override { @@ -35,22 +35,22 @@ struct LinearCoordinateMapping: CoordinateMapping { class Display { 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) {} - NSFastLED::CRGB& pixelAt(const PhysicalCoordinates coords); - NSFastLED::CRGB& pixelAt(const VirtualCoordinates coords); - NSFastLED::CRGB& pixelAt(int idx); + CRGB& pixelAt(const PhysicalCoordinates coords); + CRGB& pixelAt(const VirtualCoordinates coords); + CRGB& pixelAt(int idx); int pixelCount() const; - NSFastLED::CRGB* pixelBacking() const; + CRGB* pixelBacking() const; const CoordinateMapping* coordinateMapping() const; void clear(); - void clear(const NSFastLED::CRGB& color); - void clear(VirtualCoordinates& start, VirtualCoordinates& end, const NSFastLED::CRGB& color); + void clear(const CRGB& color); + void clear(VirtualCoordinates& start, VirtualCoordinates& end, const CRGB& color); private: - NSFastLED::CRGB* m_pixels; + CRGB* m_pixels; int m_pixelCount; const CoordinateMapping* m_coordMap; }; diff --git a/firmware/Figments/Figment.h b/lib/Figments/Figment.h similarity index 86% rename from firmware/Figments/Figment.h rename to lib/Figments/Figment.h index d4e5933..db21fba 100644 --- a/firmware/Figments/Figment.h +++ b/lib/Figments/Figment.h @@ -1,6 +1,7 @@ #pragma once -#include "application.h" +#include #include +#include class Display; class InputEvent; @@ -25,8 +26,8 @@ struct Task : public virtual Loopable { Task(const char* name) : Task(name, Running) {} Task(const char* name, State initialState) : name(name), state(initialState) {} - void start() { Log.info("* Starting %s...", name); state = Running; onStart(); } - void stop() { Log.info("* Stopping %s...", name); onStop(); state = Stopped; } + void start() { state = Running; onStart(); } + void stop() { onStop(); state = Stopped; } virtual bool isFigment() const { return false; } const char* name = 0; diff --git a/firmware/Figments/Figments.h b/lib/Figments/Figments.h similarity index 100% rename from firmware/Figments/Figments.h rename to lib/Figments/Figments.h diff --git a/firmware/Figments/Geometry.h b/lib/Figments/Geometry.h similarity index 71% rename from firmware/Figments/Geometry.h rename to lib/Figments/Geometry.h index 170b171..7d9d8a7 100644 --- a/firmware/Figments/Geometry.h +++ b/lib/Figments/Geometry.h @@ -8,12 +8,12 @@ template struct Coordinates { T y; }; -struct VirtualCoordinates: Coordinates { - VirtualCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {} +struct VirtualCoordinates: Coordinates { + VirtualCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {} }; -struct PhysicalCoordinates: Coordinates { - PhysicalCoordinates(uint8_t _x, uint8_t _y) : Coordinates(_x, _y) {} +struct PhysicalCoordinates: Coordinates { + PhysicalCoordinates(uint16_t _x, uint16_t _y) : Coordinates(_x, _y) {} }; template struct Vector3d { diff --git a/firmware/Figments/Input.cpp b/lib/Figments/Input.cpp similarity index 83% rename from firmware/Figments/Input.cpp rename to lib/Figments/Input.cpp index b4f6d51..1a52d2e 100644 --- a/firmware/Figments/Input.cpp +++ b/lib/Figments/Input.cpp @@ -1,11 +1,11 @@ -#include "application.h" +#include #include "./Input.h" #include "./MainLoop.h" -NSFastLED::CRGB +CRGB Variant::asRGB() const { - return NSFastLED::CRGB(m_value.asRGB[0], m_value.asRGB[1], m_value.asRGB[2]); + return CRGB(m_value.asRGB[0], m_value.asRGB[1], m_value.asRGB[2]); } const char* diff --git a/firmware/Figments/Input.h b/lib/Figments/Input.h similarity index 81% rename from firmware/Figments/Input.h rename to lib/Figments/Input.h index b1ec111..deec40d 100644 --- a/firmware/Figments/Input.h +++ b/lib/Figments/Input.h @@ -1,9 +1,9 @@ #pragma once -#include "application.h" +#include #include "./Geometry.h" #include "./Figment.h" #include "./Ringbuf.h" -#include "FastLED/FastLED.h" +#include typedef Vector3d MotionVec; @@ -21,7 +21,7 @@ struct Variant { Variant(const char* v) : type(String), m_value{.asString=v} {} - Variant(const NSFastLED::CRGB &v) + Variant(const CRGB &v) : type(Color), m_value{.asRGB={v.r, v.g, v.b}} {} Variant() @@ -30,7 +30,7 @@ struct Variant { Type type; const char* asString() const; - NSFastLED::CRGB asRGB() const; + CRGB asRGB() const; int asInt() const; private: @@ -142,7 +142,7 @@ private: class InputMapper: public BufferedInputSource { public: - InputMapper(std::function f) : BufferedInputSource(), m_func(f) {} + InputMapper(std::function f, const char* name) : BufferedInputSource(name), m_func(f) {} void handleEvent(const InputEvent& evt) override { setEvent(m_func(evt)); @@ -151,3 +151,28 @@ public: private: std::function m_func; }; + +class OnlineTaskMixin : public virtual Loopable { + public: + void handleEvent(const InputEvent &evt) override { + if (evt.intent == InputEvent::NetworkStatus) { + m_online = evt.asInt(); + } + if (m_online) { + handleEventOnline(evt); + } + } + + virtual void handleEventOnline(const InputEvent &evt) = 0; + + void loop() override { + if (m_online) { + loopOnline(); + } + } + + virtual void loopOnline() = 0; + + private: + bool m_online = false; +}; diff --git a/firmware/Figments/MainLoop.cpp b/lib/Figments/MainLoop.cpp similarity index 70% rename from firmware/Figments/MainLoop.cpp rename to lib/Figments/MainLoop.cpp index 3bbc8ba..5f9625c 100644 --- a/firmware/Figments/MainLoop.cpp +++ b/lib/Figments/MainLoop.cpp @@ -2,6 +2,8 @@ #include "./Input.h" #include "./Figment.h" +#include + void MainLoop::dispatch(const InputEvent& evt) { @@ -19,10 +21,12 @@ MainLoop::loop() 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 (!strcmp(figmentJob->name, evt.asString())) { if (jobState) { + //Log.notice("Starting %s", figmentJob->name); figmentJob->start(); } else { + //Log.notice("Stopping %s", figmentJob->name); figmentJob->stop(); } } @@ -34,18 +38,19 @@ MainLoop::loop() } } for(Task* task : scheduler) { - //Log.info("Running %s", task->name); + //Log.notice("Running %s", task->name); task->loop(); - //Log.info("next"); + //Log.notice("next"); } } void MainLoop::start() { - Log.info("*** Starting %d tasks...", scheduler.tasks.size()); + Log.notice("*** Starting %d tasks...", scheduler.tasks.size()); Serial.flush(); for(auto task: scheduler) { + Log.notice("** Starting %s", task->name); task->start(); } } diff --git a/firmware/Figments/MainLoop.h b/lib/Figments/MainLoop.h similarity index 100% rename from firmware/Figments/MainLoop.h rename to lib/Figments/MainLoop.h diff --git a/firmware/Figments/Renderer.cpp b/lib/Figments/Renderer.cpp similarity index 60% rename from firmware/Figments/Renderer.cpp rename to lib/Figments/Renderer.cpp index 7b3c84b..3e7b2aa 100644 --- a/firmware/Figments/Renderer.cpp +++ b/lib/Figments/Renderer.cpp @@ -1,21 +1,24 @@ #include "./Renderer.h" #include "./Display.h" +#include + void Renderer::loop() { for(Display* dpy : m_displays) { for(Figment* figment : m_figments) { if (figment->state == Task::Running) { - //Log.info("Rendering %s", figment->name); + //Log.notice("Rendering %s", figment->name); figment->render(dpy); - //Log.info("next"); + //Log.notice("next"); } else { - //Log.info("Not rendering %s", figment->name); + //Log.notice("Not rendering %s", figment->name); } }; } - NSFastLED::FastLED.show(); + FastLED.show(); + FastLED.countFPS(); } void @@ -24,5 +27,5 @@ Renderer::onStart() for(Display* dpy : m_displays) { dpy->clear(); } - NSFastLED::FastLED.show(); + FastLED.show(); } diff --git a/firmware/Figments/Renderer.h b/lib/Figments/Renderer.h similarity index 100% rename from firmware/Figments/Renderer.h rename to lib/Figments/Renderer.h diff --git a/firmware/Figments/Ringbuf.h b/lib/Figments/Ringbuf.h similarity index 100% rename from firmware/Figments/Ringbuf.h rename to lib/Figments/Ringbuf.h diff --git a/firmware/Figments/Service.h b/lib/Figments/Service.h similarity index 100% rename from firmware/Figments/Service.h rename to lib/Figments/Service.h diff --git a/firmware/Figments/Surface.cpp b/lib/Figments/Surface.cpp similarity index 53% rename from firmware/Figments/Surface.cpp rename to lib/Figments/Surface.cpp index 9cbd580..fd34204 100644 --- a/firmware/Figments/Surface.cpp +++ b/lib/Figments/Surface.cpp @@ -1,5 +1,6 @@ #include "./Surface.h" #include "./Display.h" +#include Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end) : start(dpy->coordinateMapping()->virtualToPhysicalCoords(start)), @@ -9,28 +10,30 @@ Surface::Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoo } Surface& -Surface::operator=(const NSFastLED::CRGB& color) +Surface::operator=(const CRGB& color) { - paintWith([&](NSFastLED::CRGB& pixel) { + paintWith([&](CRGB& pixel) { pixel = color; }); return *this; } Surface& -Surface::operator+=(const NSFastLED::CRGB& color) +Surface::operator+=(const CRGB& color) { - paintWith([&](NSFastLED::CRGB& pixel) { + paintWith([&](CRGB& pixel) { pixel += color; }); return *this; } void -Surface::paintWith(std::function func) +Surface::paintWith(std::function func) { - for(uint8_t x = start.x; x <= end.x; x++) { - for(uint8_t y = start.y; y <= end.y; y++) { + //Log.verbose("Painting startx=%d endx=%d starty=%d endy=%d", start.x, end.x, start.y, end.y); + for(auto x = start.x; x <= end.x; x++) { + for(auto y = start.y; y <= end.y; y++) { + //Log.verbose("x=%d y=%d", x, y); func(m_display->pixelAt(PhysicalCoordinates{x, y})); } } diff --git a/firmware/Figments/Surface.h b/lib/Figments/Surface.h similarity index 57% rename from firmware/Figments/Surface.h rename to lib/Figments/Surface.h index d629770..2e221f0 100644 --- a/firmware/Figments/Surface.h +++ b/lib/Figments/Surface.h @@ -1,5 +1,6 @@ -#include "FastLED/FastLED.h" +#include #include "./Geometry.h" +#include class Display; @@ -7,10 +8,10 @@ class Surface { public: Surface(Display* dpy, const VirtualCoordinates& start, const VirtualCoordinates& end); - Surface& operator=(const NSFastLED::CRGB& color); - Surface& operator+=(const NSFastLED::CRGB& color); + Surface& operator=(const CRGB& color); + Surface& operator+=(const CRGB& color); - void paintWith(std::function func); + void paintWith(std::function func); const PhysicalCoordinates start; const PhysicalCoordinates end; diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/out.log b/out.log new file mode 100644 index 0000000..6d8bf29 --- /dev/null +++ b/out.log @@ -0,0 +1,33 @@ +ets Jun 8 2016 00:22:57 + +rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) +configsip: 0, SPIWP:0xee +clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 +mode:DIO, clock div:2 +load:0x3fff0018,len:4 +load:0x3fff001c,len:1044 +load:0x40078000,len:10124 +load:0x40080400,len:5828 +entry 0x400806a8 +N: 🐛 Booting Renderbug! +N: 🐞 I am built for 255 LEDs running on 2000mA +N: Boot pin configuration: +N: 2: Setup - 0 +N: 3: Serial - 0 +N: 4: Flash - 0 +N: 💡 Starting FastLED... +N: 🌌 Starting Figment... +N: *** Starting 20 tasks... +N: * Starting Configuration... +N: * Starting MPU5060... +N: * Starting Buttons... +N: * Starting ... +N: * Starting SceneSequencer... +N: * Starting CircadianRhythm... +N: * Starting Pulse... +N: * Starting Solid... +N: * Starting Power... +N: * Starting lambda... +N: * Starting UpdateStatusAnimation... +N: * Starting Renderer... +N: 🚀 Setup complete! Ready to rock and roll. diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..1fc65a7 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,24 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:featheresp32] +platform = espressif32 +board = featheresp32 +framework = arduino +lib_deps = + fastled/FastLED@^3.4.0 + thijse/ArduinoLog@^1.0.3 + knolleary/PubSubClient@^2.8.0 + bblanchon/ArduinoJson@^6.17.3 + ricaun/ArduinoUniqueID@^1.1.0 + sstaub/NTP@^1.4.0 +src_filter = "+<*> -<.git/> -<.svn/> - +" +; upload_protocol = espota +; upload_port = 10.0.0.171 diff --git a/src/BootOptions.cpp b/src/BootOptions.cpp new file mode 100644 index 0000000..0429b35 --- /dev/null +++ b/src/BootOptions.cpp @@ -0,0 +1,42 @@ +#include "BootOptions.h" + +#ifdef PLATFORM_PHOTON +LEDStatus serialStatus = LEDStatus(RGB_COLOR_ORANGE, LED_PATTERN_FADE, LED_SPEED_FAST, LED_PRIORITY_BACKGROUND); +LEDStatus configStatus = LEDStatus(RGB_COLOR_YELLOW, LED_PATTERN_FADE, LED_SPEED_NORMAL, LED_PRIORITY_IMPORTANT); +retained bool LAST_BOOT_WAS_FLASH; +retained bool LAST_BOOT_WAS_SERIAL; +#endif + +void +BootOptions::initPins() +{ +#ifdef PLATFORM_PHOTON + pinMode(2, INPUT_PULLDOWN); + pinMode(3, INPUT_PULLDOWN); + pinMode(4, INPUT_PULLDOWN); +#endif +} + +BootOptions::BootOptions() +{ +#ifdef PLATFORM_PHOTON + isSetup = digitalRead(2) == HIGH; + isSerial = digitalRead(3) == HIGH || LAST_BOOT_WAS_SERIAL; + isFlash = digitalRead(4) == HIGH; + + LAST_BOOT_WAS_FLASH = isFlash; + LAST_BOOT_WAS_SERIAL |= isSerial; + lastBootWasFlash = LAST_BOOT_WAS_FLASH; + + configStatus.setActive(isSetup); + serialStatus.setActive(isSerial); +#endif +} + +void +BootOptions::waitForRelease() +{ +#ifdef PLATFORM_PHOTON + while(digitalRead(2) == HIGH || digitalRead(3) == HIGH) {}; +#endif +} diff --git a/src/BootOptions.h b/src/BootOptions.h new file mode 100644 index 0000000..1925a84 --- /dev/null +++ b/src/BootOptions.h @@ -0,0 +1,14 @@ +#pragma once + +struct BootOptions { + static void initPins(); + + BootOptions(); + + void waitForRelease(); + + bool isSetup = false; + bool isSerial = false; + bool isFlash = false; + bool lastBootWasFlash = false; +}; diff --git a/src/Config.cpp b/src/Config.cpp new file mode 100644 index 0000000..f924afd --- /dev/null +++ b/src/Config.cpp @@ -0,0 +1,109 @@ +#include "./Config.h" +#include "./Static.h" +#include + +constexpr uint16_t HardwareConfig::MAX_LED_NUM; + +HardwareConfig +HardwareConfig::load() { + HardwareConfig ret; +#ifdef PLATFORM_PHOTON + EEPROM.get(0, ret); +#endif + return ret; +} + +void +HardwareConfig::save() { + HardwareConfig dataCopy{*this}; + dataCopy.checksum = getCRC(); +#ifdef PLATFORM_PHOTON + EEPROM.put(0, dataCopy); +#endif +} + +LinearCoordinateMapping +HardwareConfig::toCoordMap() const +{ + auto pixelCount = min(HardwareConfig::MAX_LED_NUM, std::max((uint16_t)1, data.pixelCount)); + auto startPixel = min(pixelCount, std::max((uint16_t)1, data.startPixel)); + return LinearCoordinateMapping{pixelCount, startPixel}; +} + +bool +HardwareConfig::isValid() const +{ + return version == 2 && checksum == getCRC() && data.pixelCount <= MAX_LED_NUM; +} + +uint8_t +HardwareConfig::getCRC() const +{ + const unsigned char* message = reinterpret_cast(&data); + constexpr uint8_t length = sizeof(data); + unsigned char i, j, crc = 0; + for(i = 0; i < length; i++) { + crc ^= message[i]; + for(j = 0; j < 8; j++) { + if (crc & 1) { + crc ^= CRC7_POLY; + } + crc >>= 1; + } + } + return crc; +} + +void +ConfigService::onStart() +{ + Log.notice("Starting configuration service..."); + m_config = HardwareConfig::load(); + if (m_config.isValid()) { + Log.notice("Configuration found!"); + } else { + Log.notice("No configuration found. Writing defaults..."); + m_config = HardwareConfig{}; + m_config.save(); + } + m_coordMap = m_config.toCoordMap(); + + Log.notice("Configured to use %d pixels, starting at %d", m_config.data.pixelCount, m_config.data.startPixel); + Log.notice("Loading task states..."); + for(int i = 0; i < 32; i++) { + auto svc = m_config.data.serviceStates[i]; + if (strlen(svc.name) > 0) { + Log.notice("* %s: %s", svc.name, svc.isDisabled? "DISABLED" : "ENABLED"); + } + } +} + +void +ConfigService::loop() +{ +} + +void +ConfigService::handleEvent(const InputEvent &evt) +{ + switch(evt.intent) { + case InputEvent::SetDisplayLength: + //Log.info("Updating pixel count from %d to %d", m_coordMap.pixelCount, evt.asInt()); + m_config.data.pixelCount = evt.asInt(); + m_coordMap = m_config.toCoordMap(); + //Log.info("Count is now %d", m_coordMap.pixelCount); + break; + case InputEvent::SetDisplayOffset: + //Log.info("Updating pixel offset from %d to %d", m_coordMap.startPixel, evt.asInt()); + m_config.data.startPixel = evt.asInt(); + m_coordMap = m_config.toCoordMap(); + //Log.info("Offset is now %d", m_coordMap.startPixel); + break; + case InputEvent::SaveConfigurationRequest: + //Log.info("Saving configuration"); + m_config.save(); + break; + } +} + +STATIC_ALLOC(ConfigService); diff --git a/firmware/Config.h b/src/Config.h similarity index 76% rename from firmware/Config.h rename to src/Config.h index 23fc211..1f4b021 100644 --- a/firmware/Config.h +++ b/src/Config.h @@ -1,6 +1,8 @@ #pragma once -#include "Particle.h" -#include "./Figments/Figments.h" +#include + +//#define PLATFORM_PHOTON +#define PLATFORM_ARDUINO struct HardwareConfig { uint8_t version = 2; @@ -27,7 +29,7 @@ struct HardwareConfig { LinearCoordinateMapping toCoordMap() const; static constexpr uint16_t MAX_LED_NUM = 255; - static constexpr bool HAS_MPU_6050 = true; + static constexpr bool HAS_MPU_6050 = false; private: uint8_t getCRC() const; @@ -46,17 +48,6 @@ struct ConfigService: public Task { 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; }; diff --git a/firmware/Sequencer.cpp b/src/Sequencer.cpp similarity index 89% rename from firmware/Sequencer.cpp rename to src/Sequencer.cpp index a42171c..e573c3c 100644 --- a/firmware/Sequencer.cpp +++ b/src/Sequencer.cpp @@ -1,5 +1,5 @@ #include "Sequencer.h" -#include "Figments/MainLoop.h" +#include Sequencer::Sequencer(std::vector &&scenes) : Task("SceneSequencer"), @@ -26,8 +26,9 @@ void Sequencer::handleEvent(const InputEvent& evt) { if (evt.intent == InputEvent::SetPattern || evt.intent == InputEvent::NextPattern || evt.intent == InputEvent::PreviousPattern) { - Log.info("Switching pattern!"); + Log.notice("Switching pattern!"); for(const char* pattern : m_scenes[m_idx].patterns) { + Log.notice("Stopping %s", pattern); MainLoop::instance()->dispatch(InputEvent{InputEvent::StopThing, pattern}); } @@ -53,6 +54,7 @@ Sequencer::handleEvent(const InputEvent& evt) } for(const char* pattern : m_scenes[m_idx].patterns) { + Log.notice("Starting %s", pattern); MainLoop::instance()->dispatch(InputEvent{InputEvent::StartThing, pattern}); } } diff --git a/firmware/Sequencer.h b/src/Sequencer.h similarity index 93% rename from firmware/Sequencer.h rename to src/Sequencer.h index ad94b70..7bda5e8 100644 --- a/firmware/Sequencer.h +++ b/src/Sequencer.h @@ -1,6 +1,6 @@ #pragma once -#include "Figments/Figment.h" +#include #include class Sequencer: public Task { diff --git a/firmware/Static.h b/src/Static.h similarity index 100% rename from firmware/Static.h rename to src/Static.h diff --git a/src/WiFiTask.cpp b/src/WiFiTask.cpp new file mode 100644 index 0000000..9c93661 --- /dev/null +++ b/src/WiFiTask.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include "Static.h" +#include "WiFiTask.h" + +WiFiTask::WiFiTask() : InputSource("WiFi") {} + +void +WiFiTask::onStart() +{ + Log.notice("Starting wifi..."); + WiFi.mode(WIFI_STA); + int n = WiFi.scanNetworks(); + if (n == 0) { + Log.notice("No wifi found"); + } else { + for(int i = 0; i < n; ++i) { + Serial.print("WiFi: "); + Serial.println(WiFi.SSID(i)); + } + } + WiFi.mode(WIFI_STA); + WiFi.begin("The Frequency", "thepasswordkenneth"); + while(WiFi.status() != WL_CONNECTED) { + Serial.print('.'); + delay(1000); + } +} + +InputEvent +WiFiTask::read() +{ + uint8_t curStatus = WiFi.status(); + if (m_lastStatus != curStatus) { + m_lastStatus = curStatus; + Log.verbose("WiFi Status: %d", curStatus); + if (curStatus == WL_CONNECTED) { + Log.notice("Connected!"); + return InputEvent{InputEvent::NetworkStatus, true}; + } else if (curStatus == WL_CONNECTION_LOST || curStatus == WL_DISCONNECTED) { + Log.notice("Lost wifi connection!"); + return InputEvent{InputEvent::NetworkStatus, false}; + } + } + return InputEvent{}; +} + +STATIC_ALLOC(WiFiTask); diff --git a/src/WiFiTask.h b/src/WiFiTask.h new file mode 100644 index 0000000..fc95d4e --- /dev/null +++ b/src/WiFiTask.h @@ -0,0 +1,12 @@ +#include +#include +#include "Static.h" + +class WiFiTask : public InputSource { + public: + WiFiTask(); + void onStart() override; + InputEvent read() override; + private: + uint8_t m_lastStatus = WL_IDLE_STATUS; +}; diff --git a/firmware/animations/Chimes.cpp b/src/animations/Chimes.cpp similarity index 88% rename from firmware/animations/Chimes.cpp rename to src/animations/Chimes.cpp index 9fff541..31efa44 100644 --- a/firmware/animations/Chimes.cpp +++ b/src/animations/Chimes.cpp @@ -2,8 +2,6 @@ #include "../sprites/Chime.h" #include "../sprites/Blob.h" -using namespace NSFastLED; - #define CHIME_LENGTH 23 #define CHIME_COUNT 4 #define BLOB_COUNT 10 @@ -63,9 +61,9 @@ public: 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); + CRGB scaledColor = CRGB(m_flashColor).nscale8_video(std::max((uint8_t)10, ease8InOutCubic(m_flashBrightness))); + fullSurface.paintWith([&](CRGB& pixel) { + pixel = blend(scaledColor, pixel, 200); //pixel = scaledColor; }); } diff --git a/firmware/animations/Drain.cpp b/src/animations/Drain.cpp similarity index 97% rename from firmware/animations/Drain.cpp rename to src/animations/Drain.cpp index 09f223a..67de550 100644 --- a/firmware/animations/Drain.cpp +++ b/src/animations/Drain.cpp @@ -1,6 +1,6 @@ -#include "../Figments/Figments.h" +#include +#include -using namespace NSFastLED; class DrainAnimation: public Figment { public: @@ -62,5 +62,3 @@ public: uint16_t m_pos; uint16_t m_burst; }; - - diff --git a/firmware/animations/Drain.h b/src/animations/Drain.h similarity index 100% rename from firmware/animations/Drain.h rename to src/animations/Drain.h diff --git a/firmware/animations/Flashlight.cpp b/src/animations/Flashlight.cpp similarity index 95% rename from firmware/animations/Flashlight.cpp rename to src/animations/Flashlight.cpp index c4d71fa..cc9426c 100644 --- a/firmware/animations/Flashlight.cpp +++ b/src/animations/Flashlight.cpp @@ -1,10 +1,8 @@ #pragma once -#include "../Figments/Figments.h" +#include #include "../sprites/Blob.h" -using NSFastLED::CHSV; - class Flashlight: public Figment { public: Flashlight(Task::State initialState) : Figment("Flashlight", initialState) { diff --git a/firmware/animations/Power.cpp b/src/animations/Power.cpp similarity index 68% rename from firmware/animations/Power.cpp rename to src/animations/Power.cpp index ba0b4b1..1451f0f 100644 --- a/firmware/animations/Power.cpp +++ b/src/animations/Power.cpp @@ -1,4 +1,4 @@ -#include "../Figments/Figments.h" +#include template class Power: public Figment { @@ -9,7 +9,7 @@ public: switch (evt.intent) { case InputEvent::PowerToggle: m_powerState = m_powerState.value() <= 128 ? 255 : 0; - Log.info("POWER TOGGLE %d", m_powerState.value()); + //Log.info("POWER TOGGLE %d", m_powerState.value()); break; case InputEvent::SetPower: m_powerState = evt.asInt() == 0 ? 0 : 255; @@ -26,10 +26,10 @@ public: 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); + const uint8_t scaledBrightness = scale8(m_powerState, clippedBrightness); + const uint8_t videoBrightness = brighten8_video(scaledBrightness); + const uint8_t powerBrightness = calculate_max_brightness_for_power_mW(videoBrightness, Watts); + FastLED.setBrightness(powerBrightness); } static constexpr uint32_t Watts = Voltage * MaxMilliAmps; diff --git a/firmware/animations/SolidAnimation.cpp b/src/animations/SolidAnimation.cpp similarity index 89% rename from firmware/animations/SolidAnimation.cpp rename to src/animations/SolidAnimation.cpp index cf21bb7..2eb32d2 100644 --- a/firmware/animations/SolidAnimation.cpp +++ b/src/animations/SolidAnimation.cpp @@ -1,10 +1,6 @@ -#include "../Figments/Figments.h" +#include #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; @@ -37,7 +33,7 @@ public: m_blue.update(); EVERY_N_MILLIS(6) { CRGB rgb{m_red, m_green, m_blue}; - CHSV hsv = NSFastLED::rgb2hsv_approximate(rgb); + CHSV hsv = rgb2hsv_approximate(rgb); m_blobs.forEach([=](Blob& blob) { blob.setHue(hsv.hue); blob.setSaturation(hsv.saturation); diff --git a/firmware/animations/TestAnimation.cpp b/src/animations/TestAnimation.cpp similarity index 93% rename from firmware/animations/TestAnimation.cpp rename to src/animations/TestAnimation.cpp index bcd338b..a3b9a00 100644 --- a/firmware/animations/TestAnimation.cpp +++ b/src/animations/TestAnimation.cpp @@ -1,8 +1,5 @@ #include "./TestAnimation.h" -#include "FastLED/FastLED.h" - -using NSFastLED::CHSV; -using NSFastLED::scale8; +#include const char* TestAnimation::name() const diff --git a/firmware/animations/TestAnimation.h b/src/animations/TestAnimation.h similarity index 90% rename from firmware/animations/TestAnimation.h rename to src/animations/TestAnimation.h index 8ece8f3..d3f9473 100644 --- a/firmware/animations/TestAnimation.h +++ b/src/animations/TestAnimation.h @@ -1,4 +1,4 @@ -#include "../Figments/Figments.h" +#include class TestAnimation: public Figment { public: diff --git a/firmware/animations/UpdateStatus.cpp b/src/animations/UpdateStatus.cpp similarity index 86% rename from firmware/animations/UpdateStatus.cpp rename to src/animations/UpdateStatus.cpp index 5bda05d..72c7981 100644 --- a/firmware/animations/UpdateStatus.cpp +++ b/src/animations/UpdateStatus.cpp @@ -1,16 +1,14 @@ #include "./UpdateStatus.h" -#include "FastLED/FastLED.h" +#include #include "../Static.h" -using NSFastLED::CRGB; - void UpdateStatus::handleEvent(const InputEvent& evt) { if (evt.intent == InputEvent::FirmwareUpdate) { static int updateCount = 0; updateCount++; - Log.info("Update count %d", updateCount); + //Log.info("Update count %d", updateCount); m_updateReady = true; } } diff --git a/firmware/animations/UpdateStatus.h b/src/animations/UpdateStatus.h similarity index 89% rename from firmware/animations/UpdateStatus.h rename to src/animations/UpdateStatus.h index 6e15634..5b8eaca 100644 --- a/firmware/animations/UpdateStatus.h +++ b/src/animations/UpdateStatus.h @@ -1,4 +1,4 @@ -#include "../Figments/Figments.h" +#include class UpdateStatus: public Figment { public: diff --git a/firmware/colors.cpp b/src/colors.cpp similarity index 100% rename from firmware/colors.cpp rename to src/colors.cpp diff --git a/firmware/colors.h b/src/colors.h similarity index 74% rename from firmware/colors.h rename to src/colors.h index 68372a8..da07df2 100644 --- a/firmware/colors.h +++ b/src/colors.h @@ -1,9 +1,9 @@ #pragma once -#include "FastLED/FastLED.h" +#include typedef struct ColorInfo { const char *name; - NSFastLED::CRGB rgb; + CRGB rgb; } ColorInfo; ColorInfo colorForName(const char *name); diff --git a/firmware/inputs/Buttons.cpp b/src/inputs/Buttons.cpp similarity index 87% rename from firmware/inputs/Buttons.cpp rename to src/inputs/Buttons.cpp index 5c19829..a8b4ed5 100644 --- a/firmware/inputs/Buttons.cpp +++ b/src/inputs/Buttons.cpp @@ -1,11 +1,10 @@ -#include "Particle.h" #include "./Buttons.h" void Buttons::onStart() { for(int i = 0; i < 3; i++) { - Log.info("Bound pin %d to button %d", 2 + i, i); + //Log.info("Bound pin %d to button %d", 2 + i, i); m_buttons[i].attach(2 + i, INPUT_PULLDOWN); m_buttons[i].interval(15); } @@ -22,7 +21,7 @@ Buttons::read() for(int j = 0; j < 3; j++ ) { if (j != i && m_buttons[j].held()) { buttonID |= m_buttonMap[j]; - Log.info("Chord with %d", j); + //Log.info("Chord with %d", j); m_wasChord[j] = true; } } diff --git a/firmware/inputs/Buttons.h b/src/inputs/Buttons.h similarity index 94% rename from firmware/inputs/Buttons.h rename to src/inputs/Buttons.h index 5c92f5a..0ddf7f8 100644 --- a/firmware/inputs/Buttons.h +++ b/src/inputs/Buttons.h @@ -1,12 +1,12 @@ #pragma once -#include "../Figments/Input.h" +#include class Bounce { public: - void attach(int pin, PinMode buttonPinMode) { + void attach(int pin, int buttonPinMode) { m_pin = pin; pinMode(m_pin, buttonPinMode); - Log.info("Attaching a button to %d", pin); + //Log.info("Attaching a button to %d", pin); } void update() { diff --git a/firmware/inputs/ColorCycle.cpp b/src/inputs/ColorCycle.cpp similarity index 100% rename from firmware/inputs/ColorCycle.cpp rename to src/inputs/ColorCycle.cpp diff --git a/firmware/inputs/ColorCycle.h b/src/inputs/ColorCycle.h similarity index 54% rename from firmware/inputs/ColorCycle.h rename to src/inputs/ColorCycle.h index baea357..6117f1a 100644 --- a/firmware/inputs/ColorCycle.h +++ b/src/inputs/ColorCycle.h @@ -1,6 +1,5 @@ -#include "../Figments/Figments.h" - -using CRGB = NSFastLED::CRGB; +#include +#include template class ColorSequenceInput: public InputSource { @@ -9,33 +8,25 @@ public: : InputSource(name, initialState), m_colors(colors) {} InputEvent read() override { + EVERY_N_SECONDS(Period) { + m_idx %= m_colors.size(); + m_reset = true; + m_idx++; + } if (m_reset) { m_reset = false; - m_override = false; + Log.notice("Cycling %s color to %d [%d, %d, %d]", name, m_idx, m_colors[m_idx].r, m_colors[m_idx].g, m_colors[m_idx].b); return InputEvent{InputEvent::SetColor, m_colors[m_idx]}; } - if (!m_override) { - EVERY_N_SECONDS(Period) { - m_idx %= m_colors.size(); - return InputEvent{InputEvent::SetColor, m_colors[m_idx++]}; - } - } return InputEvent{}; } - void handleEvent(const InputEvent& event) { - if (event.intent == InputEvent::SetColor) { - m_override = true; - } - } - void onStart() override { m_reset = true; } private: std::vector m_colors; - int m_idx = 0; + unsigned int m_idx = 0; bool m_reset = true; - bool m_override = false; }; diff --git a/firmware/inputs/DMX.cpp b/src/inputs/DMX.cpp similarity index 100% rename from firmware/inputs/DMX.cpp rename to src/inputs/DMX.cpp diff --git a/firmware/inputs/MPU6050.cpp b/src/inputs/MPU6050.cpp similarity index 100% rename from firmware/inputs/MPU6050.cpp rename to src/inputs/MPU6050.cpp diff --git a/firmware/inputs/MPU6050.h b/src/inputs/MPU6050.h similarity index 98% rename from firmware/inputs/MPU6050.h rename to src/inputs/MPU6050.h index b05a26b..107c5c3 100644 --- a/firmware/inputs/MPU6050.h +++ b/src/inputs/MPU6050.h @@ -1,3 +1,5 @@ +#include + class MPU5060: public InputSource { const int ACCEL_XOUT_HIGH = 0x3B; const int ACCEL_XOUT_LOW = 0x3C; @@ -43,7 +45,7 @@ public: Wire.write(PWR_MGMT_1); Wire.write(1); Wire.endTransmission(true); - Wire.end(); + //Wire.end(); } InputEvent read() override { diff --git a/firmware/inputs/Serial.h b/src/inputs/Serial.h similarity index 100% rename from firmware/inputs/Serial.h rename to src/inputs/Serial.h diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1052e94 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,558 @@ +#define RENDERBUG_VERSION 1 + +#include "Arduino.h" + +#include +#include + +#ifndef PLATFORM_PHOTON +#include +#include +#endif + +#include "BootOptions.h" + +#include "Static.h" +#include "Config.h" +#include "colors.h" + +#include "Sequencer.h" + +#include "animations/Power.cpp" +#include "animations/SolidAnimation.cpp" +#include "animations/Chimes.cpp" +#include "animations/Flashlight.cpp" +#include "animations/Drain.cpp" +#include "animations/UpdateStatus.h" + +#include "inputs/ColorCycle.h" +#include "inputs/Buttons.h" +#include "inputs/MPU6050.h" + +#ifdef PLATFORM_PHOTON +#include "platform/particle/inputs/Photon.h" +#include "platform/particle/inputs/CloudStatus.h" +#include "platform/particle/PhotonTelemetry.h" +#include "platform/particle/WebTelemetry.cpp" +#include "platform/particle/MDNSService.cpp" +#else +#include "WiFiTask.h" +#include "platform/arduino/MQTTTelemetry.h" +#include +#endif + +//SerialLogHandler logHandler; + +#define MAX_BRIGHTNESS 255 +//#define PSU_MILLIAMPS 4800 +//#define PSU_MILLIAMPS 500 +//#define PSU_MILLIAMPS 1000 +#define PSU_MILLIAMPS 1000 + +// Enable system thread, so rendering happens while booting +//SYSTEM_THREAD(ENABLED); + +// Setup FastLED and the display +CRGB leds[HardwareConfig::MAX_LED_NUM]; +Display dpy(leds, HardwareConfig::MAX_LED_NUM, Static::instance()->coordMap()); + +LinearCoordinateMapping neckMap{60, 0}; +Display neckDisplay(leds, HardwareConfig::MAX_LED_NUM, &neckMap); + +// Setup power management +Power power; + +// Clip the display at whatever is configured while still showing over-paints +FigmentFunc displayClip([](Display* dpy) { + auto coords = Static::instance()->coordMap(); + for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) { + if (i < coords->startPixel || i > coords->pixelCount + coords->startPixel) { + dpy->pixelAt(i) %= 40; + } + } +}); + +FigmentFunc configDisplay([](Display* dpy) { + uint8_t brightness = brighten8_video(beatsin8(60)); + auto coords = Static::instance()->coordMap(); + for(int i = 0; i < HardwareConfig::MAX_LED_NUM; i++) { + if (i < coords->startPixel || i > coords->startPixel + coords->pixelCount) { + dpy->pixelAt(i) += CRGB(brightness, 0, 0); + } else { + dpy->pixelAt(i) += CRGB(255 - brightness, 255 - brightness, 255 - brightness); + } + } +}); + +class InputBlip: public Figment { +public: + InputBlip() : Figment("InputBlip", Task::Stopped) {} + + void handleEvent(const InputEvent& evt) override { + if (evt.intent != InputEvent::None) { + m_time = qadd8(m_time, 5); + } + } + void loop() override { + if (m_time > 0) { + m_time--; + } + } + void render(Display* dpy) const override { + if (m_time > 0) { + dpy->pixelAt(0) = CRGB(0, brighten8_video(ease8InOutApprox(m_time)), 0); + } + } +private: + uint8_t m_time = 0; +}; + +InputBlip inputBlip; + +class ArduinoOTAUpdater : public BufferedInputSource { + public: + ArduinoOTAUpdater() : BufferedInputSource("ArduinoOTA") { + ArduinoOTA.onStart(&ArduinoOTAUpdater::s_onStart).onProgress(&ArduinoOTAUpdater::s_onProgress); + } + + void loop() override { + ArduinoOTA.handle(); + BufferedInputSource::loop(); + } + private: + static void s_onStart() { + Static::instance()->setEvent(InputEvent::FirmwareUpdate); + } + + static void s_onProgress(unsigned int progress, unsigned int total) { + Static::instance()->setEvent(InputEvent{InputEvent::FirmwareUpdate, progress}); + } +}; + +InputFunc randomPulse([]() { + static unsigned int pulse = 0; + EVERY_N_MILLISECONDS(25) { + if (pulse == 0) { + pulse = random(25) + 25; + } + } + if (pulse > 0) { + if (random(255) >= 25) { + pulse--; + return InputEvent{InputEvent::Acceleration, beatsin16(60 * 8, 0, 4972)}; + } + } + return InputEvent{}; +}, "Pulse", Task::Running); + +InputMapper keyMap([](const InputEvent& evt) { + if (evt.intent == InputEvent::UserInput) { + Buttons::Chord chord = (Buttons::Chord)evt.asInt(); + switch(chord) { + case Buttons::Circle: + return InputEvent::PowerToggle; + break; + case Buttons::Triangle: + return InputEvent::NextPattern; + break; + case Buttons::Cross: + return InputEvent::UserInput; + break; + } + } + return InputEvent::None; +}, "Keymap"); + +ChimesAnimation chimes{Task::Stopped}; +SolidAnimation solid{Task::Running}; +DrainAnimation drain{Task::Stopped}; +Flashlight flashlight{Task::Stopped}; + +Sequencer sequencer{{ + {"Idle", {"Solid", "MPU5060", "Pulse", "Hackerbots", "Kieryn", "CircadianRhythm"}}, + {"Solid", {"Solid", "MPU5060", "Pulse", "CircadianRhythm"}}, + {"Interactive", {"Drain", "CircadianRhythm"}}, + {"Flashlight", {"Flashlight"}}, + {"Nightlight", {"Drain", "Pulse", "Noisebridge"}}, + {"Gay", {"Solid", "Pulse", "Rainbow", "Hackerbots", "Kieryn"}}, + {"Acid", {"Chimes", "Pulse", "MPU5060", "Hackerbots", "Rainbow"}}, +}}; + + +// Render all layers to the displays +Renderer renderer{ + //{&dpy, &neckDisplay}, + {&dpy}, + { + &chimes, + &drain, + &solid, + &flashlight, + Static::instance(), + &displayClip, + &inputBlip, + &power, + } +}; + +Renderer configRenderer{ + {&dpy}, + {&drain, &configDisplay, &inputBlip, &power} +}; + +// Cycle some random colors +ColorSequenceInput<7> noisebridgeCycle{{colorForName("Red").rgb}, "Noisebridge", Task::Stopped}; + +ColorSequenceInput<13> kierynCycle{{ + CRGB(0, 123, 167), // Cerulean + CRGB(80, 200, 120), // Emerald + CRGB(207, 113, 175), // Sky Magenta +}, "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::Stopped}; + +ColorSequenceInput<7> hackerbotsCycle{{ + CRGB(128, 0, 128), // Purple + CRGB(255, 255, 255), // White + CRGB(0, 255, 255), // Cyan +}, "Hackerbots", Task::Running}; + +struct ConfigInputTask: public BufferedInputSource { +public: + ConfigInputTask() : BufferedInputSource("ConfigInput") {} + + void handleEvent(const InputEvent& evt) override { + if (evt.intent == InputEvent::UserInput) { + Buttons::Chord chord = (Buttons::Chord) evt.asInt(); + switch (chord) { + case Buttons::Circle: + m_currentIntent = nextIntent(); + //Log.info("Next setting... (%d)", m_currentIntent); + break; + case Buttons::CircleTriangle: + //Log.info("Increment..."); + increment(); + break; + case Buttons::CircleCross: + //Log.info("Decrement..."); + decrement(); + break; + case Buttons::Triangle: + //Log.info("Save..."); + setEvent(InputEvent::SaveConfigurationRequest); + break; + } + } + } + +private: + InputEvent::Intent m_currentIntent = InputEvent::SetDisplayLength; + + void decrement() { + int current = 0; + switch (m_currentIntent) { + case InputEvent::SetDisplayLength: + current = Static::instance()->coordMap()->pixelCount; + break; + case InputEvent::SetDisplayOffset: + current = Static::instance()->coordMap()->startPixel; + break; + } + setEvent(InputEvent{m_currentIntent, current - 1}); + } + + void increment() { + int current = 0; + switch (m_currentIntent) { + case InputEvent::SetDisplayLength: + current = Static::instance()->coordMap()->pixelCount; + break; + case InputEvent::SetDisplayOffset: + current = Static::instance()->coordMap()->startPixel; + break; + } + setEvent(InputEvent{m_currentIntent, current + 1}); + } + + InputEvent::Intent nextIntent() { + switch (m_currentIntent) { + case InputEvent::SetDisplayLength: + return InputEvent::SetDisplayOffset; + case InputEvent::SetDisplayOffset: + return InputEvent::SetDisplayLength; + } + } +}; + +enum Phase { + Null, + AstronomicalDay, + NauticalDay, + CivilDay, + CivilNight, + NauticalNight, + AstronomicalNight, + Evening, + Bedtime, +}; + +struct ScheduleEntry { + uint8_t hour; + uint8_t brightness; +}; + +std::array schedule{{ + {0, 0}, + {5, 0}, + {6, 0}, + {7, 10}, + {8, 80}, + {11, 120}, + {18, 200}, + {19, 255}, + {22, 120}, + {23, 20} +}}; + +uint8_t brightnessForTime(uint8_t hour, uint8_t minute) { + ScheduleEntry start = schedule.back(); + ScheduleEntry end = schedule.front(); + for(ScheduleEntry cur : schedule) { + // Find the last hour that is prior to or equal to now + if (cur.hour <= hour) { + start = cur; + } else { + break; + } + } + for(ScheduleEntry cur : schedule) { + // Find the first hour that is after now + // If no such hour exists, we should automatically wrap back to hour 0 + if (cur.hour > hour) { + end = cur; + break; + } + } + + if (start.hour > end.hour) { + end.hour += 24; + } + + uint16_t startTime = start.hour * 60; + uint16_t endTime = end.hour * 60; + uint16_t nowTime = hour * 60 + minute; + + uint16_t duration = endTime - startTime; + uint16_t curDuration = nowTime - startTime; + + uint8_t frac = ((double)curDuration / (double)duration) * 255.0; + + return lerp8by8(start.brightness, end.brightness, frac); +} + +WiFiUDP wifiUdp; +NTP ntp(wifiUdp); + +InputFunc circadianRhythm([]() { + static bool needsUpdate = true; + EVERY_N_SECONDS(60) { + needsUpdate = true; + } +#ifdef PLATFORM_PHOTON + if (Time.isValid() && needsUpdate) { + needsUpdate = false; + return InputEvent{InputEvent::SetBrightness, brightnessForTime(Time.hour(), Time.minute())}; + } +#else + ntp.update(); + if (needsUpdate) { + needsUpdate = false; + return InputEvent{InputEvent::SetBrightness, brightnessForTime(ntp.hours(), ntp.minutes())}; + } +#endif + return InputEvent{}; +}, "CircadianRhythm", Task::Running); + +// A special mainloop app for configuring hardware settings that reboots the +// device when the user is finished. +MainLoop configApp{{ + // Manage read/write of configuration data + Static::instance(), + +#ifdef PLATFORM_PHOTON + // Update photon telemetry + Static::instance(), +#endif + + // Read hardware inputs + new Buttons(), + + // Map input buttons to configuration commands + new ConfigInputTask(), + + // Fill the entire display with a color, to see size + &configDisplay, + // Render some basic input feedback + &inputBlip, + // Render it all + &configRenderer, +}}; + +// Turn on, +MainLoop renderbugApp{{ + + // Load/update graphics configuration from EEPROM and Particle + Static::instance(), + + // Platform inputs +#ifdef PLATFORM_PHOTON + // Particle cloud status + Static::instance(), + + // Monitor network state and provide particle API events + Static::instance(), +#else + // ESP Wifi + Static::instance(), +#endif + + // Hardware drivers + //new MPU5060(), + //new Buttons(), + + // Map buttons to events + &keyMap, + + // Pattern sequencer + &sequencer, + + // Daily rhythm activities + &circadianRhythm, + + // Periodic motion input + &randomPulse, + + // Periodic color inputs + &noisebridgeCycle, + &kierynCycle, + &rainbowCycle, + &hackerbotsCycle, + + // Animations + &chimes, + &drain, + &solid, + &flashlight, + + // Update UI layer + &power, + &displayClip, + Static::instance(), + &inputBlip, + + // Render everything + &renderer, + + // Platform telemetry +#ifdef PLATFORM_PHOTON + // Update photon telemetry + Static::instance(), + + // Web telemetry UI + Static::instance(), + + // MQTT telemetry + Static::instance(), + + // Network discovery + Static::instance(), + + //Watchdog + Static::instance(), +#else + // MQTT + Static::instance(), +#endif +}}; + +MainLoop &runner = renderbugApp; + +#ifdef PLATFORM_PHOTON +STARTUP(BootOptions::initPins()); +retained BootOptions bootopts; +#else +BootOptions bootopts; + +void printNewline(Print* logOutput) { + logOutput->print("\r\n"); +} +#endif + +// Tune in, +void setup() { + Serial.begin(115200); +#ifdef PLATFORM_PHOTON + System.enableFeature(FEATURE_RETAINED_MEMORY); + if (bootopts.isFlash) { + System.dfu(); + } + if (bootopts.isSerial) { + bootopts.waitForRelease(); + while(!Serial.isConnected()) { + Particle.process(); + } + //Log.info("\xf0\x9f\x94\x8c Serial connected"); + } +#else + Log.begin(LOG_LEVEL_VERBOSE, &Serial, true); + Log.setSuffix(printNewline); + Log.notice("Test log?"); +#endif + Log.notice(u8"🐛 Booting Renderbug!"); + Log.notice(u8"🐞 I am built for %d LEDs running on %dmA", HardwareConfig::MAX_LED_NUM, PSU_MILLIAMPS); +#ifdef PLATFORM_PHOTON + Log.notice(u8"📡 Particle version %s", System.version().c_str()); +#endif + Log.notice(u8" Boot pin configuration:"); + Log.notice(u8" 2: Setup - %d", bootopts.isSetup); + Log.notice(u8" 3: Serial - %d", bootopts.isSerial); + Log.notice(u8" 4: Flash - %d", bootopts.isFlash); + + //Log.info(u8" Setting timezone to UTC-7"); + //Time.zone(-7); + + Log.notice(u8"💡 Starting FastLED..."); +#ifdef PLATFORM_PHOTON + FastLED.addLeds(leds, HardwareConfig::MAX_LED_NUM); +#else + FastLED.addLeds(leds, HardwareConfig::MAX_LED_NUM); +#endif + + if (bootopts.isSetup) { + Log.notice(u8"🌌 Starting Figment in configuration mode..."); + runner = configApp; + } else { + Log.notice(u8"🌌 Starting Figment..."); + } + Serial.flush(); + runner.start(); + + //Log.info(u8"💽 %lu bytes of free RAM", System.freeMemory()); + Log.notice(u8"🚀 Setup complete! Ready to rock and roll."); + Serial.flush(); + ntp.begin(); + ArduinoOTA.begin(); +} + +// Drop out. +void loop() { + runner.loop(); +} diff --git a/src/platform/arduino/MQTTTelemetry.h b/src/platform/arduino/MQTTTelemetry.h new file mode 100644 index 0000000..a676a36 --- /dev/null +++ b/src/platform/arduino/MQTTTelemetry.h @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include + +WiFiClient wifi; +PubSubClient m_mqtt(wifi); + +class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin { + public: + MQTTTelemetry() : BufferedInputSource("MQTT") {} + + void handleEventOnline(const InputEvent& evt) override { + if (!m_mqtt.connected()) { + Log.notice("Connecting to MQTT..."); + const IPAddress server(10, 0, 0, 2); + uint64_t chipid = ESP.getEfuseMac(); + char deviceID[15]; + snprintf(deviceID, 15, "%08X", (uint32_t)chipid); + Log.verbose("Device ID %s", deviceID); + m_mqtt.setServer(server, 1883); + m_mqtt.setBufferSize(512); + m_mqtt.setCallback(&MQTTTelemetry::s_callback); + if (m_mqtt.connect(deviceID)) { + Log.notice("Connected to MQTT"); + + const String deviceName = String("Renderbug ESP32 ") + (char*)deviceID; + const String rootTopic = String("homeassistant/light/renderbug/") + (char*)deviceID; + const String configTopic = rootTopic + "/config"; + Log.verbose("root topic %s", rootTopic.c_str()); + Log.verbose("config topic %s", configTopic.c_str()); + const String statTopic = rootTopic + "/state"; + const String cmdTopic = rootTopic + "/set"; + const String attrTopic = rootTopic + "/attributes"; + strcpy(m_statTopic, statTopic.c_str()); + strcpy(m_attrTopic, attrTopic.c_str()); + strcpy(m_cmdTopic, cmdTopic.c_str()); + + StaticJsonDocument<1024> configJson; + configJson["~"] = rootTopic; + configJson["name"] = deviceName; + configJson["ret"] = true; + configJson["unique_id"] = (char*) deviceID; + configJson["cmd_t"] = "~/set"; + configJson["stat_t"] = "~/state"; + configJson["json_attr_t"] = "~/attributes"; + configJson["schema"] = "json"; + configJson["brightness"] = true; + configJson["rgb"] = true; + + configJson["dev"]["name"] = "Renderbug"; +#ifdef PLATFORM_PHOTON + configJson["dev"]["mdl"] = "Photon"; +#else + configJson["dev"]["mdl"] = "ESP32"; +#endif + configJson["dev"]["sw"] = RENDERBUG_VERSION; + configJson["dev"]["mf"] = "Phong Robotics"; + configJson["dev"]["ids"][0] = (char*)deviceID; + char buf[1024]; + serializeJson(configJson, buf, sizeof(buf)); + Log.verbose("Publish %s %s", configTopic.c_str(), buf); + m_mqtt.publish(configTopic.c_str(), buf); + m_mqtt.subscribe(m_cmdTopic); + } else { + Log.warning("Could not connect to MQTT"); + } + } else { + if (evt.intent == InputEvent::SetPower) { + StaticJsonDocument<256> doc; + char buf[256]; + doc["state"] = evt.asInt() ? "ON" : "OFF"; + serializeJson(doc, buf, sizeof(buf)); + m_mqtt.publish(m_statTopic, buf); + } else if (evt.intent == InputEvent::SetBrightness) { + StaticJsonDocument<256> doc; + char buf[256]; + doc["brightness"] = evt.asInt(); + serializeJson(doc, buf, sizeof(buf)); + m_mqtt.publish(m_statTopic, buf); + } else if (evt.intent == InputEvent::SetColor) { + StaticJsonDocument<256> doc; + char buf[256]; + CRGB color = evt.asRGB(); + doc["color"]["r"] = color.r; + doc["color"]["g"] = color.g; + doc["color"]["b"] = color.b; + serializeJson(doc, buf, sizeof(buf)); + m_mqtt.publish(m_statTopic, buf); + } else if (evt.intent == InputEvent::SetPattern) { + StaticJsonDocument<256> doc; + char buf[256]; + doc["effect"] = evt.asString(); + serializeJson(doc, buf, sizeof(buf)); + m_mqtt.publish(m_statTopic, buf); + } + } + } + + void loop() override { + BufferedInputSource::loop(); + OnlineTaskMixin::loop(); + } + + void loopOnline() override { + m_mqtt.loop(); + EVERY_N_SECONDS(10) { + char buf[254]; + StaticJsonDocument<200> response; + response["fps"] = FastLED.getFPS(); + response["RSSI"] = WiFi.RSSI(); + response["localip"] = WiFi.localIP().toString(); + response["free_ram"] = ESP.getFreeHeap(); + response["os_version"] = ESP.getSdkVersion(); + response["sketch_version"] = ESP.getSketchMD5(); + serializeJson(response, buf, sizeof(buf)); + m_mqtt.publish(m_attrTopic, buf); + } + } + + private: + char m_statTopic[100]; + char m_attrTopic[100]; + char m_cmdTopic[100]; + + void callback(char* topic, byte* payload, unsigned int length) { + DynamicJsonDocument doc(1024); + deserializeJson(doc, payload, length); + + if (doc.containsKey("state")) { + if (doc["state"] == "ON") { + setEvent(InputEvent{InputEvent::SetPower, true}); + } else if (doc["state"] == "OFF") { + setEvent(InputEvent{InputEvent::SetPower, false}); + } + } + + if (doc.containsKey("effect")) { + strcpy(m_patternBuf, doc["effect"].as()); + setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf}); + } + + if (doc.containsKey("color")) { + uint8_t r = doc["color"]["r"]; + uint8_t g = doc["color"]["g"]; + uint8_t b = doc["color"]["b"]; + setEvent(InputEvent{InputEvent::SetColor, CRGB(r, g, b)}); + } + + if (doc.containsKey("brightness")) { + setEvent(InputEvent{InputEvent::SetBrightness, (int)doc["brightness"]}); + } + } + + static void s_callback(char* topic, byte* payload, unsigned int length) { + Static::instance()->callback(topic, payload, length); + } + + char m_patternBuf[48]; +}; + +STATIC_ALLOC(MQTTTelemetry); diff --git a/firmware/MDNSService.cpp b/src/platform/particle/MDNSService.cpp similarity index 96% rename from firmware/MDNSService.cpp rename to src/platform/particle/MDNSService.cpp index 1250f66..8d5eb6a 100644 --- a/firmware/MDNSService.cpp +++ b/src/platform/particle/MDNSService.cpp @@ -31,3 +31,5 @@ class MDNSService : public Task { } } }; + +STATIC_ALLOC(MDNSService); diff --git a/src/platform/particle/MQTTTelemetry.cpp b/src/platform/particle/MQTTTelemetry.cpp new file mode 100644 index 0000000..426f214 --- /dev/null +++ b/src/platform/particle/MQTTTelemetry.cpp @@ -0,0 +1,205 @@ +#include "MQTT/MQTT.h" + +class MQTTTelemetry : public BufferedInputSource, OnlineTaskMixin { + public: + MQTTTelemetry() : BufferedInputSource("MQTT"), m_client("relay.malloc.hackerbots.net", 1883, 512, 15, MQTTTelemetry::s_callback, true) { + strcpy(m_deviceName, System.deviceID().c_str()); + } + + void loop() override { + BufferedInputSource::loop(); + OnlineTaskMixin::loop(); + } + + void handleEventOnline(const InputEvent& event) override { + char response[255]; + if (event.intent == InputEvent::SetPower) { + JSONBufferWriter writer(response, sizeof(response)); + writer.beginObject(); + writer.name("state").value(event.asInt() == 0 ? "OFF" : "ON"); + writer.endObject(); + writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; + m_client.publish(m_statTopic, response, MQTT::QOS1); + } else if (event.intent == InputEvent::SetBrightness) { + JSONBufferWriter writer(response, sizeof(response)); + writer.beginObject(); + writer.name("brightness").value(event.asInt()); + writer.endObject(); + writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; + m_client.publish(m_statTopic, response, MQTT::QOS1); + } else if (event.intent == InputEvent::SetColor) { + JSONBufferWriter writer(response, sizeof(response)); + writer.beginObject(); + writer.name("color").beginObject(); + CRGB rgb = event.asRGB(); + writer.name("r").value(rgb.r); + writer.name("g").value(rgb.g); + writer.name("b").value(rgb.b); + writer.endObject(); + writer.endObject(); + writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; + m_client.publish(m_statTopic, response, MQTT::QOS1); + } else if (event.intent == InputEvent::SetPattern) { + JSONBufferWriter writer(response, sizeof(response)); + writer.beginObject(); + writer.name("effect").value(event.asString()); + writer.endObject(); + writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; + m_client.publish(m_statTopic, response, MQTT::QOS1); + } else { + if (m_lastIntent != event.intent) { + m_lastIntent = event.intent; + JSONBufferWriter writer(response, sizeof(response)); + writer.beginObject(); + writer.name("intent").value(event.intent); + writer.endObject(); + writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; + m_client.publish("renderbug/events", response, MQTT::QOS1); + } + } + } + + void loopOnline() override { + if (m_client.isConnected()) { + m_client.loop(); + EVERY_N_SECONDS(10) { + char heartbeatBuf[255]; + JSONBufferWriter writer(heartbeatBuf, sizeof(heartbeatBuf)); + writer.beginObject(); + writer.name("fps").value(FastLED.getFPS()); + writer.name("os_version").value(System.version()); + writer.name("free_ram").value((unsigned int)System.freeMemory()); + //writer.name("uptime").value(System.uptime()); + /*WiFiSignal sig = WiFi.RSSI(); + writer.name("strength").value(sig.getStrength()); + writer.name("quality").value(sig.getQuality());*/ + writer.name("RSSI").value(WiFi.RSSI()); + writer.name("SSID").value(WiFi.SSID()); + //writer.name("MAC").value(WiFi.macAddress()); + writer.name("localip").value(WiFi.localIP().toString()); + writer.name("startPixel").value(Static::instance()->coordMap()->startPixel); + writer.name("pixelCount").value(Static::instance()->coordMap()->pixelCount); + writer.endObject(); + writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; + m_client.publish(m_attrTopic, heartbeatBuf); + } + } else { + m_client.connect("renderbug_" + String(m_deviceName) + "_" + String(Time.now())); + char response[512]; + JSONBufferWriter writer(response, sizeof(response)); + + String devTopic = String("homeassistant/light/renderbug/") + m_deviceName; + writer.beginObject(); + writer.name("~").value(devTopic.c_str()); + writer.name("name").value("Renderbug Photon"); + writer.name("unique_id").value(m_deviceName); + writer.name("cmd_t").value("~/set"); + writer.name("stat_t").value("~/state"); + writer.name("json_attr_t").value("~/attributes"); + writer.name("schema").value("json"); + writer.name("brightness").value(true); + writer.name("rgb").value(true); + writer.name("ret").value(true); + + writer.name("dev").beginObject(); + writer.name("name").value("Renderbug"); + writer.name("mdl").value("Renderbug"); + writer.name("sw").value(RENDERBUG_VERSION); + writer.name("mf").value("Phong Robotics"); + + writer.name("ids").beginArray(); + writer.value(m_deviceName); + writer.endArray(); + + writer.endObject(); + + writer.name("fx_list").beginArray(); + for(const Sequencer::Scene& scene : sequencer.scenes()) { + writer.value(scene.name); + } + writer.endArray(); + + writer.endObject(); + writer.buffer()[std::min(writer.bufferSize(), writer.dataSize())] = 0; + + String configTopic = devTopic + "/config"; + m_client.publish(configTopic, response, true); + + String statTopic = devTopic + "/state"; + String cmdTopic = devTopic + "/set"; + String attrTopic = devTopic + "/attributes"; + strcpy(m_statTopic, statTopic.c_str()); + strcpy(m_attrTopic, attrTopic.c_str()); + strcpy(m_cmdTopic, cmdTopic.c_str()); + m_client.subscribe(m_cmdTopic, MQTT::QOS1); + } + } + + private: + void callback(char* topic, byte* payload, unsigned int length) { + MainLoop::instance()->dispatch(InputEvent::NetworkActivity); + if (!strcmp(topic, m_cmdTopic)) { + JSONValue cmd = JSONValue::parseCopy((char*)payload, length); + JSONObjectIterator cmdIter(cmd); + while(cmdIter.next()) { + if (cmdIter.name() == "state" && cmdIter.value().toString() == "ON") { + setEvent({InputEvent::SetPower, 1}); + } else if (cmdIter.name() == "state" && cmdIter.value().toString() == "OFF") { + setEvent({InputEvent::SetPower, 0}); + } + + if (cmdIter.name() == "color") { + JSONObjectIterator colorIter(cmdIter.value()); + uint8_t r, g, b; + while(colorIter.next()) { + if (colorIter.name() == "r") { + r = colorIter.value().toInt(); + } else if (colorIter.name() == "g") { + g = colorIter.value().toInt(); + } else if (colorIter.name() == "b") { + b = colorIter.value().toInt(); + } + } + setEvent(InputEvent{InputEvent::SetColor, CRGB{r, g, b}}); + } + + if (cmdIter.name() == "brightness") { + uint8_t brightness = cmdIter.value().toInt(); + setEvent(InputEvent{InputEvent::SetBrightness, brightness}); + } + + if (cmdIter.name() == "effect") { + strcpy(m_patternBuf, cmdIter.value().toString().c_str()); + setEvent(InputEvent{InputEvent::SetPattern, m_patternBuf}); + } + + if (cmdIter.name() == "pixelCount") { + setEvent(InputEvent{InputEvent::SetDisplayLength, cmdIter.value().toInt()}); + } + + if (cmdIter.name() == "startPixel") { + setEvent(InputEvent{InputEvent::SetDisplayOffset, cmdIter.value().toInt()}); + } + + if (cmdIter.name() == "save") { + setEvent(InputEvent{InputEvent::SaveConfigurationRequest}); + } + } + } + } + + static void s_callback(char* topic, byte* payload, unsigned int length) { + Static::instance()->callback(topic, payload, length); + }; + + MQTT m_client; + InputEvent::Intent m_lastIntent; + char m_deviceName[100]; + char m_statTopic[100]; + char m_attrTopic[100]; + char m_cmdTopic[100]; + char m_patternBuf[48]; +}; + +STATIC_ALLOC(MQTTTelemetry); + diff --git a/firmware/platform/particle/PhotonTelemetry.cpp b/src/platform/particle/PhotonTelemetry.cpp similarity index 100% rename from firmware/platform/particle/PhotonTelemetry.cpp rename to src/platform/particle/PhotonTelemetry.cpp diff --git a/firmware/platform/particle/PhotonTelemetry.h b/src/platform/particle/PhotonTelemetry.h similarity index 100% rename from firmware/platform/particle/PhotonTelemetry.h rename to src/platform/particle/PhotonTelemetry.h diff --git a/src/platform/particle/Watchdog.cpp b/src/platform/particle/Watchdog.cpp new file mode 100644 index 0000000..a12d43e --- /dev/null +++ b/src/platform/particle/Watchdog.cpp @@ -0,0 +1,30 @@ +#include + +BootOpts bootopts; + +void watchdogHandler() { + for(int i = 0; i < 8; i++) { + leds[i] = CRGB(i % 3 ? 35 : 255, 0, 0); + } + FastLED.show(); + if (bootopts.lastBootWasFlash) { + System.dfu(); + } else { + System.enterSafeMode(); + } +} + +class Watchdog : public Task { + public: + Watchdog() : Task("Watchdog") { + m_watchdog = new ApplicationWatchdog(5000, watchdogHandler, 1536); + } + + void loop() override { + m_watchdog->checkin(); + } + private: + ApplicationWatchdog *m_watchdog; +}; + +STATIC_ALLOC(Watchdog); diff --git a/firmware/WebTelemetry.cpp b/src/platform/particle/WebTelemetry.cpp similarity index 100% rename from firmware/WebTelemetry.cpp rename to src/platform/particle/WebTelemetry.cpp diff --git a/firmware/platform/particle/inputs/CloudStatus.cpp b/src/platform/particle/inputs/CloudStatus.cpp similarity index 100% rename from firmware/platform/particle/inputs/CloudStatus.cpp rename to src/platform/particle/inputs/CloudStatus.cpp diff --git a/firmware/platform/particle/inputs/CloudStatus.h b/src/platform/particle/inputs/CloudStatus.h similarity index 100% rename from firmware/platform/particle/inputs/CloudStatus.h rename to src/platform/particle/inputs/CloudStatus.h diff --git a/firmware/platform/particle/inputs/Photon.cpp b/src/platform/particle/inputs/Photon.cpp similarity index 76% rename from firmware/platform/particle/inputs/Photon.cpp rename to src/platform/particle/inputs/Photon.cpp index 561bab0..fd38e42 100644 --- a/firmware/platform/particle/inputs/Photon.cpp +++ b/src/platform/particle/inputs/Photon.cpp @@ -19,6 +19,16 @@ PhotonInput::onConnected() Particle.function("reboot", &PhotonInput::reboot, this); Particle.function("start", &PhotonInput::startThing, this); Particle.function("stop", &PhotonInput::stopThing, this); + + //Log.info("Connecting photon configuration..."); + Particle.function("pixelCount", &PhotonInput::setPixelCount, this); + Particle.function("startPixel", &PhotonInput::setStartPixel, this); + + Particle.function("save", &PhotonInput::photonSave, this); + Particle.variable("pixelCount", m_pixelCountInt); + Particle.variable("startPixel", m_startPixelInt); + + publishConfig(); } int @@ -141,4 +151,37 @@ PhotonInput::setBrightness(String command) return 0; } +void +PhotonInput::publishConfig() const +{ + char buf[255]; + snprintf(buf, sizeof(buf), "{\"pixels\": \"%d\", \"offset\": \"%d\"}", m_config.data.pixelCount, m_config.data.startPixel); + Particle.publish("renderbug/config", buf, PRIVATE); +} + +int +PhotonInput::photonSave(String command) +{ + setEvent(InputEvent::SaveConfiguration); + return 0; +} + +int +PhotonInput::setPixelCount(String command) +{ + m_pixelCount = command.toInt(); + setEvent(InputEvent{InputEvent::SetDisplayLength, m_pixelCount}) + return 0; +} + +int +PhotonInput::setStartPixel(String command) +{ + m_startPixel = command.toInt(); + setEvent(InputEvent{InputEvent::SetDisplayLength, m_startPixel}) + return 0; +} + + + STATIC_ALLOC(PhotonInput); diff --git a/firmware/platform/particle/inputs/Photon.h b/src/platform/particle/inputs/Photon.h similarity index 82% rename from firmware/platform/particle/inputs/Photon.h rename to src/platform/particle/inputs/Photon.h index 63124b4..d74bdf2 100644 --- a/firmware/platform/particle/inputs/Photon.h +++ b/src/platform/particle/inputs/Photon.h @@ -24,7 +24,15 @@ private: int startThing(String command); int stopThing(String command); + void publishConfig() const; + int photonSave(String command); + int setPixelCount(String command); + int setStartPixel(String command); + static void onReset(system_event_t event, int param); static void onButtonClick(system_event_t event, int param); static void onFirmwareUpdate(system_event_t event, int param); + + int m_startPixel; + int m_pixelCount; }; diff --git a/firmware/sprites/Blob.h b/src/sprites/Blob.h similarity index 89% rename from firmware/sprites/Blob.h rename to src/sprites/Blob.h index ab425a5..916251c 100644 --- a/firmware/sprites/Blob.h +++ b/src/sprites/Blob.h @@ -1,6 +1,6 @@ #pragma once -using namespace NSFastLED; +#include class Blob { uint16_t m_pos; @@ -50,14 +50,17 @@ public: void render(Display* display) const { const uint8_t width = 25; + //Log.notice("get coords"); 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); + //Log.notice("blob w=%d x=%d", scaledWidth, startPos.x); for(uint8_t i = 0;i < scaledWidth; i++) { // Blobs desaturate towards their tail + //Log.notice("blob i=%d w=%d x=%d", i, scaledWidth, startPos.x); CHSV blobColor(m_hue, m_saturation, quadwave8((i / (double)scaledWidth) * m_brightness)); PhysicalCoordinates pos{startPos.x + (i*m_fadeDir), 0}; diff --git a/firmware/sprites/Chime.h b/src/sprites/Chime.h similarity index 87% rename from firmware/sprites/Chime.h rename to src/sprites/Chime.h index b9b4dc5..cef0ad5 100644 --- a/firmware/sprites/Chime.h +++ b/src/sprites/Chime.h @@ -1,7 +1,5 @@ #pragma once -using namespace NSFastLED; - template class Chime { uint16_t m_pos; @@ -67,10 +65,10 @@ public: dpy->pixelAt(i + m_offset) = CHSV(0, 0, 0); } else { uint8_t distance = m_pos - i; - uint8_t brightness = scale8(quadwave8((ChimeLength / (double)distance) * 255), m_brightness); + uint16_t brightness = scale8(quadwave8((ChimeLength / (double)distance) * 255), m_brightness); if (brightness <= 0.2) brightness = 0; - dpy->pixelAt(VirtualCoordinates{i + m_offset, 0}) = CHSV(m_hue, min(m_saturation, brightness), brightness); + dpy->pixelAt(VirtualCoordinates{i + m_offset, 0}) = CHSV(m_hue, std::min(m_saturation, brightness), brightness); } } } diff --git a/test/README b/test/README new file mode 100644 index 0000000..b94d089 --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html