-
Network Code
04/04/2022 at 10:21 • 0 commentsThe WiFi shield from Adafruit allows me to control the TARDIS from a tablet or smartphone.
Code Snippet for triggering actions based on messages received via AdaFruit cloud
/* * tardis_network.h * Claude Felizardo 2018-10-06 * * uses wifi board form Adafruit */ #ifndef _tardis_network_h_ #define _tardis_network_h_ #define TARDIS_NETWORK_VERSION "$Id$" #include <SPI.h> #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" #include <WiFi101.h> #include "network_config.h" // wifi and adafruit io config info int network_status = WL_NO_SHIELD; //Set up the wifi client WiFiClient client; Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY); // Setup various feeds to control the tardis Adafruit_MQTT_Subscribe mqtt_power_off = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.power-off"); Adafruit_MQTT_Subscribe mqtt_door_switch = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.door-switch"); Adafruit_MQTT_Subscribe mqtt_stop_sound = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.stop-sound"); Adafruit_MQTT_Subscribe mqtt_play_sfx = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.play-sfx"); Adafruit_MQTT_Subscribe mqtt_play_theme = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.play-theme"); Adafruit_MQTT_Subscribe mqtt_lights_off = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.lights-off"); Adafruit_MQTT_Subscribe mqtt_roof_light = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.roof-light"); Adafruit_MQTT_Subscribe mqtt_sign_color = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.sign-color"); Adafruit_MQTT_Subscribe mqtt_sound_bank = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/tardis.sound-bank"); void tardis_network_setup() { Serial.println("Setting up WiFi..."); WiFi.setPins(WINC_CS, WINC_IRQ, WINC_RST, WINC_EN); // Initialise the Client Serial.print(F("\nInit the WiFi module...")); // check for the presence of the breakout if ((network_status = WiFi.status()) == WL_NO_SHIELD) { Serial.println("WINC1500 not present"); // don't continue: // while (true); } else { Serial.println("ATWINC OK!"); } mqtt.subscribe(&mqtt_power_off); mqtt.subscribe(&mqtt_stop_sound); mqtt.subscribe(&mqtt_play_sfx); mqtt.subscribe(&mqtt_play_theme); mqtt.subscribe(&mqtt_lights_off); mqtt.subscribe(&mqtt_roof_light); mqtt.subscribe(&mqtt_sign_color); mqtt.subscribe(&mqtt_door_switch); mqtt.subscribe(&mqtt_sound_bank); Serial.println("WiFi setup done."); } // tardis_network_setup // Function to connect and reconnect as necessary to the MQTT server. // Should be called in the loop function and it will take care of connecting. void MQTT_connect() { // attempt to connect to Wifi network: while ((network_status = WiFi.status()) != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(WIFI_SSID); network_status = WiFi.begin(WIFI_SSID, WIFI_PASS); Serial.print("network_status="); Serial.println(network_status); // wait 10 seconds for connection: uint8_t timeout = 10; while (timeout && ((network_status = WiFi.status()) != WL_CONNECTED)) { timeout--; delay(1000); } } //if (WiFi.status() == WL_CONNECTED) { // Serial.print("Connected to SSID"); //} // return if already connected. if (mqtt.connected()) { return; } Serial.print("Connecting to MQTT... "); int8_t ret; while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected Serial.println(mqtt.connectErrorString(ret)); Serial.println("Retrying MQTT connection in 5 seconds..."); mqtt.disconnect(); delay(5000); // wait 5 seconds } Serial.println("MQTT Connected!"); } // MQTT_connect() void tardis_network_check() { // Ensure the connection to the MQTT server is alive (this will make the first // connection and automatically reconnect when disconnected). See the MQTT_connect // function definition further below. if (network_status != WL_NO_SHIELD) MQTT_connect(); Adafruit_MQTT_Subscribe *subscription; while ((subscription = mqtt.readSubscription(50/*00*/))) { if (subscription == &mqtt_power_off) { Serial.print(F("Got power_off: ")); Serial.println((char *)mqtt_power_off.lastread); if (0 == strcmp((char *)mqtt_power_off.lastread, "OFF")) { TardisAudio::stopAll(); atx_shield.powerOff(); sign_color.disable(); roof_light.disable(); } } else if (subscription == &mqtt_stop_sound) { Serial.print(F("Got stop_sound: ")); Serial.println((char *)mqtt_stop_sound.lastread); if (0 == strcmp((char *)mqtt_stop_sound.lastread, "STOP-SOUND")) { TardisAudio::stopAll(); } } else if (subscription == &mqtt_play_sfx) { Serial.print(F("Got play_sfx: ")); Serial.println((char *)mqtt_play_sfx.lastread); if (0 == strcmp((char *)mqtt_play_sfx.lastread, "PLAY-SFX")) { if (TardisAudio::sound_bank == 0) sound_track = TARDIS_list.getNext(); else sound_track = Alt_list.getNext(); sprintf(buffer, "Playing sound_track %d", sound_track); Serial.println(buffer); TardisAudio::playTrack(sound_track); } } else if (subscription == &mqtt_play_theme) { Serial.print(F("Got play_theme: ")); Serial.println((char *)mqtt_play_theme.lastread); if (0 == strcmp((char *)mqtt_play_theme.lastread, "PLAY-THEME")) { if (TardisAudio::sound_bank == 0) sound_track = Theme_list.getNext(); else sound_track = Alt_list.getNext(); sprintf(buffer, "Playing sound_track %d", sound_track); Serial.println(buffer); TardisAudio::playTrack(sound_track); } } else if (subscription == &mqtt_sound_bank) { Serial.print(F("Got sound_bank: ")); Serial.println((char *)mqtt_sound_bank.lastread); if (0 == strcmp((char *)mqtt_sound_bank.lastread, "SOUND-BANK")) { TardisAudio::change_sound_bank(); } } else if (subscription == &mqtt_lights_off) { Serial.print(F("Got lights_off: ")); Serial.println((char *)mqtt_lights_off.lastread); if (0 == strcmp((char *)mqtt_lights_off.lastread, "LIGHTS-OFF")) { roof_override = 0; sign_color_override = 0xffffff; } } else if (subscription == &mqtt_door_switch) { Serial.print(F("Got door_switch: ")); Serial.println((char *)mqtt_door_switch.lastread); if (0 == strcmp((char *)mqtt_door_switch.lastread, "OPEN")) { door_override = 1; } if (0 == strcmp((char *)mqtt_door_switch.lastread, "CLOSED")) { door_override = 0; } } else if (subscription == &mqtt_roof_light) { Serial.print(F("Got roof_light: ")); Serial.print((char *)mqtt_roof_light.lastread); roof_override = atoi((char *)mqtt_roof_light.lastread); Serial.print(F(" => ")); Serial.println(roof_override); } else if (subscription == &mqtt_sign_color) { Serial.print(F("Got sign_color: ")); Serial.print((char *)mqtt_sign_color.lastread); Serial.print(F(" -> ")); Serial.print((char *)mqtt_sign_color.lastread+1); sign_color_override = strtol((char *)mqtt_sign_color.lastread+1, 0, 16) ^ 0xffffff; Serial.print(F(" => ")); Serial.println(sign_color_override); } } if (door_override != -1) door_position = door_override; } // tardis_network_check #endif // _tardis_network_h_
-
ATX power shield code
04/04/2022 at 09:31 • 0 commentsI use the SparkFun ATX Power Shield to control the 6 PWM LED channels.
SparkFun Power Driver Shield Kit
Code snippet for controlling the ATX shield
/* ATX shield with ATX control claude felizardo 2018-08-06 https://www.sparkfun.com/products/10618 Uses modified ATX shield to use pin 2 to control PS_ON control pin on ATX connector. */ #ifndef _atx_shield_h_ #define _atx_shield_h_ #define ATX_SHIELD_VERSION "$Id$" class ATX_Shield { int atx_ctl_pin = 0; // output for controlling ATX power supply int atx_enabled = 0; // current state public: ATX_Shield(int output_pin) { atx_ctl_pin = output_pin; } void output_power() { Serial.print("atx_enabled = "); Serial.println(atx_enabled, DEC); digitalWrite(atx_ctl_pin, atx_enabled == 0 ? HIGH : LOW); } // output_power // this should be called once void setup() { pinMode(atx_ctl_pin, OUTPUT); atx_enabled = 0; output_power(); } int isEnabled() { return atx_enabled; } void powerOn() { if (!atx_enabled) { atx_enabled = 1; output_power(); } } void powerOff() { if (atx_enabled) { atx_enabled = 0; output_power(); } } }; // class ATX_Shield #endif // _atx_shield_h_
-
Door Detection
04/04/2022 at 09:22 • 0 commentsThe original plan was to use analog inputs to somehow measure the angle of the doors and adjust the audio sound effects on the fly as well as trigger the lights. Couldn't figure it out so I settled for using Hall effect sensors in the cabinet that are triggered by magnets in the doors.
Code snippet for detecting if any of the 4 doors are open or closed.const int DOOR_1 = 40; const int DOOR_2 = 41; const int DOOR_3 = 42; const int DOOR_4 = 43; // live or prototype int invert = -1; // initialize inputs pinMode(DOOR_SWITCH, INPUT_PULLUP); pinMode(DOOR_1, INPUT_PULLUP); pinMode(DOOR_2, INPUT_PULLUP); pinMode(DOOR_3, INPUT_PULLUP); pinMode(DOOR_4, INPUT_PULLUP); int getDoorPosition() { return 0 + 1 * (digitalRead(DOOR_1) ^ (1 - invert)) + 2 * (digitalRead(DOOR_2) ^ (1 - invert)) + 4 * (digitalRead(DOOR_3) ^ (1 - invert)) + 8 * (digitalRead(DOOR_4) ^ (1 - invert)) //+ 16 * (digitalRead(DOOR_SWITCH) ^ (1 - invert)) ; }
-
Sound effects
04/04/2022 at 08:52 • 0 commentsI really like the Robertson WAVE shield from Adafruit that can play multiple channels at the same time. Initially I used individual digital output pins from the Arduino to pull the level inputs on the WAV board but I eventually switched to using the MIDI interface. I wanted to preserver the ability to upload new code into the Arduino using the default serial port so I needed another serial port which is when I upgraded to an Arduino Mega board. The code assumes particular tracks are stored on the SD card on the shield.
Code snippet for controlling audio playback/* * tardis_audio.h * Claude Felizardo 2018-08-29 * * uses Wave Trigger library for Robertson WavTrigger board from Adafruit */ #ifndef _tardis_audio_h_ #define _tardis_audio_h_ #if 1 // Wave trigger library using software UART // https://github.com/robertsonics/WAV-Trigger-Arduino-Serial-Library #include <Metro.h> // Timer class used by AltSoftSerial #include <AltSoftSerial.h> // Software serial on Uno uses 16-bit timer, Rx8, Tx9, PWM10 not available // https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html #define __WT_USE_ALTSOFTSERIAL__ #else #define __WT_USE_SERIAL3__ #endif #include <wavTrigger.h> // wave trigger library #define TARDIS_AUDIO_VERSION "$Id$" class List { int* array; int count; int index; enum {ORDERED, SHUFFLED, RANDOM} order; public: List(int list[], int count) { this->array = (int*)calloc(count, sizeof(int)); this->count = count; this->index = 0; this->order = ORDERED; for (int idx = 0; idx < this->count; idx++) this->array[idx] = list[idx]; } int size() { return this->count; } int getNext() { if (this->index >= this->count) { switch (this->order) { case SHUFFLED: case RANDOM: shuffle(); break; } // switch this->index = 0; } return this->array[this->index++]; } void shuffle() { for (int idx = 0; idx < this->count; idx++) { int r = random(idx, this->count); int temp = this->array[idx]; this->array[idx] = this->array[r]; this->array[r] = temp; } this->order = SHUFFLED; } void print(const char* name) { char buffer[200]; sprintf(buffer, "%s num tracks is %d", name, size()); Serial.println(buffer); for (int i = 0; i < size(); i++) { int z = getNext(); sprintf(buffer, "%d: track=%d", i, z); Serial.println(buffer); } } }; // class List int TARDIS_tracks[] = {1009, 1010, 1011, 1012}; List TARDIS_list(TARDIS_tracks, sizeof(TARDIS_tracks) / sizeof(int)); int Theme_tracks[] = {2009, 2010, 2011, 2012}; List Theme_list(Theme_tracks, sizeof(Theme_tracks) / sizeof(int)); int Num_tracks[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; List Num_list(Num_tracks, 9);//sizeof(Num_tracks) / sizeof(int)); int Alt_tracks[] = {500, 501, 502, 503, 504, 505}; List Alt_list(Alt_tracks, sizeof(Alt_tracks) / sizeof(int)); class TardisAudio { static wavTrigger wTrig; // Our WAV Trigger object public: static void setup() { Serial.println("TardisAudio::setup(): calling wTrig.start()"); // WAV Trigger startup at 57600 wTrig.start(); // If the Uno is powering the WAV Trigger, we should wait for the WAV Trigger // to finish reset before trying to send commands. Serial.println("TardisAudio::setup(): waiting for WAV trigger to start"); delay(1000); // If we're not powering the WAV Trigger, send a stop-all command in case it // was already playing tracks. If we are powering the WAV Trigger, it doesn't // hurt to do this. Serial.println(F("TardisAudio::setup(): calling stopAllTracks")); wTrig.stopAllTracks(); TARDIS_list.shuffle(); TARDIS_list.print("TARDIS"); Theme_list.shuffle(); Theme_list.print("Theme"); Num_list.shuffle(); Num_list.print("Num"); Alt_list.shuffle(); Alt_list.print("Alt"); TardisAudio::stopAll(); Serial.println("TardisAudio::setup(): returning"); } // setup static void stopAll() { Serial.println("Stopping audio"); wTrig.stopAllTracks(); } // stopAll static void playTrack(int track) { wTrig.trackPlaySolo(track); } // playTrack static int sound_bank; static void change_sound_bank() { sound_bank = 1 - sound_bank; Serial.print(F("sound_bank="));Serial.println(sound_bank, DEC); } // change_sound_bank }; // class TardisAudio wavTrigger TardisAudio::wTrig; // Our WAV Trigger object int TardisAudio::sound_bank = 0; #endif // _tardis_audio_h_
-
Code For Pulsing Lights
04/04/2022 at 08:20 • 0 commentsI defined a set of classes for controlling the LED lights. The base class is called Flasher which will cycle an LED on and off at a specified rate. The Pulser class will slowly brighten and dim an LED using a PWM output pin. Finally the RGB_Pulser class will work on a set of three PWM pins.
I use these classes to control the various lights as follows:
// Flasher ctor takes led, on perid and off period Flasher builtin_led(LED_BUILTIN, 10, 1000); // flash power light at a steady interval // Pulser ctor takes pin, interval, maxValue, initial, increment, offset Pulser roof_light(ROOF_LIGHT, 5, 63, 20, 10, 0); //Flasher roof_light(ROOF_LIGHT, 100, 50); //Pulser cabinet_light(CABINET_LIGHT, 5, 63, 20, 10, 0); Flasher cabinet_light(CABINET_LIGHT); // RGB_Pulser ctor takes pins for red green and blue RGB_Pulser sign_color(SIGN_RED_LED, SIGN_GREEN_LED, SIGN_BLUE_LED);
// Flasher -- based on tutorial from Adafruit // https://learn.adafruit.com/multi-tasking-the-arduino-part-1 #ifndef _Flasher_h_ #define _Flasher_h_ #define FLASHER_VERSION "$Id: Flasher.h 2 2018-08-29 07:08:22Z cafelizardo $" enum { DISABLED = -1, OFF=LOW, ON=HIGH } LED_STATE; class Flasher { protected: // configuration variables int ledPin; // number of the LED pin long onTime; // milliseconds of on-time long offTime; // milliseconds of off-time bool inverted; // invert output // state variables int ledState; // current value: DISABLED, OFF or ON unsigned long previousMillis; // last time LED was last updated public: // constructor -- creates a Flasher object // and initialized the member variables and state Flasher(int pin = 0, long on = 0, long off = 0) { this->ledPin = pin; pinMode(ledPin, OUTPUT); this->onTime = on; this->offTime = off; this->inverted = false; this->ledState = OFF; this->previousMillis = 0; } // ctor // modifiers Flasher& setOnTime(long onTime) { this->onTime = onTime; return *this; } Flasher& setOffTime(long offTime) { this->offTime = offTime; return *this; } Flasher& setInvert(bool invert) { this->inverted = invert; return *this; } virtual Flasher& output(int value) { if (this->ledState == DISABLED) { digitalWrite(this->ledPin, this->inverted ? HIGH : LOW); } else { digitalWrite(this->ledPin, constrain(this->inverted ? 255 - value : value, 0, 255) ); } return *this; } virtual Flasher& output() { //Serial.print("Flasher.output "); Serial.print(this->ledPin, DEC); Serial.println(""); this->output(this->ledState); return *this; } virtual Flasher& disable() { //Serial.print("disabling "); Serial.print(this->ledPin, DEC); Serial.println(""); this->ledState = DISABLED; this->output(); return *this; } virtual Flasher& enable() { //Serial.print("enabling "); Serial.print(this->ledPin, DEC); Serial.println(""); this->ledState = OFF; this->previousMillis = 0; this->output(); return *this; } virtual Flasher& update() { if (this->ledState == DISABLED) return *this; //Serial.print("flasher update "); Serial.print(this->ledPin, DEC); Serial.println(""); // check to see if it's tme to change the state of the LED unsigned long currentMillis = millis(); bool toggle = false; if ( (this->ledState == ON) && (currentMillis - this->previousMillis >= this->onTime) ) toggle = true; else if ( (this->ledState == OFF) && (currentMillis - this->previousMillis >= this->offTime) ) toggle = true; if (toggle) { this->ledState = !this->ledState; this->output(); this->previousMillis = currentMillis; } return *this; } // update }; // class Flasher #endif // _flasher_h_
// Pulser -- extends Flasher by gradually increasing brightness then back down again // initial version using cosine function. #ifndef _Pulser_h_ #define _Pulser_h_ #include "Flasher.h" #define PULSER_VERSION "$Id: Pulser.h 9 2018-10-01 07:19:06Z cafelizardo $" class Pulser : public Flasher { long interval; long offset; // offset to be applied after computing value to force clipping either low or high long increment; // increment on step_angle long maxValue; // max brightness int step_angle; public: // constructor -- creates a Pulser object Pulser() : Flasher() {} Pulser(int pin, long interval = 0, long maxValue = 255, long initial = 0, long increment = 1, long offset = 0) : Flasher(pin, interval, offset) { this->interval = interval; this->offset = offset; this->increment = max(1, increment); this->maxValue = maxValue; this->step_angle = initial; } // ctor // modifiers Pulser& setInterval(long interval) { this->interval = interval; return *this; } Pulser& setMaxValue(long maxValue) { this->maxValue = maxValue; return *this; } Pulser& setInitial(long initial) { this->step_angle = initial; return *this; } Pulser& setIncrement(long increment) { this->increment = increment; return *this; } Pulser& setOffset(long offset) { this->offset = offset; return *this; } virtual Pulser& output(int value) { //Serial.print("Pulser.output "); Serial.print(this->ledPin, DEC); Serial.print(" "); Serial.print(value, HEX); Serial.println(""); if (this->ledState == DISABLED) { digitalWrite(this->ledPin, this->inverted ? HIGH : LOW); } else { analogWrite(this->ledPin, constrain(this->inverted ? 255 - value : value, 0, 255) ); } return *this; } virtual Pulser& output() { //Serial.print("Pulser.output "); Serial.print(this->ledPin, DEC); Serial.print(" ledState="); Serial.print(this->ledState, DEC); Serial.println(""); this->output(this->ledState); return *this; } // update -- uses sin function to slowly ramp up and down virtual Pulser& update() { if (this->ledState == DISABLED) return *this; //Serial.print("pulser update "); Serial.print(this->ledPin, DEC); Serial.print(" ledState="); Serial.print(this->ledState, DEC); Serial.println(""); // get current time unsigned long currentMillis = millis(); if ( currentMillis - this->previousMillis >= this->interval ) { //convert 0-360 angle to radian (needed for cos function) float rad = DEG_TO_RAD * this->step_angle + this->offTime; //calculate cosine of angle as number between 0 and maxvalue this->ledState = constrain((-cos(rad) * this->maxValue + this->maxValue) + this->offset, 0, this->maxValue); this->step_angle += this->increment; while (this->step_angle > 360) step_angle -= 360; this->previousMillis = currentMillis; } this->output(); return *this; } // update virtual int step(const int dir = 1) { this->ledState += dir; if (this->ledState > this->maxValue) this->ledState = this->maxValue; if (this->ledState < 0) this->ledState = 0; this->output(); return this->ledState; } // step }; // class Pulser #endif // _Pulser_h
// RGB_Pulser -- combines 3 Pulser's which extends the basic Flasher interface #ifndef _RGB_Pulser_h_ #define _RGB_Pulser_h_ #include "Flasher.h" #include "Pulser.h" #define RGB_PULSER_VERSION "$Id: RGB_Pulser.h 9 2018-10-01 07:19:06Z cafelizardo $" class RGB_Pulser : public Pulser { Pulser *red_led, *green_led, *blue_led; public: // ctor RGB_Pulser(int red_pin, int green_pin, int blue_pin, int interval = 0, int maxValue = 255) { this->red_led = new Pulser(red_pin, interval, maxValue); this->green_led = new Pulser(green_pin, interval, maxValue); this->blue_led = new Pulser(blue_pin, interval, maxValue); } // access functions for Pulser objects Pulser* red() { return this->red_led; } Pulser* green() { return this->green_led; } Pulser* blue() { return this->blue_led; } virtual RGB_Pulser& output(int red, int green, int blue) { this->red_led->output(red); this->green_led->output(blue); this->blue_led->output(green); } virtual RGB_Pulser& output(long rgb) { #if 0 Serial.print(F("RGB_Pulser.output ")); Serial.print(rgb, HEX); Serial.print(F(" ")); Serial.print((rgb & 0xff0000) >> 16, HEX); Serial.print(F(" ")); Serial.print((rgb & 0x00ff00) >> 8, 16); Serial.print(F(" ")); Serial.print(rgb & 0x0000ff, 16); Serial.println(); #endif this->red_led->output((rgb & 0xff0000) >> 16); this->green_led->output((rgb & 0x00ff00) >> 8); this->blue_led->output(rgb & 0x0000ff); return *this; } virtual RGB_Pulser& disable() { Serial.println("RGB_light.disable "); this->red_led->disable(); this->red_led->output(255); this->green_led->disable(); this->green_led->output(255); this->blue_led->disable(); this->blue_led->output(255); return *this; } virtual RGB_Pulser& enable() { Serial.println("RGB_light.enable "); this->red_led->enable(); this->green_led->enable(); this->blue_led->enable(); return *this; } virtual RGB_Pulser& update() { //Serial.println("RGB_light.update "); this->red_led->update(); this->green_led->update(); this->blue_led->update(); return *this; } }; // class RGB_Pulser #endif // _RGB_Pulser_h