Created
November 9, 2020 23:08
-
-
Save lucjross/1ccb6741f200798899dc2b075f3f3a3c to your computer and use it in GitHub Desktop.
Dark Sorceror of Treats - Arduino program
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <DFMiniMp3.h> | |
#include <AccelStepper.h> | |
#define GUY_PUL_PIN 9 | |
#define GUY_DIR_PIN 10 | |
#define GUY_ENA_PIN 11 | |
#define CONVEY_PUL_PIN 6 | |
#define CONVEY_DIR_PIN 7 | |
#define CONVEY_ENA_PIN 8 | |
#define CONVEY_LS_PIN 5 | |
#define ENTRYWAY_IR_PIN 4 | |
#define CIRCLE_IR_PIN 3 | |
#define TREAT_IR_PIN 2 | |
#define DFPLAYER_RX_PIN 0 // RX0 | |
#define DFPLAYER_TX_PIN 1 // TX1 | |
#define LIGHT_OVERHEAD_RELAY_K1_PIN 12 | |
#define LIGHT_CIRCLE_RELAY_K2_PIN 14 // A0 | |
#define LIGHT_TREAT_RELAY_K3_PIN 15 // A1 | |
#define LIGHT_RED_FACE_RELAY_K4_PIN 16 // A2 | |
#define TRACK_LIGHT_ON 1 | |
#define TRACK_GREETING 2 | |
#define TRACK_CIRCLE_LIGHT_ON 3 | |
#define TRACK_CONJURE 4 | |
#define TRACK_CIRCLE_REJECTED 5 | |
#define TRACK_TREAT_REVEAL 6 | |
#define TRACK_RED_LIGHT_ON 7 | |
#define TRACK_TREAT_SCARE_SCREAM 8 | |
#define MP3_PLAY_DELAY 100 | |
bool mp3Playing; | |
bool resetMp3 = false; | |
class Mp3Notify | |
{ | |
public: | |
static void PrintlnSourceAction(DfMp3_PlaySources source, const char* action) | |
{ | |
if (source & DfMp3_PlaySources_Sd) | |
{ | |
Serial.print("SD Card, "); | |
} | |
if (source & DfMp3_PlaySources_Usb) | |
{ | |
Serial.print("USB Disk, "); | |
} | |
if (source & DfMp3_PlaySources_Flash) | |
{ | |
Serial.print("Flash, "); | |
} | |
Serial.println(action); | |
} | |
static void OnError(uint16_t errorCode) | |
{ | |
// see DfMp3_Error for code meaning | |
Serial.println(); | |
Serial.print("Com Error "); | |
Serial.println(errorCode); | |
resetMp3 = true; | |
mp3Playing = false; | |
} | |
static void OnPlayFinished(DfMp3_PlaySources source, uint16_t track) | |
{ | |
Serial.print("Play finished for #"); | |
Serial.println(track); | |
mp3Playing = false; | |
} | |
static void OnPlaySourceOnline(DfMp3_PlaySources source) | |
{ | |
PrintlnSourceAction(source, "online"); | |
mp3Playing = false; | |
resetMp3 = true; | |
} | |
static void OnPlaySourceInserted(DfMp3_PlaySources source) | |
{ | |
PrintlnSourceAction(source, "inserted"); | |
} | |
static void OnPlaySourceRemoved(DfMp3_PlaySources source) | |
{ | |
PrintlnSourceAction(source, "removed"); | |
mp3Playing = false; | |
} | |
}; | |
class SpookyTimer { | |
private: | |
unsigned long start_time_ = 0; | |
unsigned long end_time_ = 0; | |
unsigned long period_ = 0; | |
bool started_ = false; | |
public: | |
SpookyTimer() { | |
started_ = false; | |
} | |
void start(unsigned long period) { | |
if (!started_) { | |
period_ = period; | |
start_time_ = millis(); | |
started_ = true; | |
} | |
} | |
bool finished() { | |
if (!started_) { | |
return false; | |
} else { | |
end_time_ = millis(); | |
if (end_time_ - start_time_ >= period_) { | |
started_ = false; | |
return true; | |
} else { | |
return false; | |
} | |
} | |
} | |
void reset() { | |
started_ = false; | |
} | |
}; | |
const int conjureLightSeq[] = { | |
1,1,1,1,1,1,1,1, | |
1,1,1,1,1,1,1,0, | |
1,1,1,1,1,0,0,1, | |
1,0,1,1,0,0,0,1, | |
1,1,0,0,1,1,0,1, | |
0,1,0,1,0,0,1,0, | |
1,0,1,0,1,0,1,1, | |
1,0,0,0,0,0,1,0, | |
0,0,0,0,1,1,0,0, | |
1,1,1,1,0,0,0,0 | |
}; | |
const int conjureLightSeqLen = sizeof(conjureLightSeq) / sizeof(conjureLightSeq[0]); | |
const int conjureLightSeqDelay = 100; | |
enum skitStage { | |
idle, // 0 | |
entrywayTriggered, // 1. subject approaches, sorceror invites subject to make way into circle | |
invitation, // 2. circle illuminated, sorcerer waits for subject to enter | |
circleRejected, // 3. subject does not enter circle | |
conjure, // 4. circle deilluminated, sorceror conjures | |
couldNotConjure, // 5. no candy left :( | |
treatReveal, // 6 | |
treatRejected, // 7 | |
treatScare, // 8 | |
theEnd // 9. cleanup | |
}; | |
AccelStepper conveyor(AccelStepper::DRIVER, CONVEY_PUL_PIN, CONVEY_DIR_PIN); | |
AccelStepper guy(AccelStepper::DRIVER, GUY_PUL_PIN, GUY_DIR_PIN); | |
DFMiniMp3<HardwareSerial, Mp3Notify> mp3(Serial1); | |
SpookyTimer mp3LoopTimer, irReadTimer, mp3Timer, afterMp3Timer, | |
waitTimer, guyTimer, conveyTimer, lsReadTimer, | |
conjureLightSeqTimer, scareTimer, treatLightOnAgainTimer; | |
long newStageLoops = 0; | |
bool conveyWait; | |
bool conveyShuffling; | |
int conveyStep; | |
int lsBlockedCount; | |
skitStage stage; | |
bool greetingPlayed; | |
bool scareTimerStarted; | |
int conjureLightSeqIdx; | |
void setupMp3() { | |
mp3.begin(); | |
uint16_t vol = mp3.getVolume(); | |
Serial.print("volume="); Serial.println(vol); | |
uint16_t tracks = mp3.getTotalTrackCount(DfMp3_PlaySource_Sd); | |
Serial.print("tracks="); Serial.println(tracks); | |
mp3Playing = false; | |
// warm up the player | |
mp3.playMp3FolderTrack(TRACK_LIGHT_ON); | |
mp3.loop(); | |
delay(100); | |
mp3.stop(); | |
} | |
void setup() { | |
Serial.begin(9600); | |
delay(3000); | |
digitalWrite(LIGHT_OVERHEAD_RELAY_K1_PIN, HIGH); | |
pinMode(LIGHT_OVERHEAD_RELAY_K1_PIN, OUTPUT); | |
digitalWrite(LIGHT_CIRCLE_RELAY_K2_PIN, HIGH); | |
pinMode(LIGHT_CIRCLE_RELAY_K2_PIN, OUTPUT); | |
digitalWrite(LIGHT_TREAT_RELAY_K3_PIN, HIGH); | |
pinMode(LIGHT_TREAT_RELAY_K3_PIN, OUTPUT); | |
digitalWrite(LIGHT_RED_FACE_RELAY_K4_PIN, HIGH); | |
pinMode(LIGHT_RED_FACE_RELAY_K4_PIN, OUTPUT); | |
pinMode(CONVEY_LS_PIN, INPUT); | |
pinMode(ENTRYWAY_IR_PIN, INPUT); | |
pinMode(TREAT_IR_PIN, INPUT); | |
pinMode(CIRCLE_IR_PIN, INPUT); | |
conveyor.setEnablePin(CONVEY_ENA_PIN); | |
conveyor.setPinsInverted(false, false, true); // ENA inverted | |
conveyor.disableOutputs(); // power-saving | |
guy.setEnablePin(GUY_ENA_PIN); | |
guy.setPinsInverted(false, false, true); | |
guy.disableOutputs(); | |
setupMp3(); | |
setStage(idle); | |
} | |
void loop() { | |
mp3LoopTimer.start(5); | |
if (mp3LoopTimer.finished()) { | |
if (resetMp3) { | |
resetMp3 = false; | |
setupMp3(); | |
} | |
mp3.loop(); | |
} | |
conveyor.run(); | |
guy.run(); | |
if (stage == idle) { | |
irReadTimer.start(100); | |
if (irReadTimer.finished()) { | |
if (isHigh(ENTRYWAY_IR_PIN)) { | |
setStage(entrywayTriggered); | |
} | |
} | |
} else if (stage == entrywayTriggered) { | |
if (isNewStage()) { | |
playMp3(TRACK_LIGHT_ON); | |
afterMp3Timer.reset(); | |
afterMp3Timer.start(MP3_PLAY_DELAY); | |
mp3Timer.reset(); | |
mp3Timer.start(1500); | |
greetingPlayed = false; | |
waitTimer.reset(); | |
waitTimer.start(15000); | |
} | |
if (afterMp3Timer.finished()) { | |
digitalWrite(LIGHT_OVERHEAD_RELAY_K1_PIN, LOW); // on | |
} | |
if (mp3Timer.finished()) { | |
playMp3(TRACK_GREETING); | |
greetingPlayed = true; | |
} | |
if (greetingPlayed) { | |
if (waitTimer.finished()) { | |
setStage(invitation); | |
} | |
} | |
} else if (stage == invitation) { | |
if (isNewStage()) { | |
playMp3(TRACK_CIRCLE_LIGHT_ON); | |
afterMp3Timer.reset(); | |
afterMp3Timer.start(MP3_PLAY_DELAY); | |
waitTimer.reset(); | |
waitTimer.start(12500); | |
} | |
if (afterMp3Timer.finished()) { | |
digitalWrite(LIGHT_CIRCLE_RELAY_K2_PIN, LOW); // on | |
} | |
irReadTimer.start(100); | |
if (irReadTimer.finished()) { | |
if (isHigh(CIRCLE_IR_PIN)) { | |
setStage(conjure); | |
} | |
} | |
if (waitTimer.finished()) { | |
setStage(circleRejected); | |
} | |
} else if (stage == circleRejected) { | |
if (isNewStage()) { | |
// playMp3(TRACK_CIRCLE_REJECTED); // todo | |
} | |
setStage(theEnd); | |
} else if (stage == conjure) { | |
if (isNewStage()) { | |
playMp3(TRACK_CONJURE); | |
afterMp3Timer.reset(); | |
afterMp3Timer.start(MP3_PLAY_DELAY); | |
guyTimer.reset(); | |
guyTimer.start(1500); | |
conveyTimer.reset(); | |
conveyTimer.start(6000); | |
conveyWait = true; | |
conveyShuffling = false; | |
conveyStep = 0; | |
lsBlockedCount = 0; | |
conjureLightSeqTimer.reset(); | |
conjureLightSeqIdx = 0; | |
waitTimer.reset(); | |
waitTimer.start(16500); | |
} | |
if (afterMp3Timer.finished()) { | |
digitalWrite(LIGHT_CIRCLE_RELAY_K2_PIN, HIGH); // off | |
} | |
if (conveyTimer.finished()) { | |
conveyor.setMaxSpeed(500); | |
conveyor.enableOutputs(); | |
conveyWait = false; | |
} | |
if (!conveyWait) { | |
lsReadTimer.start(10); | |
if (lsReadTimer.finished()) { | |
if (isHigh(CONVEY_LS_PIN)) { | |
if (lsBlockedCount > 0 && !conveyShuffling) { | |
conveyor.stop(); | |
conveyStep = -1; | |
} | |
} else { | |
// blocked -- candy falling | |
++lsBlockedCount; | |
Serial.print("lsBlockedCount="); Serial.println(lsBlockedCount); | |
conveyShuffling = true; | |
conveyStep == 1; | |
} | |
} | |
if (!conveyor.isRunning()) { | |
if (conveyStep == 0 && conveyShuffling) { | |
conveyShuffling = false; | |
} else if (conveyStep == 0) { | |
conveyor.setAcceleration(3000); | |
conveyor.move(200); | |
++conveyStep; | |
} else if (conveyStep == 1) { | |
conveyor.setAcceleration(4000); | |
conveyor.move(-20); | |
--conveyStep; | |
} | |
} | |
} | |
if (!conveyWait && conjureLightSeqIdx < conjureLightSeqLen) { | |
conjureLightSeqTimer.start(conjureLightSeqDelay); | |
if (conjureLightSeqTimer.finished()) { | |
digitalWrite(LIGHT_OVERHEAD_RELAY_K1_PIN, | |
conjureLightSeq[conjureLightSeqIdx] ? LOW : HIGH); | |
++conjureLightSeqIdx; | |
} | |
} else if (conjureLightSeqIdx == conjureLightSeqLen) { | |
digitalWrite(LIGHT_OVERHEAD_RELAY_K1_PIN, LOW); | |
} | |
if (guyTimer.finished()) { | |
guy.enableOutputs(); | |
guy.setMaxSpeed(1500); | |
guy.setAcceleration(666); | |
guy.move(4250); | |
} | |
if (waitTimer.finished()) { | |
if (conveyor.isRunning()) { | |
conveyor.stop(); | |
} | |
guy.setMaxSpeed(1500); | |
guy.setAcceleration(666); | |
guy.move(-4250); | |
if (lsBlockedCount > 0) { | |
setStage(treatReveal); | |
} else { | |
setStage(couldNotConjure); | |
} | |
} | |
} else if (stage == couldNotConjure) { | |
setStage(theEnd); | |
} else if (stage == treatReveal) { | |
// note: guy should still be moving in this stage | |
if (isNewStage()) { | |
playMp3(TRACK_TREAT_REVEAL); | |
afterMp3Timer.reset(); | |
afterMp3Timer.start(MP3_PLAY_DELAY); | |
waitTimer.reset(); | |
waitTimer.start(12500); | |
scareTimerStarted = false; | |
irReadTimer.reset(); | |
} | |
if (afterMp3Timer.finished()) { | |
digitalWrite(LIGHT_CIRCLE_RELAY_K2_PIN, HIGH); // off | |
digitalWrite(LIGHT_TREAT_RELAY_K3_PIN, LOW); // on | |
conveyor.disableOutputs(); | |
} | |
if (waitTimer.finished()) { | |
setStage(treatRejected); | |
} | |
if (!scareTimerStarted) { | |
irReadTimer.start(100); | |
if (irReadTimer.finished()) { | |
if (isHigh(TREAT_IR_PIN)) { | |
scareTimer.start(666); | |
scareTimerStarted = true; | |
} | |
} | |
} | |
if (scareTimerStarted && scareTimer.finished()) { | |
setStage(treatScare); | |
} | |
} else if (stage == treatRejected) { | |
lightsOff(); | |
setStage(theEnd); | |
} else if (stage == treatScare) { | |
if (isNewStage()) { | |
playMp3(TRACK_RED_LIGHT_ON); | |
afterMp3Timer.reset(); | |
afterMp3Timer.start(MP3_PLAY_DELAY); | |
mp3Timer.reset(); | |
mp3Timer.start(1000); | |
treatLightOnAgainTimer.reset(); | |
treatLightOnAgainTimer.start(3000); | |
} | |
if (afterMp3Timer.finished()) { | |
digitalWrite(LIGHT_OVERHEAD_RELAY_K1_PIN, HIGH); | |
digitalWrite(LIGHT_RED_FACE_RELAY_K4_PIN, LOW); | |
digitalWrite(LIGHT_TREAT_RELAY_K3_PIN, HIGH); | |
} | |
if (mp3Timer.finished()) { | |
playMp3(TRACK_TREAT_SCARE_SCREAM); | |
waitTimer.start(17000); | |
} | |
if (treatLightOnAgainTimer.finished()) { | |
digitalWrite(LIGHT_TREAT_RELAY_K3_PIN, LOW); | |
} | |
if (waitTimer.finished()) { | |
setStage(theEnd); | |
} | |
} else if (stage == theEnd) { | |
if (!guy.isRunning()) { | |
guy.disableOutputs(); | |
lightsOff(); | |
irReadTimer.reset(); | |
setStage(idle); | |
} | |
} else { | |
Serial.print("unhandled stage(?): "); Serial.println(stage); | |
} | |
if (stage != idle) { | |
++newStageLoops; | |
} | |
} | |
void setStage(skitStage newStage) { | |
Serial.print("stage="); Serial.println(newStage); | |
stage = newStage; | |
newStageLoops = 0; | |
} | |
bool isNewStage() { | |
return newStageLoops <= 1; | |
} | |
void playMp3(int track) { | |
mp3.playMp3FolderTrack(track); | |
mp3Playing = true; | |
} | |
void lightsOff() { | |
digitalWrite(LIGHT_OVERHEAD_RELAY_K1_PIN, HIGH); | |
digitalWrite(LIGHT_CIRCLE_RELAY_K2_PIN, HIGH); | |
digitalWrite(LIGHT_TREAT_RELAY_K3_PIN, HIGH); | |
digitalWrite(LIGHT_RED_FACE_RELAY_K4_PIN, HIGH); | |
} | |
bool isHigh(int pin) { | |
return digitalRead(pin) == HIGH; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment