diff --git a/.travis.yml b/.travis.yml index a8cfd64e..6256dac0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,5 +31,7 @@ matrix: if: branch = dev AND type = push - env: BUILD_TARGET=fastled if: branch = dev AND type = push + - env: BUILD_TARGET=neopixelbus + if: branch = dev AND type = push - env: BUILD_TARGET=sonoff-b1 if: branch = dev AND type = push diff --git a/examples/neopixelbus/neopixelbus.cpp b/examples/neopixelbus/neopixelbus.cpp new file mode 100644 index 00000000..3764156d --- /dev/null +++ b/examples/neopixelbus/neopixelbus.cpp @@ -0,0 +1,35 @@ +#include + +using namespace esphomelib; + +uint16_t pixel_count = 30; +uint8_t pixel_pin = 2; +// in Neo800KbpsMethod (dma) the pixel_pin is not used and is always PIN 2 (RX) +NeoPixelBus strip(pixel_count, pixel_pin); + +void setup() { + App.set_name("neopixelbus"); + App.init_log(); + + App.init_wifi("YOUR_SSID", "YOUR_PASSWORD"); + App.init_mqtt("MQTT_HOST", "USERNAME", "PASSWORD"); + App.init_ota()->start_safe_mode(); + + auto neopixel = App.make_neo_pixel_bus_rgbw_light("NeoPixelBus SK 6812 Light"); + neopixel.output->set_pixel_order(light::ESPNeoPixelOrder::GRBW); + neopixel.output->add_leds(&strip); + neopixel.state->set_default_transition_length(800); + + neopixel.state->add_effects({ + new light::RandomLightEffect("Random"), + new light::AddressableColorWipeEffect("Color Wipe"), + new light::AddressableRainbowLightEffect("Rainbow"), + }); + + App.setup(); +} + +void loop() { + App.loop(); + delay(16); +} diff --git a/examples/neopixelbus/neopixelbus.ino b/examples/neopixelbus/neopixelbus.ino new file mode 100644 index 00000000..136c2b60 --- /dev/null +++ b/examples/neopixelbus/neopixelbus.ino @@ -0,0 +1,2 @@ +// This file intentionally left blank +// See other tab for the code diff --git a/library.json b/library.json index 78362586..12198e43 100644 --- a/library.json +++ b/library.json @@ -31,6 +31,10 @@ { "name": "FastLED", "version": "3.2.0" + }, + { + "name": "NeoPixelBus", + "version": "2.4.1" } ], "build": { diff --git a/platformio.ini b/platformio.ini index ea7e086e..9e0a1400 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,6 +11,7 @@ lib_deps = ArduinoJson-esphomelib@5.13.3 ESP Async WebServer@1.1.1 FastLED@3.2.0 + NeoPixelBus@2.4.1 build_flags = -Wno-reorder src_filter = + @@ -104,3 +105,11 @@ framework = arduino lib_deps = ${common.lib_deps} build_flags = ${common.build_flags} src_filter = ${common.src_filter} + + +[env:neopixelbus] +platform = espressif32 +board = nodemcu-32s +framework = arduino +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} +src_filter = ${common.src_filter} + diff --git a/src/esphomelib/application.h b/src/esphomelib/application.h index 1e02baef..7fdeffd5 100644 --- a/src/esphomelib/application.h +++ b/src/esphomelib/application.h @@ -56,6 +56,7 @@ #include "esphomelib/light/light_output_component.h" #include "esphomelib/light/light_state.h" #include "esphomelib/light/mqtt_json_light_component.h" +#include "esphomelib/light/neo_pixel_bus_light_output.h" #include "esphomelib/mqtt/custom_mqtt_device.h" #include "esphomelib/mqtt/mqtt_client_component.h" #include "esphomelib/mqtt/mqtt_component.h" @@ -1196,6 +1197,23 @@ class Application { MakeFastLEDLight make_fast_led_light(const std::string &name); #endif +#ifdef USE_NEO_PIXEL_BUS_LIGHT + template + struct MakeNeoPixelBusLight { + light::NeoPixelRGBLightOutput *output; + light::LightState *state; + light::MQTTJSONLightComponent *mqtt; + }; + + /// Create an RGB NeoPixelBus light. + template + MakeNeoPixelBusLight make_neo_pixel_bus_rgb_light(const std::string &name); + + /// Create an RGBW NeoPixelBus light. + template + MakeNeoPixelBusLight make_neo_pixel_bus_rgbw_light(const std::string &name); +#endif + @@ -1489,6 +1507,31 @@ GlobalVariableComponent *Application::make_global_variable(T initial_value) { return this->register_component(new GlobalVariableComponent(initial_value)); } +#ifdef USE_NEO_PIXEL_BUS_LIGHT +template +Application::MakeNeoPixelBusLight Application::make_neo_pixel_bus_rgb_light(const std::string &name) { + auto *neo_pixel = this->register_component(new light::NeoPixelRGBLightOutput()); + auto make = this->make_light_for_light_output(name, neo_pixel); + + return MakeNeoPixelBusLight { + .output = neo_pixel, + .state = make.state, + .mqtt = make.mqtt, + }; +} +template +Application::MakeNeoPixelBusLight Application::make_neo_pixel_bus_rgbw_light(const std::string &name) { + auto *neo_pixel = this->register_component(new light::NeoPixelRGBWLightOutput()); + auto make = this->make_light_for_light_output(name, neo_pixel); + + return MakeNeoPixelBusLight { + .output = neo_pixel, + .state = make.state, + .mqtt = make.mqtt, + }; +} +#endif + ESPHOMELIB_NAMESPACE_END #endif //ESPHOMELIB_APPLICATION_H diff --git a/src/esphomelib/defines.h b/src/esphomelib/defines.h index 1cc978f3..a460b68b 100644 --- a/src/esphomelib/defines.h +++ b/src/esphomelib/defines.h @@ -133,6 +133,7 @@ #define USE_HOMEASSISTANT_SENSOR #define USE_HOMEASSISTANT_TEXT_SENSOR #define USE_APDS9960 + #define USE_NEO_PIXEL_BUS_LIGHT #endif #ifdef USE_REMOTE_RECEIVER diff --git a/src/esphomelib/light/fast_led_light_effect.cpp b/src/esphomelib/light/fast_led_light_effect.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/esphomelib/light/fast_led_light_effect.h b/src/esphomelib/light/fast_led_light_effect.h deleted file mode 100644 index e69de29b..00000000 diff --git a/src/esphomelib/light/neo_pixel_bus_light_output.h b/src/esphomelib/light/neo_pixel_bus_light_output.h new file mode 100644 index 00000000..7329886f --- /dev/null +++ b/src/esphomelib/light/neo_pixel_bus_light_output.h @@ -0,0 +1,126 @@ +#ifndef ESPHOMELIB_LIGHT_NEO_PIXEL_BUS_LIGHT_OUTPUT_H +#define ESPHOMELIB_LIGHT_NEO_PIXEL_BUS_LIGHT_OUTPUT_H + +#include "esphomelib/defines.h" + +#ifdef USE_NEO_PIXEL_BUS_LIGHT + +#include "esphomelib/helpers.h" +#include "esphomelib/light/light_state.h" +#include "esphomelib/light/addressable_light.h" +#include "esphomelib/power_supply_component.h" +#include "NeoPixelBus.h" + +ESPHOMELIB_NAMESPACE_BEGIN + +namespace light { + +enum class ESPNeoPixelOrder { + GBWR = 0b11000110, + GBRW = 0b10000111, GBR = 0b10000111, + GWBR = 0b11001001, + GRBW = 0b01001011, GRB = 0b01001011, + GWRB = 0b10001101, + GRWB = 0b01001110, + BGWR = 0b11010010, + BGRW = 0b10010011, BGR = 0b10010011, + WGBR = 0b11011000, + RGBW = 0b00011011, RGB = 0b00011011, + WGRB = 0b10011100, + RGWB = 0b00011110, + BWGR = 0b11100001, + BRGW = 0b01100011, BRG = 0b01100011, + WBGR = 0b11100100, + RBGW = 0b00100111, RBG = 0b00100111, + WRGB = 0b01101100, + RWGB = 0b00101101, + BWRG = 0b10110001, + BRWG = 0b01110010, + WBRG = 0b10110100, + RBWG = 0b00110110, + WRBG = 0b01111000, + RWBG = 0b00111001, +}; + +/** This component implements support for many types of addressable LED lights. + * + * To do this, it uses the NeoPixelBus library. The API for setting up the different + * types of lights NeoPixelBus supports is intentionally kept as close to NeoPixelBus defaults + * as possible. To use NeoPixelBus lights with esphomelib, first set up the component using + * the helper in Application, then add the LEDs using the `add_leds` helper functions. + * + * These add_leds helpers can, however, only be called once on a NeoPixelBusLightOutputComponent. + */ +template +class NeoPixelBusLightOutputBase : public LightOutput, public Component, public AddressableLight { + public: + void schedule_show(); + +#ifdef USE_OUTPUT + void set_power_supply(PowerSupplyComponent *power_supply); +#endif + + NeoPixelBus *get_controller_() const; + + void set_correction(float red, float green, float blue, float white = 0.0f); + + void clear_effect_data() override; + + void setup_state(LightState *state) override; + + /// Add some LEDS, can only be called once. + void add_leds(uint16_t count_pixels, uint8_t pin); + void add_leds(uint16_t count_pixels, uint8_t pin_clock, uint8_t pin_data); + void add_leds(uint16_t count_pixels); + void add_leds(NeoPixelBus *controller); + + // ========== INTERNAL METHODS ========== + void write_state(LightState *state) override; + + void setup() override; + + void loop() override; + + float get_setup_priority() const override; + + int32_t size() const override; + + void set_pixel_order(ESPNeoPixelOrder order); + + protected: + NeoPixelBus *controller_{nullptr}; + bool next_show_{true}; + ESPColorCorrection correction_{}; + uint8_t *effect_data_{nullptr}; + uint8_t rgb_offsets_[4]; +#ifdef USE_OUTPUT + PowerSupplyComponent *power_supply_{nullptr}; + bool has_requested_high_power_{false}; +#endif +}; + +template +class NeoPixelRGBLightOutput : public NeoPixelBusLightOutputBase { + public: + inline ESPColorView operator[](int32_t index) const override; + + LightTraits get_traits() override; +}; + +template +class NeoPixelRGBWLightOutput : public NeoPixelBusLightOutputBase { + public: + inline ESPColorView operator[](int32_t index) const override; + + LightTraits get_traits() override; +}; + +} // namespace light + +ESPHOMELIB_NAMESPACE_END + +#include "esphomelib/light/neo_pixel_bus_light_output.tcc" + +#endif //USE_NEO_PIXEL_BUS_LIGHT + +#endif //ESPHOMELIB_LIGHT_NEO_PIXEL_BUS_LIGHT_OUTPUT_H diff --git a/src/esphomelib/light/neo_pixel_bus_light_output.tcc b/src/esphomelib/light/neo_pixel_bus_light_output.tcc new file mode 100644 index 00000000..b476b94e --- /dev/null +++ b/src/esphomelib/light/neo_pixel_bus_light_output.tcc @@ -0,0 +1,189 @@ +#include "esphomelib/defines.h" + +#ifdef USE_NEO_PIXEL_BUS_LIGHT + +#include "esphomelib/light/neo_pixel_bus_light_output.h" +#include "esphomelib/helpers.h" + +ESPHOMELIB_NAMESPACE_BEGIN + +namespace light { + +template +void NeoPixelBusLightOutputBase::schedule_show() { + this->next_show_ = true; +} + +#ifdef USE_OUTPUT +template +void NeoPixelBusLightOutputBase::set_power_supply(PowerSupplyComponent *power_supply) { + this->power_supply_ = power_supply; +} +#endif + +template +NeoPixelBus *NeoPixelBusLightOutputBase::get_controller_() const { + return this->controller_; +} +template +void NeoPixelBusLightOutputBase::set_correction(float red, + float green, + float blue, + float white) { + this->correction_.set_max_brightness(ESPColor( + uint8_t(roundf(red * 255.0f)), + uint8_t(roundf(green * 255.0f)), + uint8_t(roundf(blue * 255.0f)), + uint8_t(roundf(white * 255.0f)) + )); +} +template +void NeoPixelBusLightOutputBase::clear_effect_data() { + for (int i = 0; i < this->size(); i++) + this->effect_data_[i] = 0; +} +template +void NeoPixelBusLightOutputBase::setup_state(LightState *state) { + this->correction_.calculate_gamma_table(state->get_gamma_correct()); +} +template +void NeoPixelBusLightOutputBase::add_leds(uint16_t count_pixels, uint8_t pin) { + this->add_leds(new NeoPixelBus(count_pixels, pin)); +} +template +void NeoPixelBusLightOutputBase::add_leds(uint16_t count_pixels, + uint8_t pin_clock, + uint8_t pin_data) { + this->add_leds(new NeoPixelBus(count_pixels, pin_clock, pin_data)); +} +template +void NeoPixelBusLightOutputBase::add_leds(uint16_t count_pixels) { + this->add_leds(new NeoPixelBus(count_pixels)); +} +template +void NeoPixelBusLightOutputBase::add_leds(NeoPixelBus *controller) { + this->controller_ = controller; + this->controller_->Begin(); +} +template +void NeoPixelBusLightOutputBase::write_state(LightState *state) { + LightColorValues value = state->get_current_values(); + uint8_t max_brightness = roundf(value.get_brightness() * value.get_state() * 255.0f); + this->correction_.set_max_brightness(max_brightness); + + if (this->is_effect_active()) + return; + + float r, g, b, w; + state->current_values_as_rgbw(&r, &g, &b, &w); + ESPColor color = ESPColor( + uint8_t(roundf(r * 255.0f)), + uint8_t(roundf(g * 255.0f)), + uint8_t(roundf(b * 255.0f)), + uint8_t(roundf(w * 255.0f)) + ); + + for (int i = 0; i < this->size(); i++) { + (*this)[i] = color; + } + + this->schedule_show(); +} +template +void NeoPixelBusLightOutputBase::setup() { + for (int i = 0; i < this->size(); i++) { + (*this)[i] = ESPColor(0, 0, 0, 0); + } + + this->effect_data_ = new uint8_t[this->size()]; +} +template +void NeoPixelBusLightOutputBase::loop() { + if (!this->next_show_ && !this->is_effect_active()) + return; + + this->next_show_ = false; + this->controller_->Dirty(); + +#ifdef USE_OUTPUT + if (this->power_supply_ != nullptr) { + bool is_light_on = false; + for (int i = 0; i < this->size(); i++) { + if ((*this)[i].get().is_on()) { + is_light_on = true; + break; + } + } + + if (is_light_on && !this->has_requested_high_power_) { + this->power_supply_->request_high_power(); + this->has_requested_high_power_ = true; + } + if (!is_light_on && this->has_requested_high_power_) { + this->power_supply_->unrequest_high_power(); + this->has_requested_high_power_ = false; + } + } +#endif + + this->controller_->Show(); +} +template +float NeoPixelBusLightOutputBase::get_setup_priority() const { + return setup_priority::HARDWARE; +} +template +int32_t NeoPixelBusLightOutputBase::size() const { + return this->controller_->PixelCount(); +} + +template +void NeoPixelBusLightOutputBase::set_pixel_order(ESPNeoPixelOrder order) { + uint8_t order_ = static_cast(order); + this->rgb_offsets_[0] = (order_ >> 6) & 0b11; + this->rgb_offsets_[1] = (order_ >> 4) & 0b11; + this->rgb_offsets_[2] = (order_ >> 2) & 0b11; + this->rgb_offsets_[3] = (order_ >> 0) & 0b11; +} + +template +ESPColorView NeoPixelRGBLightOutput::operator[](int32_t index) const { + uint8_t *base = this->controller_->Pixels() + 3ULL * index; + return ESPColorView( + base + this->rgb_offsets_[0], + base + this->rgb_offsets_[1], + base + this->rgb_offsets_[2], + nullptr, + this->effect_data_ + index, + &this->correction_ + ); +} + +template +ESPColorView NeoPixelRGBWLightOutput::operator[](int32_t index) const { + uint8_t *base = this->controller_->Pixels() + 3ULL * index; + return ESPColorView( + base + this->rgb_offsets_[0], + base + this->rgb_offsets_[1], + base + this->rgb_offsets_[2], + base + this->rgb_offsets_[3], + this->effect_data_ + index, + &this->correction_ + ); +} + +template +LightTraits NeoPixelRGBLightOutput::get_traits() { + return {true, true, false, false}; +} + +template +LightTraits NeoPixelRGBWLightOutput::get_traits() { + return {true, true, true, false}; +} + +} // namespace light + +ESPHOMELIB_NAMESPACE_END + +#endif //USE_NEO_PIXEL_BUS_LIGHT