Skip to content

Commit

Permalink
Update E1.31 support
Browse files Browse the repository at this point in the history
- Change to `E131AddressableLightEffect`
- Update log messages
- Add MONO mode of operation
  • Loading branch information
ayufan committed Jan 12, 2020
1 parent 1a21040 commit 48b5dcc
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 120 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ switch:

A component to support [E1.31](https://www.doityourselfchristmas.com/wiki/index.php?title=E1.31_(Streaming-ACN)_Protocol). This allows to control addressable LEDs over WiFi, by pushing data right into LEDs.

The most popular application would be: [JINX](http://www.live-leds.de/jinx-v1-3-with-resizable-mainwindow-real-dmx-and-sacne1-31/).
The most popular application to push data would be: [JINX](http://www.live-leds.de/jinx-v1-3-with-resizable-mainwindow-real-dmx-and-sacne1-31/).

```yaml
e131:
Expand All @@ -157,8 +157,9 @@ light:

There are two modes of operation:

- `RGB`: this supports 3 components per channel (RGB), up-to 170 LEDs (3*170 = 510 bytes) per universe
- `RGBW`: this supports 4 components per channel (RGBW), up-to 128 LEDs (4*128 = 512 bytes) per universe
- `MONO`: this supports 1 channel per LED (luminance), up-to 512 LEDs per universe
- `RGB`: this supports 3 channels per LED (RGB), up-to 170 LEDs (3*170 = 510 bytes) per universe
- `RGBW`: this supports 4 channels per LED (RGBW), up-to 128 LEDs (4*128 = 512 bytes) per universe

If there's more LEDs than allowed per-universe, additional universe will be used.
In the above example of 189 LEDs, first 170 LEDs will be assigned to 1 universe,
Expand All @@ -167,8 +168,8 @@ the rest of 19 LEDs will be automatically assigned to 2 universe.
It is possible to enable multiple light platforms to listen to the same universe concurrently,
allowing to replicate the behaviour on multiple strips.

Sometimes it might be advised to improved stability if `multicast` mode does not work
to connect directly via IP to the esp-node.
Sometimes it might be advised to improved of connection. By default `multicast` is used,
but in some circumstances it might be advised to connect directly via IP to the esp-node.

### 2.4. `memory`

Expand Down
11 changes: 6 additions & 5 deletions e131/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import re
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import light
from esphome.components.light.types import AddressableLightEffect
from esphome.components.light.effects import register_effect, ADDRESSABLE_EFFECTS
from esphome.const import CONF_ID, CONF_NAME, CONF_METHOD, CONF_CHANNELS
from esphome.core import coroutine

E131LightEffect = cg.global_ns.class_('E131LightEffect', AddressableLightEffect)
E131Component = cg.global_ns.class_('E131Component', cg.Component)
e131_ns = cg.esphome_ns.namespace('e131')
E131AddressableLightEffect = e131_ns.class_('E131AddressableLightEffect', AddressableLightEffect)
E131Component = e131_ns.class_('E131Component', cg.Component)

METHODS = {
'UNICAST': 'E131_UNICAST',
'MULTICAST': 'E131_MULTICAST'
}

CHANNELS = {
'MONO': 'E131_MONO',
'RGB': 'E131_RGB',
'RGBW': 'E131_RGBW'
}
Expand All @@ -37,7 +38,7 @@ def register_addressable_effect(name, effect_type, default_name, schema, *extra_
ADDRESSABLE_EFFECTS.append(name)
return register_effect(name, effect_type, default_name, schema, *extra_validators)

@register_addressable_effect('e131', E131LightEffect, "E1.31", {
@register_addressable_effect('e131', E131AddressableLightEffect, "E1.31", {
cv.GenerateID(CONF_E131_ID): cv.use_id(E131Component),
cv.Required(CONF_UNIVERSE): cv.int_range(min=1, max=512),
cv.Optional(CONF_CHANNELS, default='RGB'): cv.one_of(*CHANNELS, upper=True)
Expand All @@ -47,7 +48,7 @@ def e131_light_effect_to_code(config, effect_id):

effect = cg.new_Pvariable(effect_id, config[CONF_NAME])
cg.add(effect.set_first_universe(config[CONF_UNIVERSE]))
cg.add(effect.set_channels(getattr(cg.global_ns, CHANNELS[config[CONF_CHANNELS]])))
cg.add(effect.set_channels(getattr(e131_ns, CHANNELS[config[CONF_CHANNELS]])))
cg.add(effect.set_e131(parent))

# https://github.com/forkineye/ESPAsyncE131/blob/master/library.json
Expand Down
46 changes: 35 additions & 11 deletions e131/e131.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
#include "e131.h"
#include "e131_light_effect.h"
#include "e131_addressable_light_effect.h"
#include "esphome/core/log.h"

#define MAX_UNIVERSES 512
#define TAG "e131"

using namespace esphome;
namespace esphome {
namespace e131 {

void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.",
light_effect->get_name().c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());

void E131Component::add_effect(E131LightEffect *light_effect) {
light_effects_.insert(light_effect);
should_rebind_ = true;
}

void E131Component::remove_effect(E131LightEffect *light_effect) {
void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.",
light_effect->get_name().c_str(),
light_effect->get_first_universe(), light_effect->get_last_universe());

light_effects_.erase(light_effect);
should_rebind_ = true;
}
Expand All @@ -31,7 +40,12 @@ void E131Component::loop() {
e131_packet_t packet;
e131_client_->pull(&packet); // Pull packet from ring buffer
auto universe = htons(packet.universe);
process(universe, packet);
auto handled = process(universe, packet);

if (!handled) {
ESP_LOGV(TAG, "Ignored packet for %d universe of size %d.",
universe, packet.property_value_count);
}
}
}

Expand All @@ -43,9 +57,9 @@ void E131Component::rebind() {
int universe_count = 0;

for (auto light_effect : light_effects_) {
first_universe = std::min(first_universe, light_effect->first_universe());
last_universe = std::max(last_universe, light_effect->last_universe());
universe_count += light_effect->universe_count();
first_universe = std::min(first_universe, light_effect->get_first_universe());
last_universe = std::max(last_universe, light_effect->get_last_universe());
universe_count += light_effect->get_universe_count();
}

if (!universe_count) {
Expand All @@ -62,13 +76,23 @@ void E131Component::rebind() {
ESP_LOGI(TAG, "Started E1.31 for universes %d-%d",
first_universe, last_universe);
} else {
ESP_LOGW(TAG, "Failed to start E1.31 for %s universes %d-%d",
ESP_LOGW(TAG, "Failed to start E1.31 for universes %d-%d",
first_universe, last_universe);
}
}

void E131Component::process(int universe, const e131_packet_t &packet) {
bool E131Component::process(int universe, const e131_packet_t &packet) {
bool handled = false;

ESP_LOGV(TAG, "Received E1.31 packet for %d universe, with %d bytes",
universe, packet.property_value_count);

for (auto light_effect : light_effects_) {
light_effect->process(universe, packet);
handled = light_effect->process(universe, packet) || handled;
}

return handled;
}

}
}
18 changes: 12 additions & 6 deletions e131/e131.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
#include <memory>
#include <set>

class E131LightEffect;
namespace esphome {
namespace e131 {

class E131AddressableLightEffect;

class E131Component : public esphome::Component {
public:
void loop() override;

public:
void add_effect(E131LightEffect *light_effect);
void remove_effect(E131LightEffect *light_effect);
void add_effect(E131AddressableLightEffect *light_effect);
void remove_effect(E131AddressableLightEffect *light_effect);

public:
void set_method(e131_listen_t listen_method) {
Expand All @@ -24,11 +27,14 @@ class E131Component : public esphome::Component {

private:
void rebind();
void process(int universe, const e131_packet_t &packet);
bool process(int universe, const e131_packet_t &packet);

private:
e131_listen_t listen_method_{E131_MULTICAST};
std::unique_ptr<ESPAsyncE131> e131_client_;
std::set<E131LightEffect*> light_effects_;
std::set<E131AddressableLightEffect*> light_effects_;
bool should_rebind_{false};
};
};

}
}
106 changes: 106 additions & 0 deletions e131/e131_addressable_light_effect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include "e131.h"
#include "e131_addressable_light_effect.h"
#include "esphome/core/log.h"

#define MAX_DATA_SIZE (sizeof(e131_packet_t::property_values)-1)

#define TAG "e131_light_effect"

namespace esphome {
namespace e131 {

E131AddressableLightEffect::E131AddressableLightEffect(const std::string &name)
: AddressableLightEffect(name) {
}

int E131AddressableLightEffect::get_data_per_universe() const {
return get_lights_per_universe() * channels_;
}

int E131AddressableLightEffect::get_lights_per_universe() const {
return MAX_DATA_SIZE / channels_;
}

int E131AddressableLightEffect::get_first_universe() const {
return first_universe_;
}

int E131AddressableLightEffect::get_last_universe() const {
return first_universe_ + get_universe_count() - 1;
}

int E131AddressableLightEffect::get_universe_count() const {
// Round up to lights_per_universe
auto lights = get_lights_per_universe();
return (get_addressable_()->size() + lights - 1) / lights;
}

void E131AddressableLightEffect::start() {
AddressableLightEffect::start();

if (this->e131_) {
this->e131_->add_effect(this);
}
}

void E131AddressableLightEffect::stop() {
if (this->e131_) {
this->e131_->remove_effect(this);
}

AddressableLightEffect::stop();
}

void E131AddressableLightEffect::apply(esphome::light::AddressableLight &it, const esphome::light::ESPColor &current_color) {
// ignore, it is run by `::update()`
}

bool E131AddressableLightEffect::process(int universe, const e131_packet_t &packet) {
auto it = get_addressable_();

// check if this is our universe and data are valid
if (universe < first_universe_ || universe > get_last_universe())
return false;

int output_offset = (universe - first_universe_) * get_lights_per_universe();
int output_end = std::min(
output_offset + get_lights_per_universe(),
it->size());
auto input_data = packet.property_values + 1;

ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.",
get_name().c_str(),
universe,
output_offset, output_end);

switch (channels_) {
case E131_MONO:
for ( ; output_offset < output_end; output_offset++, input_data++) {
auto output = (*it)[output_offset];
output.set(esphome::light::ESPColor(
input_data[0], input_data[0], input_data[0], input_data[0]));
}
break;

case E131_RGB:
for ( ; output_offset < output_end; output_offset++, input_data += 3) {
auto output = (*it)[output_offset];
output.set(esphome::light::ESPColor(
input_data[0], input_data[1], input_data[2], (input_data[0] + input_data[1] + input_data[2]) / 3));
}
break;

case E131_RGBW:
for ( ; output_offset < output_end; output_offset++, input_data += 4) {
auto output = (*it)[output_offset];
output.set(esphome::light::ESPColor(
input_data[0], input_data[1], input_data[2], input_data[3]));
}
break;
}

return true;
}

}
}
26 changes: 17 additions & 9 deletions e131/e131_light_effect.h → e131/e131_addressable_light_effect.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,32 @@

#include "ESPAsyncE131.h"

namespace esphome {
namespace e131 {

class E131Component;

enum E131LightChannels {
E131_MONO = 1,
E131_RGB = 3,
E131_RGBW = 4
};

class E131LightEffect : public esphome::light::AddressableLightEffect {
class E131AddressableLightEffect : public esphome::light::AddressableLightEffect {
public:
E131LightEffect(const std::string &name);
E131AddressableLightEffect(const std::string &name);

public:
void start() override;
void stop() override;
void apply(esphome::light::AddressableLight &it, const esphome::light::ESPColor &current_color) override;
void process(int universe, const e131_packet_t &packet);

public:
int data_per_universe() const;
int lights_per_universe() const;
int first_universe() const;
int last_universe() const;
int universe_count() const;
int get_data_per_universe() const;
int get_lights_per_universe() const;
int get_first_universe() const;
int get_last_universe() const;
int get_universe_count() const;

public:
void set_first_universe(int universe) {
Expand All @@ -47,12 +50,17 @@ class E131LightEffect : public esphome::light::AddressableLightEffect {
}

private:
void process_packet(esphome::light::AddressableLight &it);
bool process(int universe, const e131_packet_t &packet);

private:
int first_universe_{0};
int last_universe_{0};
E131LightChannels channels_{E131_RGB};
esphome::light::AddressableLight *addressable_light_{nullptr};
E131Component *e131_{nullptr};

friend class E131Component;
};

}
}
Loading

0 comments on commit 48b5dcc

Please sign in to comment.