/*
Name : Adyen IPP Staging Terminal State Indicator
Version : 0.8 -0 AVR ONLY !
Date : 2025-03-22
Author : Bas van Ritbergen <[email protected]>
*/
// Basic defaults
#define CONFIG_IDENTIFIER "ADYENLED"
#define IDENTIFIER_DEFAULT "STGSTRIP"
#define NUM_LEDS_PER_STRIP_DEFAULT 48
#define NUM_STRIPS_DEFAULT 3
#define NUM_GROUPS_PER_STRIP_DEFAULT 6
#define SPACER_WIDTH_DEFAULT 1
#define MAX_TERMINALS 24
#define BLINK_INTERVAL 200
#define COLOR_STATE_1 CRGB::Green
#define COLOR_STATE_2 CRGB::DarkOrange
#define COLOR_STATE_3 CRGB::Red
#define COLOR_STATE_4 0x000080
#define DEBUG true
// ###########################################################################
// No configurable items below
#define VERSION 1.8
// Define max length for Input/Ouput buffers:
#define MAX_INPUT_LEN 16
#define MAX_OUTPUT_LEN 50
// Auto-set MCU type
#define AVR 1
#define MCU "AVR"
// We definitely need these libraries
#include <Arduino.h>
#include <FastLED.h>
#include <avr/wdt.h>
#include <EEPROM.h>
#define HAS_EEPROM true
#ifndef BOARD_NAME
#define BOARD_NAME "Unknown AVR"
#endif
// Figure MCU type/serial
#include <MicrocontrollerID.h>
// Declare LedStrip control arrays
//CRGB leds[NUM_STRIPS_DEFAULT+1][NUM_LEDS_PER_STRIP_DEFAULT];
CRGB leds[NUM_STRIPS_DEFAULT+1][288];
// Config data is conviently stored in a struct (to easy store and retrieve from EEPROM/Flash)
// Set defaults, they will be overwritten by load from EEPROM
struct LedData {
//char *identifier = nullptr;
char identifier[16] = IDENTIFIER_DEFAULT;
uint16_t numLedsPerStrip = NUM_LEDS_PER_STRIP_DEFAULT;
uint8_t numStrips = NUM_STRIPS_DEFAULT;
uint8_t numGroupsPerStrip = NUM_GROUPS_PER_STRIP_DEFAULT;
uint8_t spacerWidth = SPACER_WIDTH_DEFAULT;
uint16_t blinkinterval = BLINK_INTERVAL;
CRGB state_color[5] = {CRGB::Black, COLOR_STATE_1, COLOR_STATE_2, COLOR_STATE_3, COLOR_STATE_4 };
} LedConfig = {};
// Input data from serial is stores in an array for further processing.
char inputBuffer[MAX_INPUT_LEN + 1]; // +1 for null terminator
uint8_t bufferIndex = 0;
// For blinking feature we need some extra global parameters
bool BlinkState[MAX_TERMINALS] = {0};
CRGB BlinkColor[MAX_TERMINALS] = {0};
bool blink=false;
uint32_t lastToggleTimes;
// HelpText
const char HelpText[705] PROGMEM =
" Syntax:\n"
" H This help\n"
" T<TermID>:<State> Set Terminal state. TermID: 1-48 and State: 0-9\n"
" X Set all states to off (same as sending'A:0'\n"
" A:<State> Set state for all Terminals, state (0-9)\n"
" D Show current configuration\n"
" C<iltswb>:value Set config for [i]dentifier, [l]eds per shelve, [t]erminals per shelve, amount of [s]helves, spacer-[w]idth, [b]link-interval.\n"
" S Store current configuration in EEPROM/FLASH\n"
" L Load stored cofiguration from EEPROM/FLASH\n"
" R Reboot controller (Disconnects serial!)\n"
" W Show startup loop\n"
"\n";
// All the messages are stored in program-memory to save CPU-memory (especially for AVR type mcu's)
const uint8_t MSG_MAX_NUMBER = 35;
const uint8_t MSG_MAX_SIZE = 48;
const char messages [MSG_MAX_NUMBER] [MSG_MAX_SIZE] PROGMEM = {
{ "-=[ USB LedStrip ]=-\n" }, // 0
{ "Invalid state, use 0-9\n" }, // 1
{ "Invalid Terminal-ID, use 1-48\n" }, // 2
{ "Syntax error: Use T<TERMINAL-ID>:<STATE>\n" }, // 3
{ "Syntax error: Use A:<STATE>\n" }, // 4x
{ "\nDisplay configuration:\n" }, // 5
{ "\nSave configuration: " }, // 6
{ "Configuration saved in EEPROM.\n" }, // 7
{ "\nReset all Terminal states.\n" }, // 8
{ "\nShowing startup loop.\n" }, // 9
{ "\nERROR: command unknown.\n" }, // 10
{ "\nSYNTAX ERROR: Use <A-Z>, H for help.\n" }, // 11
{ "Identifier : " }, // 12
{ "LEDs per shelve : " }, // 13
{ "Terminals per shelve : " }, // 14
{ "Amount of shelves : " }, // 15
{ "Spacer width : " }, // 16
{ "Blinking interval : " }, // 17
{ "\nNo EEPROM or FLASH on this MCU\n" }, // 18
{ "\nRebooting contoller...\n" }, // 19
{ "\nLoad configuration: " }, // 20
{ "All terminals set to state" }, // 21
{ "Configuration loaded\n" }, // 22
{ "* Opening Failed" }, // 23
{ "* Writing failed" }, // 24
{ "ERROR, configfile is empty" }, // 25
{ "Valid config found in EEPROM, loading it.\n" }, // 26
{ "No config stored in EEPROM, using defaults.\n\n" },// 27
{ "Configuration stored in EEPROM.\n" }, // 28
{ "Version " }, // 29
{ "MCU : " }, // 30
{ "SN : " }, // 31
{ " compiled for " }, // 32
{ "\nTesting EEPROM availability & contents\n" }, // 33
{ "Failed to initialise EEPROM, Restarting...\n" }, // 34
};
// Setup MCU
void setup() {
char MCUid [41];
// Setup SB -serial port
Serial.begin(115200);
Serial.setTimeout(0);
// DEBUG('Ready, waiting fo serial to become active.');
while (!Serial) {
// wait for serial port to connect. Needed for native USB-serial port only.
// Flash the status led green to indicate we are ready to start.
delay(500);
}
// Show some details about the MCU and the codeversion
printMSG(0);
printMSG(29);
Serial.print(VERSION);
printMSG(32);
Serial.println(MCU);
Serial.println();
printMSG(30);
Serial.println(BOARD_NAME);
printMSG(31);
MicroID.getUniqueIDString(MCUid,16);
Serial.println(MCUid);
printMSG(33);
EEPROM.begin();
if (!eeprom_is_ready()){
printMSG(34);
delay(1000);
RebootMCU();
}
loadConfiguration();
// Enable current config (default or loaded from EEPRON/Flash)
updateConfiguration();
//startupAnimation();
StartupLoop();
// Show help & config:
// ShowHelp();
displayConfiguration() ;
// Ready to go, show prompt.
Serial.print("> ");
}
// Main loop
void loop() {
while (Serial.available() > 0) {
char c = Serial.read();
if (c == '\n' || c == '\r') {
inputBuffer[bufferIndex] = '\0'; // Null-terminate the string
checkInput(inputBuffer);
Serial.print("> ");
bufferIndex = 0;
} else if ( c == 0x08 ) { // Backspace
inputBuffer[bufferIndex--] = '\0';
Serial.print(c);
Serial.print(' ');
Serial.print(c);
} else if (bufferIndex < MAX_INPUT_LEN - 1 && c >= 0x20 && c < 0x7E && c!= 0x5C && c!=0x60 ) { // only printable chars, -1 to leave space for null terminator at end.
inputBuffer[bufferIndex++] = c;
Serial.print(c);
}
}
SetLEDs();
}
void DEBUGLOG(String *message){
if (DEBUG == true) {
Serial.println(*message);
}
}
void printMSG (int i) {
if (i <= MSG_MAX_NUMBER) {
const char *ptr = reinterpret_cast<const char*>(&messages[i]);
char chr;
if (!ptr)
return;
while ((chr = pgm_read_byte(ptr++))) {
Serial.print(chr);
}
}
}
void writeStringToEEPROM(int addrOffset, const String &strToWrite) {
byte len = strToWrite.length();
EEPROM.write(addrOffset, len);
int rlen = EEPROM.read(addrOffset);
for (int i = 0; i < len; i++) {
EEPROM.write(addrOffset + 1 + i, strToWrite[i]);
}
}
String readStringFromEEPROM(int addrOffset) {
int len = EEPROM.read(addrOffset);
char data[len + 1];
for (int i = 0; i < len; i++)
data[i] = EEPROM.read(addrOffset + 1 + i);
data[len] = '\0';
return (String) data;
}
void loadConfiguration() {
String check = readStringFromEEPROM(0x0);
if ( check == CONFIG_IDENTIFIER ) {
printMSG(26);
EEPROM.get(0x10, LedConfig);
} else {
printMSG(27);
}
}
void saveConfiguration() {
writeStringToEEPROM(0x0, CONFIG_IDENTIFIER);
EEPROM.put(0x10, LedConfig);
printMSG(28);
}
void updateConfiguration() {
// delete[] leds;
// CRGB leds[LedConfig.numStrips][LedConfig.numLedsPerStrip];
// leds = new CRGB[LedConfig.numStrips][LedConfig.numLedsPerStrip];
// Code below does not work as the FastLED class expects the pin to be a constant :-/
// FastLED.addLeds<[LEDTYPE], [PIN], [RGB-ORDER]>( [LED-array], [NUMBER OF LEDS]);
// for (const int n = 0; n < LedConfig.numStrips; n++) {
// FastLED.addLeds<WS2812, n+2, GRB>(leds[n], LedConfig.numLedsPerStrip);
// }
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
// pinMode(5, OUTPUT);
// pinMode(6, OUTPUT);
// pinMode(7, OUTPUT);
// pinMode(8, OUTPUT);
// pinMode(9, OUTPUT);
FastLED.addLeds<WS2812B, 2, GRB>(leds[1], LedConfig.numLedsPerStrip);
FastLED.addLeds<WS2812B, 3, GRB>(leds[2], LedConfig.numLedsPerStrip);
FastLED.addLeds<WS2812B, 4, GRB>(leds[3], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 5, GRB>(leds[4], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 6, GRB>(leds[5], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 7, GRB>(leds[7], LedConfig.numLedsPerStrip);
}
void checkInput(char input[16]) {
if (input[0] == 0) {
Serial.println("");
return;
}
char output[MAX_OUTPUT_LEN];
memset(output, '\0', sizeof(output));
int test=0;
int State;
int TermID;
// sprintf(output,"Input: [%s].",input);
// Serial.println(output);
char Command = input[0];
char *Data = input +1;
if (Command >= 'A' && Command <= 'Z') {
// sprintf(output,"Command [%c] with Data [%s]",Command, Data);
// Serial.println(output);
switch(Command) {
case 'C':
SetConfigParameters(Data);
break;
case 'T':
test = sscanf(Data, "%2d:%d", &TermID, &State);
if ( test == 2) {
if (TermID >= 1 && TermID <= MAX_TERMINALS) {
if (State >= 0 && State <= 9) {
setGroupState(TermID -1,State);
// sprintf(output,"Terminal %d state set to %d", TermID, State);
// Serial.println(output);
} else {
printMSG(1);
}
} else {
printMSG(2);
}
} else {
printMSG(3);
}
break;
case 'A':
test = sscanf(Data, ":%d", &State);
if (test == 1) {
if (State >= 0 && State <= 9) {
printMSG(21);
Serial.print(State);
for(int i=0; i < MAX_TERMINALS; i++){
setGroupState(i,State);
}
} else {
printMSG(1);
}
} else
printMSG(4);
break;
case 'D':
printMSG(5);
displayConfiguration();
break;
case 'S':
printMSG(6);
saveConfiguration();
break;
case 'L':
printMSG(20);
loadConfiguration();
updateConfiguration();
break;
case 'X':
printMSG(8);
for(int i=0; i < MAX_TERMINALS; i++){
setGroupState(i,0);
}
SetAllLEDs(CRGB::Black);
break;
case 'W':
printMSG(9);
StartupLoop();
break;
case 'R':
printMSG(19);
RebootMCU();
break;
case 'H':
ShowHelp();
break;
default:
printMSG(10);
break;
}
} else
printMSG(11);
}
void RebootMCU() {
wdt_enable(WDTO_15MS);
while (1);
}
void ShowHelp(){
const char *ptr = reinterpret_cast<const char*>(&HelpText);
char chr;
if (!ptr)
return;
while ((chr = pgm_read_byte(ptr++))) {
Serial.print(chr);
}
}
void SetConfigParameters(char *Data){
// char output[MAX_OUTPUT_LEN];
char Parameter = Data[0];
char *Value = Data +2;
if (Data[1] == ':') {
switch (Parameter) {
case 'i':
Serial.println();
printMSG(12);
// if sizeof(Value)
strcpy(LedConfig.identifier,Value);
Serial.println(LedConfig.identifier);
break;
case 'l':
Serial.println();
printMSG(13);
LedConfig.numLedsPerStrip = atoi(Value);
Serial.println(LedConfig.numLedsPerStrip);
break;
case 't':
Serial.println();
printMSG(14);
LedConfig.numGroupsPerStrip = atoi(Value);
Serial.println(LedConfig.numGroupsPerStrip);
break;
case 's':
Serial.println();
printMSG(15);
LedConfig.numStrips = atoi(Value);
Serial.println(LedConfig.numStrips);
break;
case 'w':
Serial.println();
printMSG(16);
LedConfig.spacerWidth = atoi(Value);
Serial.println(LedConfig.spacerWidth);
break;
case 'b':
Serial.println();
printMSG(17);
LedConfig.blinkinterval = atoi(Value);
Serial.println(LedConfig.blinkinterval);
break;
default:
printMSG(11);
break;
}
updateConfiguration();
} else {
printMSG(11);
}
}
void displayConfiguration() {
char output[MAX_OUTPUT_LEN];
memset(output, '\0', sizeof(output));
printMSG(12);
sprintf(output,"%s",LedConfig.identifier);
Serial.println(output);
printMSG(13);
sprintf(output,"%d",LedConfig.numLedsPerStrip);
Serial.println(output);
printMSG(14);
sprintf(output,"%d",LedConfig.numGroupsPerStrip);
Serial.println(output);
printMSG(15);
sprintf(output,"%d",LedConfig.numStrips);
Serial.println(output);
printMSG(16);
sprintf(output,"%d",LedConfig.spacerWidth);
Serial.println(output);
printMSG(17);
sprintf(output,"%d",LedConfig.blinkinterval);
Serial.println(output);
Serial.println();
}
void setGroupState(int group, int state) {
switch (state) {
case 0:
BlinkState[group] = false;
BlinkColor[group] = CRGB::Black;
break;
case 1:
case 2:
case 3:
case 4:
BlinkState[group] = false;
BlinkColor[group] = LedConfig.state_color[state];
break;
case 5:
case 6:
case 7:
case 8:
BlinkState[group] = true;
BlinkColor[group] = LedConfig.state_color[state -4];
break;
case 9:
BlinkColor[group] = CRGB::White;
break;
}
}
void SetLEDGroupColor(int group, CRGB color) {
int stripIndex = (group / LedConfig.numGroupsPerStrip)+1;
int groupIndex = group % LedConfig.numGroupsPerStrip;
int startLEDIndex = groupIndex * (LedConfig.numLedsPerStrip / LedConfig.numGroupsPerStrip);
int widthLED = (LedConfig.numLedsPerStrip / LedConfig.numGroupsPerStrip) - LedConfig.spacerWidth;
fill_solid(&(leds[stripIndex][startLEDIndex]), widthLED, color);
}
void FadeAll(int StartLed=1, int EndLed=LedConfig.numLedsPerStrip) {
for(int i = StartLed; i < EndLed; i++)
for(int n = 0; n < LedConfig.numStrips; n++)
leds[n][i].nscale8(200);
}
void StartupLoop() {
// Boot animmation only on 1st strip.
// Blue ><><
uint8_t DELAY (LedConfig.numLedsPerStrip / 8);
CRGB color = 0x0080FF;
for(int i = 0; i < LedConfig.numLedsPerStrip/2; i+=1) {
for(int n = 0; n < LedConfig.numStrips; n++) {
leds[n][i] = color;
leds[n][LedConfig.numLedsPerStrip -i -1] = color;
}
FastLED.show();
FadeAll(0,LedConfig.numLedsPerStrip);
delay(DELAY);
}
// White Flash
SetAllLEDs(CRGB::White);
FastLED.show();
delay(75);
for(int i = 0; i < 16; i++) {
FadeAll(0,LedConfig.numLedsPerStrip);
FastLED.show();
delay(65);
}
// All Off
SetAllLEDs(CRGB::Black);
}
void SetAllLEDs(CRGB color){
for(int n = 0; n < LedConfig.numStrips; n++)
fill_solid(leds[n], LedConfig.numLedsPerStrip, color);
FastLED.show();
}
void SetLEDs() {
// Check if it's time to toggle each group's state
if ( millis() - lastToggleTimes >= LedConfig.blinkinterval ) {
lastToggleTimes = millis(); // Update last toggle time
blink = blink == 0 ? 1 : 0;
for(int i=0; i < LedConfig.numGroupsPerStrip*LedConfig.numStrips; i++){
if (BlinkState[i] == true) {
if (blink == true){
SetLEDGroupColor(i, BlinkColor[i]);
} else {
SetLEDGroupColor(i, CRGB::Black);
}
} else {
SetLEDGroupColor(i, BlinkColor[i]);
}
}
FastLED.show();
}
}