Code so far, be warned it's a little long:
in the main section
#include "libISD.h"
using namespace libISD;
void setup() {
Serial.begin(ISDController::SERIAL_BAUD);
// put your setup code here, to run once:
isdModel.init();
Serial.println("called isd modele init");
}
void loop() {
// put your main code here, to run repeatedly:
isdModel.loop();
}
Header for "libISD.h":
#ifndef _LIB_ISD_H__
#define _LIB_ISD_H__
#define TLC_5940_ENABLED 1
#define NEOPIXELS_ENABLED 1
#include <time.h>
#if NEOPIXELS_ENABLED
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
#endif //NEOPIXELS_ENABLED
#if TLC_5940_ENABLED
#include "Tlc5940.h"
#endif //TLC_5940_ENABLED
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
namespace libISD {
class LedController {
public:
int idx;
bool modified;
LedController(): idx(0), modified(false) {
}
virtual ~LedController() {
}
virtual bool enabled(unsigned long timeFromStart) const {
return false;
}
virtual void updateLed() {}
virtual void preUpdate() {}
void update() {
if (modified) {
updateLed();
}
modified = false;
}
};
class LedCollection {
public:
LedController** collectionPtr;
int size;
unsigned long startTime;
bool enabled;
LedCollection():
collectionPtr(NULL),
size(0),
startTime(0),
enabled(true){
}
virtual ~LedCollection() {
}
void init(LedController** coll, int sz) {
collectionPtr = coll;
size = sz;
startTime = millis();
enabled = true;
}
bool isEnabled() const { return enabled; }
void setEnabled(bool v) {
enabled = v;
}
bool empty() const { return size;}
void loop() {
unsigned long now = millis();
if (enabled == false) {
return;
}
//only loop every X millseconds. simulate delay()
int delayFactor = 20;
unsigned long delta = now-startTime;
if (now % delayFactor == 0 ) {
for (int i=0;i<size;i++ ) {
LedController* led = collectionPtr[i];
if ( led->enabled(delta) ) {
led->preUpdate();
led->update();
}
}
}
}
};
#if TLC_5940_ENABLED
void writeLED(Tlc5940& tlcIC, int idx, float value, float intensity)
{
int v = min( (intensity * value) * 4095.0, 4095);
tlcIC.set(idx, v);
tlcIC.update();
}
#endif //TLC_5940_ENABLED
class Led : public LedController {
public:
int startTime;
int idxOffset;
float intensityIncr;
float value;
float intensity;
#if TLC_5940_ENABLED
Tlc5940* tlcICPtr;
#endif
Led():LedController(),startTime(0),idxOffset(0),intensityIncr(0.0025),value(0),intensity(0)
#if TLC_5940_ENABLED
,tlcICPtr(NULL)
#endif
{
}
virtual ~Led() {
}
#if TLC_5940_ENABLED
void init(Tlc5940& ic, int index, int offset, float v, int st, float intensIncr=0.0025 ) {
tlcICPtr = ⁣
startTime = st;
idx = index;
idxOffset = offset;
intensityIncr = intensIncr;
setValue(v);
modified = true;
}
#endif //TLC_5940_ENABLED
void setValue(float v) {
if ( value != v ) {
value = v;
modified = true;
}
}
setIntensity(float v) {
if ( intensity != v ) {
intensity = v;
modified = true;
}
}
void setIntensityIncr(float v) {
intensityIncr = v;
}
void incrIntensity() {
if (intensity < 1.0) {
intensity = min(intensity + intensityIncr,1.0);
modified = true;
}
}
virtual bool enabled(unsigned long timeFromStart) const {
if (-1 == startTime) {
return false;
}
return timeFromStart >= startTime;
}
virtual void preUpdate() {
incrIntensity();
}
void stop() {
setIntensity(0);
#if TLC_5940_ENABLED
if (tlcICPtr) {
writeLED(*tlcICPtr, idx + idxOffset,0,intensity);
}
#endif
}
virtual void updateLed() {
#if TLC_5940_ENABLED
if (tlcICPtr) {
writeLED(*tlcICPtr, idx + idxOffset,value,intensity);
}
#endif
}
};
enum BigEngines {
LEFT_ENGINE=1,
CENTER_ENGINE=3,
RIGHT_ENGINE=5,
};
enum MiniEngines {
LEFT_ENGINE_TOP=0,
LEFT_ENGINE_BOTTOM=2,
RIGHT_ENGINE_TOP=4,
RIGHT_ENGINE_BOTTOM=6,
};
#if NEOPIXELS_ENABLED
void writeRgbLED(Adafruit_NeoPixel& pixels, int led, float r, float g, float b, float intensity)
{
int rval = (intensity * r) * 255.0;//min( (intensity * r) * 255, 255);
int gval = (intensity * g) * 255.0;//min( (intensity * g) * 255, 255);
int bval = (intensity * b) * 255.0;//min( (intensity * b) * 255, 255);
pixels.setPixelColor(led, pixels.Color(rval, gval, bval));
pixels.show(); // Send the updated pixel colors to the hardware.
/*
Serial.print("writeRgbLED called, led: ");Serial.print(led);
Serial.print(" r: ");Serial.print(rval);
Serial.print(", g: ");Serial.print(gval);
Serial.print(", b: ");Serial.print(bval);Serial.println("");
*/
}
#endif //NEOPIXELS_ENABLED
#define ENGINE_BASE_COLOR_R 100
#define ENGINE_BASE_COLOR_G 90
#define ENGINE_BASE_COLOR_B 255
#define ENGINE_BASE_COLORF_R float(ENGINE_BASE_COLOR_R)/255.0
#define ENGINE_BASE_COLORF_G float(ENGINE_BASE_COLOR_G)/255.0
#define ENGINE_BASE_COLORF_B float(ENGINE_BASE_COLOR_B)/255.0
struct rgb_color {
enum {
MAX_COLOR_VAL = 255
};
float intensity;
float r;
float g;
float b;
rgb_color():intensity(0), r(0),g(0),b(0) {}
};
class RgbLed : public LedController {
public:
int startTime;
int idxOffset;
rgb_color color;
float r,g,b;
float intensityIncr;
#if NEOPIXELS_ENABLED
Adafruit_NeoPixel* pixelsPtr;
#endif //NEOPIXELS_ENABLED
RgbLed():
startTime(-1),
idxOffset(0),
r(0),g(0),b(0),
intensityIncr(0.0025)
#if NEOPIXELS_ENABLED
,pixelsPtr(NULL)
#endif //NEOPIXELS_ENABLED
{
}
virtual ~RgbLed() {
}
#if NEOPIXELS_ENABLED
void init(Adafruit_NeoPixel& pixels, int index, int offset, float rr, float gg, float bb, int st, float intensIncr=0.0025 ) {
pixelsPtr = &pixels;
startTime = st;
idx = index;
idxOffset = offset;
intensityIncr = intensIncr;
color.r = color.g = color.b = 0.0;
color.intensity = 0.0;
setRGB(rr,gg,bb);
modified = true;
}
#endif //NEOPIXELS_ENABLED
virtual bool enabled(unsigned long timeFromStart) const {
bool result = false;
if (-1 == startTime) {
return result;
}
result = (timeFromStart >= startTime) ? true : false;
return result;
}
void setIntensityIncr(float v) {
intensityIncr = v;
}
void setIntensity( float i ) {
if (color.intensity != i) {
color.intensity = i;
modified = true;
}
}
void incrIntensity() {
if (color.intensity < 1.0) {
color.intensity = min(color.intensity + intensityIncr,1.0);
modified = true;
}
}
void setRGB( float rv, float gv, float bv) {
r=rv;
g=gv;
b=bv;
setColor(r,g,b);
modified = true;
}
void setColor( float rv, float gv, float bv) {
if (rv != color.r) {
color.r = rv;
modified = true;
}
if (gv != color.g) {
color.g = gv;
modified = true;
}
if (bv != color.b) {
color.b = bv;
modified = true;
}
}
virtual void preUpdate() {
incrIntensity();
}
void stop() {
setIntensity(0);
if (pixelsPtr) {
writeRgbLED(*pixelsPtr, idx + idxOffset,0,0,0,color.intensity);
}
}
virtual void updateLed() {
#if NEOPIXELS_ENABLED
if (pixelsPtr) {
writeRgbLED(*pixelsPtr, idx + idxOffset,color.r,color.g,color.b,color.intensity);
//Serial.print(" led ");Serial.print(idx);Serial.print(" intens: ");Serial.print(color.intensity);Serial.println(" updateLed() ");
}
#endif //NEOPIXELS_ENABLED
}
};
class Engine : public RgbLed {
public:
Engine():
RgbLed() {
}
virtual ~Engine() {
}
void pulse() {
int range = 50;
float rv = min(r + (float((rand() % range) - (range/2)) / 255.0), 1.0);
float gv = min(g + (float((rand() % range) - (range/2))) / 255.0, 1.0);
float bv = min(b + (float((rand() % range) - (range/2))) / 255.0, 1.0);
setColor(rv,gv,bv);
}
virtual void preUpdate() {
incrIntensity();
pulse();
}
};
class ISDEngines : public LedCollection {
public:
#if NEOPIXELS_ENABLED
Adafruit_NeoPixel& pixels;
#endif
enum {
ENGINE_COUNT = 7
};
Engine* engines[ENGINE_COUNT];
int engineIdxOffset;
#if NEOPIXELS_ENABLED
ISDEngines(Adafruit_NeoPixel& p):pixels(p),
#else
ISDEngines():
#endif //#if NEOPIXELS_ENABLED
engineIdxOffset(0) {
for (int i=0;i<ENGINE_COUNT;i++ ) {
engines[i] = new Engine();
}
}
~ISDEngines() {
for (int i=0;i<ENGINE_COUNT;i++ ) {
delete engines[i];
}
}
void stop() {
for (int i=0;i<ENGINE_COUNT;i++ ) {
engines[i]->stop();
}
setEnabled(false);
}
void start() {
for (int i=0;i<ENGINE_COUNT;i++ ) {
engines[i]->stop();
}
setEnabled(true);
startTime = millis();
}
void initEngines(int offset)
{
engineIdxOffset = offset;
srand (time(NULL));
init( &engines[0], ENGINE_COUNT );
int engineId = 0;
int engStart = 0;
for (int i=0;i<ENGINE_COUNT;i++ ) {
Engine* engine = engines[i];
switch(i) {
case 0:{
engineId = CENTER_ENGINE;
engStart = 2500;
}
break;
case 1:{
engineId = LEFT_ENGINE;
engStart = 1500;
}
break;
case 2:{
engineId = RIGHT_ENGINE;
engStart = 1800;
}
break;
case 3:{
engineId = LEFT_ENGINE_TOP;
engStart = 0;
}
break;
case 4:{
engineId = LEFT_ENGINE_BOTTOM;
engStart = 500;
}
break;
case 5:{
engineId = RIGHT_ENGINE_TOP;
engStart = 200;
}
break;
case 6:{
engineId = RIGHT_ENGINE_BOTTOM;
engStart = 700;
}
break;
}
#if NEOPIXELS_ENABLED
engine->init(pixels, engineId,engineIdxOffset,
ENGINE_BASE_COLORF_R,ENGINE_BASE_COLORF_G,ENGINE_BASE_COLORF_B,
engStart);
#endif //NEOPIXELS_ENABLED
int range = 50;
float rn = float((rand() % range) - (range/2)) / 255.0;
float gn = float((rand() % range) - (range/2)) / 255.0;
float bn = float((rand() % range) - (range/2)) / 255.0;
engine->setRGB(ENGINE_BASE_COLORF_R+rn,ENGINE_BASE_COLORF_G+gn,ENGINE_BASE_COLORF_B+bn);
}
}
};
class ShipSection : public RgbLed {
public:
virtual ~ShipSection() {
}
};
class ISDSections : public LedCollection {
public:
enum Parts {
TOWER = 8,
TOP_SECTION = 13,
BOTTOM_SECTION = 7,
SECTION_COUNT = TOWER + TOP_SECTION + BOTTOM_SECTION
};
#if NEOPIXELS_ENABLED
Adafruit_NeoPixel& pixels;
#endif
ShipSection* sections[SECTION_COUNT];//[SECTION_COUNT];
#if NEOPIXELS_ENABLED
ISDSections(Adafruit_NeoPixel& p):pixels(p) {
#else
ISDSections(){
#endif //#if NEOPIXELS_ENABLED
for (int i=0;i<SECTION_COUNT;i++ ) {
sections[i] = new ShipSection();
}
}
~ISDSections() {
for (int i=0;i<SECTION_COUNT;i++ ) {
delete sections[i];
}
}
void initSections() {
srand (time(NULL));
init( §ions[0], SECTION_COUNT );
for (int i=0;i<SECTION_COUNT;i++ ) {
#if NEOPIXELS_ENABLED
sections[i]->init(pixels, i,0,
1.0,1.0,1.0,
0);
#endif //NEOPIXELS_ENABLED
sections[i]->setRGB(1.0,1.0,1.0);
}
}
void stop() {
for (int i=0;i<SECTION_COUNT;i++ ) {
sections[i]->stop();
}
setEnabled(false);
}
void start() {
for (int i=0;i<SECTION_COUNT;i++ ) {
sections[i]->stop();
}
setEnabled(true);
startTime = millis();
}
};
class ISDSpecialSections : public LedCollection {
public:
enum Parts {
GARBAGE_CHUTE = 1,
SIDE_LIGHTS = 6,
SECTION_COUNT = GARBAGE_CHUTE + SIDE_LIGHTS
};
#if TLC_5940_ENABLED
Tlc5940& tlcIc;
#endif
Led* sections[SECTION_COUNT];
#if TLC_5940_ENABLED
ISDSpecialSections(Tlc5940& p):tlcIc(p) {
for (int i=0;i<SECTION_COUNT;i++ ) {
sections[i] = new Led();
}
}
#else
ISDSpecialSections() {
for (int i=0;i<SECTION_COUNT;i++ ) {
sections[i] = new Led();
}
}
#endif //TLC_5940_ENABLED
~ISDSpecialSections() {
for (int i=0;i<SECTION_COUNT;i++ ) {
delete sections[i];
}
}
void stop() {
for (int i=0;i<SECTION_COUNT;i++ ) {
sections[i]->stop();
}
setEnabled(false);
}
void start() {
for (int i=0;i<SECTION_COUNT;i++ ) {
sections[i]->stop();
}
setEnabled(true);
startTime = millis();
}
void initSections() {
srand (time(NULL));
init( §ions[0], SECTION_COUNT );
for (int i=0;i<SECTION_COUNT;i++ ) {
#if TLC_5940_ENABLED
sections[i]->init(tlcIc, i,0,1.0,5000, 0.01);
#endif //TLC_5940_ENABLED
}
}
};
class Sound {
public:
enum Constants{
MAX_VOLUME = 20,
};
int startTime;
int idx;
float intensityIncr;
float intensity;
float value;
bool modified;
bool playStarted;
bool loopMode;
int playCount;
DFRobotDFPlayerMini* playerPtr;
Sound():
startTime(-1),
idx(0),
intensityIncr(0.00),
intensity(0),
value(0),
modified(false),
playStarted(false),
loopMode(false),
playCount(0),
playerPtr(NULL)
{
}
void setIntensityIncr(float v) {
intensityIncr = v;
}
void incrIntensity() {
if (intensity < 1.0) {
intensity = min(intensity + intensityIncr,1.0);
modified = true;
}
}
virtual bool enabled(unsigned long timeFromStart) const {
if (-1 == startTime) {
return false;
}
return timeFromStart >= startTime;
}
void init(DFRobotDFPlayerMini& p, int index, float v, int st, float initialIntensity, float intensIncr=0.0025 ) {
playerPtr = &p;
startTime = st;
idx = index;
intensityIncr = intensIncr;
intensity = initialIntensity;
setValue(v);
modified = true;
}
void setValue(float v) {
if (v != value) {
value = v;
modified = true;
}
}
void setLoop( bool v) {
if (v != loopMode) {
loopMode = v;
modified = true;
}
}
void playStopped() { playStarted = false; }
bool isPlaying() const { return true == playStarted; }
void stop() {
startTime = -1;
if (playerPtr) {
if (playerPtr->readCurrentFileNumber() == idx) {
playerPtr->pause();
}
}
}
void update() {
incrIntensity();
if (modified) {
if (playerPtr) {
int vol = min(value*intensity,1.0) * float(MAX_VOLUME);
playerPtr->volume(vol); //Set volume value. From 0 to 30
Serial.print("vol changed to ");Serial.println(vol);
if (!playStarted) {
if ( (playCount==0) || ((playCount > 0) && loopMode)) {
playerPtr->play(idx);
Serial.print("Playing item ");Serial.println(idx);
playStarted = true;
playCount ++;
}
}
}
}
modified = false;
}
};
class SoundController {
public:
enum Constants {
SERIAL_CTRL_BAUD = 9600,
MAX_SOUND_COUNT = 5
};
Sound* sounds[MAX_SOUND_COUNT];
SoftwareSerial serialControl;
DFRobotDFPlayerMini soundPlayer;
unsigned long startTime;
SoundController( int rxPin, int txPin ):
serialControl(rxPin,txPin),startTime(0) {
for (int i=0;i<MAX_SOUND_COUNT;i++ ) {
sounds[i] = new Sound();
}
}
~SoundController() {
for (int i=0;i<MAX_SOUND_COUNT;i++ ) {
delete sounds[i];
}
}
Sound* getSound(int idx) {
Sound* result = NULL;
if (idx >= 0 && idx <MAX_SOUND_COUNT) {
result = sounds[idx];
}
return result;
}
void stopSounds() {
for (int i=0;i<MAX_SOUND_COUNT;i++ ) {
sounds[i]->stop();
}
}
void playSound( int idx ) {
stopSounds();
Sound* sound = getSound(idx);
if (NULL != sound ) {
sound->startTime = millis();
}
}
void init() {
serialControl.begin(SERIAL_CTRL_BAUD);
Serial.println();
Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
if (!soundPlayer.begin(serialControl)) { //Use softwareSerial to communicate with mp3.
Serial.println(F("Unable to begin:"));
Serial.println(F("1.Please recheck the connection!"));
Serial.println(F("2.Please insert the SD card!"));
while(true){
delay(0); // Code to compatible with ESP8266 watch dog.
}
}
Serial.println(F("Sound controller online."));
soundPlayer.EQ(DFPLAYER_EQ_CLASSIC);
soundPlayer.volume(0);
for (int i=0;i<MAX_SOUND_COUNT;i++ ) {
Sound* sound = sounds[i];
sound->init(soundPlayer, i, 1, -1, 1 );
}
sounds[0]->init(soundPlayer, 1, 1, 3000, 0, 0.01 );
startTime = millis();
}
void soundStatus(uint8_t type, int value){
switch (type) {
case TimeOut:
Serial.println(F("Time Out!"));
break;
case WrongStack:
Serial.println(F("Stack Wrong!"));
break;
case DFPlayerCardInserted:
Serial.println(F("Card Inserted!"));
break;
case DFPlayerCardRemoved:
Serial.println(F("Card Removed!"));
break;
case DFPlayerCardOnline:
Serial.println(F("Card Online!"));
break;
case DFPlayerUSBInserted:
Serial.println("USB Inserted!");
break;
case DFPlayerUSBRemoved:
Serial.println("USB Removed!");
break;
case DFPlayerPlayFinished:
Serial.print(F("Number:"));
Serial.print(value);
Serial.println(F(" Play Finished!"));
Sound* sound = getSound(value);
if (sound != NULL) {
sound->playStopped();
}
break;
case DFPlayerError:
Serial.print(F("DFPlayerError:"));
switch (value) {
case Busy:
Serial.println(F("Card not found"));
break;
case Sleeping:
Serial.println(F("Sleeping"));
break;
case SerialWrongStack:
Serial.println(F("Get Wrong Stack"));
break;
case CheckSumNotMatch:
Serial.println(F("Check Sum Not Match"));
break;
case FileIndexOut:
Serial.println(F("File Index Out of Bound"));
break;
case FileMismatch:
Serial.println(F("Cannot Find File"));
break;
case Advertise:
Serial.println(F("In Advertise"));
break;
default:
break;
}
break;
default:
break;
}
}
void loop() {
if (soundPlayer.available()) {
//Print the detail message from DFPlayer to handle different errors and states.
soundStatus(soundPlayer.readType(), soundPlayer.read());
}
unsigned long now = millis();
//only loop every X millseconds. simulate delay()
int delayFactor = 20;
unsigned long delta = now-startTime;
if (now % delayFactor == 0 ) {
for (int i=0;i<MAX_SOUND_COUNT;i++ ) {
Sound* sound = sounds[i];
if ( sound->enabled(delta) ) {
sound->update();
if (sound->isPlaying()) {
break;//don't check anyone else, busy now
}
}
}
}
}
};
class ISDController {
public:
enum Constants{
/*
* note that 3 is the default here for neopixels, but we
* can't use that because it'salready needed by the
* tlc5940 lib. If we try and use it here it will cause
* a conflict and the neopixels won't work correctly
* tlc5940 lib uses the following pins:
* 13
* 11
* 10
* 9
* 3
* these can't be changed AFAIK, unless rebuild the library
* so for all practical purposes we need to NOT use these IO pins
*/
NEOPIXELS_PIN = 5,
SOFTWARE_SERIAL_RX = 7, //does this need to be PWM???
SOFTWARE_SERIAL_TX = 8,
NEOPIXELS_LEDCOUNT = ISDEngines::ENGINE_COUNT + ISDSections::SECTION_COUNT,
SERIAL_BAUD = 115200,
};
#if NEOPIXELS_ENABLED
Adafruit_NeoPixel rgbNeoPixels;
#endif //NEOPIXELS_ENABLED
//sections
ISDSpecialSections miscLights;
ISDSections mainLights;
ISDEngines engines;
SoundController sndController;
ISDController():
#if NEOPIXELS_ENABLED
rgbNeoPixels(NEOPIXELS_LEDCOUNT, NEOPIXELS_PIN, NEO_RGB + NEO_KHZ800),
#endif //NEOPIXELS_ENABLED
#if TLC_5940_ENABLED
miscLights(Tlc),
#else
miscLights(),
#endif //TLC_5940_ENABLED
#if NEOPIXELS_ENABLED
mainLights(rgbNeoPixels),
engines(rgbNeoPixels),
#else
mainLights(),
engines(),
#endif //NEOPIXELS_ENABLED
sndController(SOFTWARE_SERIAL_RX,SOFTWARE_SERIAL_TX)
{
}
void init() {
#if NEOPIXELS_ENABLED
//neo pixels initialization
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
// Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
// END of Trinket-specific code.
rgbNeoPixels.begin();
rgbNeoPixels.clear();
#endif //NEOPIXELS_ENABLED
//tlc5940 init
#if TLC_5940_ENABLED
Tlc.init();
Tlc.clear();
Tlc.update();
#endif //TLC_5940_ENABLED
miscLights.initSections();
mainLights.initSections();
engines.initEngines(0);
Serial.println("ISDController::init() done");
sndController.init();
}
void startMainLights() {
mainLights.start();
}
void startMiscLights() {
miscLights.start();
}
void startEngines() {
engines.start();
}
void loop() {
miscLights.loop();
//mainLights.loop();
engines.loop();
sndController.loop();
}
};
ISDController isdModel;
} //namespace libISD
#endif //_LIB_ISD_H__