Skip to content

Instantly share code, notes, and snippets.

@lucjross
Created November 9, 2020 23:08
Show Gist options
  • Save lucjross/1ccb6741f200798899dc2b075f3f3a3c to your computer and use it in GitHub Desktop.
Save lucjross/1ccb6741f200798899dc2b075f3f3a3c to your computer and use it in GitHub Desktop.
Dark Sorceror of Treats - Arduino program
#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