Replace NeoPixelBus with TasmotaLED on ESP32x (#22556)

* Replace NeoPixelBus with TasmotaLED on ESP32x

* update changelog
This commit is contained in:
s-hadinger 2024-11-27 22:11:57 +01:00 committed by GitHub
parent 8cb9345a97
commit db0287e566
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 3126 additions and 1296 deletions

View File

@ -11,12 +11,14 @@ All notable changes to this project will be documented in this file.
- ESP32 Hybrid compile take custom boards settings in account (#22542)
### Breaking Changed
- ArtNet on ESP32 switches from GRB to RGB encoding
### Changed
- ESP32 max number of supported switches/buttons/relays from 28 to 32
- ESP32 max number of interlocks from 14 to 16
- ESP32 Platform from 2024.11.30 to 2024.11.31, Framework (Arduino Core) from v3.1.0.241030 to v3.1.0.241117 and IDF to 5.3.1.241024 (#22504)
- Prevent active BLE operations with unencrypted MI-format beacons (#22453)
- Replace NeoPixelBus with TasmotaLED on ESP32x
### Fixed
- ESP32 upgrade by file upload response based on file size (#22500)

View File

@ -69,7 +69,7 @@ typedef struct {
rmt_symbol_word_t reset_code;
} rmt_led_strip_encoder_t;
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
static IRAM_ATTR size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;

View File

@ -0,0 +1,17 @@
{
"name": "TasmotaLED",
"version": "0.1",
"keywords": [
"ws2816", "sk6812", "leds"
],
"description": "Lightweight implementation for adressable leds.",
"repository":
{
"type": "git",
"url": "https://github.com/arendst/Tasmota/lib/lib_basic/TasmotaLED"
},
"frameworks": "arduino",
"platforms": [
"espressif32"
]
}

View File

@ -0,0 +1,237 @@
/*
TasmotaLED.cpp - Lightweight implementation for adressable leds.
Copyright (C) 2024 Stephan Hadinger
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#ifdef ESP32
#include "TasmotaLEDPusher.h"
#include "TasmotaLED.h"
// DRAM_ATTR to force in IRAM because we use this in show loop
static const DRAM_ATTR uint8_t TASMOTALED_CHANNEL_ORDERS[6][3] = {
{1, 0, 2}, // GRB (0)
{2, 0, 1}, // GBR (1)
{0, 1, 2}, // RGB (2)
{0, 2, 1}, // RBG (3)
{2, 1, 0}, // BRG (4)
{1, 2, 0} // BGR (5)
};
static const TasmotaLED_Timing TasmotaLED_Timings[] = {
// WS2812
// RmtBit0 0x00228010 RmtBit1 0x00128020 RmtReset 0x800207D0
{
.T0H = 400,
.T0L = 850,
.T1H = 800,
.T1L = 450,
.Reset = 80000 // it is 50000 for WS2812, but for compatibility with SK6812, we raise to 80000
},
// SK6812
// RmtBit0 0x0024800C RmtBit1 0x00188018 RmtReset 0x80020C80
{
.T0H = 300,
.T0L = 900,
.T1H = 600,
.T1L = 600,
.Reset = 80000
},
};
// enable AddLog
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
TasmotaLED::TasmotaLED(uint16_t type, uint16_t num_leds) :
_type(type),
_pixel_order((type >> 4) & 0x07),
_w_before(type & 0x08),
_timing((type >> 8) & 0xFF),
_started(false),
_dirty(true),
_raw_format(false),
_pixel_count(num_leds),
_buf_work(nullptr),
_buf_show(nullptr),
_pixel_matrix(&TASMOTALED_CHANNEL_ORDERS[0]),
_pusher(nullptr)
{
if (_timing > (TasmotaLed_TimingEnd >> 8)) {
_timing = 0;
}
switch (_type & 0x0F) {
// case TasmotaLed_1_W:
// _pixel_size = 1;
// break;
case TasmotaLed_4_WRGB:
_pixel_size = 4;
break;
case TasmotaLed_3_RGB:
default: // fallback
_pixel_size = 3;
break;
}
_pixel_matrix = &TASMOTALED_CHANNEL_ORDERS[_pixel_order];
_buf_work = new uint8_t[_pixel_count * _pixel_size];
memset(_buf_work, 0, _pixel_count * _pixel_size);
_buf_show = new uint8_t[_pixel_count * _pixel_size];
memset(_buf_show, 0, _pixel_count * _pixel_size);
// AddLog(LOG_LEVEL_DEBUG, "LED: type=0x%04X pixel_order=0x%02X _timing=%i ", _type, _pixel_order, _timing);
}
TasmotaLED::~TasmotaLED() {
if (_pusher) {
delete _pusher;
_pusher = nullptr;
}
delete _buf_work;
_buf_work = nullptr;
delete _buf_show;
_buf_show = nullptr;
}
// Color is passed as 0xWWRRGGBB and copied as WWRRGGBB in _buf_work
void TasmotaLED::ClearTo(uint32_t wrgb, int32_t first, int32_t last) {
// adjust first and last to be in range of 0 to _pixel_count-1
if (first <0) { first += _pixel_count; }
if (last <0) { last += _pixel_count; }
if (first < 0) { first = 0; }
if (last >= _pixel_count) { last = _pixel_count - 1; }
if (first > last) { return; }
// adjust to pixel format
uint8_t b0 = (wrgb >> 24) & 0xFF;
uint8_t b1 = (wrgb >> 16) & 0xFF;
uint8_t b2 = (wrgb >> 8) & 0xFF;
uint8_t b3 = (wrgb ) & 0xFF;
if ((b0 | b1 | b2 | b3) == 0) {
// special version for clearing to black
memset(_buf_work + first * _pixel_size, 0, (last - first + 1) * _pixel_size);
} else {
// fill sub-buffer with RRGGBB or WWRRGGBB (or raw)
uint8_t *buf = _buf_work + first * _pixel_size;
for (uint32_t i = first; i <= last; i++) {
if (_pixel_size == 4) { *buf++ = b0;}
*buf++ = b1;
*buf++ = b2;
*buf++ = b3;
}
}
}
void TasmotaLED::Show(void) {
if (_pusher) {
_dirty = false; // we don't use the _dirty attribute and always show
// copy the input buffer to the work buffer in format to be understood by LED strip
if (_raw_format) {
memmove(_buf_show, _buf_work, _pixel_count * _pixel_size); // copy buffer in next buffer so we start with the current content
} else {
uint8_t *buf_from = _buf_work;
uint8_t *buf_to = _buf_show;
if (_pixel_size == 3) {
// copying with swapping 512 pixels (1536 bytes) takes 124 microseconds to copy, so it's negligeable
for (uint32_t i = 0; i < _pixel_count; i++) {
buf_to[(*_pixel_matrix)[0]] = buf_from[0]; // R
buf_to[(*_pixel_matrix)[1]] = buf_from[1]; // G
buf_to[(*_pixel_matrix)[2]] = buf_from[2]; // B
buf_to += 3;
buf_from += 3;
}
} else if (_pixel_size == 4) {
for (uint32_t i = 0; i < _pixel_count; i++) {
if (_w_before) { *buf_to++ = buf_from[3]; }
buf_to[(*_pixel_matrix)[0]] = buf_from[0]; // R
buf_to[(*_pixel_matrix)[1]] = buf_from[1]; // G
buf_to[(*_pixel_matrix)[2]] = buf_from[2]; // B
if (!_w_before) { *buf_to++ = buf_from[3]; }
buf_to += 3; // one increment already happened
buf_from += 4;
}
}
}
_pusher->Push(_buf_show); // push to leds
}
}
void TasmotaLED::SetPixelColor(int32_t index, uint32_t wrgb) {
if (index < 0) { index += _pixel_count; }
if ((index >= 0) && (index < _pixel_count)) {
uint8_t *buf = _buf_work + index * _pixel_size;
uint8_t b0 = (wrgb >> 24) & 0xFF;
uint8_t b1 = (wrgb >> 16) & 0xFF;
uint8_t b2 = (wrgb >> 8) & 0xFF;
uint8_t b3 = (wrgb ) & 0xFF;
if (_pixel_size == 4) { *buf++ = b0;}
*buf++ = b1;
*buf++ = b2;
*buf++ = b3;
_dirty = true;
}
}
uint32_t TasmotaLED::GetPixelColor(int32_t index) {
if (index < 0) { index += _pixel_count; }
if ((index >= 0) && (index < _pixel_count)) {
uint8_t *buf = _buf_work + index * _pixel_size;
uint32_t wrgb = 0;
if (_pixel_size == 4) { wrgb = (*buf++) << 24; }
wrgb |= (*buf++) << 16;
wrgb |= (*buf++) << 8;
wrgb |= (*buf++);
return wrgb;
} else {
return 0;
}
}
void TasmotaLED::SetPusher(TasmotaLEDPusher *pusher) {
if (_pusher) {
delete _pusher;
}
_pusher = pusher;
_started = false;
}
bool TasmotaLED::Begin(void) {
if (_pusher) {
if (_started) {
return true;
} else {
const TasmotaLED_Timing * timing = &TasmotaLED_Timings[_timing];
// AddLog(LOG_LEVEL_DEBUG, "LED: T0H=%i T0L=%i T1H=%i T1L=%i Reset=%i", timing.T0H, timing.T0L, timing.T1H, timing.T1L, timing.Reset);
return _pusher->Begin(_pixel_count, _pixel_size, timing);
}
} else {
return false;
}
}
bool TasmotaLED::CanShow(void) const {
if (_pusher) {
return _pusher->CanShow();
}
return false;
}
#endif // ESP32

View File

@ -0,0 +1,129 @@
/*
TasmotaLED.h - Lightweight implementation for adressable leds.
Copyright (C) 2024 Stephan Hadinger
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TASMOTALED_H__
#define __TASMOTALED_H__
enum TasmotaLEDTypesEncoding : uint16_t {
// bits 0..3 encode for number of bytes per pixel
TasmotaLed_1_W = 0x0, // 1 byte per pixel (not used yet)
TasmotaLed_3_RGB = 0x1, // 3 bytes per pixel
TasmotaLed_4_WRGB = 0x2, // 4 bytes per pixel
// bits 4..6 encode for pixel order
TasmotaLed_GRB = 0b000 << 4,
TasmotaLed_GBR = 0b001 << 4,
TasmotaLed_RGB = 0b010 << 4,
TasmotaLed_RBG = 0b011 << 4,
TasmotaLed_BRG = 0b100 << 4,
TasmotaLed_BGR = 0b101 << 4,
// bit 7 sets the position for W channel
TasmotaLed_xxxW = 0b0 << 7, // W channel after color
TasmotaLed_Wxxx = 0b1 << 7, // W channel before color
// bits 8..15 encode for timing specifics
TasmotaLed_WS2812 = 0 << 8,
TasmotaLed_SK6812 = 1 << 8,
TasmotaLed_TimingEnd = 2 << 8,
};
enum TasmotaLEDHardware : uint32_t {
// low-order bits are reserved for channels numbers and hardware flags - currenlty not useds
// bits 16..23
TasmotaLed_HW_Default = 0x0 << 16,
TasmotaLed_RMT = 1 << 16,
TasmotaLed_SPI = 2 << 16,
TasmotaLed_I2S = 3 << 16,
TasmotaLed_HW_None = 0xFF << 16, // indicates that the specified HW is not supported
};
// Below is the encoding for full strips
// We need to keep backwards compatibility so:
// 0 = WS2812 (GRB)
// 1 = SK6812 with White (GRBW)
enum TasmotaLEDTypes : uint16_t {
ws2812_grb = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_WS2812, // 1 for backwards compatibility
sk6812_grbw = TasmotaLed_4_WRGB | TasmotaLed_GRB | TasmotaLed_xxxW | TasmotaLed_SK6812, // 2 for backwards compatibility
sk6812_grb = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_SK6812,
};
#ifdef __cplusplus
/*******************************************************************************************\
* class TasmotaLED
*
* This class is a lightweight replacement for NeoPixelBus library with a smaller
* implementation focusing only on pushing a buffer to the leds.
*
* It supports:
* - RMT and I2S hardware support
* Possible enhancements could be considered with SPI and Serial
* - Led size of 3 bytes (GRB) and 4 bytes (GRBW)
* APIs take 0xRRGGBB or 0xRRGGBBWW as input
* but Internal buffers use GGRRBB and GGRRBBWW
* - Led type of WS2812 and SK6812
* - There is no buffer swapping, the working buffer is copied to an internal
* buffer just before display, so you can keep a reference to the buffer
* and modify it without having to worry about the display
* - buffer is cleared at start
* - "Dirty" is kept for API compatibility with NeoPixelBus but is glbally ignored
* so any call to `Show()` pushes the pixels even if they haven't changed.
* Control for dirty pixels should be done by the caller if required.
* - We tried to keep as close as possible to NeoPixelBus method names to ease transition
\*******************************************************************************************/
class TasmotaLEDPusher; // forward definition
class TasmotaLED {
public:
TasmotaLED(uint16_t type, uint16_t num_leds);
~TasmotaLED();
bool Begin(void);
void SetPusher(TasmotaLEDPusher *pusher); // needs to be called before `Begin()`, sets the hardware implementation
void Show(void); // pushes the pixels to the LED strip
inline void SetRawFormat(bool raw_format) { _raw_format = raw_format; }
void ClearTo(uint32_t rgbw, int32_t first = 0, int32_t last = -1);
void SetPixelColor(int32_t index, uint32_t wrgb);
uint32_t GetPixelColor(int32_t index);
uint8_t GetType(void) const { return _type; }
uint16_t PixelCount(void) const { return _pixel_count; }
uint8_t PixelSize(void) const { return _pixel_size; }
inline uint8_t * Pixels(void) const { return _buf_work; }
inline bool IsDirty(void) const { return _dirty; }
inline void Dirty(void) { _dirty = true; }
bool CanShow(void) const;
protected:
uint16_t _type; // the composite type
uint8_t _pixel_order; // permutation between RGB and position of W
bool _w_before; // true if W channel comes first (4 channels only)
uint8_t _timing; // timing code for strip, 0=WS2812, 1=SK6812...
bool _started; // true if the hardware implementation is configured
bool _dirty; // for NeoPixelBus compatibility, but ignored by `Push()`
bool _raw_format; // if true, copy raw to leds, if false, convert from RGB to GRB or LED format
uint16_t _pixel_count; // how many pixels in the strip
uint8_t _pixel_size; // how many bytes per pixels, only 3 and 4 are supported
uint8_t *_buf_work; // buffer used to draw into, can be modified directly by the caller
uint8_t *_buf_show; // copy of the buffer used to push to leds, private to this class
const uint8_t (*_pixel_matrix)[3]; // pointer to the pixer_order_matrix
TasmotaLEDPusher *_pusher; // pixels pusher implementation based on hardware (RMT, I2S...)
};
#endif // __cplusplus
#endif // __TASMOTALED_H__

View File

@ -0,0 +1,97 @@
/*
TasmotaLEDPusher.cpp - Implementation to push Leds via hardware acceleration
Copyright (C) 2024 Stephan Hadinger
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32
#include "TasmotaLEDPusher.h"
#include "TasmotaLED.h"
//**************************************************************************************************************
// enable AddLog support within a C++ library
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
//**************************************************************************************************************
// convert to the appropriate hardware acceleration based on capacities of the SOC
uint32_t TasmotaLEDPusher::ResolveHardware(uint32_t hw) {
uint32_t hw_orig = hw;
// Step 1. discard any unsupported hardware, and replace with TasmotaLed_HW_Default
uint32_t hw_type = hw & 0xFF0000; // discard bits 0..15
#if !TASMOTALED_HARDWARE_RMT
if (hw_type == TasmotaLed_RMT) {
hw = TasmotaLed_HW_None;
}
#endif // TASMOTALED_HARDWARE_RMT
#if !TASMOTALED_HARDWARE_SPI
if (hw_type == TasmotaLed_SPI) {
hw = TasmotaLed_HW_None;
}
#endif // TASMOTALED_HARDWARE_SPI
#if !TASMOTALED_HARDWARE_I2S
if (hw_type == TasmotaLed_I2S) {
hw = TasmotaLed_HW_None;
}
#endif // TASMOTALED_HARDWARE_I2S
// Step 2. If TasmotaLed_HW_Default, find a suitable scheme, RMT preferred
#if TASMOTALED_HARDWARE_RMT
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
hw = TasmotaLed_RMT;
}
#endif // TASMOTALED_HARDWARE_RMT
#if TASMOTALED_HARDWARE_I2S
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
hw = TasmotaLed_I2S;
}
#endif // TASMOTALED_HARDWARE_I2S
#if TASMOTALED_HARDWARE_SPI
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
hw = TasmotaLed_SPI;
}
#endif // TASMOTALED_HARDWARE_SPI
return hw;
}
TasmotaLEDPusher * TasmotaLEDPusher::Create(uint32_t hw, int8_t gpio) {
TasmotaLEDPusher * pusher = nullptr;
hw = TasmotaLEDPusher::ResolveHardware(hw);
switch (hw & 0XFF0000) {
#if TASMOTALED_HARDWARE_RMT
case TasmotaLed_RMT:
pusher = new TasmotaLEDPusherRMT(gpio);
AddLog(LOG_LEVEL_DEBUG, "LED: RMT gpio %i", gpio);
break;
#endif // TASMOTALED_HARDWARE_RMT
#if TASMOTALED_HARDWARE_SPI
case TasmotaLed_SPI:
pusher = new TasmotaLEDPusherSPI(gpio);
AddLog(LOG_LEVEL_DEBUG, "LED: SPI gpio %i", gpio);
break;
#endif // TASMOTALED_HARDWARE_SPI
default:
break;
}
return pusher;
}
#endif // ESP32

View File

@ -0,0 +1,161 @@
/*
TasmotaLEDPusher.h - Abstract class for Leds pusher (RMT, SPI, I2S...)
Copyright (C) 2024 Stephan Hadinger
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __TASMOTALEDPUSHER_H__
#define __TASMOTALEDPUSHER_H__
#include <Arduino.h>
// Below are flags to enable of disable each hardware support: RMT, I2S, SPI
// By default, only enable RMT support, and SPI is used as fallback if no protocol works
//
// Use de defines below:
// #define TASMOTALED_HARDWARE_RMT 0/1
// #define TASMOTALED_HARDWARE_I2S 0/1
// #define TASMOTALED_HARDWARE_SPI 0/1
//
#ifndef TASMOTALED_HARDWARE_RMT
#define TASMOTALED_HARDWARE_RMT 1
#endif
#ifndef TASMOTALED_HARDWARE_I2S
#define TASMOTALED_HARDWARE_I2S 0
#endif
#ifndef TASMOTALED_HARDWARE_SPI
#define TASMOTALED_HARDWARE_SPI 0
#endif
// Disable any hardware if not supported by the SOC
#if TASMOTALED_HARDWARE_RMT && !defined(SOC_RMT_SUPPORTED)
#undef TASMOTALED_HARDWARE_RMT
#define TASMOTALED_HARDWARE_RMT 0
#endif
#if TASMOTALED_HARDWARE_I2S && !defined(SOC_I2S_SUPPORTED)
#undef TASMOTALED_HARDWARE_I2S
#define TASMOTALED_HARDWARE_I2S 0
#endif
#if TASMOTALED_HARDWARE_SPI && !defined(SOC_GPSPI_SUPPORTED)
#undef TASMOTALED_HARDWARE_SPI
#define TASMOTALED_HARDWARE_SPI 0
#endif
// if no protocol is defined, use SPI as fallback
#if !TASMOTALED_HARDWARE_RMT && !TASMOTALED_HARDWARE_I2S && !TASMOTALED_HARDWARE_SPI
#undef TASMOTALED_HARDWARE_SPI
#define TASMOTALED_HARDWARE_SPI 1
#endif
// Timing structure for LEDS - in nanoseconds
// It is passed by TasmotaLed to the pushers
typedef struct TasmotaLED_Timing {
uint16_t T0H, T0L, T1H, T1L;
uint32_t Reset;
} TasmotaLED_Timing;
/*******************************************************************************************\
* class TasmotaLEDPusher
*
* This is an virtual abstract class for Leds pusher (RMT, SPI, I2S...)
*
* Below are interfaces for current implementations
\*******************************************************************************************/
class TasmotaLEDPusher {
public:
TasmotaLEDPusher() : _pixel_count(0), _pixel_size(0), _led_timing(nullptr) {};
virtual ~TasmotaLEDPusher() {};
virtual bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
_pixel_count = pixel_count;
_pixel_size = pixel_size;
_led_timing = led_timing;
return true;
}
virtual bool Push(uint8_t *buf) = 0;
virtual bool CanShow(void) = 0;
static uint32_t ResolveHardware(uint32_t hw); // convert to the appropriate hardware acceleration based on capacities of the SOC
static TasmotaLEDPusher * Create(uint32_t hw, int8_t gpio); // create instance for the provided type, or nullptr if failed
protected:
uint16_t _pixel_count;
uint16_t _pixel_size;
const TasmotaLED_Timing * _led_timing;
};
/*******************************************************************************************\
* class TasmotaLEDPusherRMT
*
* Implementation based on RMT driver
\*******************************************************************************************/
#if TASMOTALED_HARDWARE_RMT
#include "driver/rmt_tx.h"
class TasmotaLEDPusherRMT : public TasmotaLEDPusher {
public:
TasmotaLEDPusherRMT(int8_t pin) : _pin(pin) {};
~TasmotaLEDPusherRMT();
bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) override;
bool Push(uint8_t *buf) override;
bool CanShow(void) override;
protected:
int8_t _pin;
rmt_transmit_config_t _tx_config = {};
rmt_channel_handle_t _channel = nullptr;;
rmt_encoder_handle_t _led_encoder = nullptr;
};
#endif // TASMOTALED_HARDWARE_RMT
/*******************************************************************************************\
* class TasmotaLEDPusherSPI
*
* Implementation based on SPI driver, mandatory for C2
\*******************************************************************************************/
#if TASMOTALED_HARDWARE_SPI
#include <driver/spi_master.h>
typedef struct led_strip_spi_obj_t {
uint8_t * pixel_buf;
uint16_t strip_len;
uint8_t bytes_per_pixel;
spi_host_device_t spi_host;
spi_device_handle_t spi_device;
spi_transaction_t tx_conf; // transaction in process if any
} led_strip_spi_obj;
class TasmotaLEDPusherSPI : public TasmotaLEDPusher {
public:
TasmotaLEDPusherSPI(int8_t pin) : _pin(pin), _spi_strip({}) {};
~TasmotaLEDPusherSPI();
bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) override;
bool Push(uint8_t *buf) override;
bool CanShow(void) override;
protected:
int8_t _pin;
struct led_strip_spi_obj_t _spi_strip;
};
#endif // TASMOTALED_HARDWARE_SPI
#endif // __TASMOTALEDPUSHER_H__

View File

@ -0,0 +1,240 @@
/*
TasmotaLEDPusherRMT.cpp - Implementation to push Leds via RMT channel
Copyright (C) 2024 Stephan Hadinger
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32
#include "TasmotaLEDPusher.h"
#include "TasmotaLED.h"
#if TASMOTALED_HARDWARE_RMT
#include <rom/gpio.h>
#include <esp_check.h>
//**************************************************************************************************************
// enable AddLog support within a C++ library
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
//**************************************************************************************************************
/*******************************************************************************************\
* Implementation for TasmotaLEDPusherRMT
*
* Code mostly copied from Tasmota patch to NeoPixelBus applied to support esp-idf 5.x
* itself inspired from esp-idf example for RMT encoder from
* https://github.com/espressif/esp-idf/tree/v5.3.1/examples/peripherals/rmt/ir_nec_transceiver
\*******************************************************************************************/
#define RMT_LED_STRIP_RESOLUTION_HZ 40000000 // 40MHz resolution, steps of 25 nanoseconds
// structure used to pass arguments to `rmt_new_led_strip_encoder`
// currently only the encoder resolution in Hz
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
} led_strip_encoder_config_t;
// structure used to store all the necessary information for the RMT encoder
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
int32_t state;
rmt_symbol_word_t reset_code;
} rmt_led_strip_encoder_t;
static IRAM_ATTR size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
rmt_encode_state_t state = RMT_ENCODING_RESET;
size_t encoded_symbols = 0;
switch (led_encoder->state) {
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state = static_cast<rmt_encode_state_t>(static_cast<uint8_t>(state) | static_cast<uint8_t>(RMT_ENCODING_MEM_FULL));
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session
state = static_cast<rmt_encode_state_t>(static_cast<uint8_t>(state) | static_cast<uint8_t>(RMT_ENCODING_COMPLETE));
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state = static_cast<rmt_encode_state_t>(static_cast<uint8_t>(state) | static_cast<uint8_t>(RMT_ENCODING_MEM_FULL));
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) {
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->bytes_encoder);
rmt_del_encoder(led_encoder->copy_encoder);
delete led_encoder;
return ESP_OK;
}
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) {
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_reset(led_encoder->bytes_encoder);
rmt_encoder_reset(led_encoder->copy_encoder);
led_encoder->state = RMT_ENCODING_RESET;
return ESP_OK;
}
static esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder, rmt_symbol_word_t bit0, rmt_symbol_word_t bit1, rmt_symbol_word_t reset_code) {
static const char* TAG = "TASMOTA_RMT";
esp_err_t ret = ESP_OK;
rmt_led_strip_encoder_t *led_encoder = NULL;
rmt_bytes_encoder_config_t bytes_encoder_config;
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
led_encoder = new rmt_led_strip_encoder_t();
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
led_encoder->base.encode = rmt_encode_led_strip;
led_encoder->base.del = rmt_del_led_strip_encoder;
led_encoder->base.reset = rmt_led_strip_encoder_reset;
led_encoder->reset_code = reset_code;
bytes_encoder_config.bit0 = bit0;
bytes_encoder_config.bit1 = bit1;
bytes_encoder_config.flags.msb_first = 1; // WS2812 transfer bit order: G7...G0R7...R0B7...B0 - TODO: more checks
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
*ret_encoder = &led_encoder->base;
return ret;
err:
AddLog(LOG_LEVEL_INFO, "RMT: could not init led encoder");
if (led_encoder) {
if (led_encoder->bytes_encoder) { rmt_del_encoder(led_encoder->bytes_encoder); }
if (led_encoder->copy_encoder) { rmt_del_encoder(led_encoder->copy_encoder); }
delete led_encoder;
}
return ret;
}
TasmotaLEDPusherRMT::~TasmotaLEDPusherRMT() {
if (_channel) {
rmt_tx_wait_all_done(_channel, 10000 / portTICK_PERIOD_MS);
rmt_del_channel(_channel);
_channel = nullptr;
}
if (_pin >= 0) {
gpio_matrix_out(_pin, 0x100, false, false);
pinMode(_pin, INPUT);
_pin = -1;
}
}
bool TasmotaLEDPusherRMT::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
esp_err_t ret = ESP_OK;
rmt_tx_channel_config_t config = {};
config.clk_src = RMT_CLK_SRC_DEFAULT;
config.gpio_num = static_cast<gpio_num_t>(_pin);
config.mem_block_symbols = 192; // memory block size, 64 * 4 = 256 Bytes
config.resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ; // 40 MHz tick resolution, i.e., 1 tick = 0.025 µs or 25 ns
config.trans_queue_depth = 4; // set the number of transactions that can pend in the background
config.flags.invert_out = false; // do not invert output signal
config.flags.with_dma = false; // do not need DMA backend
ret = rmt_new_tx_channel(&config, &_channel);
if (ret != ESP_OK) {
AddLog(LOG_LEVEL_INFO, "RMT: cannot initialize Gpio %i err=%i", _pin, ret);
return false;
}
led_strip_encoder_config_t encoder_config = {
.resolution = RMT_LED_STRIP_RESOLUTION_HZ,
};
_tx_config.loop_count = 0; // no loop
rmt_symbol_word_t RmtBit0 = {
.duration0 = (uint16_t) (led_timing->T0H * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
.level0 = 1,
.duration1 = (uint16_t) (led_timing->T0L * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
.level1 = 0,
};
rmt_symbol_word_t RmtBit1 = {
.duration0 = (uint16_t) (led_timing->T1H * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
.level0 = 1,
.duration1 = (uint16_t) (led_timing->T1L * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
.level1 = 0,
};
rmt_symbol_word_t RmtReset = {
.duration0 = (uint16_t) (led_timing->Reset * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
.level0 = 0,
.duration1 = 50 * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000,
.level1 = 1,
};
// AddLog(LOG_LEVEL_INFO, "RMT: RmtBit0 0x%08X RmtBit1 0x%08X RmtReset 0x%08X", RmtBit0.val, RmtBit1.val, RmtReset.val);
ret = rmt_new_led_strip_encoder(&encoder_config, &_led_encoder, RmtBit0, RmtBit1, RmtReset);
if (ret != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "RMT: cannot initialize led strip encoder err=%i", ret);
return false;
}
ret = rmt_enable(_channel);
if (ret != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "RMT: cannot enable channel err=%i", ret);
return false;
}
return true;
}
bool TasmotaLEDPusherRMT::CanShow(void) {
if (_channel) {
return (ESP_OK == rmt_tx_wait_all_done(_channel, 0));
} else {
return false;
}
}
bool TasmotaLEDPusherRMT::Push(uint8_t *buf) {
// wait for not actively sending data
// this will time out at 1 second, an arbitrarily long period of time
// and do nothing if this happens
esp_err_t ret = rmt_tx_wait_all_done(_channel, 1000 / portTICK_PERIOD_MS);
if (ESP_OK == ret) {
// now start the RMT transmit with the editing buffer before we swap
ret = rmt_transmit(_channel, _led_encoder, buf, _pixel_count * _pixel_size, &_tx_config);
if (ESP_OK != ret) {
AddLog(LOG_LEVEL_DEBUG, "RMT: cannot transmit err=%i", ret);
return false;
}
}
return true;
}
#endif // TASMOTALED_HARDWARE_RMT
#endif // ESP32

View File

@ -0,0 +1,191 @@
/*
TasmotaLEDPusherRMT.cpp - Implementation to push Leds via SPI channel
Copyright (C) 2024 Stephan Hadinger
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32
#include "TasmotaLEDPusher.h"
#include "TasmotaLED.h"
#if TASMOTALED_HARDWARE_SPI
#include <rom/gpio.h>
//**************************************************************************************************************
// enable AddLog support within a C++ library
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
//**************************************************************************************************************
/*******************************************************************************************\
* Implementation for TasmotaLEDPusherSPI
*
\*******************************************************************************************/
#define LED_STRIP_SPI_DEFAULT_RESOLUTION (25 * 100 * 1000) // 2.5MHz resolution
#define LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE 4
#define SPI_BYTES_PER_COLOR_BYTE 3
#define SPI_BITS_PER_COLOR_BYTE (SPI_BYTES_PER_COLOR_BYTE * 8)
static void __led_strip_spi_bit(uint8_t data, uint8_t *buf)
{
// Each color of 1 bit is represented by 3 bits of SPI, low_level:100 ,high_level:110
// So a color byte occupies 3 bytes of SPI.
buf[0] = (data & BIT(5) ? BIT(1) | BIT(0) : BIT(1)) | (data & BIT(6) ? BIT(4) | BIT(3) : BIT(4)) | (data & BIT(7) ? BIT(7) | BIT(6) : BIT(7));
buf[1] = (BIT(0)) | (data & BIT(3) ? BIT(3) | BIT(2) : BIT(3)) | (data & BIT(4) ? BIT(6) | BIT(5) : BIT(6));
buf[2] = (data & BIT(0) ? BIT(2) | BIT(1) : BIT(2)) | (data & BIT(1) ? BIT(5) | BIT(4) : BIT(5)) | (data & BIT(2) ? BIT(7) : 0x00);
}
esp_err_t led_strip_spi_refresh(led_strip_spi_obj * spi_strip)
{
spi_strip->tx_conf.length = spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BITS_PER_COLOR_BYTE;
spi_strip->tx_conf.tx_buffer = spi_strip->pixel_buf;
spi_strip->tx_conf.rx_buffer = NULL;
spi_device_transmit(spi_strip->spi_device, &spi_strip->tx_conf);
return ESP_OK;
}
void led_strip_transmit_buffer(led_strip_spi_obj * spi_strip, uint8_t * buffer_rgbw) {
// Timing for 512 pixels (extreme test)
// Copying to buffer: 418 us
// sending pixels: 16.2 ms
uint8_t * buf = buffer_rgbw;
uint8_t * pix_buf = spi_strip->pixel_buf;
for (int i = 0; i < spi_strip->strip_len; i++) {
// LED_PIXEL_FORMAT_GRB takes 72bits(9bytes)
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
if (spi_strip->bytes_per_pixel > 3) {
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
}
}
/* Refresh the strip to send data */
led_strip_spi_refresh(spi_strip);
}
TasmotaLEDPusherSPI::~TasmotaLEDPusherSPI() {
if (_spi_strip.spi_device) {
spi_bus_remove_device(_spi_strip.spi_device);
}
if (_spi_strip.spi_host) {
spi_bus_free(_spi_strip.spi_host);
}
if (_pin >= 0) {
gpio_matrix_out(_pin, 0x100, false, false);
pinMode(_pin, INPUT);
_pin = -1;
}
}
bool TasmotaLEDPusherSPI::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
_spi_strip.bytes_per_pixel = _pixel_size;
_spi_strip.strip_len = _pixel_count;
esp_err_t err = ESP_OK;
uint32_t mem_caps = MALLOC_CAP_DEFAULT;
// spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT;
spi_bus_config_t spi_bus_cfg;
spi_device_interface_config_t spi_dev_cfg;
spi_host_device_t spi_host = SPI2_HOST;
bool with_dma = true; /// TODO: pass value or compute based on pixelcount
int clock_resolution_khz = 0;
if (with_dma) { // TODO
// DMA buffer must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
}
_spi_strip.pixel_buf = (uint8_t *)heap_caps_calloc(1, _pixel_count * _pixel_size * SPI_BYTES_PER_COLOR_BYTE, mem_caps);
if (_spi_strip.pixel_buf == nullptr) {
AddLog(LOG_LEVEL_INFO, PSTR("LED: Error no mem for spi strip"));
goto err;
}
spi_bus_cfg = {
.mosi_io_num = _pin,
//Only use MOSI to generate the signal, set -1 when other pins are not used.
.miso_io_num = -1,
.sclk_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = _pixel_count * _pixel_size * SPI_BYTES_PER_COLOR_BYTE,
};
err = spi_bus_initialize(spi_host, &spi_bus_cfg, with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED);
if (err != ESP_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("LED: Error create SPI bus failed"));
goto err;
}
_spi_strip.spi_host = spi_host; // confirmed working, so keep it's value to free it later
spi_dev_cfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0,
//set -1 when CS is not used
.clock_source = SPI_CLK_SRC_DEFAULT, // clk_src,
.clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION,
.spics_io_num = -1,
.queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE,
};
err = spi_bus_add_device(_spi_strip.spi_host, &spi_dev_cfg, &_spi_strip.spi_device);
if (err != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "LED: Error failed to add spi device");
goto err;
}
spi_device_get_actual_freq(_spi_strip.spi_device, &clock_resolution_khz);
if (err != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "LED: Error failed to get spi frequency");
goto err;
}
// TODO: ideally we should decide the SPI_BYTES_PER_COLOR_BYTE by the real clock resolution
// But now, let's fixed the resolution, the downside is, we don't support a clock source whose frequency is not multiple of LED_STRIP_SPI_DEFAULT_RESOLUTION
if (clock_resolution_khz != LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000) {
// AddLog(LOG_LEVEL_INFO, "LED: Error unsupported clock resolution: %dKHz", clock_resolution_khz);
goto err;
}
return true;
err:
if (_spi_strip.spi_device) {
spi_bus_remove_device(_spi_strip.spi_device);
}
if (_spi_strip.spi_host) {
spi_bus_free(_spi_strip.spi_host);
}
return false;
}
bool TasmotaLEDPusherSPI::CanShow(void) {
return true; // TODO
}
bool TasmotaLEDPusherSPI::Push(uint8_t *buf) {
if (CanShow()) {
led_strip_transmit_buffer(&_spi_strip, buf);
}
return true;
}
#endif // TASMOTALED_HARDWARE_SPI
#endif // ESP32

