Rfid reader with ws2812 leds and sound from sd card

Making a project for grandkids where they scan a rfid sticker and the object lights one or two ws2812 led strips depending on uid and plays a sound. I have been trying for months to make the led non-blocking so the two strips run at the same time and the sound plays from sd card. I have tried all the examples and even if I get the lights to run at the same time when I combine it with the rfid card reader and the audio the lights either do not light up at all or only does the first light of the wipe then starts the sound. In the code that I will post it works as I would like it except that the sound plays after all the light animations are done. I worked around the two strips since one just turns on and stays on as the other does animation, then both turn off.


```cpp
//Vader.ino
// Arduino IDE 2.3.5
// Adafruit NeoPixel 1.12.5
// ESP32-audioI2S-master 3.0.13
// RFID_MFRC522v2 2.0.6
// Board esp32 3.2.0
// Partition scheme: No OTA (2MB APP/2MB SPIFFS)


#include "Adafruit_NeoPixel.h"
#include "Audio.h"
#include "SD.h"
#include "FS.h"
#include "Arduino.h"
#include <MFRC522v2.h>
#include <MFRC522DriverSPI.h>
#include <MFRC522DriverPinSimple.h>
#include <MFRC522Debug.h>


MFRC522DriverPinSimple ss_pin(5);   // Configurable, see typical pin layout above.
MFRC522DriverSPI driver{ ss_pin };  // Create SPI driver.
MFRC522 mfrc522{ driver };          // Create MFRC522 instance.

#define LED_PIN 12
#define LED_COUNT 12
#define LED_PIN2 13
#define LED_COUNT2 15
#define I2S_DOUT 25
#define I2S_BCLK 27
#define I2S_LRC 26
#define RST_PIN 15
#define SPI_MOSI 23  // SD Card
#define SPI_MISO 19
#define SPI_SCK 18
#define SD_CS 17

Audio audio;

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip2(LED_COUNT2, LED_PIN2, NEO_GRB + NEO_KHZ800);



void setup() {
  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
  Serial.begin(115200);  // Initialize serial communications with the PC for debugging.
  while (!Serial)
    ;                  // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4).
  mfrc522.PCD_Init();  // Init MFRC522 board.
  mfrc522.PCD_SetAntennaGain(112);
  MFRC522Debug::PCD_DumpVersionToSerial(mfrc522, Serial);  // Show details of PCD - MFRC522 Card Reader details.
  //Serial.println(F("Scan PICC to see UID"));
  // LED
  strip.begin();
  strip2.begin();
  strip.setBrightness(255);
  strip2.setBrightness(200);
  strip.show();   // Initialize all pixels to 'off'
  strip2.show();  // Initialize all pixels to 'off'

  //Audio
  pinMode(SD_CS, OUTPUT);
  digitalWrite(SD_CS, HIGH);
  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
  SPI.setFrequency(1000000);
  if (!SD.begin(SD_CS)) {
    //Serial.println("Error accessing microSD card!");
    while (true)
      ;
  }
}



void loop() {
  audio.loop();

  if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  // Dump debug info about the card; PICC_HaltA() is automatically called.
  Serial.print("Card UID: ");
  MFRC522Debug::PrintUID(Serial, (mfrc522.uid));
  Serial.println();


  String uidString = "";
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    if (mfrc522.uid.uidByte[i] < 0x10) {
      uidString += "0";
    }
    uidString += String(mfrc522.uid.uidByte[i], HEX);
  }
  Serial.println(uidString);
  if (uidString == "93abe51a") {
    audio.setVolume(20);
    audio.connecttoFS(SD, "/DSeverly.mp3");
    Serial.println(F("Welcome Everly"));
    colorWipe(strip.Color(255, 0, 0), 60), theaterChase(strip2.Color(255, 255, 255), 40);
    colorWipe2(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(255, 255, 255), 30);
    theaterChase(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(255, 0, 0), 20);
    theaterChase(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(255, 0, 0), 10);
    theaterChase(strip2.Color(0, 0, 0), 1), colorWipe(strip.Color(0, 0, 0), 1);


  } else if (uidString == "048cce72971190") {

    Serial.println(F("Access Granted BlackCard"));

    audio.setVolume(20);
    audio.connecttoFS(SD, "/Join.mp3");
    brighten();
    theaterChase(strip2.Color(255, 0, 0), 40), theaterChase(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(255, 0, 0), 30), theaterChase(strip2.Color(0, 0, 0), 1);
    darken(), colorWipe(strip.Color(0, 0, 0), 1);

  } else if (uidString == "187a3999") {
    audio.setVolume(20);
    audio.connecttoFS(SD, "/DSolivia.mp3");
    Serial.println(F("Welcome Olivia"));

    colorWipe(strip.Color(20, 80, 255), 60), theaterChase(strip2.Color(255, 255, 255), 40);
    colorWipe2(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(255, 255, 255), 30);
    theaterChase(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(20, 80, 255), 20);
    theaterChase(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(20, 80, 255), 10);
    theaterChase(strip2.Color(0, 0, 0), 1), colorWipe(strip.Color(0, 0, 0), 1);

  } else {
    audio.setVolume(20);
    audio.connecttoFS(SD, "/comeback.mp3");
    Serial.println(F("Access Denied"));
    theaterChase(strip2.Color(255, 255, 255), 60);
    theaterChase(strip2.Color(0, 0, 0), 1);
    theaterChase(strip2.Color(255, 255, 255), 40);
    theaterChase(strip2.Color(0, 0, 0), 1);
  }
}

// 0 to 255
void brighten() {
  uint16_t i, j;

  for (j = 0; j < 255; j++) {
    for (i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, j, 0, 0);
    }
    strip.show();
    //delay(10);
    Serial.println(j);
  }
  //delay(100);
}

// 255 to 0
void darken() {

  uint16_t i, j;

  for (j = 255; j > 0; j--) {
    for (i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, j, 0, 0);
    }
    strip.show();
    delay(10);
    Serial.println(j);
  }
  //delay(100);
}

// Fill the dots one after the other with a color
void colorWipe(uint32_t c, uint8_t wait) {
  for (uint16_t i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

void colorWipe2(uint32_t c2, uint8_t wait2) {
  for (int i = strip2.numPixels(); i >= 0; i--) {
    strip2.setPixelColor(i, c2);
    strip2.show();
    delay(wait2);
  }
}

void colorWipe3(uint32_t c3, uint8_t wait3) {
  for (uint16_t i = 0; i < strip2.numPixels(); i++) {
    strip2.setPixelColor(i, c3);
    strip2.show();
    delay(wait3);
  }
}

void theaterChase(uint32_t c, uint8_t wait) {
  for (int j = 0; j < 10; j++) {  //do 10 cycles of chasing
    for (int q = 0; q < 3; q++) {
      for (uint16_t i = 0; i < 15; i = i + 3) {
        strip2.setPixelColor(i + q, c);  //turn every third pixel on
      }
      strip2.show();
      delay(wait);
      for (uint16_t i = 0; i < 15; i = i + 3) {
        strip2.setPixelColor(i + q, 0);  //turn every third pixel off
      }
    }
  }
}

If you want the strips to animate at the same time, you need to re-write the code to be non-blocking. That means no use of delay() for timing and no use of for-loops or while-loops for sequencing. This is one of the most commonly asked questions on this forum, so there are lots of examples of how to convert code like this to be non-blocking. It's not a simple matter of removing delay() because that would only break the timing of the animations. You cannot simply use millis() instead of delay() because that doesn't correct the blocking nature of the code.

As for playing sounds from SD card, I think if you want this to happen concurrently with the strip animations, that is probably asking too much of the Arduino. Playing audio smoothly, even at low quality, probably requires 100% of the CPU time. So I would suggest getting a DFplayer Mini which will offload the work of playing the sound from the Arduino, and also give far better quality sound than the Arduino can achieve.

I have read countless of threads from this forum and have yet to find one that offers a solution for Neopiixels (ws2812). Most just point people to the blink no delay which is not the same as calling strips. Also most threads never end up posting a solution for the original question. Grumpy Mike offered some good advice but I don't believe the OP ever offered a resolution and the thread was closed. I have tried all the examples from Adafruit, Fastled and Ws2812FX and some work with the strip alone but the ones that do, don't work with the combined components. Many don't even compile anymore with the updates to IDE. I am running an esp32 Dev module with 4mb. Is that any different from the Ardruino. With the DFplayer Mini do I get rid of the I2s amp? Could I have it play a specific sound for each correct uid from the rfid reader? Thanks for your suggestions.

Have you got that link?

Unfortunately not. been through sooo many threads and searches in different ways.

You are missing the point of the example. Many beginners do that because it's not a ready made solution for their particular problem. It demonstrates how to perform timing of actions without using delay() or other blocking code.

I missed that you are using ESP32 and an i2s device. You did not mention this in your previous posts, so I made the safest assumption which is that you were using an Arduino Uno. But now I notice that these things are mentioned in the comments in your code. Maybe animating led strips and playing audio at the same time will be practical with this setup, I don't know.

But if you did change to use a DFplayer Mini, you would not need the i2s amp and you can play different sounds for different RFIDs.

I think I remember Mike posting a set of examples for Ws2812 where he had re-coded some of the commonly used animation patterns in a non-blocking way.

So maybe you can use the forum search to find posts relating to ws2812 or neopixel posted by Mike. Try something like "@Grumpy_Mike ws2812" in the search box.

I'll try to help by showing how to change one of your functions to be non-blocking, to give you an example.

// 255 to 0
void darken() {

  uint16_t i, j;

  for (j = 255; j > 0; j--) {
    for (i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, j, 0, 0);
    }
    strip.show();
    delay(10);
    Serial.println(j);
  }
  //delay(100);
}

this could become (untested)

// 255 to 0
bool darken() {

  static uint16_t j = 255;
  static unsigned long prevTime;
  bool completed = false;

  if (j > 0) {
    if (millis() - prevTime >= 10) {
      j--;
      for (uint16_t i = 0; i < strip.numPixels(); i++) {
        strip.setPixelColor(i, j, 0, 0);
      }
      strip.show();
      Serial.println(j);
      prevTime = millis();
    }
  }
  else if (millis() - prevTime >= 100) {
    j = 255;
    prevTime = millis();
    completed = true;
  }
  return completed;
}

This, I hope, will return true when the animation cycle is complete. If it returns false, it needs to be called again, frequently, to allow the animation to be smooth.

Thanks Paul for the code. I have tried several bools that I found during my searches and all give me the same results. I tried yours by replacing the void darken with your bool darken and I get the same results. None of my lights turn on even if it has not called the bool. That's what confuses me and frustrates me with this whole process. I would expect the entire sketch to run as before except for when the corresponding rfid returns matches the id that calls for the bool. Instead none of the lights turn on. So it's silent and dark for the length of the animation and then the sound plays as usual after everything else. Thanks for the try.

OK you are going to have to use the same language as the rest of us.

A bool, is a type of variable, it contains only two values true or false. The full name for this Boolean.

In order to run anything it must be in a function. A bool is not a function.

Now:-

Is just a function that when it is finished returns a Boolean variable. A lot of beginners think there is a function called void loop. This is wrong it is a function called loop and it returns no value at all. That is what the void bit is telling you.

Likewise the bool darken() is a function that returns a boolean variable. In this case this variable is called "completed" and the bit of the code that calls that function needs to do something with that variable.

If you just call the function it is just like Oman and its seed is split in the sand. From the bible and in modern parlance this is known as wanking. See Oman

The code that calls this function should do something with the variable. Something like:-

if( darken() == true){
Serial.println("task was successful");
   }
else {Serial.println("task was unsuccessful");
}

That is just a simple example. Note that when you call a function that returns a variable, be it char, int, long or what ever it is the same as saying this variable is created in place of the original call.

I think this is the basic knowledge you are, at the moment, not understanding.

You have chosen to use an ESP processor. These produce output signals of only 3V3. This is not sufficient to turn on a Neopixel. Normally you need some sort of level shifter. Unfortunately the level shifters you can normally buy are for shifting I2C levels, and are not fast enough, or at best marginal, when it comes to driving a Neopixel.

This is the circuit I recommend for boosting a 3V3 signal to a 5V signal capable of running Neopixels. It is drawn as if you had a Raspberry Pi, but it is the same principal as driving any other 3V3 processor.

As well as the 74LS14, you can also use 74HCT14, or other 74XX14 chips.

Thanks Mike, power is not the problem as the lights run off the 5v pins of the esp32 dev module which gets it power from the usb cord which is native 5v. The lights work fine except not at the same time as the sound. I will try to look more into how to use boolean. Thanks.

It is not the voltage from the USB lead, it is the voltage from the pins of the ESP processor that is the problem.

You seem to be in denial about this.

My code as I have it now. Audio waits for all animation to stop. Just to give a visual. Unfortunately no audio in animated gifs and no one (understandably) wants to follow a link. Not in denial just not understanding how a voltage problem does not allow the code to run in sequence. I would probably expect the sound to maybe cut in and out or the lights to not run correctly or out of color.
IMG_5087

You did say

Now you show an animated gif with the lights going on. I am confused by the contradiction in what you are reporting.

Because that is what you have programmed it to do.

I did, here is the code:-

// Multiple patterns in a state machine format
// using the Adafruit Neopixel libiary each pattern runs in its own 8 pixel length of the strip
// by Mike Cook Feb 2020

#define PINforControl   4 // pin connected to the small NeoPixels strip
#define NUMPIXELS1      64 // number of LEDs on strip

#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS1, PINforControl, NEO_GRB + NEO_KHZ800);

// first set up the parameters to use in the pattern calling
unsigned long patternInterval [] = { 500, 40, 20, 200, 5 }; // how often each pattern updates
unsigned long lastUpdate [5] ;  // for millis() when last update occurred
boolean patternEnabled [] = {true,true,false,true,true}; // should the pattern be called at all
byte patternState[5]; // state machine variable for patterns - this initialises them to zero

// now set up the array of pointers to each pattern
void (*patternPtrs[5])(int index, byte state); //the array of pattern pointers
const byte button = 3; // pin to connect button switch to between pin and ground

void setup() {
  //initialises the array of pattern pointers
  patternPtrs[0] = blinkOne; 
  patternPtrs[1] = rainbow();
  patternPtrs[2] = rainbowCycle();
  patternPtrs[3] = colorWipe;
  patternPtrs[4] = rainbowCycle;
  strip.begin(); // This initialises the NeoPixel library.
  pinMode(button, INPUT_PULLUP); // change pattern button
}

void loop() {
  for(int i = 0; i<5; i++) { // go through all the patterns and see if it is time to call one
    if(patternEnabled[i] && millis() - lastUpdate[i] > patternInterval[i]){
      lastUpdate[i] = millis();
      callPatterns(i, patternState[i]);
    }
  }
}

void callPatterns(int index, byte state) {
  (*patternPtrs[index])(index,state); //calls the pattern at the index of `index` in the array
}

// These are the pattern functions written as a state machine
// this is the Blink program in FastLED's example folder
void blinkOne(int index,byte state) {
  if(state == 0){
    andPixelColour(3, 0x800000); 
    strip.show(); // display
    patternState[index] = 1; // move on the state machine for the next call
  }
  if(state == 1){
    xorPixelColour(3, 0x800000); 
     strip.show(); // display
     patternState[index] = 0;
   }
  }

void rainbow() { // modified from Adafruit example to make it a state machine
  static uint16_t j=0;
    for(int i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
     j++;
  if(j >= 256) j=0;
  lastUpdate = millis(); // time for finding the next change to the display
 
}
void rainbowCycle() { // modified from Adafruit example to make it a state machine
  static uint16_t j=0;
    for(int i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
  j++;
  if(j >= 256*5) j=0;
  lastUpdate = millis(); // time for next change to the display
}

void theaterChaseRainbow() { // modified from Adafruit example to make it a state machine
  static int j=0, q = 0;
  static boolean on = true;
     if(on){
            for (int i=0; i < strip.numPixels(); i=i+3) {
                strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
             }
     }
      else {
           for (int i=0; i < strip.numPixels(); i=i+3) {
               strip.setPixelColor(i+q, 0);        //turn every third pixel off
                 }
      }
     on = !on; // toggel pixelse on or off for next time
      strip.show(); // display
      q++; // update the q variable
      if(q >=3 ){ // if it overflows reset it and update the J variable
        q=0;
        j++;
        if(j >= 256) j = 0;
      }
  lastUpdate = millis(); // time for next change to the display   
}

void colorWipe(uint32_t c) { // modified from Adafruit example to make it a state machine
  static int i =0;
    strip.setPixelColor(i, c);
    strip.show();
  i++;
  if(i >= strip.numPixels()){
    i = 0;
    wipe(); // blank out strip
  }
  lastUpdate = millis(); // time for next change to the display
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

void andPixelColour(int i, long colour){ // ANDs the colour with what is already there
  strip.setPixelColor(i, colour & strip.getPixelColor(i));
}

void xorPixelColour(int i, long colour){ // Exclusive ORs the colour with what is already there
  strip.setPixelColor(i, colour ^ strip.getPixelColor(i));
}

void orPixelColour(int i, long colour){ // ORs the colour with what is already there
  strip.setPixelColor(i, colour | strip.getPixelColor(i));
}
1 Like

I did that to show how the lights work with the sketch as I posted (starting point). This is why I don't think the 3.3v signal voltage is the problem.

Could you show me where I programmed the audio to come on after the light because I don't understand if all the audio code is listed first. Honestly it doesn't matter where I put the code lines for the audio it always plays after.

Yes, that's the posting that I had seen and could not find again. I need to save the link and keep trying.

Trying this sketch with only changing the pin number and I get this error.

:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino: In function 'void setup()':
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:24:27: error: void value not ignored as it ought to be
   24 |   patternPtrs[1] = rainbow();
      |                    ~~~~~~~^~
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:25:32: error: void value not ignored as it ought to be
   25 |   patternPtrs[2] = rainbowCycle();
      |                    ~~~~~~~~~~~~^~
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:26:20: error: invalid conversion from 'void (*)(uint32_t)' {aka 'void (*)(long unsigned int)'} to 'void (*)(int, byte)' {aka 'void (*)(int, unsigned char)'} [-fpermissive]
   26 |   patternPtrs[3] = colorWipe;
      |                    ^~~~~~~~~
      |                    |
      |                    void (*)(uint32_t) {aka void (*)(long unsigned int)}
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:27:20: error: invalid conversion from 'void (*)()' to 'void (*)(int, byte)' {aka 'void (*)(int, unsigned char)'} [-fpermissive]
   27 |   patternPtrs[4] = rainbowCycle;
      |                    ^~~~~~~~~~~~
      |                    |
      |                    void (*)()
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino: In function 'void rainbow()':
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:68:14: error: incompatible types in assignment of 'long unsigned int' to 'long unsigned int [5]'
   68 |   lastUpdate = millis(); // time for finding the next change to the display
      |   ~~~~~~~~~~~^~~~~~~~~~
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino: In function 'void rainbowCycle()':
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:79:14: error: incompatible types in assignment of 'long unsigned int' to 'long unsigned int [5]'
   79 |   lastUpdate = millis(); // time for next change to the display
      |   ~~~~~~~~~~~^~~~~~~~~~
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino: In function 'void theaterChaseRainbow()':
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:103:14: error: incompatible types in assignment of 'long unsigned int' to 'long unsigned int [5]'
  103 |   lastUpdate = millis(); // time for next change to the display
      |   ~~~~~~~~~~~^~~~~~~~~~
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino: In function 'void colorWipe(uint32_t)':
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:113:5: error: 'wipe' was not declared in this scope; did you mean 'pipe'?
  113 |     wipe(); // blank out strip
      |     ^~~~
      |     pipe
C:\Users\ubear\AppData\Local\Temp\.arduinoIDE-unsaved202544-4788-129rcqj.ms9r\sketch_may4a\sketch_may4a.ino:115:14: error: incompatible types in assignment of 'long unsigned int' to 'long unsigned int [5]'
  115 |   lastUpdate = millis(); // time for next change to the display
      |   ~~~~~~~~~~~^~~~~~~~~~
exit status 1

Compilation error: void value not ignored as it ought to be

This obviously compiled when I wrote this.

What I think has happened in the mean time is a combination of two things.

  1. The Adafruit_NeoPixel library has changed. Luckily Adafruit do it right so we can, if needed, find a version perhaps more compatible than the latest version..

  2. And, this is a more difficult problem, the C++ compile that is used by the Arduino IDE has changed and has become a bit more strict in what it flags up as an error. So I need to tighten up on the syntax.

I will be working on it tomorrow to see if I can come up with a solution.