/*
Name : Adyen IPP Staging Terminal State Indicator
Version : 0.8-RP2040 only
Date : 2025-03-22
Author : Bas van Ritbergen <[email protected]>
RP2040 : Make sure to use Mbed OS setup for the MCU
*/
// Basic defaults
#define CONFIG_IDENTIFIER "ADYENLED"
#define IDENTIFIER_DEFAULT "STGSTRIP"
#define NUM_LEDS_PER_STRIP_DEFAULT 48
#define NUM_STRIPS_DEFAULT 6
#define NUM_GROUPS_PER_STRIP_DEFAULT 6
#define SPACER_WIDTH_DEFAULT 1
#define MAX_TERMINALS 48
#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 0.8
// Define max length for Input/Ouput buffers:
#define MAX_INPUT_LEN 50
#define MAX_OUTPUT_LEN 60
#define RP2040 1
#define MCU "RP2040"
#define CPULED 25
// We definitely need these libraries
#include <Arduino.h>
#include <FastLED.h>
#include "LittleFS_Mbed_RP2040.h"
#include "mbed.h"
#define HAS_LittleFS true
#define _LFS_LOGLEVEL_ 1
#define RP2040_FS_SIZE_KB 16
#define FORCE_REFORMAT false
#define CONFIG_FILENAME MBED_LITTLEFS_FILE_PREFIX"/config.bin"
LittleFS_MBED *mbed_FS;
// Figure MCU type/serial
#include <MicrocontrollerID.h>
char MCUid [41];
// Declare LedStrip control arrays
//CRGB leds[NUM_STRIPS_DEFAULT+1][NUM_LEDS_PER_STRIP_DEFAULT];
// We cannot dynamically change this without crashing... :-(
CRGB leds[NUM_STRIPS_DEFAULT+1][144];
//leds = new CRGB[NUM_STRIPS_DEFAULT+1][144];
// 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;
// For heartbeat-led on RP2040-zero (array with 1 position)
CRGB cpuled[1];
// HelpText
const char HelpText[850] 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"
" M:<State><State><State>... Set state for multiple Terminals, sequentually listed\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 shelf, [t]erminals per shelf, 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 = 38;
const uint8_t MSG_MAX_SIZE = 50;
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" }, // 4
{ "Display configuration:\n" }, // 5
{ "Save configuration: " }, // 6
{ "Configuration saved in EEPROM.\n" }, // 7
{ "Reset all Terminal states.\n" }, // 8
{ "Showing startup loop.\n" }, // 9
{ "ERROR: command unknown.\n" }, // 10
{ "SYNTAX ERROR: Use <A-Z>, H for help.\n" }, // 11
{ "Identifier : " }, // 12
{ "LEDs per shelf : " }, // 13
{ "Terminals per shelf : " }, // 14
{ "Amount of shelves : " }, // 15
{ "Spacer width : " }, // 16
{ "Blinking interval : " }, // 17
{ "No EEPROM or FLASH on this MCU\n" }, // 18 !!!!
{ "Rebooting contoller...\n" }, // 19
{ "Load configuration: " }, // 20
{ "All terminals set to state " }, // 21
{ "Configuration loaded\n" }, // 22
{ "* Opening Failed\n" }, // 23
{ "* Writing failed\n" }, // 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 ????
{ "Testing EEPROM availability & contents\n" }, // 33 !!!!
{ "Failed to initialise EEPROM, Restarting...\n" }, // 34 !!!!
{ "LITTLEFS Mount Failed, Restarting...\n" }, // 35
{ "Ok\n"}, // 36
{ "Syntax error: Use M:<STATE><STATE<<STATE>...\n" } // 37
};
// Setup MCU
void setup() {
// Setup USB-serial port
Serial.begin(115200);
//Serial.setTimeout(0);
// Initialize status led, set to blue to show we are waitong for input
// FastLED.addLeds<WS2812B, 16, GRB>(cpuled, 1);
// FastLED.addLeds<WS2812B, 23, GRB>(cpuled, 1);
FastLED.addLeds<WS2812B, 24, GRB>(cpuled, 1);
cpuled[0] = CRGB::Blue;
FastLED.show();
while (!Serial) {
// wait for serial port to connect.
// Flash the status led green to indicate we are ready to start.
delay(750);
digitalWrite(CPULED, HIGH);
cpuled[0] = CRGB::Green;
FastLED.show();
delay(750);
digitalWrite(CPULED, LOW);
cpuled[0] = CRGB::Black;
FastLED.show();
}
// Set led to blue and Show welcome to let us know the controller is booting.
// Also show some details abour the MCU and the codeversion
cpuled[0] = CRGB::Blue;
FastLED.show();
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);
Serial.println();
// Initialize LittleFS if available
mbed_FS = new LittleFS_MBED();
if (!mbed_FS->init()) {
printMSG(35);
delay(2000);
//RebootMCU();
} else {
loadConfiguration();
}
// Enable current config (default or loaded from EEPRON/Flash)
//updateConfiguration();
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
// This cannot yet be change in runtime without chrashes or FastLED to break...
// FastLED class expects the pin to be a constant!
FastLED.addLeds<WS2812B, 2, GRB>(leds[0], LedConfig.numLedsPerStrip);
FastLED.addLeds<WS2812B, 3, GRB>(leds[1], LedConfig.numLedsPerStrip);
FastLED.addLeds<WS2812B, 4, GRB>(leds[2], LedConfig.numLedsPerStrip);
FastLED.addLeds<WS2812B, 5, GRB>(leds[3], LedConfig.numLedsPerStrip);
FastLED.addLeds<WS2812B, 6, GRB>(leds[4], LedConfig.numLedsPerStrip);
FastLED.addLeds<WS2812B, 7, GRB>(leds[5], LedConfig.numLedsPerStrip);
//startupAnimation();
StartupLoop();
// Show help & config:
// ShowHelp();
displayConfiguration() ;
// Ready to go, show prompt.
Serial.print("> ");
}
// Main loop
void loop() {
while (Serial.available() > 0) {
CRGB curcol = cpuled[0];
cpuled[0] = CRGB::Blue;
FastLED.show();
char c = Serial.read();
if (c == '\n' || c == '\r') {
inputBuffer[bufferIndex] = '\0'; // Null-terminate the string
Serial.println();
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);
}
cpuled[0] = curcol;
SetLEDs();
}
// delay(10);
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);
}
}
}
char* readFile(const char * path) {
// Serial.print("Reading file: ");
// Serial.println(path);
FILE *file = fopen(path, "r");
if (!file) {
printMSG(23);
return 0;
}
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
if (fileSize == 0) {
printMSG(25);
fclose(file);
return 0;
}
char *data = new char[fileSize];
int pos=0;
char c;
uint32_t numRead = 1;
while (numRead) {
numRead = fread((uint8_t *) &c, sizeof(c), 1, file);
if (numRead)
data[pos++]=(char)c;
}
fclose(file);
return data;
}
bool writeFile(const char * path, const char * message, size_t messageSize) {
FILE *file = fopen(path, "w");
if (!file) {
printMSG(23);
return false;
}
if (!fwrite((uint8_t *) message, 1, messageSize, file)) {
printMSG(24);
return false;
}
fclose(file);
return true;
}
void loadConfiguration() {
char *buffer;
buffer = readFile(CONFIG_FILENAME);
if (buffer != 0) {
// DeSeriallize buffer into LedData Struct
int structSize=sizeof(LedData);
memcpy(&LedConfig, buffer, structSize);
}
}
bool saveConfiguration() {
// Seriallize LedData Struct into char-array so we can save it
char buffer[sizeof(LedData)];
memcpy(buffer, &LedConfig, sizeof(LedData));
writeFile(CONFIG_FILENAME, buffer, sizeof(buffer));
}
// 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);
// // Reconfiguring these cause a crash... :-(
// FastLED.addLeds<WS2812B, 2, GRB>(leds[0], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 3, GRB>(leds[1], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 4, GRB>(leds[2], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 5, GRB>(leds[3], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 6, GRB>(leds[4], LedConfig.numLedsPerStrip);
// FastLED.addLeds<WS2812B, 7, GRB>(leds[5], 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;
String AllStates;
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) {
// Display curren configuration
case 'D':
printMSG(5);
displayConfiguration();
break;
// Set configuration
case 'C':
SetConfigParameters(Data);
break;
// Store current configuration
case 'S':
printMSG(6);
saveConfiguration();
printMSG(36);
break;
// Load configuration from Flash/EEPROM
case 'L':
printMSG(20);
loadConfiguration();
printMSG(22);
break;
// Set Terminal state
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;
// Set state for all Terminals
case 'A':
test = sscanf(Data, ":%d", &State);
if (test == 1) {
if (State >= 0 && State <= 9) {
printMSG(21);
Serial.println(State);
for(int i=0; i < MAX_TERMINALS; i++){
setGroupState(i,State);
}
} else {
printMSG(1);
}
} else
printMSG(4);
break;
// Set mass state, a digit for each terminal (48 max)
case 'M':
char ST;
Serial.println();
if ( Data[0] == ':') {
int Term=1;
while ( Data[Term] != 0 && Term<=MAX_TERMINALS ) {
// Serial.print("Set Terminal ");
// Serial.print(i);
ST=Data[Term];
State = atoi(&ST);
if (State >= 0 && State <= 9) {
// Serial.print(" to state ");
// Serial.print(State);
setGroupState(Term -1,State);
}
Term++;
// Serial.println();
}
// Serial.println();
} else
printMSG(37);
break;
// Reset all states to off
case 'X':
printMSG(8);
for(int i=0; i < MAX_TERMINALS; i++){
setGroupState(i,0);
}
SetAllLEDs(CRGB::Black);
break;
// Show startup loop
case 'W':
printMSG(9);
StartupLoop();
break;
// Reboot controller
case 'R':
printMSG(19);
RebootMCU();
break;
// Show help
case 'H':
ShowHelp();
break;
default:
printMSG(10);
break;
}
} else
printMSG(11);
}
void RebootMCU() {
NVIC_SystemReset();
}
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 ConfigItem = Data[0];
char *Value = Data +2;
// 1111
// Data: 012356890123
// Data: i:bastest
// Value: 0123456789
if (Data[1] == ':') {
switch (ConfigItem) {
// set Idenitfier
case 'i':
Serial.println();
printMSG(12);
strcpy(LedConfig.identifier,Value);
Serial.println(LedConfig.identifier);
break;
// set led per strip
case 'l':
Serial.println();
printMSG(13);
LedConfig.numLedsPerStrip = atoi(Value);
Serial.println(LedConfig.numLedsPerStrip);
break;
// set terminals per shelf
case 't':
Serial.println();
printMSG(14);
LedConfig.numGroupsPerStrip = atoi(Value);
Serial.println(LedConfig.numGroupsPerStrip);
break;
// set number of shelves
case 's':
Serial.println();
printMSG(15);
LedConfig.numStrips = atoi(Value);
Serial.println(LedConfig.numStrips);
break;
// set spacer width
case 'w':
Serial.println();
printMSG(16);
LedConfig.spacerWidth = atoi(Value);
Serial.println(LedConfig.spacerWidth);
break;
// Set blink interval
case 'b':
Serial.println();
printMSG(17);
LedConfig.blinkinterval = atoi(Value);
Serial.println(LedConfig.blinkinterval);
break;
// set colors
// 1111
// Data: 012356890123
// Data: c:1:0000FF
// Value: 0123456789
case 'c':
char buffer[8];
Serial.println();
if (Value[1] == ':') {
char StateChar = Data[2];
int state = atoi(&StateChar);
if ( state >0 and state<5) {
char *Color = Data +5;
uint32_t RGB=strtoul(Color, NULL, 16);
Serial.println(RGB);
if (RGB >0 and RGB < 0xFFFFFFFF) {
LedConfig.state_color[state] = RGB;;
Serial.print("Color for state ");
Serial.print(state);
Serial.print(" is set to : ");
sprintf(buffer, "%06X", LedConfig.state_color[state]);
Serial.print(buffer);
Serial.println (" (BBGGRR)");
} else {
Serial.println("invalid color");
}
}
else {
Serial.println("invalid state, 1-4 only");
}
}
else {
Serial.println("syntax error: use Cc:<STATE_1-4>:<BBGGRR HEX>\n");
}
break;
default:
printMSG(11);
break;
}
} 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);
char buffer[6];
for (int state=1; state<5; state++){
Serial.print("Color state ");
Serial.print(state);
Serial.print(" : ");
sprintf(buffer, "%06X", LedConfig.state_color[state]);
Serial.print(buffer);
Serial.println (" (BBGGRR)");
Serial.println((uint32_t)LedConfig.state_color[state]);
}
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:
BlinkState[group] = false;
BlinkColor[group] = CRGB::White;
break;
}
}
void SetLEDGroupColor(int group, CRGB color) {
int stripIndex = (group / LedConfig.numGroupsPerStrip);
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 animation
// Blue ><><
uint8_t DELAY (LedConfig.numLedsPerStrip / 6);
CRGB color = 0x00FF00;
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::Green);
FastLED.show();
delay(75);
for(int i = 0; i < 12; i++) {
FadeAll(0,LedConfig.numLedsPerStrip);
FastLED.show();
delay(65);
}
// All Off
//SetAllLEDs(CRGB::Black);
FastLED.clear(true);
}
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;
digitalWrite(CPULED, blink);
cpuled[0] = blink ==1 ? CRGB::Red : CRGB::Black;
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(); // Update on change only
}
// Update 4 times within a flashing cyclus.
if ( millis() - lastToggleTimes >= LedConfig.blinkinterval/4) {
FastLED.show(); // Updat on change only
}
// FastLED.show(); // Update every loop, to fastfor WS2812B?
}