View File

@ -152,7 +152,7 @@ BERRY_LOCAL const bntvmodule_t* const be_module_table[] = {
&be_native_module(unishox),
#endif // USE_UNISHOX_COMPRESSION
#ifdef USE_WS2812
#if defined(USE_WS2812) && !defined(USE_WS2812_FORCE_NEOPIXELBUS)
&be_native_module(animate),
#endif // USE_WS2812
@ -293,7 +293,7 @@ BERRY_LOCAL bclass_array be_class_table = {
#ifdef USE_BERRY_TCPSERVER
&be_native_class(tcpserver),
#endif // USE_BERRY_TCPSERVER
#ifdef USE_WS2812
#if defined(USE_WS2812) && !defined(USE_WS2812_FORCE_NEOPIXELBUS)
&be_native_class(Leds_ntv),
&be_native_class(Leds),
#endif // USE_WS2812

View File

@ -45,10 +45,10 @@ extern "C" {
uint32_t g2 = (color_b >> 8) & 0xFF;
uint32_t b2 = (color_b ) & 0xFF;
uint32_t a2 = (color_b >> 24) & 0xFF;
uint32_t r3 = changeUIntScale(alpha, 0, 255, r2, r);
uint32_t g3 = changeUIntScale(alpha, 0, 255, g2, g);
uint32_t b3 = changeUIntScale(alpha, 0, 255, b2, b);
uint32_t a3 = changeUIntScale(alpha, 0, 255, a2, a);
uint8_t r3 = changeUIntScale(alpha, 0, 255, r2, r);
uint8_t g3 = changeUIntScale(alpha, 0, 255, g2, g);
uint8_t b3 = changeUIntScale(alpha, 0, 255, b2, b);
uint8_t a3 = changeUIntScale(alpha, 0, 255, a2, a);
uint32_t rgb = (a3 << 24) | (r3 << 16) | (g3 << 8) | b3;
be_pushint(vm, rgb);
be_return(vm);
@ -97,9 +97,9 @@ extern "C" {
uint32_t fore_g = (fore_argb >> 8) & 0xFF;
uint32_t back_b = (back_argb ) & 0xFF;
uint32_t fore_b = (fore_argb ) & 0xFF;
uint32_t dest_r_new = changeUIntScale(fore_alpha, 0, 255, fore_r, back_r);
uint32_t dest_g_new = changeUIntScale(fore_alpha, 0, 255, fore_g, back_g);
uint32_t dest_b_new = changeUIntScale(fore_alpha, 0, 255, fore_b, back_b);
uint8_t dest_r_new = changeUIntScale(fore_alpha, 0, 255, fore_r, back_r);
uint8_t dest_g_new = changeUIntScale(fore_alpha, 0, 255, fore_g, back_g);
uint8_t dest_b_new = changeUIntScale(fore_alpha, 0, 255, fore_b, back_b);
dest_rgb_new = (dest_r_new << 16) | (dest_g_new << 8) | dest_b_new;
}
dest[i] = dest_rgb_new;
@ -135,7 +135,7 @@ extern "C" {
// Leds_frame.paste_pixels(neopixel:bytes(), led_buffer:bytes(), bri:int 0..100, gamma:bool)
//
// Copy from ARGB buffer to GRB
// Copy from ARGB buffer to RGB
int32_t be_leds_paste_pixels(bvm *vm);
int32_t be_leds_paste_pixels(bvm *vm) {
int32_t top = be_top(vm); // Get the number of arguments
@ -162,8 +162,8 @@ extern "C" {
uint32_t src_r = (src_argb >> 16) & 0xFF;
uint32_t src_g = (src_argb >> 8) & 0xFF;
uint32_t src_b = (src_argb ) & 0xFF;
dest_buf[i * 3 + 0] = src_g;
dest_buf[i * 3 + 1] = src_r;
dest_buf[i * 3 + 0] = src_r;
dest_buf[i * 3 + 1] = src_g;
dest_buf[i * 3 + 2] = src_b;
}
}

View File

@ -7,7 +7,9 @@
#ifdef USE_WS2812
extern int be_neopixelbus_call_native(bvm *vm);
#include "TasmotaLED.h"
extern int be_tasmotaled_call_native(bvm *vm);
extern int be_leds_blend_color(bvm *vm);
extern int be_leds_apply_bri_gamma(bvm *vm);
@ -16,10 +18,15 @@ class be_class_Leds_ntv (scope: global, name: Leds_ntv, strings: weak) {
_p, var
_t, var
WS2812_GRB, int(1)
SK6812_GRBW, int(2)
WS2812_GRB, int(ws2812_grb)
SK6812_GRBW, int(sk6812_grbw)
SK6812_GRB, int(sk6812_grb)
call_native, func(be_neopixelbus_call_native)
RMT, int(TasmotaLed_RMT)
SPI, int(TasmotaLed_SPI)
I2S, int(TasmotaLed_I2S)
call_native, func(be_tasmotaled_call_native)
blend_color, static_func(be_leds_blend_color)
apply_bri_gamma, static_func(be_leds_apply_bri_gamma)

View File

@ -31,8 +31,8 @@ class Leds : Leds_ntv
# leds:int = number of leds of the strip
# gpio:int (optional) = GPIO for NeoPixel. If not specified, takes the WS2812 gpio
# typ:int (optional) = Type of LED, defaults to WS2812 RGB
# rmt:int (optional) = RMT hardware channel to use, leave default unless you have a good reason
def init(leds, gpio_phy, typ, rmt) # rmt is optional
# hardware:int (optional) = hardware support (Leds.RMT, Leds.SPI)
def init(leds, gpio_phy, typ, hardware)
import gpio
self.gamma = true # gamma is enabled by default, it should be disabled explicitly if needed
if (gpio_phy == nil) || (gpio_phy == gpio.pin(gpio.WS2812, 0))
@ -47,7 +47,7 @@ class Leds : Leds_ntv
self.bri = 127 # 50% brightness by default
# initialize the structure
self.ctor(self.leds, gpio_phy, typ, rmt)
self.ctor(self.leds, gpio_phy, typ, hardware)
end
if self._p == nil raise "internal_error", "couldn't not initialize noepixelbus" end
@ -56,44 +56,6 @@ class Leds : Leds_ntv
self.begin()
end
# assign RMT
static def assign_rmt(gpio_phy)
gpio_phy = int(gpio_phy)
if gpio_phy < 0 raise "value_error", "invalid GPIO number" end
import global
var rmt
# if "_rmt" is not initialized, set to an array of GPIO of size MAX_RMT
if !global.contains("_rmt")
rmt = []
global._rmt = rmt
for i:0..gpio.MAX_RMT-1
rmt.push(-1)
end
# if default WS2812 is set, assign RMT0
if gpio.pin_used(gpio.WS2812, 0)
rmt[0] = gpio.pin(gpio.WS2812, 0)
end
end
rmt = global._rmt
# find an already assigned slot or try to assign a new one
var i = 0
var first_free = -1
while i < gpio.MAX_RMT
var elt = rmt[i]
if elt == gpio_phy return i end # already assigned
if elt < 0 && first_free < 0 first_free = i end # found a free slot
i += 1
end
if first_free >= 0
rmt[first_free] = gpio_phy
return first_free
end
# no more slot
raise "internal_error", "no more RMT channel available"
end
def clear()
self.clear_to(0x000000)
self.show()
@ -109,17 +71,14 @@ class Leds : Leds_ntv
return self.bri
end
def ctor(leds, gpio_phy, typ, rmt)
def ctor(leds, gpio_phy, typ, hardware)
if gpio_phy == nil
self.call_native(0) # native driver
else
if typ == nil
typ = self.WS2812_GRB
end
if rmt == nil
rmt = self.assign_rmt(gpio_phy)
end
self.call_native(0, leds, gpio_phy, typ, rmt)
self.call_native(0, leds, gpio_phy, typ, hardware)
end
end
def begin()
@ -155,10 +114,14 @@ class Leds : Leds_ntv
def pixel_offset()
return 0
end
def clear_to(col, bri)
def clear_to(col, bri, index, len)
if (bri == nil) bri = self.bri end
if index != nil && len != nil
self.call_native(9, self.to_gamma(col, bri), index, len)
else
self.call_native(9, self.to_gamma(col, bri))
end
end
def set_pixel_color(idx, col, bri)
if (bri == nil) bri = self.bri end
self.call_native(10, idx, self.to_gamma(col, bri))
@ -403,15 +366,15 @@ anim()
#-
var s = Leds_matrix(5, 5, gpio.pin(gpio.WS2812, 1))
var s = Leds(25, gpio.pin(gpio.WS2812, 1)).create_matrix(5, 5)
s.set_alternate(true)
s.clear_to(0x300000)
s.clear_to(0x400000)
s.show()
x = 0
y = 0
def anim()
s.clear_to(0x300000)
s.clear_to(0x400000)
s.set_matrix_pixel_color(x, y, 0x004000)
s.show()
y = (y + 1) % 5

File diff suppressed because it is too large Load Diff

View File

@ -563,13 +563,6 @@
// -- Optional light modules ----------------------
#define USE_LIGHT // Add support for light control
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
// #define USE_WS2812_DMA // ESP8266 only, DMA supports only GPIO03 (= Serial RXD) (+1k mem). When USE_WS2812_DMA is enabled expect Exceptions on Pow
#define USE_WS2812_RMT 0 // ESP32 only, hardware RMT support (default). Specify the RMT channel 0..7. This should be preferred to software bit bang.
// #define USE_WS2812_I2S 0 // ESP32 only, hardware I2S support. Specify the I2S channel 0..2. This is exclusive from RMT. By default, prefer RMT support
// #define USE_WS2812_INVERTED // Use inverted data signal
#define USE_WS2812_HARDWARE NEO_HW_WS2812 // Hardware type (NEO_HW_WS2812, NEO_HW_WS2812X, NEO_HW_WS2813, NEO_HW_SK6812, NEO_HW_LC8812, NEO_HW_APA106, NEO_HW_P9813)
#define USE_WS2812_CTYPE NEO_GRB // Color type (NEO_RGB, NEO_GRB, NEO_BRG, NEO_RBG, NEO_RGBW, NEO_GRBW)
#define USE_MY92X1 // Add support for MY92X1 RGBCW led controller as used in Sonoff B1, Ailight and Lohas
#define USE_SM16716 // Add support for SM16716 RGB LED controller (+0k7 code)
#define USE_SM2135 // Add support for SM2135 RGBCW led control as used in Action LSC (+0k6 code)
@ -583,6 +576,18 @@
#define USE_DGR_LIGHT_SEQUENCE // Add support for device group light sequencing (requires USE_DEVICE_GROUPS) (+0k2 code)
//#define USE_LSC_MCSL // Add support for GPE Multi color smart light as sold by Action in the Netherlands (+1k1 code)
// -- Optional adressable leds ----------------------
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
// -------- below is for ESP8266 only
// #define USE_WS2812_DMA // ESP8266 only, DMA supports only GPIO03 (= Serial RXD) (+1k mem). When USE_WS2812_DMA is enabled expect Exceptions on Pow
#define USE_WS2812_RMT 0 // ESP32 only, hardware RMT support (default). Specify the RMT channel 0..7. This should be preferred to software bit bang.
// #define USE_WS2812_I2S 0 // ESP32 only, hardware I2S support. Specify the I2S channel 0..2. This is exclusive from RMT. By default, prefer RMT support
// #define USE_WS2812_INVERTED // Use inverted data signal
#define USE_WS2812_HARDWARE NEO_HW_WS2812 // Hardware type (NEO_HW_WS2812, NEO_HW_WS2812X, NEO_HW_WS2813, NEO_HW_SK6812, NEO_HW_LC8812, NEO_HW_APA106, NEO_HW_P9813)
#define USE_WS2812_CTYPE NEO_GRB // Color type (NEO_RGB, NEO_GRB, NEO_BRG, NEO_RBG, NEO_RGBW, NEO_GRBW)
// -------- below if for ESP32x only -- ESP32 uses a lightweight library instead of NeoPixelBus
// #define USE_WS2812_FORCE_NEOPIXELBUS // this option forces to use NeoPixelBus (like ESP866), which disables Berry support and limits features -- DO NOT USE unless you have a good reason
// #define USE_LIGHT_ARTNET // Add support for DMX/ArtNet via UDP on port 6454 (+3.5k code)
#define USE_LIGHT_ARTNET_MCAST 239,255,25,54 // Multicast address used to listen: 239.255.25.54

View File

@ -386,7 +386,7 @@ bool ArtNetStart(void) {
Settings->light_rotation = 0;
Ws2812InitStrip();
} else {
Ws2812Clear();
Ws2812Clear(true);
}
}

View File

@ -24,22 +24,8 @@
#ifdef USE_WS2812
#include <NeoPixelBus.h>
enum {
ws2812_grb = 1,
sk6812_grbw = 2,
neopixel_type_end
};
#ifdef CONFIG_IDF_TARGET_ESP32C2
typedef NeoPixelBus<NeoGrbFeature, NeoEsp32SpiN800KbpsMethod> neopixel_ws2812_grb_t;
typedef NeoPixelBus<NeoGrbwFeature, NeoEsp32SpiNSk6812Method> neopixel_sk6812_grbw_t;
#else
typedef NeoPixelBus<NeoGrbFeature, NeoEsp32RmtN800KbpsMethod> neopixel_ws2812_grb_t;
typedef NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtNSk6812Method> neopixel_sk6812_grbw_t;
#endif
#include "TasmotaLED.h"
#include "TasmotaLEDPusher.h"
/*********************************************************************************************\
* Functions from Tasmota WS2812 driver
@ -86,12 +72,12 @@ extern "C" {
// # 22 : ShiftLeft (rot:int [, first:int, last:int]) -> void
// # 23 : ShiftRight (rot:int [, first:int, last:int]) -> void
void * be_get_neopixelbus(bvm *vm) {
void * be_get_tasmotaled(bvm *vm) {
be_getmember(vm, 1, "_p");
void * strip = (void*) be_tocomptr(vm, -1);
be_pop(vm, 1);
if (strip == nullptr) {
be_raise(vm, "internal_error", "neopixelbus object not initialized");
be_raise(vm, "internal_error", "tasmotaled object not initialized");
}
return strip;
}
@ -99,70 +85,65 @@ extern "C" {
be_getmember(vm, 1, "_t");
int32_t type = be_toint(vm, -1);
be_pop(vm, 1);
if (type < 0 || type >= neopixel_type_end) {
if (type < 0) {
be_raise(vm, "internal_error", "invalid leds type");
}
return type;
}
int be_neopixelbus_call_native(bvm *vm);
int be_neopixelbus_call_native(bvm *vm) {
int be_tasmotaled_call_native(bvm *vm);
int be_tasmotaled_call_native(bvm *vm) {
int32_t argc = be_top(vm); // Get the number of arguments
if (argc >= 2 && be_isint(vm, 2)) {
int32_t cmd = be_toint(vm, 2);
if (0 == cmd) { // 00 : ctor (leds:int, gpio:int) -> void
if ((argc != 2) && !(argc >= 6 && be_isint(vm, 3) && be_isint(vm, 4) && be_isint(vm, 5) && be_isint(vm, 6))) {
be_raise(vm, "value_error", "bad arguments for neopixelbus:ctor");
if ((argc != 2) && !(argc >= 5 && be_isint(vm, 3) && be_isint(vm, 4) && be_isint(vm, 5))) {
be_raise(vm, "value_error", "bad arguments for tasmotaled:ctor");
}
int32_t leds = -1;
int32_t gpio = -1;
int32_t neopixel_type = 0;
int32_t rmt = 0;
void * strip = nullptr;
int32_t led_type = 0;
int32_t hardware = 0;
if (argc >= 6 && be_isint(vm, 6)) {
hardware = be_toint(vm, 6) & 0xFF0000; // remove the low 16 bits to avoid any interference with legacy parameter for RMT channels
}
TasmotaLED * strip = nullptr;
if (argc > 2) {
leds = be_toint(vm, 3);
gpio = be_toint(vm, 4);
neopixel_type = be_toint(vm, 5);
rmt = be_toint(vm, 6);
led_type = be_toint(vm, 5);
}
if (-1 == gpio) {
// if GPIO is '-1'
neopixel_type = 0;
Ws2812InitStrip(); // ensure the NeoPixelbus object is initialized, because Berry code runs before the driver is initialized
strip = Ws2812GetStrip();
led_type = 0;
Ws2812InitStrip(); // ensure the tasmotaled object is initialized, because Berry code runs before the driver is initialized
// strip = Ws2812GetStrip(); TODO
} else {
// allocate a new RMT
if (neopixel_type < 1) { neopixel_type = 1; }
if (neopixel_type >= neopixel_type_end) { neopixel_type = neopixel_type_end - 1; }
if (rmt < 0) { rmt = 0; }
if (rmt >= MAX_RMT) { rmt = MAX_RMT - 1; }
switch (neopixel_type) {
case ws2812_grb: strip = new neopixel_ws2812_grb_t(leds, gpio, (NeoBusChannel) rmt);
break;
case sk6812_grbw: strip = new neopixel_sk6812_grbw_t(leds, gpio, (NeoBusChannel) rmt);
break;
if (led_type < 1) { led_type = 1; }
TasmotaLEDPusher * pusher = TasmotaLEDPusher::Create(hardware, gpio);
if (pusher == nullptr) {
be_raise(vm, "value_error", "LED interface not supported");
}
strip = new TasmotaLED(led_type, leds);
strip->SetPusher(pusher);
}
// AddLog(LOG_LEVEL_DEBUG, "LED: leds %i gpio %i type %i", leds, gpio, led_type);
// store type in attribute `_t`
be_pushint(vm, neopixel_type);
be_pushint(vm, led_type);
be_setmember(vm, 1, "_t");
be_pop(vm, 1);
be_pushcomptr(vm, (void*) strip);
be_pushcomptr(vm, (void*) strip); // if native driver, it is NULL
be_setmember(vm, 1, "_p");
be_pop(vm, 1);
be_pushnil(vm);
} else {
// all other commands need a valid neopixelbus pointer
// all other commands need a valid tasmotaled pointer
int32_t leds_type = be_get_leds_type(vm);
const void * s = be_get_neopixelbus(vm); // raises an exception if pointer is invalid
// initialize all possible variants
neopixel_ws2812_grb_t * s_ws2812_grb = (leds_type == ws2812_grb) ? (neopixel_ws2812_grb_t*) s : nullptr;
neopixel_sk6812_grbw_t * s_sk6812_grbw = (leds_type == sk6812_grbw) ? (neopixel_sk6812_grbw_t*) s : nullptr;
TasmotaLED * strip = (TasmotaLED*) be_get_tasmotaled(vm); // raises an exception if pointer is invalid
// detect native driver
bool native = (leds_type == 0);
@ -171,8 +152,7 @@ extern "C" {
switch (cmd) {
case 1: // # 01 : begin void -> void
if (native) Ws2812Begin();
if (s_ws2812_grb) s_ws2812_grb->Begin();
if (s_sk6812_grbw) s_sk6812_grbw->Begin();
else if (strip) strip->Begin();
break;
case 2: // # 02 : show void -> void
{
@ -195,49 +175,50 @@ extern "C" {
}
}
uint32_t pixels_size; // number of bytes to push
if (native) { Ws2812Show(); pixels_size = Ws2812PixelsSize(); }
if (s_ws2812_grb) { s_ws2812_grb->Show(); pixels_size = s_ws2812_grb->PixelsSize(); }
if (s_sk6812_grbw) { s_sk6812_grbw->Show(); pixels_size = s_sk6812_grbw->PixelsSize(); }
bool update_completed = false;
if (native) {
Ws2812Show();
pixels_size = Ws2812PixelsSize();
update_completed =Ws2812CanShow();
}
else if (strip) {
strip->Show();
pixels_size = strip->PixelCount() * strip->PixelSize();
update_completed = strip->CanShow();
}
// Wait for RMT/I2S to complete fixes distortion due to analogRead
// 1ms is needed for 96 bytes
if (!update_completed) {
SystemBusyDelay((pixels_size + 95) / 96);
}
}
break;
case 3: // # 03 : CanShow void -> bool
if (native) be_pushbool(vm, Ws2812CanShow());
if (s_ws2812_grb) be_pushbool(vm, s_ws2812_grb->CanShow());
if (s_sk6812_grbw) be_pushbool(vm, s_sk6812_grbw->CanShow());
else if (strip) be_pushbool(vm, strip->CanShow());
break;
case 4: // # 04 : IsDirty void -> bool
if (native) be_pushbool(vm, Ws2812IsDirty());
if (s_ws2812_grb) be_pushbool(vm, s_ws2812_grb->IsDirty());
if (s_sk6812_grbw) be_pushbool(vm, s_sk6812_grbw->IsDirty());
else if (strip) be_pushbool(vm, strip->IsDirty());
break;
case 5: // # 05 : Dirty void -> void
if (native) Ws2812Dirty();
if (s_ws2812_grb) s_ws2812_grb->Dirty();
if (s_sk6812_grbw) s_sk6812_grbw->Dirty();
else if (strip) strip->Dirty();
break;
case 6: // # 06 : Pixels void -> bytes() (mapped to the buffer)
{
uint8_t * pixels;
if (native) pixels = Ws2812Pixels();
if (s_ws2812_grb) pixels = s_ws2812_grb->Pixels();
if (s_sk6812_grbw) pixels = s_sk6812_grbw->Pixels();
be_pushcomptr(vm, pixels);
if (native) be_pushcomptr(vm, Ws2812Pixels());
else if (strip) be_pushcomptr(vm, strip->Pixels());
}
break;
case 7: // # 07 : PixelSize void -> int
if (native) be_pushint(vm, Ws2812PixelSize());
if (s_ws2812_grb) be_pushint(vm, s_ws2812_grb->PixelSize());
if (s_sk6812_grbw) be_pushint(vm, s_sk6812_grbw->PixelSize());
else if (strip) be_pushint(vm, strip->PixelSize());
break;
case 8: // # 08 : PixelCount void -> int
if (native) be_pushint(vm, Ws2812PixelCount());
if (s_ws2812_grb) be_pushint(vm, s_ws2812_grb->PixelCount());
if (s_sk6812_grbw) be_pushint(vm, s_sk6812_grbw->PixelCount());
else if (strip) be_pushint(vm, strip->PixelCount());
break;
case 9: // # 09 : ClearTo (color:??) -> void
{
@ -253,42 +234,35 @@ extern "C" {
if (len < 0) { len = 0; }
if (native) Ws2812ClearTo(r, g, b, w, from, from + len - 1);
if (s_ws2812_grb) s_ws2812_grb->ClearTo(RgbColor(r, g, b), from, from + len - 1);
if (s_sk6812_grbw) s_sk6812_grbw->ClearTo(RgbwColor(r, g, b, w), from, from + len - 1);
else if (strip) strip->ClearTo(rgbw, from, from + len - 1);
} else {
if (native) Ws2812ClearTo(r, g, b, w, -1, -1);
if (s_ws2812_grb) s_ws2812_grb->ClearTo(RgbColor(r, g, b));
if (s_sk6812_grbw) s_sk6812_grbw->ClearTo(RgbwColor(r, g, b, w));
else if (strip) strip->ClearTo(rgbw);
}
}
break;
case 10: // # 10 : SetPixelColor (idx:int, color:??) -> void
case 10: // # 10 : SetPixelColor (idx:int, color:int wrgb) -> void
{
int32_t idx = be_toint(vm, 3);
uint32_t rgbw = be_toint(vm, 4);
uint8_t w = (rgbw >> 24) & 0xFF;
uint8_t r = (rgbw >> 16) & 0xFF;
uint8_t g = (rgbw >> 8) & 0xFF;
uint8_t b = (rgbw ) & 0xFF;
if (native) Ws2812SetPixelColor(idx, r, g, b, w);
if (s_ws2812_grb) s_ws2812_grb->SetPixelColor(idx, RgbColor(r, g, b));
if (s_sk6812_grbw) s_sk6812_grbw->SetPixelColor(idx, RgbwColor(r, g, b, w));
uint32_t wrgb = be_toint(vm, 4);
if (native) {
uint8_t w = (wrgb >> 24) & 0xFF;
uint8_t r = (wrgb >> 16) & 0xFF;
uint8_t g = (wrgb >> 8) & 0xFF;
uint8_t b = (wrgb ) & 0xFF;
Ws2812SetPixelColor(idx, r, g, b, w);
} else if (strip) {
strip->SetPixelColor(idx, wrgb);
}
}
break;
case 11: // # 11 : GetPixelColor (idx:int) -> color:??
case 11: // # 11 : GetPixelColor (idx:int) -> color:int wrgb
{
int32_t idx = be_toint(vm, 3);
if (native) {
be_pushint(vm, Ws2812GetPixelColor(idx));
}
if (s_ws2812_grb) {
RgbColor rgb = s_ws2812_grb->GetPixelColor(idx);
be_pushint(vm, (rgb.R << 16) | (rgb.G << 8) | rgb.B);
}
if (s_sk6812_grbw) {
RgbwColor rgbw = s_sk6812_grbw->GetPixelColor(idx);
be_pushint(vm, (rgbw.W << 24) | (rgbw.R << 16) | (rgbw.G << 8) | rgbw.B);
} else if (strip) {
be_pushint(vm, strip->GetPixelColor(idx));
}
}
break;

View File

@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if defined(ESP8266) || defined(USE_WS2812_FORCE_NEOPIXELBUS)
#ifdef USE_LIGHT
#ifdef USE_WS2812
@ -553,12 +554,15 @@ void Ws2812DDP(void)
}
#endif // USE_NETWORK_LIGHT_SCHEMES
void Ws2812Clear(void)
void Ws2812Clear(bool display = true);
void Ws2812Clear(bool display)
{
strip->ClearTo(0);
if (display) {
Ws2812LibStripShow();
Ws2812.show_next = 1;
}
}
void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white)
{
@ -965,3 +969,4 @@ bool Xlgt01(uint32_t function)
#endif // USE_WS2812
#endif // USE_LIGHT
#endif // defined(ESP8266) || defined(USE_WS2812_FORCE_NEOPIXELBUS)

View File

@ -0,0 +1,899 @@
/*
xlgt_01_ws2812.ino - led string support for Tasmota
Copyright (C) 2021 Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32
#ifdef USE_LIGHT
#if defined(USE_WS2812) && !defined(USE_WS2812_FORCE_NEOPIXELBUS)
/*********************************************************************************************\
* WS2812 RGB / RGBW Leds using NeopixelBus library
*
* light_scheme WS2812 3+ Colors 1+2 Colors Effect
* ------------ ------ --------- ---------- -----------------
* 0 (5) yes no no Clock
* 1 (6) yes no no Incandescent
* 2 (7) yes no no RGB
* 3 (8) yes no no Christmas
* 4 (9) yes no no Hanukkah
* 5 (10) yes no no Kwanzaa
* 6 (11) yes no no Rainbow
* 7 (12) yes no no Fire
* 8 (13) yes no no Stairs
* 9 (14) yes no no Clear (= Berry)
* 10 (15) yes no no Optional DDP
\*********************************************************************************************/
#define XLGT_01 1
const uint8_t WS2812_SCHEMES = 10; // Number of WS2812 schemes
const char kWs2812Commands[] PROGMEM = "|" // No prefix
D_CMND_LED "|" D_CMND_PIXELS "|" D_CMND_ROTATION "|" D_CMND_WIDTH "|" D_CMND_STEPPIXELS ;
void (* const Ws2812Command[])(void) PROGMEM = {
&CmndLed, &CmndPixels, &CmndRotation, &CmndWidth, &CmndStepPixels };
#include <TasmotaLED.h>
const uint16_t kLedType = 0;
// select the right pixel size
#if (USE_WS2812_CTYPE > NEO_3LED)
const uint16_t kTasLed_PixelSize = TasmotaLed_4_WRGB;
#else
const uint16_t kTasLed_PixelSize = TasmotaLed_3_RGB;
#endif
// select the right pixel order
#if (USE_WS2812_CTYPE == NEO_GRB)
const uint16_t kTasLed_PixelOrder = TasmotaLed_GRB;
#elif (USE_WS2812_CTYPE == NEO_BRG)
const uint16_t kTasLed_PixelOrder = TasmotaLed_BRG;
#elif (USE_WS2812_CTYPE == NEO_RBG)
const uint16_t kTasLed_PixelOrder = TasmotaLed_RBG;
#elif (USE_WS2812_CTYPE == NEO_RGBW)
const uint16_t kTasLed_PixelOrder = TasmotaLed_RGB;
#elif (USE_WS2812_CTYPE == NEO_GRBW)
const uint16_t kTasLed_PixelOrder = TasmotaLed_GRB;
#else
const uint16_t kTasLed_PixelOrder = TasmotaLed_RGB;
#endif
// all leds have always W at the end
const uint16_t kTasLed_PixelWhite = TasmotaLed_xxxW;
// We drop support for NEO_HW_P9813, as it is too hard to support with hardwarre
#if (USE_WS2812_HARDWARE == NEO_HW_P9813)
#error "P9813 is not supported by this library"
#endif // USE_WS2812_CTYPE
// select timing
#if (USE_WS2812_HARDWARE == NEO_HW_WS2812X)
const uint16_t kTasLed_Timing = TasmotaLed_WS2812;
#elif (USE_WS2812_HARDWARE == NEO_HW_SK6812)
const uint16_t kTasLed_Timing = TasmotaLed_SK6812;
#elif (USE_WS2812_HARDWARE == NEO_HW_APA106)
#error "APA106 is not supported by this library"
#else // USE_WS2812_HARDWARE
const uint16_t kTasLed_Timing = TasmotaLed_WS2812;
#endif // USE_WS2812_HARDWARE
const uint16_t kTasLed_Type = kTasLed_PixelSize | kTasLed_PixelOrder | kTasLed_PixelWhite | kTasLed_Timing;
// select hardware acceleration - bitbanging is not supported on ESP32 due to interference of interrupts
#if CONFIG_IDF_TARGET_ESP32C2
const uint32_t kTasLed_Hardware = TasmotaLed_I2S; // I2S
#else // all other ESP32 variants
#if defined(USE_WS2812_DMA)
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
#elif defined(USE_WS2812_RMT)
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
#elif defined(USE_WS2812_I2S)
const uint32_t kTasLed_Hardware = TasmotaLed_I2S; // I2S
#else
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
#endif
#endif
#if (USE_WS2812_HARDWARE == NEO_HW_P9813)
#error "P9813 is not supported by this library"
#endif
/*******************************************************************************************\
* Support for TasmotaLED
*
* From here we have defined:
* kTasLed_Type: encodes pixel size, pixel order, pixel white, timing
* kTasLed_Hardware: encodes the harware support to push to Leds: RMT, SPI, I2S
*******************************************************************************************/
TasmotaLED *strip = nullptr;
typedef union LedColor {
uint32_t C; // encoded as 0xWWRRGGBB
struct {
uint8_t B, G, R, W; // WRGB in little endian
};
} LedColor;
struct ColorScheme {
const LedColor* colors;
uint8_t count;
};
const LedColor kIncandescent[2] = { 0xFF8C14, 0x000000 };
const LedColor kRgb[3] = { 0xFF0000, 0x00FF00, 0x0000FF };
const LedColor kChristmas[2] = { 0xFF0000, 0x00FF00 };
const LedColor kHanukkah[2] = { 0x0000FF, 0xFFFFFF };
const LedColor kwanzaa[3] = { 0xFF0000, 0x000000, 0x00FF00 };
const LedColor kRainbow[7] = { 0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF };
const LedColor kFire[3] = { 0xFF0000, 0xFF6600, 0xFFC000 };
const LedColor kStairs[2] = { 0x000000, 0xFFFFFF0 };
const ColorScheme kSchemes[WS2812_SCHEMES -2] = { // Skip clock and clear scheme
kIncandescent, 2,
kRgb, 3,
kChristmas, 2,
kHanukkah, 2,
kwanzaa, 3,
kRainbow, 7,
kFire, 3,
kStairs, 2
};
const uint8_t kWidth[5] = {
1, // Small
2, // Medium
4, // Large
8, // Largest
255 // All
};
const uint8_t kWsRepeat[5] = {
8, // Small
6, // Medium
4, // Large
2, // Largest
1 // All
};
struct WS2812 {
uint8_t show_next = 1;
uint8_t scheme_offset = 0;
bool suspend_update = false;
} Ws2812;
/********************************************************************************************/
// For some reason map fails to compile so renamed to wsmap
long wsmap(long x, long in_min, long in_max, long out_min, long out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void Ws2812LibStripShow(void) {
strip->Show();
#if defined(USE_WS2812_DMA) || defined(USE_WS2812_RMT) || defined(USE_WS2812_I2S)
// Wait for DMA/RMT/I2S to complete fixes distortion due to analogRead
// delay((Settings->light_pixels >> 6) +1); // 256 / 64 = 4 +1 = 5
SystemBusyDelay( (Settings->light_pixels + 31) >> 5); // (256 + 32) / 32 = 8
#endif
}
void Ws2812StripShow(void)
{
LedColor c;
if (Settings->light_correction) {
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
c.C = strip->GetPixelColor(i);
c.R = ledGamma(c.R);
c.G = ledGamma(c.G);
c.B = ledGamma(c.B);
#if (USE_WS2812_CTYPE > NEO_3LED)
c.W = ledGamma(c.W);
#endif
strip->SetPixelColor(i, c.C);
}
}
Ws2812LibStripShow();
}
int mod(int a, int b)
{
int ret = a % b;
if (ret < 0) ret += b;
return ret;
}
void Ws2812UpdatePixelColor(int position, LedColor hand_color, float offset);
void Ws2812UpdatePixelColor(int position, LedColor hand_color, float offset)
{
LedColor color;
uint32_t mod_position = mod(position, (int)Settings->light_pixels);
color.C = strip->GetPixelColor(mod_position);
float dimmer = 100 / (float)Settings->light_dimmer;
color.R = tmin(color.R + ((hand_color.R / dimmer) * offset), 255);
color.G = tmin(color.G + ((hand_color.G / dimmer) * offset), 255);
color.B = tmin(color.B + ((hand_color.B / dimmer) * offset), 255);
strip->SetPixelColor(mod_position, color.C);
}
void Ws2812UpdateHand(int position, uint32_t index)
{
uint32_t width = Settings->light_width;
if (index < WS_MARKER) { width = Settings->ws_width[index]; }
if (!width) { return; } // Skip
position = (position + Settings->light_rotation) % Settings->light_pixels;
if (Settings->flag.ws_clock_reverse) { // SetOption16 - Switch between clockwise or counter-clockwise
position = Settings->light_pixels -position;
}
LedColor hand_color = {0};
hand_color.R = Settings->ws_color[index][WS_RED];
hand_color.G = Settings->ws_color[index][WS_GREEN];
hand_color.B = Settings->ws_color[index][WS_BLUE];
Ws2812UpdatePixelColor(position, hand_color, 1);
uint32_t range = ((width -1) / 2) +1;
for (uint32_t h = 1; h < range; h++) {
float offset = (float)(range - h) / (float)range;
Ws2812UpdatePixelColor(position -h, hand_color, offset);
Ws2812UpdatePixelColor(position +h, hand_color, offset);
}
}
void Ws2812Clock(void)
{
strip->ClearTo(0); // Reset strip
int clksize = 60000 / (int)Settings->light_pixels;
Ws2812UpdateHand((RtcTime.second * 1000) / clksize, WS_SECOND);
Ws2812UpdateHand((RtcTime.minute * 1000) / clksize, WS_MINUTE);
Ws2812UpdateHand((((RtcTime.hour % 12) * 5000) + ((RtcTime.minute * 1000) / 12 )) / clksize, WS_HOUR);
if (Settings->ws_color[WS_MARKER][WS_RED] + Settings->ws_color[WS_MARKER][WS_GREEN] + Settings->ws_color[WS_MARKER][WS_BLUE]) {
for (uint32_t i = 0; i < 12; i++) {
Ws2812UpdateHand((i * 5000) / clksize, WS_MARKER);
}
}
Ws2812StripShow();
}
void Ws2812GradientColor(uint32_t schemenr, LedColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i);
void Ws2812GradientColor(uint32_t schemenr, LedColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i)
{
/*
* Compute the color of a pixel at position i using a gradient of the color scheme.
* This function is used internally by the gradient function.
*/
ColorScheme scheme = kSchemes[schemenr];
uint32_t curRange = i / range;
uint32_t rangeIndex = i % range;
uint32_t colorIndex = rangeIndex / gradRange;
uint32_t start = colorIndex;
uint32_t end = colorIndex +1;
if (curRange % 2 != 0) {
start = (scheme.count -1) - start;
end = (scheme.count -1) - end;
}
float dimmer = 100 / (float)Settings->light_dimmer;
float fmyRed = (float)wsmap(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].R, scheme.colors[end].R) / dimmer;
float fmyGrn = (float)wsmap(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].G, scheme.colors[end].G) / dimmer;
float fmyBlu = (float)wsmap(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].B, scheme.colors[end].B) / dimmer;
mColor->R = (uint8_t)fmyRed;
mColor->G = (uint8_t)fmyGrn;
mColor->B = (uint8_t)fmyBlu;
}
void Ws2812Gradient(uint32_t schemenr)
{
/*
* This routine courtesy Tony DiCola (Adafruit)
* Display a gradient of colors for the current color scheme.
* Repeat is the number of repetitions of the gradient (pick a multiple of 2 for smooth looping of the gradient).
*/
LedColor c;
ColorScheme scheme = kSchemes[schemenr];
if (scheme.count < 2) { return; }
uint32_t repeat = kWsRepeat[Settings->light_width]; // number of scheme.count per ledcount
uint32_t range = (uint32_t)ceil((float)Settings->light_pixels / (float)repeat);
uint32_t gradRange = (uint32_t)ceil((float)range / (float)(scheme.count - 1));
uint32_t speed = ((Settings->light_speed * 2) -1) * (STATES / 10);
uint32_t offset = speed > 0 ? Light.strip_timer_counter / speed : 0;
LedColor oldColor, currentColor;
Ws2812GradientColor(schemenr, &oldColor, range, gradRange, offset);
currentColor = oldColor;
speed = speed ? speed : 1; // should never happen, just avoid div0
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
if (kWsRepeat[Settings->light_width] > 1) {
Ws2812GradientColor(schemenr, &currentColor, range, gradRange, i + offset + 1);
}
// Blend old and current color based on time for smooth movement.
c.R = wsmap(Light.strip_timer_counter % speed, 0, speed, oldColor.R, currentColor.R);
c.G = wsmap(Light.strip_timer_counter % speed, 0, speed, oldColor.G, currentColor.G);
c.B = wsmap(Light.strip_timer_counter % speed, 0, speed, oldColor.B, currentColor.B);
strip->SetPixelColor(i, c.C);
oldColor = currentColor;
}
Ws2812StripShow();
}
void Ws2812Bars(uint32_t schemenr)
{
/*
* This routine courtesy Tony DiCola (Adafruit)
* Display solid bars of color for the current color scheme.
* Width is the width of each bar in pixels/lights.
*/
LedColor c;
ColorScheme scheme = kSchemes[schemenr];
uint32_t maxSize = Settings->light_pixels / scheme.count;
if (kWidth[Settings->light_width] > maxSize) { maxSize = 0; }
uint32_t speed = ((Settings->light_speed * 2) -1) * (STATES / 10);
uint32_t offset = (speed > 0) ? Light.strip_timer_counter / speed : 0;
LedColor mcolor[scheme.count];
memcpy(mcolor, scheme.colors, sizeof(mcolor));
float dimmer = 100 / (float)Settings->light_dimmer;
for (uint32_t i = 0; i < scheme.count; i++) {
float fmyRed = (float)mcolor[i].R / dimmer;
float fmyGrn = (float)mcolor[i].G / dimmer;
float fmyBlu = (float)mcolor[i].B / dimmer;
mcolor[i].R = (uint8_t)fmyRed;
mcolor[i].G = (uint8_t)fmyGrn;
mcolor[i].B = (uint8_t)fmyBlu;
}
uint32_t colorIndex = offset % scheme.count;
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
if (maxSize) { colorIndex = ((i + offset) % (scheme.count * kWidth[Settings->light_width])) / kWidth[Settings->light_width]; }
c.R = mcolor[colorIndex].R;
c.G = mcolor[colorIndex].G;
c.B = mcolor[colorIndex].B;
strip->SetPixelColor(i, c.C);
}
Ws2812StripShow();
}
void Ws2812Steps(uint32_t schemenr) {
LedColor c;
ColorScheme scheme = kSchemes[schemenr];
// apply main color if current sheme == kStairs
if (scheme.colors == kStairs) {
// we patch the colors
static LedColor colors_stairs[2] = {
{ 0x000000 },
{ .B = Settings->light_color[2], .G = Settings->light_color[1], .R = Settings->light_color[0] }
};
scheme.colors = colors_stairs;
}
uint8_t scheme_count = scheme.count;
if (Settings->light_fade) {
scheme_count = Settings->ws_width[WS_HOUR]; // Width4
}
if (scheme_count < 2) {
scheme_count = 2;
}
LedColor mcolor[scheme_count];
uint8_t color_start = 0;
uint8_t color_end = 1;
if (Settings->light_rotation & 0x01) {
color_start = 1;
color_end = 0;
}
if (Settings->light_fade) {
// generate gradient (width = Width4)
for (uint32_t i = 1; i < scheme_count - 1; i++) {
mcolor[i].R = (uint8_t) wsmap(i, 0, scheme_count, scheme.colors[color_start].R, scheme.colors[color_end].R);
mcolor[i].G = (uint8_t) wsmap(i, 0, scheme_count, scheme.colors[color_start].G, scheme.colors[color_end].G);
mcolor[i].B = (uint8_t) wsmap(i, 0, scheme_count, scheme.colors[color_start].B, scheme.colors[color_end].B);
}
} else {
memcpy(mcolor, scheme.colors, sizeof(mcolor));
}
// Repair first & last color in gradient; apply scheme rotation if fade==0
mcolor[0].R = scheme.colors[color_start].R;
mcolor[0].G = scheme.colors[color_start].G;
mcolor[0].B = scheme.colors[color_start].B;
mcolor[scheme_count-1].R = scheme.colors[color_end].R;
mcolor[scheme_count-1].G = scheme.colors[color_end].G;
mcolor[scheme_count-1].B = scheme.colors[color_end].B;
// Adjust to dimmer value
float dimmer = 100 / (float)Settings->light_dimmer;
for (uint32_t i = 0; i < scheme_count; i++) {
float fmyRed = (float)mcolor[i].R / dimmer;
float fmyGrn = (float)mcolor[i].G / dimmer;
float fmyBlu = (float)mcolor[i].B / dimmer;
mcolor[i].R = (uint8_t)fmyRed;
mcolor[i].G = (uint8_t)fmyGrn;
mcolor[i].B = (uint8_t)fmyBlu;
}
uint32_t speed = Settings->light_speed;
int32_t current_position = Light.strip_timer_counter / speed;
//all pixels are shown already | rotation change will not change current state
if (current_position > Settings->light_pixels / Settings->light_step_pixels + scheme_count ) {
return;
}
int32_t colorIndex;
int32_t step_nr;
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
step_nr = i / Settings->light_step_pixels;
colorIndex = current_position - step_nr;
if (colorIndex < 0) { colorIndex = 0; }
if (colorIndex > scheme_count - 1) { colorIndex = scheme_count - 1; }
c.R = mcolor[colorIndex].R;
c.G = mcolor[colorIndex].G;
c.B = mcolor[colorIndex].B;
// Adjust the scheme rotation
if (Settings->light_rotation & 0x02) {
strip->SetPixelColor(Settings->light_pixels - i - 1, c.C);
} else {
strip->SetPixelColor(i, c.C);
}
}
Ws2812StripShow();
}
#ifdef USE_NETWORK_LIGHT_SCHEMES
void Ws2812DDP(void)
{
LedColor c = {0};
// Can't be trying to initialize UDP too early.
if (TasmotaGlobal.restart_flag || TasmotaGlobal.global_state.network_down) return;
// Start DDP listener, if fail, just set last ddp_color
if (!ddp_udp_up) {
if (!ddp_udp.begin(4048)) return;
ddp_udp_up = 1;
AddLog(LOG_LEVEL_DEBUG_MORE, "DDP: UDP Listener Started: WS2812 Scheme");
}
// Get the DDP payload over UDP
std::vector<uint8_t> payload;
while (uint16_t packet_size = ddp_udp.parsePacket()) {
payload.resize(packet_size);
if (!ddp_udp.read(&payload[0], payload.size())) {
continue;
}
}
// No verification checks performed against packet besides length
if (payload.size() > (9+3*Settings->light_pixels)) {
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
c.R = payload[10+3*i];
c.G = payload[11+3*i];
c.B = payload[12+3*i];
strip->SetPixelColor(i, c.C);
}
Ws2812StripShow();
}
}
#endif // USE_NETWORK_LIGHT_SCHEMES
void Ws2812Clear(bool display = true);
void Ws2812Clear(bool display)
{
strip->ClearTo(0);
if (display) {
Ws2812LibStripShow();
Ws2812.show_next = 1;
}
}
void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white)
{
LedColor lcolor = {0};
lcolor.R = red;
lcolor.G = green;
lcolor.B = blue;
if (led) {
strip->SetPixelColor(led -1, lcolor.C); // Led 1 is strip Led 0 -> substract offset 1
} else {
// strip->ClearTo(lcolor); // Set WS2812_MAX_LEDS pixels
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
strip->SetPixelColor(i, lcolor.C);
}
}
if (!Ws2812.suspend_update) {
Ws2812LibStripShow();
Ws2812.show_next = 1;
}
}
char* Ws2812GetColor(uint32_t led, char* scolor)
{
uint8_t sl_ledcolor[4];
LedColor lcolor;
lcolor.C = strip->GetPixelColor(led -1);
sl_ledcolor[0] = lcolor.R;
sl_ledcolor[1] = lcolor.G;
sl_ledcolor[2] = lcolor.B;
sl_ledcolor[3] = lcolor.W;
scolor[0] = '\0';
for (uint32_t i = 0; i < Light.subtype; i++) {
if (Settings->flag.decimal_text) { // SetOption17 - Switch between decimal or hexadecimal output (0 = hexadecimal, 1 = decimal)
snprintf_P(scolor, 25, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", sl_ledcolor[i]);
} else {
snprintf_P(scolor, 25, PSTR("%s%02X"), scolor, sl_ledcolor[i]);
}
}
return scolor;
}
/*********************************************************************************************\
* Public - used by scripter only
\*********************************************************************************************/
void Ws2812ForceSuspend (void)
{
Ws2812.suspend_update = true;
}
void Ws2812ForceUpdate (void)
{
Ws2812.suspend_update = false;
Ws2812LibStripShow();
Ws2812.show_next = 1;
}
/********************************************************************************************/
bool Ws2812SetChannels(void)
{
uint8_t *cur_col = (uint8_t*)XdrvMailbox.data;
Ws2812SetColor(0, cur_col[0], cur_col[1], cur_col[2], cur_col[3]);
return true;
}
void Ws2812ShowScheme(void)
{
uint32_t scheme = Settings->light_scheme - Ws2812.scheme_offset;
#ifdef USE_NETWORK_LIGHT_SCHEMES
if ((scheme != 10) && (ddp_udp_up)) {
ddp_udp.stop();
ddp_udp_up = 0;
AddLog(LOG_LEVEL_DEBUG_MORE, "DDP: UDP Stopped: WS2812 Scheme not DDP");
}
#endif
switch (scheme) {
case 0: // Clock
if ((1 == TasmotaGlobal.state_250mS) || (Ws2812.show_next)) {
Ws2812Clock();
Ws2812.show_next = 0;
}
break;
case 9: // Clear
if (Settings->light_scheme != Light.last_scheme) {
Ws2812Clear();
}
break;
#ifdef USE_NETWORK_LIGHT_SCHEMES
case 10:
Ws2812DDP();
break;
#endif // USE_NETWORK_LIGHT_SCHEMES
default:
if(Settings->light_step_pixels > 0){
Ws2812Steps(scheme -1);
} else {
if (1 == Settings->light_fade) {
Ws2812Gradient(scheme -1);
} else {
Ws2812Bars(scheme -1);
}
}
Ws2812.show_next = 1;
break;
}
}
bool Ws2812InitStrip(void)
{
if (strip != nullptr) {
return true;
}
if (PinUsed(GPIO_WS2812)) { // RGB led
int32_t gpio = Pin(GPIO_WS2812);
TasmotaLEDPusher * pusher = TasmotaLEDPusher::Create(kTasLed_Hardware, gpio);
if (pusher == nullptr) {
AddLog(LOG_LEVEL_ERROR, "LED: No hardware supported");
return false;
}
strip = new TasmotaLED(kTasLed_Type, Settings->light_pixels);
strip->SetPusher(pusher);
strip->Begin();
Ws2812Clear();
return true;
}
return false;
}
void Ws2812ModuleSelected(void)
{
if (Ws2812InitStrip()) {
Ws2812.scheme_offset = Light.max_scheme +1;
Light.max_scheme += WS2812_SCHEMES;
#ifdef USE_NETWORK_LIGHT_SCHEMES
Light.max_scheme++;
#endif
#if (USE_WS2812_CTYPE > NEO_3LED)
TasmotaGlobal.light_type = LT_RGBW;
#else
TasmotaGlobal.light_type = LT_RGB;
#endif
TasmotaGlobal.light_driver = XLGT_01;
}
}
#ifdef ESP32
#ifdef USE_BERRY
/********************************************************************************************/
// Callbacks for Berry driver
//
// Since we dont' want to export all the template stuff, we need to encapsulate the calls
// in plain functions
//
void *Ws2812GetStrip(void) {
return strip;
}
void Ws2812Begin(void) {
if (strip) { strip->Begin(); }
}
void Ws2812Show(void) {
if (strip) { strip->Show(); }
}
uint32_t Ws2812PixelsSize(void) {
if (strip) { return strip->PixelCount(); }
return 0;
}
bool Ws2812CanShow(void) {
if (strip) { return strip->CanShow(); }
return false;
}
bool Ws2812IsDirty(void) {
if (strip) { return strip->IsDirty(); }
return false;
}
void Ws2812Dirty(void) {
if (strip) { strip->Dirty(); }
}
uint8_t * Ws2812Pixels(void) {
if (strip) { return strip->Pixels(); }
return nullptr;
}
size_t Ws2812PixelSize(void) {
if (strip) { return strip->PixelSize(); }
return 0;
}
size_t Ws2812PixelCount(void) {
if (strip) { return strip->PixelCount(); }
return 0;
}
void Ws2812ClearTo(uint8_t r, uint8_t g, uint8_t b, uint8_t w, int32_t from, int32_t to) {
LedColor lcolor;
lcolor.W = w;
lcolor.R = r;
lcolor.G = g;
lcolor.B = b;
if (strip) {
if (from < 0) {
strip->ClearTo(lcolor.C);
} else {
strip->ClearTo(lcolor.C, from, to);
}
}
}
void Ws2812SetPixelColor(uint32_t idx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
{
LedColor lcolor;
lcolor.W = w;
lcolor.R = r;
lcolor.G = g;
lcolor.B = b;
if (strip) {
strip->SetPixelColor(idx, lcolor.C);
}
}
uint32_t Ws2812GetPixelColor(uint32_t idx) {
LedColor lcolor;
if (strip) {
lcolor.C = strip->GetPixelColor(idx);
return lcolor.C; // already encoded as WWRRGGBB
// return (lcolor.W << 24) | (lcolor.R << 16) | (lcolor.G << 8) | lcolor.B;
}
return 0;
}
#endif // ESP32
#endif // USE_BERRY
/********************************************************************************************/
void CmndLed(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Settings->light_pixels)) {
if (XdrvMailbox.data_len > 0) {
char *p;
uint16_t idx = XdrvMailbox.index;
Ws2812ForceSuspend();
for (char *color = strtok_r(XdrvMailbox.data, " ", &p); color; color = strtok_r(nullptr, " ", &p)) {
if (LightColorEntry(color, strlen(color))) {
Ws2812SetColor(idx, Light.entry_color[0], Light.entry_color[1], Light.entry_color[2], Light.entry_color[3]);
idx++;
if (idx > Settings->light_pixels) { break; }
} else {
break;
}
}
Ws2812ForceUpdate();
}
char scolor[LIGHT_COLOR_SIZE];
ResponseCmndIdxChar(Ws2812GetColor(XdrvMailbox.index, scolor));
}
}
void CmndPixels(void)
{
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= WS2812_MAX_LEDS)) {
/*
Settings->light_pixels = XdrvMailbox.payload;
Settings->light_rotation = 0;
Ws2812ReinitStrip(); -- does not work with latest NeoPixelBus driver
Light.update = true;
*/
Ws2812Clear(); // Clear all known pixels
Settings->light_pixels = XdrvMailbox.payload;
Settings->light_rotation = 0;
TasmotaGlobal.restart_flag = 2; // reboot instead
}
ResponseCmndNumber(Settings->light_pixels);
}
void CmndStepPixels(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) {
Settings->light_step_pixels = (XdrvMailbox.payload > WS2812_MAX_LEDS) ? WS2812_MAX_LEDS : XdrvMailbox.payload;
// Ws2812ReinitStrip(); -- not sure it's actually needed
Light.update = true;
}
ResponseCmndNumber(Settings->light_step_pixels);
}
void CmndRotation(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings->light_pixels)) {
Settings->light_rotation = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings->light_rotation);
}
void CmndWidth(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
if (1 == XdrvMailbox.index) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) {
Settings->light_width = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings->light_width);
} else {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32)) {
Settings->ws_width[XdrvMailbox.index -2] = XdrvMailbox.payload;
}
ResponseCmndIdxNumber(Settings->ws_width[XdrvMailbox.index -2]);
}
}
}
/*********************************************************************************************\
* Internal calls for ArtNet
\*********************************************************************************************/
// check is the Neopixel strip is configured
bool Ws2812StripConfigured(void) {
return strip != nullptr;
}
size_t Ws2812StripGetPixelSize(void) {
return strip->PixelSize();
}
// return true if strip was dirty and an actual refresh was triggered
bool Ws2812StripRefresh(void) {
if (strip->IsDirty()) {
Ws2812LibStripShow();
return true;
} else {
return false;
}
}
void Ws2812CopyPixels(const uint8_t *buf, size_t len, size_t offset_in_matrix) {
uint8_t *pixels = strip->Pixels();
memmove(&pixels[offset_in_matrix], buf, len);
strip->Dirty();
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xlgt01(uint32_t function)
{
bool result = false;
switch (function) {
case FUNC_SET_CHANNELS:
result = Ws2812SetChannels();
break;
case FUNC_SET_SCHEME:
Ws2812ShowScheme();
break;
case FUNC_COMMAND:
result = DecodeCommand(kWs2812Commands, Ws2812Command);
break;
case FUNC_MODULE_INIT:
Ws2812ModuleSelected();
break;
}
return result;
}
#endif // USE_WS2812
#endif // USE_LIGHT
#endif // ESP32