/*
Name : Adyen IPP Staging Terminal State Indicator
Version : 0.7
Date : 2024-03-18
Author : Bas van Ritbergen <[email protected]>
Note:
Make sure to use an Arduino Mbed OS setup for the MCU : ATmega(AVR) or RP2040 (others might work as well but untested)
*/
// extern "C" {
// #include "pico.h"
// #include "pico/time.h"
// #include "pico/bootrom.h"
// }
// Set MCU type
#ifndef AVR
#define RP2040
#endif
// 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 48
#define BLINK_INTERVAL 250
#define COLOR_STATE_1 CRGB::Green
#define COLOR_STATE_2 CRGB::DarkOrange
#define COLOR_STATE_3 CRGB::Red
#define COLOR_STATE_4 0x000080
// Define max length for Input/Ouput buffers:
#define MAX_INPUT_LEN 15
#define MAX_OUTPUT_LEN 60
// ##########################################################################################################################
// No configurable items below
#define CONFIG_FILENAME "/config.bin"
#ifdef AVR
#include <Arduino.h>
#include <EEPROM.h>
#ifndef BOARD_NAME
#define BOARD_NAME "Unknown AVR"
#endif
#endif
#ifdef RP2040
#ifndef BOARD_NAME
#define BOARD_NAME "Unknown RP2040"
#endif
#define LFS_MBED_RP2040_VERSION_MIN_TARGET "LittleFS_Mbed_RP2040 v1.1.0"
#define LFS_MBED_RP2040_VERSION_MIN 1001000
#define _LFS_LOGLEVEL_ 1
#define RP2040_FS_SIZE_KB 16
#define FORCE_REFORMAT false
// #include "LittleFS_Mbed_RP2040.h"
// LittleFS_MBED *mbed_FS;
// #include <kvstore_global_api.h>
// #include <mbed_error.h>
#endif
// We definitely need these libraries
#include <FastLED.h>
#include <stdlib.h>
// Figure MCU type/serial
#include <MicrocontrollerID.h>
char MCUid [41];
// Declare LedStrip control arrays
//CRGB leds[NUM_STRIPS_DEFAULT][NUM_LEDS_PER_STRIP_DEFAULT];
CRGB leds[NUM_STRIPS_DEFAULT][60];
// 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[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[640] 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\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 = 21;
const uint8_t MSG_MAX_SIZE = 48;
const char messages [MSG_MAX_NUMBER] [MSG_MAX_SIZE] PROGMEM = {
{ "-=[ USB LedStrip ]=-\n" },
{ "Invalid state, use 0-9\n" },
{ "Invalid Terminal-ID, use 1-48\n" },
{ "Syntax error: Use T<TERMINAL-ID>:<STATE>\n" },
{ "Syntax error: Use A:<STATE>\n" },
{ "\nDisplay configuration:\n" },
{ "\nSave configuration: " },
{ "Configuration saved in EEPROM.\n" },
{ "\nReset all Terminal states.\n" },
{ "\nShowing startup loop.\n" },
{ "\nERROR: command unknown.\n" },
{ "\nSYNTAX ERROR: Use <A-Z>, H for help.\n" },
{ "Identifier : " },
{ "LEDs per shelve : " },
{ "Terminals per shelve : " },
{ "Amount of shelves : " },
{ "Spacer width : " },
{ "Blinking interval : " },
{ "\nNo EEPROM or FLASH on this MCU\n"},
{ "\nRebooting contoller...\n"},
{ ""}
};
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);
}
}
}
// Setup MCU
void setup() {
// Setup SB -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);
cpuled[0] = CRGB::Blue;
FastLED.show();
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(750);
cpuled[0] = CRGB::Green;
FastLED.show();
delay(750);
cpuled[0] = CRGB::Black;
FastLED.show();
}
// Set led to blue and Show welcome to let us knwo the controller is booting.
cpuled[0] = CRGB::Blue;
FastLED.show();
printMSG(0);
Serial.print (BOARD_NAME);
MicroID.getUniqueIDString(MCUid);
Serial.println(MCUid);
// mbed_FS = new LittleFS_MBED();
// if (!mbed_FS->init()) {
// Serial.println("LITTLEFS Mount Failed");
// return;
// }
loadConfiguration();
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);
}
}
// delay(10);
SetLEDs();
}
#ifdef AVR
void writeStringToEEPROM(int addrOffset, const String &strToWrite) {
byte len = strToWrite.length();
EEPROM.write(addrOffset, len);
for (int i = 0; i < len; i++) {
EEPROM.write(addrOffset + 1 + i, strToWrite[i]);
}
}
String readStringFromEEPROM(int addrOffset) {
int newStrLen = EEPROM.read(addrOffset);
char data[newStrLen + 1];
for (int i = 0; i < newStrLen; i++) {
data[i] = EEPROM.read(addrOffset + 1 + i);
}
data[newStrLen] = '\0';
return String(data);
}
#endif
void loadConfiguration() {
#ifdef AVR
if ( readStringFromEEPROM(0) == CONFIG_IDENTIFIER ) {
EEPROM.get(10, LedConfig);
updateConfiguration();
}
#endif
#ifdef RP2040
// FILE *configFile = fopen(CONFIG_FILENAME, "r");
// if (!configFile) {
// Serial.println("Failed to open config file for reading");
// return;
// }
// fseek(configFile, 0, SEEK_END);
// long fileSize = ftell(configFile);
// fseek(configFile, 0, SEEK_SET);
// if (fileSize == 0) {
// Serial.println("ERROR, Config file is empty");
// fclose(configFile);
// return;
// }
// size_t bytesRead = fread((uint8_t*)&LedConfig, fileSize, 1, configFile);
// if (bytesRead != 1) {
// Serial.println("ERROR, Failed to read config data");
// fclose(configFile);
// return;
// }
// fclose(configFile);
// Serial.println("Configuration loaded");
// updateConfiguration();
printMSG(18);
#endif
#ifndef AVR
#ifndef RP2040
printMSG(18);
#endif
#endif
}
void saveConfiguration() {
#ifdef AVR
writeStringToEEPROM(0, CONFIG_IDENTIFIER);
EEPROM.put(10, LedConfig);
printMSG(7);
#endif
#ifdef RP2040
// FILE *configFile = fopen(CONFIG_FILENAME, "w");
// if (!configFile) {
// Serial.println("ERROR, Failed to open config file for writing");
// return;
// }
// if (!fwrite((uint8_t *) (const char*)&LedConfig, 1, sizeof(LedConfig), configFile)) {
// Serial.println("ERROR, Writing failed");
// fclose(configFile);
// return;
// }
// fclose(configFile);
// Serial.println("Configuration stored");
printMSG(18);
#endif
#ifndef AVR
#ifndef RP2040
printMSG(18);
#endif
#endif
}
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);
// }
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[6], 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) {
sprintf(output,"All terminals set to state %d.",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 '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() {
// extern "C" void mbed_reset();
// RP2040::Reboot();
}
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);
//FastLED.show();
}
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;
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;
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();
}
}