Hello everyone,
I am trying to use a custom component to control a WLED node directly from an ESPHome node. The component uses UDP for the controls. Here’s the location to the GitHub I’m using Muxa’s.
I’ve worked through a few different errors such as the IPv4 data type (added comments to the python init and cpp docs) but I am getting stuck with the following. I found a few different posts about the functions being defined multiple times is a problem but I am having a hard time understanding exactly how to fix it. I haven’t used CPP a lot either so I am hoping it is something super easy that I am just missing. Here’s the log output from compiling:
Linking .pioenvs/jeep1esp/firmware.elf
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp.o: in function `esphome::wled::WLEDLightOutput::dump_config()':
/data/build/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp:33: multiple definition of `esphome::wled::WLEDLightOutput::dump_config()'; .pioenvs/jeep1esp/src/components/wled_output/light/wled_light_output.cpp.o:/data/build/jeep1esp/src/components/wled_output/light/wled_light_output.cpp:33: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp.o: in function `esphome::wled::WLEDLightOutput::dump_config()':
/data/build/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp:33: multiple definition of `non-virtual thunk to esphome::wled::WLEDLightOutput::dump_config()'; .pioenvs/jeep1esp/src/components/wled_output/light/wled_light_output.cpp.o:/data/build/jeep1esp/src/components/wled_output/light/wled_light_output.cpp:33: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp.o: in function `esphome::wled::WLEDLightOutput::on_shutdown()':
/data/build/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp:15: multiple definition of `esphome::wled::WLEDLightOutput::on_shutdown()'; .pioenvs/jeep1esp/src/components/wled_output/light/wled_light_output.cpp.o:/data/build/jeep1esp/src/components/wled_output/light/wled_light_output.cpp:15: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp.o: in function `non-virtual thunk to esphome::wled::WLEDLightOutput::on_shutdown()':
wled_light_output.cpp:(.text._ZThn4_N7esphome4wled15WLEDLightOutput11on_shutdownEv+0x0): multiple definition of `non-virtual thunk to esphome::wled::WLEDLightOutput::on_shutdown()'; .pioenvs/jeep1esp/src/components/wled_output/light/wled_light_output.cpp.o:wled_light_output.cpp:(.text._ZThn4_N7esphome4wled15WLEDLightOutput11on_shutdownEv+0x0): first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp.o: in function `esphome::wled::WLEDLightOutput::write_state(esphome::light::LightState*)':
/data/build/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp:43: multiple definition of `esphome::wled::WLEDLightOutput::write_state(esphome::light::LightState*)'; .pioenvs/jeep1esp/src/components/wled_output/light/wled_light_output.cpp.o:/data/build/jeep1esp/src/components/wled_output/light/wled_light_output.cpp:43: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp.o: in function `esphome::wled::WLEDLightOutput::setup()':
/data/build/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp:24: multiple definition of `esphome::wled::WLEDLightOutput::setup()'; .pioenvs/jeep1esp/src/components/wled_output/light/wled_light_output.cpp.o:/data/build/jeep1esp/src/components/wled_output/light/wled_light_output.cpp:24: first defined here
/data/cache/platformio/packages/toolchain-xtensa-esp32/bin/../lib/gcc/xtensa-esp32-elf/8.4.0/../../../../xtensa-esp32-elf/bin/ld: .pioenvs/jeep1esp/src/esphome/components/wled_output/light/wled_light_output.cpp.o: in function `non-virtual thunk to esphome::wled::WLEDLightOutput::setup()':
wled_light_output.cpp:(.text._ZThn4_N7esphome4wled15WLEDLightOutput5setupEv+0x0): multiple definition of `non-virtual thunk to esphome::wled::WLEDLightOutput::setup()'; .pioenvs/jeep1esp/src/components/wled_output/light/wled_light_output.cpp.o:wled_light_output.cpp:(.text._ZThn4_N7esphome4wled15WLEDLightOutput5setupEv+0x0): first defined here
Here’s the .h file:
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/network/ip_address.h"
#include <IPAddress.h>
#include "esphome/components/light/addressable_light.h"
#include <vector>
#include <memory>
#include <WiFi.h>
// class UDP;
namespace esphome {
namespace wled {
class WLEDLightOutput : public light::AddressableLight {
public:
explicit WLEDLightOutput(int num_leds) {
this->num_leds_ = num_leds;
this->leds_ = new Color[num_leds]; // NOLINT
this->leds_last_state = new Color[num_leds]; // NOLINT
for (int i = 0; i < num_leds; i++) {
this->leds_[i] = Color::BLACK;
this->leds_last_state[i] = Color::WHITE; // init to differ color to change
}
this->effect_data_ = new uint8_t[num_leds]; // NOLINT
}
light::LightTraits get_traits() override {
auto traits = light::LightTraits();
traits.set_supported_color_modes({light::ColorMode::RGB});
return traits;
}
void setup();
void dump_config();
// void loop() override;
void write_state(light::LightState *state) override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void clear_effect_data() override {
for (int i = 0; i < this->size(); i++)
this->effect_data_[i] = 0;
}
void on_shutdown() override;
inline int32_t size() const override { return num_leds_; }
void set_address(network::IPAddress address) { this->address_ = IPAddress((esphome::network::IPAddress)address); }
void set_port(uint16_t port) { this->port_ = port; }
protected:
IPAddress address_;
uint16_t port_{0};
std::unique_ptr<WiFiUDP> udp_;
light::ESPColorView get_view_internal(int32_t index) const override {
return {&this->leds_[index].r, &this->leds_[index].g, &this->leds_[index].b, nullptr,
&this->effect_data_[index], &this->correction_};
}
Color *leds_{nullptr};
Color *leds_last_state{nullptr};
uint8_t *effect_data_{nullptr};
int num_leds_{0};
uint32_t write_count_{0};
uint32_t write_duration_{0};
};
} // namespace wled
} // namespace esphome
Here’s the .cpp file:
t#ifdef USE_ARDUINO
#include "wled_light_output.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32
#include <WiFi.h>
#endif
#ifdef USE_ESP8266
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#endif
namespace esphome {
namespace wled {
// Description of protocols:
// https://github.com/Aircoookie/WLED/wiki/UDP-Realtime-Control
enum Protocol { WLED_NOTIFIER = 0, WARLS = 1, DRGB = 2, DRGBW = 3, DNRGB = 4 };
static const char *const TAG = "wled_light_output";
void WLEDLightOutput::on_shutdown() {
if (udp_) {
udp_->stop();
udp_.reset();
}
Component::on_shutdown();
}
void WLEDLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Setting up WLED Light Output...");
// Init UDP lazily
if (!udp_) {
udp_ = make_unique<WiFiUDP>();
}
}
void WLEDLightOutput::dump_config() {
ESP_LOGCONFIG(TAG, "WLED Light Output:");
ESP_LOGCONFIG(TAG, " Num LEDs: %u", this->num_leds_);
//ESP_LOGCONFIG(TAG, " Max refresh rate: %u", *this->max_refresh_rate_);
}
// void WLEDLightOutput::loop() {
// }
void WLEDLightOutput::write_state(light::LightState *state) {
if (!state->remote_values.is_on())
return;
// send packets here
uint32_t start_micros = micros();
if (!udp_->beginPacket(address_, port_)) {
ESP_LOGW(TAG, "Cannot connect WLEDLightOutput to %d.", port_);
return;
}
// Byte 0 of the UDP packet tells the server which realtime protocol to use.
udp_->write(WARLS);
// In every protocol, Byte 1 tells the server how many seconds to wait after the last received packet before returning to normal mode,
// in practice you should use 1-2 (seconds) here in most cases so that the module returns to normal mode quickly after the end of transmission.
// Use 255 to stay on the UDP data without a timeout until a request is requested via another method.
udp_->write(1);
for (int i=0; i<this->num_leds_; i++) {
if (this->leds_last_state[i].raw_32 == this->leds_[i].raw_32)
continue; // no change in LED
// After this the LED color information is transmitted like this:
// WARLS
// Byte Description
// 2 + n*4 LED Index
// 3 + n*4 Red Value
// 4 + n*4 Green Value
// 5 + n*4 Blue Value
udp_->write(i);
udp_->write(this->leds_[i].r);
udp_->write(this->leds_[i].g);
udp_->write(this->leds_[i].b);
this->leds_last_state[i] = this->leds_[i];
}
if (!udp_->endPacket()) {
ESP_LOGW(TAG, "Cannot send packet from WLEDLightOutput to %d.", port_);
return;
}
start_micros = micros() - start_micros;
this->write_duration_ += start_micros;
this->write_count_++;
if (this->write_count_ % 50 == 0) {
ESP_LOGD(TAG, "Write duration: %d us (%d us avg)", start_micros, this->write_duration_ / this->write_count_);
}
// this->mark_shown_();
// if (state->remote_values.is_on()) {
// }
this->schedule_show();
}
} // namespace wled
} // namespace esphome
And the init.py
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import light
from esphome.components.network import IPAddress
from esphome.const import (
CONF_ADDRESS,
CONF_PORT,
CONF_OUTPUT_ID,
CONF_NUM_LEDS,
CONF_MAX_REFRESH_RATE,
)
wled_ns = cg.esphome_ns.namespace("wled")
WLEDLightOutput = wled_ns.class_("WLEDLightOutput", light.AddressableLight)
CONFIG_SCHEMA = cv.All(
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(WLEDLightOutput),
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Required(CONF_ADDRESS): cv.string, # Edited from github
cv.Optional(CONF_PORT, 21324): cv.positive_not_null_int,
}
).extend(cv.COMPONENT_SCHEMA)
, cv.only_with_arduino)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_NUM_LEDS])
await cg.register_component(var, config)
if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
await light.register_light(var, config)
#cg.add(var.add_leds(config[CONF_NUM_LEDS]))
cg.add(var.set_port(config[CONF_PORT]))
cg.add(var.set_address(IPAddress(config[CONF_ADDRESS]))); # Edited from github
Any guidance or ideas? I apologize if this is the wrong place or if I didn’t follow best practice with the post, I don’t post much on forums.
Automate on,
DaviBoi
EDIT: Clarification