Skip to content

Instantly share code, notes, and snippets.

@perigalacticon
Last active August 11, 2022 22:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save perigalacticon/54f39706190525388261e0076768920a to your computer and use it in GitHub Desktop.
Save perigalacticon/54f39706190525388261e0076768920a to your computer and use it in GitHub Desktop.
ESPixelStick_mod_ssd1306.ino
/*
ESPixelStick_mod_ssd1306.ino
Original Project: ESPixelStick.ino
Project: ESPixelStick - An ESP8266 and E1.31 based pixel driver
Copyright (c) 2016 Shelby Merrick
http://www.forkineye.com
This program is provided free for you to use in any way that you wish,
subject to the laws and regulations where you are using it. Due diligence
is strongly suggested before using this code. Please give credit where due.
The Author makes no warranty of any kind, express or implied, with regard
to this program or the documentation contained in this document. The
Author shall not be liable in any event for incidental or consequential
damages in connection with, or arising out of, the furnishing, performance
or use of these programs.
Modified to use OLED display SSD1306. changed sections are seperated by lines of "=",
lines 45-51, 117-141, 341-355.
*/
/*****************************************/
/* BEGIN - Configuration */
/*****************************************/
/* Fallback configuration if config.json is empty or fails */
const char ssid[] = "";
const char passphrase[] = "";
/*****************************************/
/* END - Configuration */
/*****************************************/
#include <ESPAsyncE131.h>
#include "ESPAsyncZCPP.h"
#include "ESPAsyncDDP.h"
#include <Hash.h>
#include <SPI.h>
#include "ESPixelStick.h"
#include "FPPDiscovery.h"
#include "EFUpdate.h"
#include "wshandler.h"
#include "gamma.h"
=======================================================
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
=======================================================
extern "C" {
#include <user_interface.h>
}
// Debugging support
#if defined(DEBUG)
extern "C" void system_set_os_print(uint8 onoff);
extern "C" void ets_install_putc1(void* routine);
static void _u0_putc(char c) {
while (((U0S >> USTXC) & 0x7F) == 0x7F);
U0F = c;
}
#endif
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH 16
/////////////////////////////////////////////////////////
//
// Globals
//
/////////////////////////////////////////////////////////
// MQTT State
const char MQTT_SET_COMMAND_TOPIC[] = "/set";
// MQTT Payloads by default (on/off)
const char LIGHT_ON[] = "ON";
const char LIGHT_OFF[] = "OFF";
// Configuration file
const char CONFIG_FILE[] = "/config.json";
ESPAsyncE131 e131(10); // ESPAsyncE131 with X buffers
ESPAsyncZCPP zcpp(5); // ESPAsyncZCPP with X buffers
ESPAsyncDDP ddp(5); // ESPAsyncDDP with X buffers
FPPDiscovery fppDiscovery(VERSION); // FPP Discovery Listener
config_t config; // Current configuration
uint32_t *seqError; // Sequence error tracking for each universe
uint32_t *seqZCPPError; // Sequence error tracking for each universe
uint16_t lastZCPPConfig; // last config we saw
uint8_t seqZCPPTracker; // sequence number of zcpp frames
uint16_t uniLast = 1; // Last Universe to listen for
bool reboot = false; // Reboot flag
AsyncWebServer web(HTTP_PORT); // Web Server
AsyncWebSocket ws("/ws"); // Web Socket Plugin
uint8_t *seqTracker; // Current sequence numbers for each Universe */
uint32_t lastUpdate; // Update timeout tracker
WiFiEventHandler wifiConnectHandler; // WiFi connect handler
WiFiEventHandler wifiDisconnectHandler; // WiFi disconnect handler
Ticker wifiTicker; // Ticker to handle WiFi
Ticker idleTicker; // Ticker for effect on idle
AsyncMqttClient mqtt; // MQTT object
Ticker mqttTicker; // Ticker to handle MQTT
EffectEngine effects; // Effects Engine
IPAddress ourLocalIP;
IPAddress ourSubnetMask;
=======================================================
//this section for ssd1306:
// SCL GPIO5
// SDA GPIO4
#define OLED_RESET 0 // GPIO0
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
B00000001, B11000000,
B00000001, B11000000,
B00000011, B11100000,
B11110011, B11100000,
B11111110, B11111000,
B01111110, B11111111,
B00110011, B10011111,
B00011111, B11111100,
B00001101, B01110000,
B00011011, B10100000,
B00111111, B11100000,
B00111111, B11110000,
B01111100, B11110000,
B01110000, B01110000,
B00000000, B00110000 };
=======================================================
// Output Drivers
#if defined(ESPS_MODE_PIXEL)
PixelDriver pixels; // Pixel object
#elif defined(ESPS_MODE_SERIAL)
SerialDriver serial; // Serial object
#else
#error "No valid output mode defined."
#endif
/////////////////////////////////////////////////////////
//
// Forward Declarations
//
/////////////////////////////////////////////////////////
void loadConfig();
void initWifi();
void initWeb();
void updateConfig();
// Radio config
RF_PRE_INIT() {
//wifi_set_phy_mode(PHY_MODE_11G); // Force 802.11g mode
system_phy_set_powerup_option(31); // Do full RF calibration on power-up
system_phy_set_max_tpw(82); // Set max TX power
}
void setup() {
// Configure SDK params
wifi_set_sleep_type(NONE_SLEEP_T);
// Initial pin states
pinMode(DATA_PIN, OUTPUT);
digitalWrite(DATA_PIN, LOW);
// Setup serial log port
LOG_PORT.begin(115200);
delay(10);
#if defined(DEBUG)
ets_install_putc1((void *) &_u0_putc);
system_set_os_print(1);
#endif
// Set default data source to E131
config.ds = DataSource::E131;
LOG_PORT.println("");
LOG_PORT.print(F("ESPixelStick v"));
for (uint8_t i = 0; i < strlen_P(VERSION); i++)
LOG_PORT.print((char)(pgm_read_byte(VERSION + i)));
LOG_PORT.print(F(" ("));
for (uint8_t i = 0; i < strlen_P(BUILD_DATE); i++)
LOG_PORT.print((char)(pgm_read_byte(BUILD_DATE + i)));
LOG_PORT.println(")");
LOG_PORT.println(ESP.getFullVersion());
// Enable SPIFFS
if (!SPIFFS.begin())
{
LOG_PORT.println("File system did not initialise correctly");
}
else
{
LOG_PORT.println("File system initialised");
}
FSInfo fs_info;
if (SPIFFS.info(fs_info))
{
LOG_PORT.print("Total bytes in file system: ");
LOG_PORT.println(fs_info.usedBytes);
Dir dir = SPIFFS.openDir("/");
while (dir.next()) {
LOG_PORT.print(dir.fileName());
File f = dir.openFile("r");
LOG_PORT.println(f.size());
}
}
else
{
LOG_PORT.println("Failed to read file system details");
}
// Load configuration from SPIFFS and set Hostname
loadConfig();
if (config.hostname)
WiFi.hostname(config.hostname);
#if defined (ESPS_MODE_PIXEL)
pixels.setPin(DATA_PIN);
updateConfig();
// Do one effects cycle as early as possible
if (config.ds == DataSource::WEB) {
effects.run();
}
// set the effect idle timer
idleTicker.attach(config.effect_idletimeout, idleTimeout);
pixels.show();
#else
updateConfig();
// Do one effects cycle as early as possible
if (config.ds == DataSource::WEB) {
effects.run();
}
// set the effect idle timer
idleTicker.attach(config.effect_idletimeout, idleTimeout);
serial.show();
#endif
// Setup WiFi Handlers
wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
// Setup MQTT Handlers
if (config.mqtt) {
mqtt.onConnect(onMqttConnect);
mqtt.onDisconnect(onMqttDisconnect);
mqtt.onMessage(onMqttMessage);
mqtt.setServer(config.mqtt_ip.c_str(), config.mqtt_port);
// Unset clean session (defaults to true) so we get retained messages of QoS > 0
mqtt.setCleanSession(config.mqtt_clean);
if (config.mqtt_user.length() > 0)
mqtt.setCredentials(config.mqtt_user.c_str(), config.mqtt_password.c_str());
}
// Fallback to default SSID and passphrase if we fail to connect
initWifi();
if (WiFi.status() != WL_CONNECTED) {
LOG_PORT.println(F("*** Timeout - Reverting to default SSID ***"));
config.ssid = ssid;
config.passphrase = passphrase;
initWifi();
}
// If we fail again, go SoftAP or reboot
if (WiFi.status() != WL_CONNECTED) {
if (config.ap_fallback) {
LOG_PORT.println(F("*** FAILED TO ASSOCIATE WITH AP, GOING SOFTAP ***"));
WiFi.mode(WIFI_AP);
String ssid = "ESPixelStick " + String(config.hostname);
WiFi.softAP(ssid.c_str());
ourLocalIP = WiFi.softAPIP();
ourSubnetMask = IPAddress(255, 255, 255, 0);
} else {
LOG_PORT.println(F("*** FAILED TO ASSOCIATE WITH AP, REBOOTING ***"));
ESP.restart();
}
}
wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWiFiDisconnect);
LOG_PORT.print("IP : ");
LOG_PORT.println(ourLocalIP);
LOG_PORT.print("Subnet mask : ");
LOG_PORT.println(ourSubnetMask);
// Configure and start the web server
initWeb();
// Setup E1.31
if (config.multicast) {
if (e131.begin(E131_MULTICAST, config.universe,
uniLast - config.universe + 1)) {
LOG_PORT.println(F("- E131 Multicast Enabled"));
} else {
LOG_PORT.println(F("*** E131 MULTICAST INIT FAILED ****"));
}
} else {
if (e131.begin(E131_UNICAST)) {
LOG_PORT.print(F("- E131 Unicast port: "));
LOG_PORT.println(E131_DEFAULT_PORT);
} else {
LOG_PORT.println(F("*** E131 UNICAST INIT FAILED ****"));
}
}
fppDiscovery.begin();
if (ddp.begin(ourLocalIP)) {
LOG_PORT.println(F("- DDP Enabled"));
} else {
LOG_PORT.println(F("*** DDP INIT FAILED ****"));
}
lastZCPPConfig = -1;
if (zcpp.begin(ourLocalIP)) {
LOG_PORT.println(F("- ZCPP Enabled"));
ZCPPSub();
} else {
LOG_PORT.println(F("*** ZCPP INIT FAILED ****"));
}
=======================================================
// this section for ssd1306:
// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
LOG_PORT.println("setting up SSD1306");
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 64x48)
// init done
// display.display();
delay(100);
display.clearDisplay();
display.display();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(32, 8);
display.println("012345678910");
LOG_PORT.println("SSD1306 setup finished");
=======================================================
}
/////////////////////////////////////////////////////////
//
// WiFi Section
//
/////////////////////////////////////////////////////////
void initWifi() {
// Switch to station mode and disconnect just in case
WiFi.mode(WIFI_STA);
WiFi.disconnect();
connectWifi();
uint32_t timeout = millis();
while (WiFi.status() != WL_CONNECTED) {
LOG_PORT.print(".");
delay(500);
if (millis() - timeout > (1000 * config.sta_timeout) ) {
LOG_PORT.println("");
LOG_PORT.println(F("*** Failed to connect ***"));
break;
}
}
}
void connectWifi() {
delay(secureRandom(100, 500));
LOG_PORT.println("");
LOG_PORT.print(F("Connecting to "));
LOG_PORT.print(config.ssid);
LOG_PORT.print(F(" as "));
LOG_PORT.println(config.hostname);
WiFi.begin(config.ssid.c_str(), config.passphrase.c_str());
if (config.dhcp) {
LOG_PORT.print(F("Connecting with DHCP"));
} else {
// We don't use DNS, so just set it to our gateway
WiFi.config(IPAddress(config.ip[0], config.ip[1], config.ip[2], config.ip[3]),
IPAddress(config.gateway[0], config.gateway[1], config.gateway[2], config.gateway[3]),
IPAddress(config.netmask[0], config.netmask[1], config.netmask[2], config.netmask[3]),
IPAddress(config.gateway[0], config.gateway[1], config.gateway[2], config.gateway[3])
);
LOG_PORT.print(F("Connecting with Static IP"));
}
}
void onWifiConnect(const WiFiEventStationModeGotIP &event) {
LOG_PORT.println("");
LOG_PORT.print(F("Connected with IP: "));
LOG_PORT.println(WiFi.localIP());
ourLocalIP = WiFi.localIP();
ourSubnetMask = WiFi.subnetMask();
// Setup MQTT connection if enabled
if (config.mqtt)
connectToMqtt();
// Setup mDNS / DNS-SD
//TODO: Reboot or restart mdns when config.id is changed?
char chipId[7] = { 0 };
snprintf(chipId, sizeof(chipId), "%06x", ESP.getChipId());
MDNS.setInstanceName(String(config.id + " (" + String(chipId) + ")").c_str());
if (MDNS.begin(config.hostname.c_str())) {
MDNS.addService("http", "tcp", HTTP_PORT);
MDNS.addService("zcpp", "udp", ZCPP_PORT);
MDNS.addService("ddp", "udp", DDP_PORT);
MDNS.addService("e131", "udp", E131_DEFAULT_PORT);
MDNS.addServiceTxt("e131", "udp", "TxtVers", String(RDMNET_DNSSD_TXTVERS));
MDNS.addServiceTxt("e131", "udp", "ConfScope", RDMNET_DEFAULT_SCOPE);
MDNS.addServiceTxt("e131", "udp", "E133Vers", String(RDMNET_DNSSD_E133VERS));
MDNS.addServiceTxt("e131", "udp", "CID", String(chipId));
MDNS.addServiceTxt("e131", "udp", "Model", "ESPixelStick");
MDNS.addServiceTxt("e131", "udp", "Manuf", "Forkineye");
} else {
LOG_PORT.println(F("*** Error setting up mDNS responder ***"));
}
}
void onWiFiDisconnect(const WiFiEventStationModeDisconnected &event) {
LOG_PORT.println(F("*** WiFi Disconnected ***"));
// Pause MQTT reconnect while WiFi is reconnecting
mqttTicker.detach();
wifiTicker.once(2, connectWifi);
}
// Subscribe to "n" universes, starting at "universe"
void multiSub() {
uint8_t count;
ip_addr_t ifaddr;
ip_addr_t multicast_addr;
count = uniLast - config.universe + 1;
ifaddr.addr = static_cast<uint32_t>(WiFi.localIP());
for (uint8_t i = 0; i < count; i++) {
multicast_addr.addr = static_cast<uint32_t>(IPAddress(239, 255,
(((config.universe + i) >> 8) & 0xff),
(((config.universe + i) >> 0) & 0xff)));
igmp_joingroup(&ifaddr, &multicast_addr);
}
}
void ZCPPSub() {
ip_addr_t ifaddr;
ifaddr.addr = static_cast<uint32_t>(ourLocalIP);
ip_addr_t multicast_addr;
multicast_addr.addr = static_cast<uint32_t>(IPAddress(224, 0, 30, 5));
igmp_joingroup(&ifaddr, &multicast_addr);
LOG_PORT.println(F("- ZCPP Subscribed to multicast 224.0.30.5"));
}
/////////////////////////////////////////////////////////
//
// MQTT Section
//
/////////////////////////////////////////////////////////
void connectToMqtt() {
LOG_PORT.print(F("- Connecting to MQTT Broker "));
LOG_PORT.println(config.mqtt_ip);
mqtt.connect();
}
void onMqttConnect(bool sessionPresent) {
LOG_PORT.println(F("- MQTT Connected"));
// Get retained MQTT state
mqtt.subscribe(config.mqtt_topic.c_str(), 0);
mqtt.unsubscribe(config.mqtt_topic.c_str());
// Setup subscriptions
mqtt.subscribe(String(config.mqtt_topic + MQTT_SET_COMMAND_TOPIC).c_str(), 0);
// Publish state
publishState();
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
LOG_PORT.println(F("- MQTT Disconnected"));
if (WiFi.isConnected()) {
mqttTicker.once(2, connectToMqtt);
}
}
void onMqttMessage(char* topic, char* payload,
AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
DynamicJsonDocument r(1024);
DeserializationError error = deserializeJson(r, payload);
if (error) {
LOG_PORT.println("MQTT: Parsing failed");
return;
}
JsonObject root = r.as<JsonObject>();
// if its a retained message and we want clean session, ignore it
if ( properties.retain && config.mqtt_clean ) {
return;
}
bool stateOn = false;
if (root.containsKey("state")) {
if (strcmp(root["state"], LIGHT_ON) == 0) {
stateOn = true;
} else if (strcmp(root["state"], LIGHT_OFF) == 0) {
stateOn = false;
}
}
if (root.containsKey("brightness")) {
effects.setBrightness((float)root["brightness"] / 255.0);
}
if (root.containsKey("speed")) {
effects.setSpeed(root["speed"]);
}
if (root.containsKey("color")) {
effects.setColor({
root["color"]["r"],
root["color"]["g"],
root["color"]["b"]
});
}
if (root.containsKey("effect")) {
// Set the explict effect provided by the MQTT client
effects.setEffect(root["effect"]);
}
if (root.containsKey("reverse")) {
effects.setReverse(root["reverse"]);
}
if (root.containsKey("mirror")) {
effects.setMirror(root["mirror"]);
}
if (root.containsKey("allleds")) {
effects.setAllLeds(root["allleds"]);
}
// Set data source based on state - Fall back to E131 when off
if (stateOn) {
if (effects.getEffect().equalsIgnoreCase("Disabled"))
effects.setEffect("Solid");
config.ds = DataSource::MQTT;
} else {
config.ds = DataSource::E131;
effects.clearAll();
}
publishState();
}
void publishHA(bool join) {
// Setup HA discovery
char chipId[7] = { 0 };
snprintf(chipId, sizeof(chipId), "%06x", ESP.getChipId());
String ha_config = config.mqtt_haprefix + "/light/" + String(chipId) + "/config";
if (join) {
DynamicJsonDocument root(1024);
root["name"] = config.id;
root["schema"] = "json";
root["state_topic"] = config.mqtt_topic;
root["command_topic"] = config.mqtt_topic + "/set";
root["rgb"] = "true";
root["brightness"] = "true";
root["effect"] = "true";
JsonArray effect_list = root.createNestedArray("effect_list");
// effect[0] is 'disabled', skip it
for (uint8_t i = 1; i < effects.getEffectCount(); i++) {
effect_list.add(effects.getEffectInfo(i)->name);
}
char buffer[measureJson(root) + 1];
serializeJson(root, buffer, sizeof(buffer));
mqtt.publish(ha_config.c_str(), 0, true, buffer);
} else {
mqtt.publish(ha_config.c_str(), 0, true, "");
}
}
void publishState() {
DynamicJsonDocument root(1024);
if ((config.ds != DataSource::E131 && config.ds != DataSource::ZCPP) && (!effects.getEffect().equalsIgnoreCase("Disabled")))
root["state"] = LIGHT_ON;
else
root["state"] = LIGHT_OFF;
JsonObject color = root.createNestedObject("color");
color["r"] = effects.getColor().r;
color["g"] = effects.getColor().g;
color["b"] = effects.getColor().b;
root["brightness"] = effects.getBrightness() * 255;
root["speed"] = effects.getSpeed();
if (!effects.getEffect().equalsIgnoreCase("Disabled")) {
root["effect"] = effects.getEffect();
}
root["reverse"] = effects.getReverse();
root["mirror"] = effects.getMirror();
root["allleds"] = effects.getAllLeds();
char buffer[measureJson(root) + 1];
serializeJson(root, buffer, sizeof(buffer));
mqtt.publish(config.mqtt_topic.c_str(), 0, true, buffer);
}
/////////////////////////////////////////////////////////
//
// Web Section
//
/////////////////////////////////////////////////////////
// Configure and start the web server
void initWeb() {
// Handle OTA update from asynchronous callbacks
Update.runAsync(true);
// Add header for SVG plot support?
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
// Setup WebSockets
ws.onEvent(wsEvent);
web.addHandler(&ws);
// Heap status handler
web.on("/heap", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
// JSON Config Handler
web.on("/conf", HTTP_GET, [](AsyncWebServerRequest * request) {
String jsonString;
serializeConfig(jsonString, true);
request->send(200, "text/json", jsonString);
});
// Firmware upload handler - only in station mode
web.on("/updatefw", HTTP_POST, [](AsyncWebServerRequest * request) {
ws.textAll("X6");
}, handle_fw_upload).setFilter(ON_STA_FILTER);
// Static Handler
web.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html");
// Raw config file Handler - but only on station
// web.serveStatic("/config.json", SPIFFS, "/config.json").setFilter(ON_STA_FILTER);
web.onNotFound([](AsyncWebServerRequest * request) {
request->send(404, "text/plain", "Page not found");
});
DefaultHeaders::Instance().addHeader(F("Access-Control-Allow-Origin"), "*");
// Config file upload handler - only in station mode
web.on("/config", HTTP_POST, [](AsyncWebServerRequest * request) {
ws.textAll("X6");
}, handle_config_upload).setFilter(ON_STA_FILTER);
web.begin();
LOG_PORT.print(F("- Web Server started on port "));
LOG_PORT.println(HTTP_PORT);
}
/////////////////////////////////////////////////////////
//
// JSON / Configuration Section
//
/////////////////////////////////////////////////////////
// Configuration Validations
void validateConfig() {
// E1.31 Limits
if (config.universe < 1)
config.universe = 1;
if (config.universe_limit > UNIVERSE_MAX || config.universe_limit < 1)
config.universe_limit = UNIVERSE_MAX;
if (config.channel_start < 1)
config.channel_start = 1;
else if (config.channel_start > config.universe_limit)
config.channel_start = config.universe_limit;
// Set default MQTT port if missing
if (config.mqtt_port == 0)
config.mqtt_port = MQTT_PORT;
// Generate default MQTT topic if blank
if (!config.mqtt_topic.length()) {
char chipId[7] = { 0 };
snprintf(chipId, sizeof(chipId), "%06x", ESP.getChipId());
config.mqtt_topic = "diy/esps/" + String(chipId);
}
// Set default Home Assistant Discovery prefix if blank
if (!config.mqtt_haprefix.length()) {
config.mqtt_haprefix = "homeassistant";
}
#if defined(ESPS_MODE_PIXEL)
// Set Mode
config.devmode.MPIXEL = true;
config.devmode.MSERIAL = false;
// Generic channel limits for pixels
if (config.channel_count % 3)
config.channel_count = (config.channel_count / 3) * 3;
if (config.channel_count > PIXEL_LIMIT * 3)
config.channel_count = PIXEL_LIMIT * 3;
else if (config.channel_count < 1)
config.channel_count = 1;
if (config.groupSize > config.channel_count / 3)
config.groupSize = config.channel_count / 3;
else if (config.groupSize < 1)
config.groupSize = 1;
// GECE Limits
if (config.pixel_type == PixelType::GECE) {
config.pixel_color = PixelColor::RGB;
if (config.channel_count > 63 * 3)
config.channel_count = 63 * 3;
}
// Default gamma value
if (config.gammaVal <= 0) {
config.gammaVal = 2.2;
}
// Default brightness value
if (config.briteVal <= 0) {
config.briteVal = 1.0;
}
#elif defined(ESPS_MODE_SERIAL)
// Set Mode
config.devmode.MPIXEL = false;
config.devmode.MSERIAL = true;
// Generic serial channel limits
if (config.channel_count > RENARD_LIMIT)
config.channel_count = RENARD_LIMIT;
else if (config.channel_count < 1)
config.channel_count = 1;
if (config.serial_type == SerialType::DMX512 && config.channel_count > UNIVERSE_MAX)
config.channel_count = UNIVERSE_MAX;
// Baud rate check
if (config.baudrate > BaudRate::BR_460800)
config.baudrate = BaudRate::BR_460800;
else if (config.baudrate < BaudRate::BR_38400)
config.baudrate = BaudRate::BR_57600;
#endif
if (config.effect_speed < 1)
config.effect_speed = 1;
if (config.effect_speed > 10)
config.effect_speed = 10;
if (config.effect_brightness > 1.0)
config.effect_brightness = 1.0;
if (config.effect_brightness < 0.0)
config.effect_brightness = 0.0;
if (config.effect_idletimeout == 0) {
config.effect_idletimeout = 10;
config.effect_idleenabled = false;
}
if (config.effect_startenabled) {
if (effects.isValidEffect(config.effect_name)) {
effects.setEffect(config.effect_name);
if ( !config.effect_name.equalsIgnoreCase("disabled")
&& !config.effect_name.equalsIgnoreCase("view") ) {
config.ds = DataSource::WEB;
}
}
}
}
void updateConfig() {
// Validate first
validateConfig();
// Find the last universe we should listen for
uint16_t span = config.channel_start + config.channel_count - 1;
if (span % config.universe_limit)
uniLast = config.universe + span / config.universe_limit;
else
uniLast = config.universe + span / config.universe_limit - 1;
// Setup the sequence error tracker
uint8_t uniTotal = (uniLast + 1) - config.universe;
if (seqTracker) free(seqTracker);
if ((seqTracker = static_cast<uint8_t *>(malloc(uniTotal))))
memset(seqTracker, 0x00, uniTotal);
seqZCPPTracker = 0;
if (seqError) free(seqError);
if ((seqError = static_cast<uint32_t *>(malloc(uniTotal * 4))))
memset(seqError, 0x00, uniTotal * 4);
seqZCPPError = 0;
// Zero out packet stats
e131.stats.num_packets = 0;
zcpp.stats.num_packets = 0;
// Initialize for our pixel type
#if defined(ESPS_MODE_PIXEL)
pixels.begin(config.pixel_type, config.pixel_color, config.channel_count / 3);
pixels.setGroup(config.groupSize, config.zigSize);
updateGammaTable(config.gammaVal, config.briteVal);
if (config.groupSize == 0) config.groupSize = 1;
effects.begin(&pixels, config.channel_count / 3 / config.groupSize);
#elif defined(ESPS_MODE_SERIAL)
serial.begin(&SEROUT_PORT, config.serial_type, config.channel_count, config.baudrate);
effects.begin(&serial, config.channel_count / 3 );
#endif
LOG_PORT.print(F("- Listening for "));
LOG_PORT.print(config.channel_count);
LOG_PORT.print(F(" channels, from Universe "));
LOG_PORT.print(config.universe);
LOG_PORT.print(F(" to "));
LOG_PORT.println(uniLast);
// Setup IGMP subscriptions if multicast is enabled
if (config.multicast)
multiSub();
// Update Home Assistant Discovery if enabled
if (config.mqtt) {
publishHA(config.mqtt_hadisco);
publishState();
}
}
// De-Serialize Network config
void dsNetworkConfig(const JsonObject &json) {
if (json.containsKey("network")) {
JsonObject networkJson = json["network"];
// Fallback to embedded ssid and passphrase if null in config
config.ssid = networkJson["ssid"].as<String>();
if (!config.ssid.length())
config.ssid = ssid;
config.passphrase = networkJson["passphrase"].as<String>();
if (!config.passphrase.length())
config.passphrase = passphrase;
// Network
for (int i = 0; i < 4; i++) {
config.ip[i] = networkJson["ip"][i];
config.netmask[i] = networkJson["netmask"][i];
config.gateway[i] = networkJson["gateway"][i];
}
config.dhcp = networkJson["dhcp"];
config.sta_timeout = networkJson["sta_timeout"] | CLIENT_TIMEOUT;
if (config.sta_timeout < 5) {
config.sta_timeout = 5;
}
config.ap_fallback = networkJson["ap_fallback"];
config.ap_timeout = networkJson["ap_timeout"] | AP_TIMEOUT;
if (config.ap_timeout < 15) {
config.ap_timeout = 15;
}
// Generate default hostname if needed
config.hostname = networkJson["hostname"].as<String>();
}
else {
LOG_PORT.println("No network settings found.");
}
if (!config.hostname.length()) {
char chipId[7] = { 0 };
snprintf(chipId, sizeof(chipId), "%06x", ESP.getChipId());
config.hostname = "esps-" + String(chipId);
}
}
// De-serialize Effect Config
void dsEffectConfig(const JsonObject &json) {
// Effects
if (json.containsKey("effects")) {
JsonObject effectsJson = json["effects"];
config.effect_name = effectsJson["name"].as<String>();
config.effect_mirror = effectsJson["mirror"];
config.effect_allleds = effectsJson["allleds"];
config.effect_reverse = effectsJson["reverse"];
if (effectsJson.containsKey("speed"))
config.effect_speed = effectsJson["speed"];
config.effect_color = { effectsJson["r"], effectsJson["g"], effectsJson["b"] };
if (effectsJson.containsKey("brightness"))
config.effect_brightness = effectsJson["brightness"];
config.effect_startenabled = effectsJson["startenabled"];
config.effect_idleenabled = effectsJson["idleenabled"];
config.effect_idletimeout = effectsJson["idletimeout"];
}
else
{
LOG_PORT.println("No effect settings found.");
}
}
// De-serialize Device Config
void dsDeviceConfig(const JsonObject &json) {
// Device
if (json.containsKey("device")) {
config.id = json["device"]["id"].as<String>();
}
else
{
LOG_PORT.println("No device settings found.");
}
// E131
if (json.containsKey("e131")) {
config.universe = json["e131"]["universe"];
config.universe_limit = json["e131"]["universe_limit"];
config.channel_start = json["e131"]["channel_start"];
config.channel_count = json["e131"]["channel_count"];
config.multicast = json["e131"]["multicast"];
}
else
{
LOG_PORT.println("No e131 settings found.");
}
// MQTT
if (json.containsKey("mqtt")) {
JsonObject mqttJson = json["mqtt"];
config.mqtt = mqttJson["enabled"];
config.mqtt_ip = mqttJson["ip"].as<String>();
config.mqtt_port = mqttJson["port"];
config.mqtt_user = mqttJson["user"].as<String>();
config.mqtt_password = mqttJson["password"].as<String>();
config.mqtt_topic = mqttJson["topic"].as<String>();
config.mqtt_clean = mqttJson["clean"] | false;
config.mqtt_hadisco = mqttJson["hadisco"] | false;
config.mqtt_haprefix = mqttJson["haprefix"].as<String>();
}
else
{
LOG_PORT.println("No mqtt settings found.");
}
#if defined(ESPS_MODE_PIXEL)
// Pixel
if (json.containsKey("pixel")) {
config.pixel_type = PixelType(static_cast<uint8_t>(json["pixel"]["type"]));
config.pixel_color = PixelColor(static_cast<uint8_t>(json["pixel"]["color"]));
config.groupSize = json["pixel"]["groupSize"];
config.zigSize = json["pixel"]["zigSize"];
config.gammaVal = json["pixel"]["gammaVal"];
config.briteVal = json["pixel"]["briteVal"];
}
else
{
LOG_PORT.println("No pixel settings found.");
}
#elif defined(ESPS_MODE_SERIAL)
// Serial
if (json.containsKey("serial")) {
config.serial_type = SerialType(static_cast<uint8_t>(json["serial"]["type"]));
config.baudrate = BaudRate(static_cast<uint32_t>(json["serial"]["baudrate"]));
}
else
{
LOG_PORT.println("No serial settings found.");
}
#endif
}
// Load configugration JSON file
void loadConfig() {
// Zeroize Config struct
memset(&config, 0, sizeof(config));
effects.setFromDefaults();
// Load CONFIG_FILE json. Create and init with defaults if not found
File file = SPIFFS.open(CONFIG_FILE, "r");
if (!file) {
LOG_PORT.println(F("- No configuration file found."));
config.ssid = ssid;
config.passphrase = passphrase;
char chipId[7] = { 0 };
snprintf(chipId, sizeof(chipId), "%06x", ESP.getChipId());
config.hostname = "esps-" + String(chipId);
config.ap_fallback = true;
config.id = "No Config Found";
saveConfig();
} else {
// Parse CONFIG_FILE json
size_t size = file.size();
if (size > CONFIG_MAX_SIZE) {
LOG_PORT.println(F("*** Configuration File too large ***"));
return;
}
std::unique_ptr<char[]> buf(new char[size]);
file.readBytes(buf.get(), size);
DynamicJsonDocument json(1024);
DeserializationError error = deserializeJson(json, buf.get());
if (error) {
LOG_PORT.println(F("*** Configuration File Format Error ***"));
return;
}
dsNetworkConfig(json.as<JsonObject>());
dsDeviceConfig(json.as<JsonObject>());
dsEffectConfig(json.as<JsonObject>());
LOG_PORT.println(F("- Configuration loaded."));
}
// Validate it
validateConfig();
effects.setFromConfig();
}
// Serialize the current config into a JSON string
void serializeConfig(String &jsonString, bool pretty, bool creds) {
// Create buffer and root object
DynamicJsonDocument json(1024);
// Device
JsonObject device = json.createNestedObject("device");
device["id"] = config.id.c_str();
device["mode"] = config.devmode.toInt();
// Network
JsonObject network = json.createNestedObject("network");
network["ssid"] = config.ssid.c_str();
if (creds)
network["passphrase"] = config.passphrase.c_str();
network["hostname"] = config.hostname.c_str();
JsonArray ip = network.createNestedArray("ip");
JsonArray netmask = network.createNestedArray("netmask");
JsonArray gateway = network.createNestedArray("gateway");
for (int i = 0; i < 4; i++) {
ip.add(config.ip[i]);
netmask.add(config.netmask[i]);
gateway.add(config.gateway[i]);
}
network["dhcp"] = config.dhcp;
network["sta_timeout"] = config.sta_timeout;
network["ap_fallback"] = config.ap_fallback;
network["ap_timeout"] = config.ap_timeout;
// Effects
JsonObject _effects = json.createNestedObject("effects");
_effects["name"] = config.effect_name;
_effects["mirror"] = config.effect_mirror;
_effects["allleds"] = config.effect_allleds;
_effects["reverse"] = config.effect_reverse;
_effects["speed"] = config.effect_speed;
_effects["brightness"] = config.effect_brightness;
_effects["r"] = config.effect_color.r;
_effects["g"] = config.effect_color.g;
_effects["b"] = config.effect_color.b;
_effects["brightness"] = config.effect_brightness;
_effects["startenabled"] = config.effect_startenabled;
_effects["idleenabled"] = config.effect_idleenabled;
_effects["idletimeout"] = config.effect_idletimeout;
// MQTT
JsonObject _mqtt = json.createNestedObject("mqtt");
_mqtt["enabled"] = config.mqtt;
_mqtt["ip"] = config.mqtt_ip.c_str();
_mqtt["port"] = config.mqtt_port;
_mqtt["user"] = config.mqtt_user.c_str();
_mqtt["password"] = config.mqtt_password.c_str();
_mqtt["topic"] = config.mqtt_topic.c_str();
_mqtt["clean"] = config.mqtt_clean;
_mqtt["hadisco"] = config.mqtt_hadisco;
_mqtt["haprefix"] = config.mqtt_haprefix.c_str();
// E131
JsonObject e131 = json.createNestedObject("e131");
e131["universe"] = config.universe;
e131["universe_limit"] = config.universe_limit;
e131["channel_start"] = config.channel_start;
e131["channel_count"] = config.channel_count;
e131["multicast"] = config.multicast;
#if defined(ESPS_MODE_PIXEL)
// Pixel
JsonObject pixel = json.createNestedObject("pixel");
pixel["type"] = static_cast<uint8_t>(config.pixel_type);
pixel["color"] = static_cast<uint8_t>(config.pixel_color);
pixel["groupSize"] = config.groupSize;
pixel["zigSize"] = config.zigSize;
pixel["gammaVal"] = config.gammaVal;
pixel["briteVal"] = config.briteVal;
#elif defined(ESPS_MODE_SERIAL)
// Serial
JsonObject serial = json.createNestedObject("serial");
serial["type"] = static_cast<uint8_t>(config.serial_type);
serial["baudrate"] = static_cast<uint32_t>(config.baudrate);
#endif
if (pretty)
serializeJsonPretty(json, jsonString);
else
serializeJson(json, jsonString);
}
#if defined(ESPS_MODE_PIXEL)
void dsGammaConfig(const JsonObject &json) {
if (json.containsKey("pixel")) {
config.gammaVal = json["pixel"]["gammaVal"];
config.briteVal = json["pixel"]["briteVal"];
updateGammaTable(config.gammaVal, config.briteVal);
}
}
#endif
// Save configuration JSON file
void saveConfig() {
// Update Config
updateConfig();
// Serialize Config
String jsonString;
serializeConfig(jsonString, true, true);
// Save Config
File file = SPIFFS.open(CONFIG_FILE, "w");
if (!file) {
LOG_PORT.println(F("*** Error creating configuration file ***"));
return;
} else {
file.println(jsonString);
LOG_PORT.println(F("* Configuration saved."));
}
}
void idleTimeout() {
idleTicker.attach(config.effect_idletimeout, idleTimeout);
if ( (config.effect_idleenabled) && (config.ds == DataSource::E131 || config.ds == DataSource::ZCPP) ) {
config.ds = DataSource::IDLEWEB;
effects.setFromConfig();
}
}
void sendZCPPConfig(ZCPP_packet_t& packet) {
LOG_PORT.println("Sending ZCPP Configuration query response.");
memset(&packet, 0x00, sizeof(packet));
memcpy(packet.QueryConfigurationResponse.Header.token, ZCPP_token, sizeof(ZCPP_token));
packet.QueryConfigurationResponse.Header.type = ZCPP_TYPE_QUERY_CONFIG_RESPONSE;
packet.QueryConfigurationResponse.Header.protocolVersion = ZCPP_CURRENT_PROTOCOL_VERSION;
packet.QueryConfigurationResponse.sequenceNumber = ntohs(lastZCPPConfig);
strncpy(packet.QueryConfigurationResponse.userControllerName, config.id.c_str(), std::min((int)strlen(config.id.c_str()), (int)sizeof(packet.QueryConfigurationResponse.userControllerName)));
if (config.channel_count == 0) {
packet.QueryConfigurationResponse.ports = 0;
}
else {
packet.QueryConfigurationResponse.ports = 1;
packet.QueryConfigurationResponse.PortConfig[0].port = 0;
#if defined(ESPS_MODE_SERIAL)
packet.QueryConfigurationResponse.PortConfig[0].port |= 0x80;
#endif
packet.QueryConfigurationResponse.PortConfig[0].string = 0;
packet.QueryConfigurationResponse.PortConfig[0].startChannel = ntohl((uint32_t)config.channel_start);
#if defined(ESPS_MODE_PIXEL)
switch (config.pixel_type) {
#elif defined(ESPS_MODE_SERIAL)
switch (config.serial_type) {
#endif
#if defined(ESPS_MODE_PIXEL)
case PixelType::WS2811:
packet.QueryConfigurationResponse.PortConfig[0].protocol = ZCPP_PROTOCOL_WS2811;
break;
case PixelType::GECE:
packet.QueryConfigurationResponse.PortConfig[0].protocol = ZCPP_PROTOCOL_GECE;
break;
#elif defined(ESPS_MODE_SERIAL)
case SerialType::DMX512:
packet.QueryConfigurationResponse.PortConfig[0].protocol = ZCPP_PROTOCOL_DMX;
break;
case SerialType::RENARD:
packet.QueryConfigurationResponse.PortConfig[0].protocol = ZCPP_PROTOCOL_RENARD;
break;
#endif
}
packet.QueryConfigurationResponse.PortConfig[0].channels = ntohl((uint32_t)config.channel_count);
#if defined(ESPS_MODE_PIXEL)
packet.QueryConfigurationResponse.PortConfig[0].grouping = config.groupSize;
switch (config.pixel_color) {
case PixelColor::RGB:
packet.QueryConfigurationResponse.PortConfig[0].directionColourOrder = ZCPP_COLOUR_ORDER_RGB;
break;
case PixelColor::RBG:
packet.QueryConfigurationResponse.PortConfig[0].directionColourOrder = ZCPP_COLOUR_ORDER_RBG;
break;
case PixelColor::GRB:
packet.QueryConfigurationResponse.PortConfig[0].directionColourOrder = ZCPP_COLOUR_ORDER_GRB;
break;
case PixelColor::GBR:
packet.QueryConfigurationResponse.PortConfig[0].directionColourOrder = ZCPP_COLOUR_ORDER_GBR;
break;
case PixelColor::BRG:
packet.QueryConfigurationResponse.PortConfig[0].directionColourOrder = ZCPP_COLOUR_ORDER_BRG;
break;
case PixelColor::BGR:
packet.QueryConfigurationResponse.PortConfig[0].directionColourOrder = ZCPP_COLOUR_ORDER_BGR;
break;
}
packet.QueryConfigurationResponse.PortConfig[0].brightness = config.briteVal * 100.0f;
packet.QueryConfigurationResponse.PortConfig[0].gamma = config.gammaVal * 10;
#else
packet.QueryConfigurationResponse.PortConfig[0].grouping = 0;
packet.QueryConfigurationResponse.PortConfig[0].directionColourOrder = 0;
packet.QueryConfigurationResponse.PortConfig[0].brightness = 100.0f;
packet.QueryConfigurationResponse.PortConfig[0].gamma = 0;
#endif
}
zcpp.sendConfigResponse(&packet);
}
/////////////////////////////////////////////////////////
//
// Main Loop
//
/////////////////////////////////////////////////////////
void loop() {
// Reboot handler
if (reboot) {
delay(REBOOT_DELAY);
ESP.restart();
}
bool doShow = true;
// Render output for current data source
if ( (config.ds == DataSource::E131) || (config.ds == DataSource::ZCPP) || (config.ds == DataSource::DDP) || (config.ds == DataSource::IDLEWEB) ) {
// Parse a packet and update pixels
while (!e131.isEmpty()) {
e131_packet_t packet;
idleTicker.attach(config.effect_idletimeout, idleTimeout);
if (config.ds == DataSource::IDLEWEB || config.ds == DataSource::ZCPP) {
config.ds = DataSource::E131;
}
e131.pull(&packet);
uint16_t universe = htons(packet.universe);
uint8_t *data = packet.property_values + 1;
//LOG_PORT.print(universe);
//LOG_PORT.println(packet.sequence_number);
if ((universe >= config.universe) && (universe <= uniLast)) {
// Universe offset and sequence tracking
uint8_t uniOffset = (universe - config.universe);
if (packet.sequence_number != seqTracker[uniOffset]++) {
LOG_PORT.print(F("Sequence Error - expected: "));
LOG_PORT.print(seqTracker[uniOffset] - 1);
LOG_PORT.print(F(" actual: "));
LOG_PORT.print(packet.sequence_number);
LOG_PORT.print(F(" universe: "));
LOG_PORT.println(universe);
seqError[uniOffset]++;
seqTracker[uniOffset] = packet.sequence_number + 1;
}
// Offset the channels if required
uint16_t offset = 0;
offset = config.channel_start - 1;
// Find start of data based off the Universe
int16_t dataStart = uniOffset * config.universe_limit - offset;
// Calculate how much data we need for this buffer
uint16_t dataStop = config.channel_count;
uint16_t channels = htons(packet.property_value_count) - 1;
if (config.universe_limit < channels)
channels = config.universe_limit;
if ((dataStart + channels) < dataStop)
dataStop = dataStart + channels;
// Set the data
uint16_t buffloc = 0;
// ignore data from start of first Universe before channel_start
if (dataStart < 0) {
dataStart = 0;
buffloc = config.channel_start - 1;
}
for (int i = dataStart; i < dataStop; i++) {
#if defined(ESPS_MODE_PIXEL)
pixels.setValue(i, data[buffloc]);
#elif defined(ESPS_MODE_SERIAL)
serial.setValue(i, data[buffloc]);
#endif
buffloc++;
}
}
}
while (!ddp.isEmpty()) {
DDP_packet_t ddpPacket;
ddp.pull(&ddpPacket);
if (ddpPacket.header.flags & 0x01) {
doShow = true;
} else {
doShow = false;
}
uint16_t len = htons(ddpPacket.header.dataLen);
uint32_t offset = htonl(ddpPacket.header.channelOffset);
bool tc = ddpPacket.header.flags & 0x10;
uint8_t *data = ddpPacket.header.data;
if (tc) {
data = ddpPacket.timeCodeHeader.data;
}
for (int i = offset; i < offset + len; i++) {
if (i < config.channel_count) {
#if defined(ESPS_MODE_PIXEL)
pixels.setValue(i, data[i - offset]);
#elif defined(ESPS_MODE_SERIAL)
serial.setValue(i, data[i - offset]);
#endif
}
}
}
bool abortPacketRead = false;
while (!zcpp.isEmpty() && !abortPacketRead) {
ZCPP_packet_t zcppPacket;
idleTicker.attach(config.effect_idletimeout, idleTimeout);
if (config.ds == DataSource::IDLEWEB || config.ds == DataSource::E131) {
config.ds = DataSource::ZCPP;
}
zcpp.pull(&zcppPacket);
switch (zcppPacket.Discovery.Header.type) {
case ZCPP_TYPE_DISCOVERY: // discovery
{
LOG_PORT.println("ZCPP Discovery received.");
int pixelPorts = 0;
int serialPorts = 0;
#if defined(ESPS_MODE_PIXEL)
pixelPorts = 1;
#elif defined(ESPS_MODE_SERIAL)
serialPorts = 1;
#endif
char version[9];
memset(version, 0x00, sizeof(version));
for (uint8_t i = 0; i < min(strlen_P(VERSION), sizeof(version) - 1); i++)
version[i] = pgm_read_byte(VERSION + i);
uint8_t mac[WL_MAC_ADDR_LENGTH];
zcpp.sendDiscoveryResponse(&zcppPacket, version, WiFi.macAddress(mac), config.id.c_str(), pixelPorts, serialPorts, 680 * 3, 512, 680 * 3, static_cast<uint32_t>(ourLocalIP), static_cast<uint32_t>(ourSubnetMask));
}
break;
case ZCPP_TYPE_CONFIG: // config
LOG_PORT.println("ZCPP Config received.");
if (htons(zcppPacket.Configuration.sequenceNumber) != lastZCPPConfig) {
// a new config to apply
LOG_PORT.print(" The config is new: ");
LOG_PORT.println(htons(zcppPacket.Configuration.sequenceNumber));
config.id = String(zcppPacket.Configuration.userControllerName);
LOG_PORT.print(" Controller Name: ");
LOG_PORT.println(config.id);
ZCPP_PortConfig* p = zcppPacket.Configuration.PortConfig;
for (int i = 0; i < zcppPacket.Configuration.ports; i++) {
if (p->port == 0) {
switch (p->protocol) {
#if defined(ESPS_MODE_PIXEL)
case ZCPP_PROTOCOL_WS2811:
config.pixel_type = PixelType::WS2811;
break;
case ZCPP_PROTOCOL_GECE:
config.pixel_type = PixelType::GECE;
break;
#elif defined(ESPS_MODE_SERIAL)
case ZCPP_PROTOCOL_DMX:
config.serial_type = SerialType::DMX512;
break;
case ZCPP_PROTOCOL_RENARD:
config.serial_type = SerialType::RENARD;
break;
#endif
default:
LOG_PORT.print("Attempt to configure invalid protocol ");
LOG_PORT.print(p->protocol);
break;
}
LOG_PORT.print(" Protocol: ");
#if defined(ESPS_MODE_PIXEL)
LOG_PORT.println((int)config.pixel_type);
#elif defined(ESPS_MODE_SERIAL)
LOG_PORT.println((int)config.serial_type);
#endif
config.channel_start = htonl(p->startChannel);
LOG_PORT.print(" Start Channel: ");
LOG_PORT.println(config.channel_start);
config.channel_count = htonl(p->channels);
LOG_PORT.print(" Channel Count: ");
LOG_PORT.println(config.channel_count);
#if defined(ESPS_MODE_PIXEL)
config.groupSize = p->grouping;
LOG_PORT.print(" Group Size: ");
LOG_PORT.println(config.groupSize);
switch (ZCPP_GetColourOrder(p->directionColourOrder)) {
case ZCPP_COLOUR_ORDER_RGB:
config.pixel_color = PixelColor::RGB;
break;
case ZCPP_COLOUR_ORDER_RBG:
config.pixel_color = PixelColor::RBG;
break;
case ZCPP_COLOUR_ORDER_GRB:
config.pixel_color = PixelColor::GRB;
break;
case ZCPP_COLOUR_ORDER_GBR:
config.pixel_color = PixelColor::GBR;
break;
case ZCPP_COLOUR_ORDER_BRG:
config.pixel_color = PixelColor::BRG;
break;
case ZCPP_COLOUR_ORDER_BGR:
config.pixel_color = PixelColor::BGR;
break;
default:
LOG_PORT.print("Attempt to configure invalid colour order ");
LOG_PORT.print(ZCPP_GetColourOrder(p->directionColourOrder));
break;
}
LOG_PORT.print(" Colour Order: ");
LOG_PORT.println((int)config.pixel_color);
config.briteVal = (float)p->brightness / 100.0f;
LOG_PORT.print(" Brightness: ");
LOG_PORT.println(config.briteVal);
config.gammaVal = ZCPP_GetGamma(p->gamma);
LOG_PORT.print(" Gamma: ");
LOG_PORT.println(config.gammaVal);
#endif
}
else {
LOG_PORT.print("Attempt to configure invalid port ");
LOG_PORT.print(p->port);
}
p += sizeof(zcppPacket.Configuration.PortConfig);
}
if (zcppPacket.Configuration.flags & ZCPP_CONFIG_FLAG_LAST) {
lastZCPPConfig = htons(zcppPacket.Configuration.sequenceNumber);
saveConfig();
if ((zcppPacket.Configuration.flags & ZCPP_CONFIG_FLAG_QUERY_CONFIGURATION_RESPONSE_REQUIRED) != 0) {
sendZCPPConfig(zcppPacket);
}
}
}
else {
LOG_PORT.println(" The config has not changed.");
}
break;
case ZCPP_TYPE_QUERY_CONFIG: // query config
sendZCPPConfig(zcppPacket);
break;
case ZCPP_TYPE_SYNC: // sync
doShow = true;
// exit read and send data to the pixels
abortPacketRead = true;
break;
case ZCPP_TYPE_DATA: // data
uint8_t seq = zcppPacket.Data.sequenceNumber;
uint32_t offset = htonl(zcppPacket.Data.frameAddress);
bool frameLast = zcppPacket.Data.flags & ZCPP_DATA_FLAG_LAST;
uint16_t len = htons(zcppPacket.Data.packetDataLength);
bool sync = (zcppPacket.Data.flags & ZCPP_DATA_FLAG_SYNC_WILL_BE_SENT) != 0;
if (sync) {
// suppress display until we see a sync
doShow = false;
}
if (seq != seqZCPPTracker) {
LOG_PORT.print(F("Sequence Error - expected: "));
LOG_PORT.print(seqZCPPTracker);
LOG_PORT.print(F(" actual: "));
LOG_PORT.println(seq);
seqZCPPError++;
}
if (frameLast)
seqZCPPTracker = seq + 1;
zcpp.stats.num_packets++;
for (int i = offset; i < offset + len; i++) {
#if defined(ESPS_MODE_PIXEL)
pixels.setValue(i, zcppPacket.Data.data[i - offset]);
#elif defined(ESPS_MODE_SERIAL)
serial.setValue(i, zcppPacket.Data.data[i - offset]);
#endif
}
break;
}
}
}
if (doShow) {
if ( (config.ds == DataSource::WEB)
|| (config.ds == DataSource::IDLEWEB)
|| (config.ds == DataSource::ZCPP)
|| (config.ds == DataSource::DDP)
|| (config.ds == DataSource::MQTT) ) {
effects.run();
}
/* Streaming refresh */
#if defined(ESPS_MODE_PIXEL)
if (pixels.canRefresh())
pixels.show();
#elif defined(ESPS_MODE_SERIAL)
if (serial.canRefresh())
serial.show();
#endif
}
// workaround crash - consume incoming bytes on serial port
if (LOG_PORT.available()) {
while (LOG_PORT.read() >= 0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment