hyperion.ng/assets/firmware/HyperSerial/HyperSerialESP32_Neopixel/HyperSerialESP32_Neopixel.ino
LordGrey bf418686e3
Fix WLED & Smoothing (#1567)
* WLED - Fix empty segment element in DB

* WLED - Fix to not overwrite on state when not isStayOnAfterStreaming

* Refactor ProviderRestApi, increase default timeout

* Fix Smoothing - Fix empty updates, consider smoothing configs for effects

* UI - Fix not removed priority

* Add missing header and code updates

* setRedirectPolicy was only introduced in Qt 5.9

* Adalight - Align to HyperSerial v9.0.0

* HyperSerial Hyperion with awa protocol v8.0.0

* Correct line-endings
2023-02-12 21:20:50 +01:00

656 lines
16 KiB
C++

#include <NeoPixelBus.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////// CONFIG SECTION STARTS /////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
#define THIS_IS_RGBW // RGBW SK6812, otherwise comment it
#define COLD_WHITE // for RGBW (THIS_IS_RGBW enabled) select COLD version, comment it if NEUTRAL
const bool skipFirstLed = false; // if set the first led in the strip will be set to black (for level shifters using sacrifice LED)
const int serialSpeed = 2000000; // serial port speed
#define DATA_PIN 2 // PIN: data output for LED strip
const bool reportStats = false; // Send back processing statistics
const int reportStatInterval_s = 10; // Send back processing every interval in seconds
/* Statistics breakdown:
FPS: Updates to the LEDs per second
F-FPS: Frames identified per second
S: Shown (Done) updates to the LEDs per given interval
F: Frames identified per interval (garbled grames cannot be counted)
G: Good frames identified per interval
B: Total bad frames of all types identified per interval
BF: Bad frames identified per interval
BS: Skipped incomplete frames
BC: Frames failing CRC check per interval
BFL Frames failing Fletcher content validation per interval
*/
//Developer configs
#define ENABLE_STRIP
#define ENABLE_CHECK_FLETCHER
const int SERIAL_SIZE_RX = 4096;
#ifndef ENABLE_STRIP
const int serial2Speed = 460800;
const bool reportInput = false;
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////// CONFIG SECTION ENDS /////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
const String version = "8.0";
#ifdef THIS_IS_RGBW
float whiteLimit = 1.0f;
#ifdef COLD_WHITE
uint8_t rCorrection = 0xA0; // adjust red -> white in 0-0xFF range
uint8_t gCorrection = 0xA0; // adjust green -> white in 0-0xFF range
uint8_t bCorrection = 0xA0; // adjust blue -> white in 0-0xFF range
#else
uint8_t rCorrection = 0xB0; // adjust red -> white in 0-0xFF range
uint8_t gCorrection = 0xB0; // adjust green -> white in 0-0xFF range
uint8_t bCorrection = 0x70; // adjust blue -> white in 0-0xFF range
#endif
#endif
int ledCount = 0; // This is dynamic, don't change it
int pixelCount = 0; // This is dynamic, don't change it
#ifdef THIS_IS_RGBW
#define LED_TYPE NeoGrbwFeature
#if defined(ARDUINO_LOLIN_S2_MINI)
#define LED_METHOD NeoEsp32I2s0Sk6812Method
#else
#define LED_METHOD NeoEsp32I2s1Sk6812Method
#endif
#else
#define LED_TYPE NeoGrbFeature
#if defined(ARDUINO_LOLIN_S2_MINI)
#define LED_METHOD NeoEsp32I2s0Ws2812xMethod
#else
#define LED_METHOD NeoEsp32I2s1Ws2812xMethod
#endif
#endif
#define LED_DRIVER NeoPixelBus<LED_TYPE, LED_METHOD>
uint8_t* ledBuffer;
int ledBufferSize;
#ifdef ENABLE_STRIP
LED_DRIVER* strip = NULL;
#endif
enum class AwaProtocol
{
HEADER_A,
HEADER_w,
HEADER_a,
HEADER_HI,
HEADER_LO,
HEADER_CRC,
CHANNELCALIB_GAIN,
CHANNELCALIB_RED,
CHANNELCALIB_GREEN,
CHANNELCALIB_BLUE,
PIXEL,
FLETCHER1,
FLETCHER2,
FLETCHER_EXT
};
AwaProtocol state = AwaProtocol::HEADER_A;
const int headerSize = 6;
const int trailerSize = 3;
const int calibInfoSize = 4;
int bytesRead = 0;
bool isVersion2 = false;
bool isChannelCalib = false;
uint8_t CRC = 0;
int count = 0;
int currentPixel = 0;
uint16_t fletcher1 = 0;
uint16_t fletcher2 = 0;
uint16_t fletcherExt = 0;
#ifdef THIS_IS_RGBW
RgbwColor inputColor;
uint8_t wChannel[256];
uint8_t rChannel[256];
uint8_t gChannel[256];
uint8_t bChannel[256];
#else
RgbColor inputColor;
#endif
bool ledsComplete = false;
// statistics
const int reportStatInterval_ms = reportStatInterval_s * 1000;
unsigned long curTime;
unsigned long stat_start = 0;
uint16_t stat_shown = 0;
uint16_t stat_frames = 0;
uint16_t stat_good = 0;
uint16_t stat_bad = 0;
uint16_t stat_bad_frame = 0;
uint16_t stat_bad_skip = 0;
uint16_t stat_bad_crc = 0;
uint16_t stat_bad_fletcher = 0;
uint16_t stat_final_shown = 0;
uint16_t stat_final_frames = 0;
uint16_t stat_final_good = 0;
uint16_t stat_final_bad = 0;
uint16_t stat_final_bad_frame = 0;
uint16_t stat_final_bad_skip = 0;
uint16_t stat_final_bad_crc = 0;
uint16_t stat_final_bad_fletcher = 0;
//Debugging
String inputString;
String inputErrorString;
String debugString;
void printStringHex(String string)
{
#ifndef ENABLE_STRIP
Serial2.println(string.length());
for (int i = 0; i < string.length(); ++i)
{
if (i % 36 == 0)
{
Serial2.println();
Serial2.print("[");
Serial2.print(i);
Serial2.print("] ");
}
if (string[i] < 16)
Serial2.print("0");
Serial2.print(string[i], HEX);
Serial2.print(":");
}
#endif
}
inline void showMe()
{
#ifdef ENABLE_STRIP
if (strip != NULL && strip->CanShow())
{
stat_shown++;
strip->Show();
}
#endif
}
// statistics
inline void showStats()
{
if (reportStats)
{
if (stat_frames > 0)
{
stat_final_shown = stat_shown;
stat_final_frames = stat_frames;
stat_final_good = stat_good;
stat_final_bad = stat_bad;
stat_final_bad_frame = stat_bad_frame;
stat_final_bad_skip = stat_bad_skip;
stat_final_bad_crc = stat_bad_crc;
stat_final_bad_fletcher = stat_bad_fletcher;
}
stat_start = curTime;
stat_shown = 0;
stat_frames = 0;
stat_good = 0;
stat_bad = 0;
stat_bad_frame = 0;
stat_bad_skip = 0;
stat_bad_crc = 0;
stat_bad_fletcher = 0;
String summary = String("FPS: ") + (stat_final_shown / reportStatInterval_s) +
" F-FPS: " + (stat_final_frames / reportStatInterval_s) +
" S: " + stat_final_shown +
" F: " + stat_final_frames +
" G: " + stat_final_good +
" B: " + stat_final_bad +
" (BF: " + stat_final_bad_frame +
" BS: " + stat_final_bad_skip +
" BC: " + stat_final_bad_crc +
" BFL: " + stat_final_bad_fletcher +
")";
#ifdef ENABLE_STRIP
Serial.println(summary);
#else
Serial2.println(summary);
#endif
}
}
void InitLeds(uint16_t ledCount, int pixelCount, bool channelCalibration = false)
{
if (ledBuffer != NULL)
delete ledBuffer;
ledBufferSize = pixelCount + (channelCalibration ? calibInfoSize : 0);
ledBuffer = new uint8_t[ledBufferSize];
#ifdef ENABLE_STRIP
if (strip != NULL)
delete strip;
strip = new LED_DRIVER(ledCount, DATA_PIN);
strip->Begin();
#endif
}
inline void processSerialData()
{
while (Serial.available()) {
char input = Serial.read();
++bytesRead;
#ifndef ENABLE_STRIP
if (reportInput)
inputString += input;
#endif
switch (state)
{
case AwaProtocol::HEADER_A:
if (input == 'A')
{
state = AwaProtocol::HEADER_w;
}
break;
case AwaProtocol::HEADER_w:
if (input == 'w')
state = AwaProtocol::HEADER_a;
else
{
state = AwaProtocol::HEADER_A;
}
break;
case AwaProtocol::HEADER_a:
if (input == 'a')
{
isVersion2 = false;
state = AwaProtocol::HEADER_HI;
}
else if (input == 'A')
{
state = AwaProtocol::HEADER_HI;
isVersion2 = true;
}
else
{
state = AwaProtocol::HEADER_A;
}
break;
case AwaProtocol::HEADER_HI:
stat_frames++;
count = input << 8;
CRC = input;
fletcher1 = 0;
fletcher2 = 0;
fletcherExt = 0;
state = AwaProtocol::HEADER_LO;
break;
case AwaProtocol::HEADER_LO:
count += input + 1;
if (ledCount != count || isChannelCalib != isVersion2)
{
ledCount = count;
isChannelCalib = isVersion2;
pixelCount = ledCount * 3;
if (isChannelCalib)
prepareCalibration();
InitLeds(ledCount, pixelCount, isChannelCalib);
}
CRC = CRC ^ input ^ 0x55;
state = AwaProtocol::HEADER_CRC;
break;
case AwaProtocol::HEADER_CRC:
// Check, if incomplete package information was skipped, set bytesread to headersize and skip wrong input
if (bytesRead != headerSize)
{
stat_bad_skip++;
bytesRead = headerSize;
}
currentPixel = 0;
if (CRC == input)
{
state = AwaProtocol::PIXEL;
}
else
{
// CRC failure
stat_bad++;
stat_bad_crc++;
state = AwaProtocol::HEADER_A;
}
break;
case AwaProtocol::PIXEL:
ledBuffer[currentPixel++] = input;
if (currentPixel == pixelCount)
{
if (isChannelCalib)
state = AwaProtocol::CHANNELCALIB_GAIN;
else
state = AwaProtocol::FLETCHER1;
}
break;
case AwaProtocol::CHANNELCALIB_GAIN:
ledBuffer[currentPixel++] = input;
state = AwaProtocol::CHANNELCALIB_RED;
break;
case AwaProtocol::CHANNELCALIB_RED:
ledBuffer[currentPixel++] = input;
state = AwaProtocol::CHANNELCALIB_GREEN;
break;
case AwaProtocol::CHANNELCALIB_GREEN:
ledBuffer[currentPixel++] = input;
state = AwaProtocol::CHANNELCALIB_BLUE;
break;
case AwaProtocol::CHANNELCALIB_BLUE:
ledBuffer[currentPixel++] = input;
state = AwaProtocol::FLETCHER1;
break;
case AwaProtocol::FLETCHER1:
fletcher1 = input;
state = AwaProtocol::FLETCHER2;
break;
case AwaProtocol::FLETCHER2:
fletcher2 = input;
state = AwaProtocol::FLETCHER_EXT;
break;
case AwaProtocol::FLETCHER_EXT:
fletcherExt = input;
ledsComplete = true;
state = AwaProtocol::HEADER_A;
break;
}
}
}
void setup()
{
// Init serial port
int bufSize = Serial.setRxBufferSize(SERIAL_SIZE_RX);
Serial.begin(serialSpeed);
Serial.setTimeout(50);
#ifndef ENABLE_STRIP
Serial2.begin(serial2Speed);
Serial2.println();
Serial2.println("Welcome!");
Serial2.println("Hyperion Awa driver " + version);
Serial2.println("!!! Debug Output !!!");
#endif
// Display config
Serial.println();
Serial.println("Welcome!");
Serial.println("Hyperion Awa driver " + version);
Serial.print("(Build: ");
Serial.print(__DATE__);
Serial.print(" ");
Serial.print(__TIME__);
Serial.println(")");
// first LED info
if (skipFirstLed)
Serial.println("First LED: disabled");
else
Serial.println("First LED: enabled");
// RGBW claibration info
#ifdef THIS_IS_RGBW
#ifdef COLD_WHITE
Serial.println("Default color mode: RGBW cold");
#else
Serial.println("Default color mode: RGBW neutral");
#endif
prepareCalibration();
#else
Serial.println("Color mode: RGB");
#endif
InitLeds(ledCount, pixelCount);
}
void prepareCalibration()
{
#ifdef THIS_IS_RGBW
// prepare LUT calibration table, cold white is much better than "neutral" white
for (uint32_t i = 0; i < 256; i++)
{
// color calibration
float red = rCorrection * i; // adjust red
float green = gCorrection * i; // adjust green
float blue = bCorrection * i; // adjust blue
wChannel[i] = (uint8_t)round(min(whiteLimit * i, 255.0f));
rChannel[i] = (uint8_t)round(min(red / 0xFF, 255.0f));
gChannel[i] = (uint8_t)round(min(green / 0xFF, 255.0f));
bChannel[i] = (uint8_t)round(min(blue / 0xFF, 255.0f));
}
Serial.write("RGBW calibration. White limit(%): ");
Serial.print(whiteLimit * 100.0f);
Serial.write(" %, red: ");
Serial.print(rCorrection);
Serial.write(" , green: ");
Serial.print(gCorrection);
Serial.write(" , blue: ");
Serial.print(bCorrection);
Serial.println();
#endif
}
void loop()
{
curTime = millis();
#ifdef __AVR__
// nothing , USART Interrupt is implemented
ESPserialEvent();
#else
// ESP8266 polling
ESPserialEvent();
#endif
if (ledsComplete)
{
#ifndef ENABLE_STRIP
if (reportInput)
{
Serial2.println();
Serial2.print("<input> L: ");
printStringHex(inputString);
Serial2.println("<\input>");
inputString = "";
Serial2.print("bytesRead: ");
Serial2.print(bytesRead);
Serial2.print(" , currentPixel: ");
Serial2.print(currentPixel);
Serial2.print(" ,pixelCount: ");
Serial2.print(pixelCount);
Serial2.println();
}
#endif
int frameSize = headerSize + ledBufferSize + trailerSize;
if (bytesRead > frameSize)
{
//Add number of frames ignored on top of frame
int frames = bytesRead / frameSize;
stat_frames += frames;
//Count frame plus frames ignored as bad frames
int badFrames = frames + 1;
stat_bad += badFrames;
stat_bad_frame += badFrames;
}
else
{
#ifdef ENABLE_CHECK_FLETCHER
//Test if content is valid
uint16_t item = 0;
uint16_t fletch1 = 0;
uint16_t fletch2 = 0;
uint16_t fletchExt = 0;
while (item < ledBufferSize)
{
fletch1 = (fletch1 + (uint16_t)ledBuffer[item]) % 255;
fletch2 = (fletch2 + fletch1) % 255;
fletcherExt = (fletcherExt + ((uint16_t)ledBuffer[item] ^ (item))) % 255;
++item;
}
if ((fletch1 == fletcher1) && (fletch2 == fletcher2) && (ledBuffer[item-1] == (fletcherExt != 0x41) ? fletcherExt : 0xaa))
{
#endif
stat_good++;
uint16_t startLed = 0;
if (skipFirstLed)
{
#ifdef ENABLE_STRIP
#ifdef THIS_IS_RGBW
strip->SetPixelColor(startLed, RgbwColor(0, 0, 0, 0));
#else
strip->SetPixelColor(startLed, RgbColor(0, 0, 0));
#endif
#endif
startLed = 1;
}
for (uint16_t led = startLed; led < ledCount; ++led)
{
inputColor.R = ledBuffer[led * 3];
inputColor.G = ledBuffer[led * 3 + 1];
inputColor.B = ledBuffer[led * 3 + 2];
#ifdef THIS_IS_RGBW
inputColor.W = min(rChannel[inputColor.R],
min(gChannel[inputColor.G],
bChannel[inputColor.B]));
inputColor.R -= rChannel[inputColor.W];
inputColor.G -= gChannel[inputColor.W];
inputColor.B -= bChannel[inputColor.W];
inputColor.W = wChannel[inputColor.W];
#endif
#ifdef ENABLE_STRIP
strip->SetPixelColor(led, inputColor);
#endif
}
showMe();
yield();
#ifdef THIS_IS_RGBW
if (isChannelCalib)
{
uint8_t incoming_gain = ledBuffer[pixelCount];
uint8_t incoming_red = ledBuffer[pixelCount + 1];
uint8_t incoming_green = ledBuffer[pixelCount + 2];
uint8_t incoming_blue = ledBuffer[pixelCount + 3];
float final_limit = (incoming_gain != 255) ? incoming_gain / 255.0f : 1.0f;
if (rCorrection != incoming_red || gCorrection != incoming_green || bCorrection != incoming_blue || whiteLimit != final_limit)
{
rCorrection = incoming_red;
gCorrection = incoming_green;
bCorrection = incoming_blue;
whiteLimit = final_limit;
prepareCalibration();
}
}
#endif
#ifdef ENABLE_CHECK_FLETCHER
}
else
{
stat_bad++;
stat_bad_fletcher++;
}
#endif
}
bytesRead = 0;
state = AwaProtocol::HEADER_A;
ledsComplete = false;
}
if ((curTime - stat_start > reportStatInterval_ms))
{
if (stat_frames > 0)
{
showStats();
}
}
}
#ifdef __AVR__
void serialEvent()
{
processSerialData();
}
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
void ESPserialEvent()
{
processSerialData();
}
#endif