mirror of https://github.com/arendst/Tasmota.git
Added WS2812 and Light ArtNet DMX control over UDP port 6454
This commit is contained in:
parent
6846bee84d
commit
2549203c13
|
@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- Support for Plantower PMSx003T AQI models with temperature and humidity (#16971)
|
- Support for Plantower PMSx003T AQI models with temperature and humidity (#16971)
|
||||||
- Support for Dingtian x595/x165 shift register based relay boards by Barbudor (#17032)
|
- Support for Dingtian x595/x165 shift register based relay boards by Barbudor (#17032)
|
||||||
- Added ``FUNC_NETWORK_UP`` and ``FUNC_NETWORK_DOWN`` events
|
- Added ``FUNC_NETWORK_UP`` and ``FUNC_NETWORK_DOWN`` events
|
||||||
|
- Added WS2812 and Light ArtNet DMX control over UDP port 6454
|
||||||
|
|
||||||
### Breaking Changed
|
### Breaking Changed
|
||||||
|
|
||||||
|
|
|
@ -370,6 +370,9 @@ uint64_t JsonParserToken::getULong(void) const { return getULong(0); }
|
||||||
float JsonParserToken::getFloat(void) const { return getFloat(0); }
|
float JsonParserToken::getFloat(void) const { return getFloat(0); }
|
||||||
const char * JsonParserToken::getStr(void) const { return getStr(""); }
|
const char * JsonParserToken::getStr(void) const { return getStr(""); }
|
||||||
|
|
||||||
|
bool JsonParserObject::getBool(const char * needle, bool val) const {
|
||||||
|
return (*this)[needle].getBool(val);
|
||||||
|
}
|
||||||
int32_t JsonParserObject::getInt(const char * needle, int32_t val) const {
|
int32_t JsonParserObject::getInt(const char * needle, int32_t val) const {
|
||||||
return (*this)[needle].getInt(val);
|
return (*this)[needle].getInt(val);
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,6 +162,7 @@ public:
|
||||||
const char * findConstCharNull(const char * needle) const;
|
const char * findConstCharNull(const char * needle) const;
|
||||||
|
|
||||||
// all-in-one methods: search for key (case insensitive), convert value and set default
|
// all-in-one methods: search for key (case insensitive), convert value and set default
|
||||||
|
bool getBool(const char *, bool val) const;
|
||||||
int32_t getInt(const char *, int32_t) const;
|
int32_t getInt(const char *, int32_t) const;
|
||||||
uint32_t getUInt(const char *, uint32_t) const;
|
uint32_t getUInt(const char *, uint32_t) const;
|
||||||
uint64_t getULong(const char *, uint64_t) const;
|
uint64_t getULong(const char *, uint64_t) const;
|
||||||
|
|
|
@ -502,6 +502,10 @@
|
||||||
#define D_CMND_PALETTE "Palette"
|
#define D_CMND_PALETTE "Palette"
|
||||||
#define D_CMND_PIXELS "Pixels"
|
#define D_CMND_PIXELS "Pixels"
|
||||||
#define D_CMND_STEPPIXELS "StepPixels"
|
#define D_CMND_STEPPIXELS "StepPixels"
|
||||||
|
#define D_CMND_ARTNET_START "ArtNetStart"
|
||||||
|
#define D_CMND_ARTNET_STOP "ArtNetStop"
|
||||||
|
#define D_CMND_ARTNET_CONFIG "ArtNetConfig"
|
||||||
|
#define D_SO_ARTNET_AUTORUN "ArtNetAutorun"
|
||||||
#define D_CMND_RGBWWTABLE "RGBWWTable"
|
#define D_CMND_RGBWWTABLE "RGBWWTable"
|
||||||
#define D_CMND_ROTATION "Rotation"
|
#define D_CMND_ROTATION "Rotation"
|
||||||
#define D_CMND_SCHEME "Scheme"
|
#define D_CMND_SCHEME "Scheme"
|
||||||
|
|
|
@ -181,7 +181,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
|
||||||
struct { // SetOption146 .. SetOption177
|
struct { // SetOption146 .. SetOption177
|
||||||
uint32_t use_esp32_temperature : 1; // bit 0 (v12.1.1.1) - SetOption146 - (ESP32) Show ESP32 internal temperature sensor
|
uint32_t use_esp32_temperature : 1; // bit 0 (v12.1.1.1) - SetOption146 - (ESP32) Show ESP32 internal temperature sensor
|
||||||
uint32_t mqtt_disable_sserialrec : 1; // bit 1 (v12.1.1.2) - SetOption147 - (MQTT) Disable publish SSerialReceived MQTT messages, you must use event trigger rules instead.
|
uint32_t mqtt_disable_sserialrec : 1; // bit 1 (v12.1.1.2) - SetOption147 - (MQTT) Disable publish SSerialReceived MQTT messages, you must use event trigger rules instead.
|
||||||
uint32_t spare02 : 1; // bit 2
|
uint32_t artnet_autorun : 1; // bit 2 (v12.2.0.4) - SetOption148 - (Light) start DMX ArtNet at boot, listen to UDP port as soon as network is up
|
||||||
uint32_t spare03 : 1; // bit 3
|
uint32_t spare03 : 1; // bit 3
|
||||||
uint32_t spare04 : 1; // bit 4
|
uint32_t spare04 : 1; // bit 4
|
||||||
uint32_t spare05 : 1; // bit 5
|
uint32_t spare05 : 1; // bit 5
|
||||||
|
@ -722,14 +722,14 @@ typedef struct {
|
||||||
char user_template_name[15]; // 720 15 bytes - Backward compatibility since v8.2.0.3
|
char user_template_name[15]; // 720 15 bytes - Backward compatibility since v8.2.0.3
|
||||||
|
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
mytmplt8285 ex_user_template8; // 72F 14 bytes (ESP8266) - Free since 9.0.0.1
|
uint8_t ex_user_template8[5]; // 72F 14 bytes (ESP8266) - Free since 9.0.0.1 - only 5 bytes referenced now
|
||||||
#endif // ESP8266
|
#endif // ESP8266
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
uint8_t webcam_clk; // 72F
|
uint8_t webcam_clk; // 72F
|
||||||
WebCamCfg2 webcam_config2; // 730
|
WebCamCfg2 webcam_config2; // 730
|
||||||
|
|
||||||
uint8_t free_esp32_734[9]; // 734
|
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
|
uint16_t artnet_universe; // 734
|
||||||
|
uint8_t free_esp32_734[7]; // 736
|
||||||
|
|
||||||
uint8_t novasds_startingoffset; // 73D
|
uint8_t novasds_startingoffset; // 73D
|
||||||
uint8_t web_color[18][3]; // 73E
|
uint8_t web_color[18][3]; // 73E
|
||||||
|
|
|
@ -571,6 +571,9 @@
|
||||||
#define USE_DGR_LIGHT_SEQUENCE // Add support for device group light sequencing (requires USE_DEVICE_GROUPS) (+0k2 code)
|
#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)
|
//#define USE_LSC_MCSL // Add support for GPE Multi color smart light as sold by Action in the Netherlands (+1k1 code)
|
||||||
|
|
||||||
|
// #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.24
|
||||||
|
|
||||||
// -- Counter input -------------------------------
|
// -- Counter input -------------------------------
|
||||||
#define USE_COUNTER // Enable inputs as counter (+0k8 code)
|
#define USE_COUNTER // Enable inputs as counter (+0k8 code)
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ const uint8_t LIGHT_COLOR_SIZE = 25; // Char array scolor size
|
||||||
const char kLightCommands[] PROGMEM = "|" // No prefix
|
const char kLightCommands[] PROGMEM = "|" // No prefix
|
||||||
// SetOptions synonyms
|
// SetOptions synonyms
|
||||||
D_SO_CHANNELREMAP "|" D_SO_MULTIPWM "|" D_SO_ALEXACTRANGE "|" D_SO_POWERONFADE "|" D_SO_PWMCT "|"
|
D_SO_CHANNELREMAP "|" D_SO_MULTIPWM "|" D_SO_ALEXACTRANGE "|" D_SO_POWERONFADE "|" D_SO_PWMCT "|"
|
||||||
D_SO_WHITEBLEND "|"
|
D_SO_WHITEBLEND "|" D_SO_ARTNET_AUTORUN "|"
|
||||||
// Other commands
|
// Other commands
|
||||||
D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_DIMMER_STEP "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|"
|
D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_DIMMER_STEP "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|"
|
||||||
D_CMND_RGBWWTABLE "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|"
|
D_CMND_RGBWWTABLE "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|"
|
||||||
|
@ -150,11 +150,14 @@ const char kLightCommands[] PROGMEM = "|" // No prefix
|
||||||
#ifdef USE_DGR_LIGHT_SEQUENCE
|
#ifdef USE_DGR_LIGHT_SEQUENCE
|
||||||
"|" D_CMND_SEQUENCE_OFFSET
|
"|" D_CMND_SEQUENCE_OFFSET
|
||||||
#endif // USE_DGR_LIGHT_SEQUENCE
|
#endif // USE_DGR_LIGHT_SEQUENCE
|
||||||
|
#ifdef USE_LIGHT_ARTNET
|
||||||
|
"|" D_CMND_ARTNET_START "|" D_CMND_ARTNET_STOP "|" D_CMND_ARTNET_CONFIG
|
||||||
|
#endif
|
||||||
"|UNDOCA" ;
|
"|UNDOCA" ;
|
||||||
|
|
||||||
SO_SYNONYMS(kLightSynonyms,
|
SO_SYNONYMS(kLightSynonyms,
|
||||||
37, 68, 82, 91, 92,
|
37, 68, 82, 91, 92,
|
||||||
105,
|
105, 148,
|
||||||
);
|
);
|
||||||
|
|
||||||
void (* const LightCommand[])(void) PROGMEM = {
|
void (* const LightCommand[])(void) PROGMEM = {
|
||||||
|
@ -171,6 +174,9 @@ void (* const LightCommand[])(void) PROGMEM = {
|
||||||
#ifdef USE_DGR_LIGHT_SEQUENCE
|
#ifdef USE_DGR_LIGHT_SEQUENCE
|
||||||
&CmndSequenceOffset,
|
&CmndSequenceOffset,
|
||||||
#endif // USE_DGR_LIGHT_SEQUENCE
|
#endif // USE_DGR_LIGHT_SEQUENCE
|
||||||
|
#ifdef USE_LIGHT_ARTNET
|
||||||
|
&CmndArtNetStart, &CmndArtNetStop, &CmndArtNetConfig,
|
||||||
|
#endif
|
||||||
&CmndUndocA };
|
&CmndUndocA };
|
||||||
|
|
||||||
// Light color mode, either RGB alone, or white-CT alone, or both only available if ct_rgb_linked is false
|
// Light color mode, either RGB alone, or white-CT alone, or both only available if ct_rgb_linked is false
|
||||||
|
@ -2229,6 +2235,10 @@ void LightSetOutputs(const uint16_t *cur_col_10) {
|
||||||
XdrvMailbox.data = (char*)cur_col;
|
XdrvMailbox.data = (char*)cur_col;
|
||||||
XdrvMailbox.topic = (char*)scale_col;
|
XdrvMailbox.topic = (char*)scale_col;
|
||||||
XdrvMailbox.command = (char*)cur_col_10;
|
XdrvMailbox.command = (char*)cur_col_10;
|
||||||
|
#ifdef USE_LIGHT_ARTNET
|
||||||
|
if (ArtNetSetChannels()) { /* Serviced */}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
if (XlgtCall(FUNC_SET_CHANNELS)) { /* Serviced */ }
|
if (XlgtCall(FUNC_SET_CHANNELS)) { /* Serviced */ }
|
||||||
else if (XdrvCall(FUNC_SET_CHANNELS)) { /* Serviced */ }
|
else if (XdrvCall(FUNC_SET_CHANNELS)) { /* Serviced */ }
|
||||||
XdrvMailbox.data = tmp_data;
|
XdrvMailbox.data = tmp_data;
|
||||||
|
@ -3413,6 +3423,9 @@ bool Xdrv04(uint32_t function)
|
||||||
LightSetOutputs(Light.fade_cur_10);
|
LightSetOutputs(Light.fade_cur_10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef USE_LIGHT_ARTNET
|
||||||
|
ArtNetLoop();
|
||||||
|
#endif // USE_LIGHT_ARTNET
|
||||||
break;
|
break;
|
||||||
case FUNC_EVERY_50_MSECOND:
|
case FUNC_EVERY_50_MSECOND:
|
||||||
LightAnimate();
|
LightAnimate();
|
||||||
|
@ -3428,12 +3441,6 @@ bool Xdrv04(uint32_t function)
|
||||||
case FUNC_BUTTON_MULTI_PRESSED:
|
case FUNC_BUTTON_MULTI_PRESSED:
|
||||||
result = XlgtCall(FUNC_BUTTON_MULTI_PRESSED);
|
result = XlgtCall(FUNC_BUTTON_MULTI_PRESSED);
|
||||||
break;
|
break;
|
||||||
case FUNC_NETWORK_UP:
|
|
||||||
XlgtCall(FUNC_NETWORK_UP);
|
|
||||||
break;
|
|
||||||
case FUNC_NETWORK_DOWN:
|
|
||||||
XlgtCall(FUNC_NETWORK_DOWN);
|
|
||||||
break;
|
|
||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
case FUNC_WEB_ADD_MAIN_BUTTON:
|
case FUNC_WEB_ADD_MAIN_BUTTON:
|
||||||
XlgtCall(FUNC_WEB_ADD_MAIN_BUTTON);
|
XlgtCall(FUNC_WEB_ADD_MAIN_BUTTON);
|
||||||
|
@ -3451,6 +3458,21 @@ bool Xdrv04(uint32_t function)
|
||||||
case FUNC_PRE_INIT:
|
case FUNC_PRE_INIT:
|
||||||
LightInit();
|
LightInit();
|
||||||
break;
|
break;
|
||||||
|
#ifdef USE_LIGHT_ARTNET
|
||||||
|
case FUNC_JSON_APPEND:
|
||||||
|
ArtNetJSONAppend();
|
||||||
|
break;
|
||||||
|
case FUNC_NETWORK_UP:
|
||||||
|
if (Settings->flag6.artnet_autorun) {
|
||||||
|
if (!ArtNetStart()) {
|
||||||
|
Settings->flag6.artnet_autorun = false; // disable autorun if it failed, avoid nasty loop errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FUNC_NETWORK_DOWN:
|
||||||
|
ArtNetStop();
|
||||||
|
break;
|
||||||
|
#endif // USE_LIGHT_ARTNET
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -0,0 +1,425 @@
|
||||||
|
/*
|
||||||
|
xdrv_04_light_artnet.ino - Converter functions for lights
|
||||||
|
|
||||||
|
Copyright (C) 2020 Stephan Hadinger & 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 USE_LIGHT
|
||||||
|
#ifdef USE_LIGHT_ARTNET
|
||||||
|
|
||||||
|
#ifndef WS2812_ARTNET_UDP_BUFFER_SIZE
|
||||||
|
#define WS2812_ARTNET_UDP_BUFFER_SIZE 140 // Max 30 columns with 4 bytes per pixel
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WS2812_ARTNET_UDP_MAX_PACKETS
|
||||||
|
#define WS2812_ARTNET_UDP_MAX_PACKETS 30 // Max 30 rows (packets consecutive)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WS2812_ARTNET_MAX_SLEEP
|
||||||
|
#define WS2812_ARTNET_MAX_SLEEP 5 // sleep at most 5ms
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t rows = 1; // number of rows (min:1)
|
||||||
|
uint8_t cols = 0; // number of columns (if cols == 0 then apply to the entire light)
|
||||||
|
uint8_t offs = 0; // offset in the led strip where the matrix starts (min: 0)
|
||||||
|
bool alt = false; // are the rows in alternate directions
|
||||||
|
uint16_t univ = 0; // start at universe number (+1)
|
||||||
|
uint16_t port = 6454; // UDP port number
|
||||||
|
uint8_t dimm = 100; // Dimmer 0..100
|
||||||
|
bool on = true;
|
||||||
|
bool matrix = true; // true if light is a WS2812 matrix, false if single light
|
||||||
|
// metrics
|
||||||
|
uint32_t packet_received = 0;
|
||||||
|
uint32_t packet_accepted = 0;
|
||||||
|
uint32_t strip_refresh = 0;
|
||||||
|
} ArtNetConfig;
|
||||||
|
|
||||||
|
uint32_t * packets_per_row = nullptr;
|
||||||
|
|
||||||
|
ArtNetConfig artnet_conf;
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include "UdpListener.h"
|
||||||
|
UdpListener<WS2812_ARTNET_UDP_BUFFER_SIZE> * ArtNetUdp = nullptr;
|
||||||
|
#else
|
||||||
|
WiFiUDP * ArtNetUdp;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool artnet_udp_connected = false;
|
||||||
|
// IPAddress artnet_udp_remote_ip; // remote IP address
|
||||||
|
// uint16_t artnet_udp_remote_port; // remote port
|
||||||
|
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* ArtNet support
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
void ArtNetLoadSettings(void) {
|
||||||
|
// read settings and copy locally
|
||||||
|
artnet_conf.dimm = Settings->light_dimmer;
|
||||||
|
artnet_conf.cols = Settings->light_step_pixels;
|
||||||
|
artnet_conf.rows = (artnet_conf.cols != 0) ? Settings->light_pixels / artnet_conf.cols : 0;
|
||||||
|
artnet_conf.offs = Settings->light_rotation;
|
||||||
|
artnet_conf.alt = Settings->flag.ws_clock_reverse; // SetOption16
|
||||||
|
artnet_conf.univ = Settings->artnet_universe;
|
||||||
|
artnet_conf.on = (Light.power & 1);
|
||||||
|
ArtNetValidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate that parameters in artnet_conf are in valid ranges
|
||||||
|
void ArtNetValidate(void) {
|
||||||
|
if (artnet_conf.dimm > 100) { artnet_conf.dimm = 100; }
|
||||||
|
if (artnet_conf.cols == 0 || artnet_conf.rows == 0) { artnet_conf.rows = 1; } // if single light, both are supposed to be 0
|
||||||
|
artnet_conf.matrix = (artnet_conf.cols > 0) && Ws2812StripConfigured();
|
||||||
|
if (artnet_conf.univ > 32767) { artnet_conf.univ = 0; }
|
||||||
|
if (artnet_conf.port == 0) { artnet_conf.port = 6454; }
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArtNetSaveSettings(void) {
|
||||||
|
ArtNetValidate();
|
||||||
|
// write to settings
|
||||||
|
Settings->light_dimmer = artnet_conf.dimm;
|
||||||
|
Settings->light_step_pixels = artnet_conf.cols;
|
||||||
|
if (artnet_conf.cols > 0) { Settings->light_pixels = artnet_conf.rows * artnet_conf.cols; }
|
||||||
|
Settings->light_rotation = artnet_conf.offs;
|
||||||
|
Settings->artnet_universe = artnet_conf.univ;
|
||||||
|
Settings->flag.ws_clock_reverse = artnet_conf.alt; // SetOption16
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ArtNetSetChannels(void)
|
||||||
|
{
|
||||||
|
if (artnet_udp_connected && ArtNetUdp != nullptr) {
|
||||||
|
// ArtNet is running
|
||||||
|
if (artnet_conf.matrix) {
|
||||||
|
if (Light.power & 1) { return true; } // serviced, don't cascade to WS2812
|
||||||
|
} else {
|
||||||
|
return false; // if regular bulb, cascade change
|
||||||
|
}
|
||||||
|
} else if (Settings->flag6.artnet_autorun) {
|
||||||
|
// ArtNet is not running but is planned to get running
|
||||||
|
return true; // if ArtNet autorun is own but has not started yet, block update to lights
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process ArtNet packet
|
||||||
|
// returns `true` if strip is dirty, i.e. we changed the value of some leds
|
||||||
|
void ArtNetProcessPacket(uint8_t * buf, size_t len) {
|
||||||
|
artnet_conf.packet_received++;
|
||||||
|
if (buf == nullptr || len <= 18) { return; }
|
||||||
|
// is the signature correct?
|
||||||
|
// 4172742D4E657400
|
||||||
|
static const char ARTNET_SIG[] = "Art-Net";
|
||||||
|
if (memcmp(buf, ARTNET_SIG, sizeof(ARTNET_SIG))) { return; }
|
||||||
|
|
||||||
|
uint16_t opcode = buf[8] | (buf[9] << 8);
|
||||||
|
uint16_t protocol = (buf[10] << 8) | buf[11]; // Big Endian
|
||||||
|
uint16_t universe = buf[14] | (buf[15] << 8);
|
||||||
|
uint16_t datalen = (buf[16] << 8) | buf[17];
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DMX: opcode=0x%04X procotol=%i universe=%i datalen=%i univ_start=%i univ_end=%i"), opcode, protocol, universe, datalen, artnet_conf.univ, artnet_conf.univ + artnet_conf.rows);
|
||||||
|
if (opcode != 0x5000 || protocol != 14) { return; }
|
||||||
|
|
||||||
|
if (len + 18 < datalen) {
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR("DMX: packet is truncated, ignoring packet"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (universe < artnet_conf.univ || universe >= artnet_conf.univ + artnet_conf.rows) { return; } // universe is not ours, ignore
|
||||||
|
size_t idx = 18; // start of payload data in the UDP frame
|
||||||
|
uint16_t row = universe - artnet_conf.univ;
|
||||||
|
|
||||||
|
if (artnet_conf.matrix) {
|
||||||
|
// Ws2812 led strip
|
||||||
|
size_t pix_size = Ws2812StripGetPixelSize();
|
||||||
|
datalen = datalen - (datalen % pix_size);
|
||||||
|
|
||||||
|
if (artnet_conf.alt && (row % 2)) {
|
||||||
|
for (int32_t i = idx, j = idx + datalen - pix_size; i < j; i += pix_size, j -= pix_size) {
|
||||||
|
for (int32_t k = 0; k < pix_size; k++) {
|
||||||
|
uint8_t temp = buf[i+k];
|
||||||
|
buf[i+k] = buf[j+k];
|
||||||
|
buf[j+k] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process dimmer
|
||||||
|
if (artnet_conf.dimm != 100) {
|
||||||
|
// No Gamma for now
|
||||||
|
if (pix_size == 3) {
|
||||||
|
for (int32_t i = idx; i < idx+datalen; i += pix_size) {
|
||||||
|
buf[i] = changeUIntScale(buf[i], 0, 100, 0, artnet_conf.dimm);
|
||||||
|
buf[i+1] = changeUIntScale(buf[i+1], 0, 100, 0, artnet_conf.dimm);
|
||||||
|
buf[i+2] = changeUIntScale(buf[i+2], 0, 100, 0, artnet_conf.dimm);
|
||||||
|
}
|
||||||
|
} else if (pix_size == 4) {
|
||||||
|
for (int32_t i = idx; i < idx+datalen; i += pix_size) {
|
||||||
|
buf[i] = changeUIntScale(buf[i], 0, 100, 0, artnet_conf.dimm);
|
||||||
|
buf[i+1] = changeUIntScale(buf[i+1], 0, 100, 0, artnet_conf.dimm);
|
||||||
|
buf[i+2] = changeUIntScale(buf[i+2], 0, 100, 0, artnet_conf.dimm);
|
||||||
|
buf[i+3] = changeUIntScale(buf[i+2], 0, 100, 0, artnet_conf.dimm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process pixels
|
||||||
|
size_t h_bytes = artnet_conf.cols * pix_size; // size in bytes of a single row
|
||||||
|
size_t offset_in_matrix = artnet_conf.offs * pix_size + row * h_bytes;
|
||||||
|
if (datalen > h_bytes) { datalen = h_bytes; } // copy at most one line
|
||||||
|
|
||||||
|
Ws2812CopyPixels(&buf[idx], datalen, offset_in_matrix);
|
||||||
|
} else {
|
||||||
|
// single light
|
||||||
|
uint8_t r8 = buf[idx+1];
|
||||||
|
uint8_t g8 = buf[idx];
|
||||||
|
uint8_t b8 = buf[idx+2];
|
||||||
|
uint16_t dimmer10 = changeUIntScale(artnet_conf.dimm, 0, 100, 0, 1023);
|
||||||
|
uint16_t color[LST_MAX] = {0};
|
||||||
|
color[0] = changeUIntScale(r8, 0, 255, 0, dimmer10);
|
||||||
|
color[1] = changeUIntScale(g8, 0, 255, 0, dimmer10);
|
||||||
|
color[2] = changeUIntScale(b8, 0, 255, 0, dimmer10);
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DMX: %02X-%02X-%02X univ=%i rows=%i max_univ=%i"), buf[idx+1], buf[idx], buf[idx+2], universe, row, artnet_conf.univ + artnet_conf.rows);
|
||||||
|
LightSetOutputs(color);
|
||||||
|
}
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("DMX: ok universe=%i datalen=%i"), universe, datalen);
|
||||||
|
artnet_conf.packet_accepted++;
|
||||||
|
if (packets_per_row) {
|
||||||
|
packets_per_row[row]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Called at event loop, checks for incoming data from the CC2530
|
||||||
|
//
|
||||||
|
void ArtNetLoop(void)
|
||||||
|
{
|
||||||
|
if (artnet_udp_connected && ArtNetUdp != nullptr) {
|
||||||
|
ArtNetLoadSettings();
|
||||||
|
bool packet_ready = false;
|
||||||
|
int32_t packet_len = 0;
|
||||||
|
#ifdef ESP8266
|
||||||
|
packet_ready = ArtNetUdp->next();
|
||||||
|
while (packet_ready) {
|
||||||
|
UdpPacket<WS2812_ARTNET_UDP_BUFFER_SIZE> *packet;
|
||||||
|
packet = ArtNetUdp->read();
|
||||||
|
uint8_t * packet_buffer = (uint8_t*) &packet->buf;
|
||||||
|
packet_len = packet->len;
|
||||||
|
#else
|
||||||
|
packet_len = ArtNetUdp->parsePacket();
|
||||||
|
packet_ready = (packet_len > 0);
|
||||||
|
while (packet_ready) {
|
||||||
|
uint8_t packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet
|
||||||
|
|
||||||
|
packet_len = ArtNetUdp->read(packet_buffer, UDP_BUFFER_SIZE);
|
||||||
|
ArtNetUdp->flush(); // Finish reading the current packet
|
||||||
|
#endif
|
||||||
|
// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet %*_H (%d)"), 32, packet_buffer, packet_len);
|
||||||
|
if (artnet_conf.on) {
|
||||||
|
ArtNetProcessPacket(packet_buffer, packet_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
packet_ready = ArtNetUdp->next();
|
||||||
|
if (!packet_ready) {
|
||||||
|
// if no more incoming packet, still wait for 20 microseconds
|
||||||
|
delay(1); // delayMicroseconds seems broken, need to
|
||||||
|
packet_ready = ArtNetUdp->next();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
packet_len = ArtNetUdp->parsePacket();
|
||||||
|
packet_ready = (packet_len > 0);
|
||||||
|
if (!packet_ready) {
|
||||||
|
// if no more incoming packet, still wait for 20 microseconds
|
||||||
|
delayMicroseconds(20);
|
||||||
|
packet_len = ArtNetUdp->parsePacket();
|
||||||
|
packet_ready = (packet_len > 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (artnet_conf.on) { // ignore action if not on
|
||||||
|
if (artnet_conf.matrix) {
|
||||||
|
if (Ws2812StripRefresh()) {
|
||||||
|
artnet_conf.strip_refresh++; // record metric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Published state
|
||||||
|
//
|
||||||
|
void ArtNetJSONAppend(void) {
|
||||||
|
if (artnet_udp_connected) {
|
||||||
|
ResponseAppend_P(PSTR(",\"ArtNet\":{\"PacketsReceived\":%u,\"PacketsAccepted\":%u,\"Frames\":%u"),
|
||||||
|
artnet_conf.packet_received, artnet_conf.packet_accepted, artnet_conf.strip_refresh);
|
||||||
|
if (packets_per_row) {
|
||||||
|
ResponseAppend_P(PSTR(",\"PacketsPerRow\":["));
|
||||||
|
for (int32_t i = 0; i < artnet_conf.rows; i++) {
|
||||||
|
ResponseAppend_P(PSTR("%s%i"), i ? "," : "", packets_per_row[i]);
|
||||||
|
}
|
||||||
|
ResponseAppend_P(PSTR("]"));
|
||||||
|
}
|
||||||
|
ResponseAppend_P(PSTR("}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Command `ArtNetConfig`
|
||||||
|
// Params: JSON
|
||||||
|
// {"Rows":5, "Cols":5, "Offset":0, "Alternate":false, "Universe":0, "Port":6454}
|
||||||
|
//
|
||||||
|
void CmndArtNetConfig() {
|
||||||
|
bool was_running = artnet_udp_connected;
|
||||||
|
if (was_running) {
|
||||||
|
ArtNetStop();
|
||||||
|
}
|
||||||
|
ArtNetLoadSettings();
|
||||||
|
|
||||||
|
TrimSpace(XdrvMailbox.data);
|
||||||
|
if (strlen(XdrvMailbox.data) > 0) {
|
||||||
|
JsonParser parser(XdrvMailbox.data);
|
||||||
|
JsonParserObject root = parser.getRootObject();
|
||||||
|
if (!root) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; }
|
||||||
|
|
||||||
|
artnet_conf.rows = root.getUInt(PSTR("Rows"), artnet_conf.rows);
|
||||||
|
artnet_conf.cols = root.getUInt(PSTR("Cols"), artnet_conf.cols);
|
||||||
|
artnet_conf.offs = root.getUInt(PSTR("Offset"), artnet_conf.offs);
|
||||||
|
artnet_conf.alt = root.getBool(PSTR("Alternate"), artnet_conf.alt);
|
||||||
|
artnet_conf.univ = root.getUInt(PSTR("Universe"), artnet_conf.univ);
|
||||||
|
artnet_conf.port = root.getUInt(PSTR("Port"), artnet_conf.port);
|
||||||
|
artnet_conf.dimm = root.getUInt(PSTR("Dimmer"), artnet_conf.dimm);
|
||||||
|
|
||||||
|
ArtNetSaveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (was_running) {
|
||||||
|
ArtNetStart();
|
||||||
|
}
|
||||||
|
// display the current or new configuration
|
||||||
|
// {"Rows":5, "Cols":5, "Offset":0, "Alternate":false, "Universe":0, "Port":6454}
|
||||||
|
Response_P(PSTR("{\"Rows\":%u,\"Cols\":%u,\"Dimmer\":%u,\"Offset\":%u"
|
||||||
|
",\"Alternate\":%s,\"Universe\":%u,\"Port\":%u}"),
|
||||||
|
artnet_conf.rows, artnet_conf.cols, artnet_conf.dimm, artnet_conf.offs,
|
||||||
|
artnet_conf.alt ? "true":"false", artnet_conf.univ, artnet_conf.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArtNetStart
|
||||||
|
// Returns true if ok
|
||||||
|
bool ArtNetStart(void) {
|
||||||
|
ArtNetLoadSettings();
|
||||||
|
if (!artnet_udp_connected && !TasmotaGlobal.restart_flag) {
|
||||||
|
if (ArtNetUdp == nullptr) {
|
||||||
|
#ifdef ESP8266
|
||||||
|
ArtNetUdp = new UdpListener<WS2812_ARTNET_UDP_BUFFER_SIZE>(WS2812_ARTNET_UDP_MAX_PACKETS);
|
||||||
|
#else
|
||||||
|
ArtNetUdp = new WiFiUDP();
|
||||||
|
#endif
|
||||||
|
if (ArtNetUdp == nullptr) {
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("DMX: cannot allocate memory"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ESP8266
|
||||||
|
ArtNetUdp->reset();
|
||||||
|
ip_addr_t addr = IPADDR4_INIT(INADDR_ANY);
|
||||||
|
if ((igmp_joingroup(WiFi.localIP(), IPAddress(USE_LIGHT_ARTNET_MCAST)) == ERR_OK) &&
|
||||||
|
(ArtNetUdp->listen(&addr, artnet_conf.port))) {
|
||||||
|
// if (ArtNetUdp->listen(&addr, artnet_conf.port)) {
|
||||||
|
#else
|
||||||
|
if (ArtNetUdp->beginMulticast(IPAddress(USE_LIGHT_ARTNET_MCAST), artnet_conf.port)) {
|
||||||
|
#endif
|
||||||
|
// OK
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("DMX: listening to port %i"), artnet_conf.port);
|
||||||
|
artnet_udp_connected = true;
|
||||||
|
|
||||||
|
packets_per_row = (uint32_t*) malloc(artnet_conf.rows * sizeof(uint32_t*));
|
||||||
|
if (packets_per_row) { memset((void*)packets_per_row, 0, artnet_conf.rows * sizeof(uint32_t*)); }
|
||||||
|
// set sleep to at most 5
|
||||||
|
if (TasmotaGlobal.sleep > WS2812_ARTNET_MAX_SLEEP) {
|
||||||
|
TasmotaGlobal.sleep = WS2812_ARTNET_MAX_SLEEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// change settings to ArtNet specific scheme
|
||||||
|
Settings->flag6.artnet_autorun = true;
|
||||||
|
|
||||||
|
// change strip configuration
|
||||||
|
if (artnet_conf.matrix) {
|
||||||
|
if ((Settings->light_pixels != artnet_conf.rows * artnet_conf.cols + artnet_conf.offs) || (Settings->light_rotation != 0)) {
|
||||||
|
Settings->light_pixels = artnet_conf.rows * artnet_conf.cols + artnet_conf.offs;
|
||||||
|
Settings->light_rotation = 0;
|
||||||
|
Ws2812ReinitStrip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn power on if it's not
|
||||||
|
if (!(Light.power & 1)) {
|
||||||
|
LightPowerOn();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AddLog(LOG_LEVEL_INFO, PSTR("DMX: error opening port %i"), artnet_conf.port);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Command `ArtNetStart`
|
||||||
|
// Params: XXX
|
||||||
|
//
|
||||||
|
void CmndArtNetStart(void) {
|
||||||
|
if (ArtNetStart()) {
|
||||||
|
ResponseCmndDone();
|
||||||
|
} else {
|
||||||
|
ResponseCmndError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the ArtNet UDP flow and disconnect server
|
||||||
|
void ArtNetStop(void) {
|
||||||
|
artnet_udp_connected = false;
|
||||||
|
if (ArtNetUdp != nullptr) {
|
||||||
|
#ifdef ESP8266
|
||||||
|
ArtNetUdp->disconnect();
|
||||||
|
#else
|
||||||
|
ArtNetUdp->stop();
|
||||||
|
#endif
|
||||||
|
delete ArtNetUdp;
|
||||||
|
ArtNetUdp = nullptr;
|
||||||
|
}
|
||||||
|
if (packets_per_row) {
|
||||||
|
free((void*)packets_per_row);
|
||||||
|
packets_per_row = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CmndArtNetStop(void) {
|
||||||
|
ArtNetStop();
|
||||||
|
// restore default scheme
|
||||||
|
Settings->light_scheme = LS_POWER;
|
||||||
|
// Restore sleep value
|
||||||
|
TasmotaGlobal.sleep = Settings->sleep;
|
||||||
|
// OK
|
||||||
|
ResponseCmndDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_LIGHT_ARTNET
|
||||||
|
#endif // USE_LIGHT
|
Loading…
Reference in New Issue