The Holo-day Text a Jingle Christmas Tree was designed to spread the holiday joy. Using Hologram's network, friends and family can play you Jingles remotely via SMS and allow users to share the Holidays even when they are not physically together.
Contents:- The Structure
- The Circuit
- The Assembly
- The Code
- The Songs
- The Cloud
- The Result
The tree structure was designed entirely in TinkerCad with the exception of the Hologram Logo tree topper which was a bitmap image that I imported it to TinkerCad. To this I simply added a stem so I could glue it onto the tree.
For the next iteration of this project, my plan is to make a tiny hole at the top of the tree to make it easier to swap out tree toppers.
The tree portion was the part that I thought would be the most complicated. I didn't have anything specific in mind, but knew that a traditional tree shape (something like a tree cookie cutter) would have too many overhangs making it hard to print. Therefore, I decided to stick to a simple cone shape and make a more abstract tree. However, I felt that a simple cone would be a bit dull, so I looked at TinkerCad's "Featured Shape Generators" section and that is where I discovered the Drill shape by Gerold Shi. The generator allowed me to modify the number of points on the drill shape as well as the degree of the twist. A little trial and error here and there and I had my tree shape which I plopped onto a disk.
After that I needed to make the LED holes. For this I created a long cylinder, duplicated it and rotated one 90 degrees to get a "+" shape. I merged those, duplicated the shape and rotated one 90 degrees again to get an "*" shape. I merged those again and made two more copies which I elevated on-top of each other. Lastly, I rotated the middle object 45 degrees.
I proceed to then duplicate my tree and disc and shrunk the copies to make an empty shell of the original tree shape. I aligned the tree and disk, the smaller tree and disk hole, and the LED holes and merged them to get the final tree shape.
Next came the box to hold the Hologram Dash and the breadboard. For this I envisioned two cylinders, one a bit larger than the base of my tree, and one a bit larger than the Dash connected by a gradient. To make the bottom cylinder I simply created the shape and made a smaller copy which I raised and turned into a hole to empty out the shape. For the gradient portion, I created a cone shape with a base that was the same size as the bottom cylinder. I followed the same procedure I did for everything else to hollow out the cone. I then created a hole version of a cylinder that was the same size as my top cylinder and used that to measure the correct spot to chop off the top of my cone leaving me the perfect gradient section. Lastly, the top cylindrical portion of the box followed the same procedure as the bottom cylindrical portion with the addition of a last step where I made an even smaller cylindrical hole which I used to cut out the bottom while leaving a lip for the tree portion to rest on. I then made a cylindrical hole to pierce one side of the base to allow the sound waves of my piezo electric speaker to leave the structure. Opposite of that I made a hole to allow me to both plug in my Dash while also leaving enough room to fit my fingers in if I needed to press the program or reset buttons. I did this by combining a rectangle with an oval hoping that an arch would print cleanly, but the result, while not, bad, required some cleanup with a knife.
Here is what it all looks when put together in TinkerCAD:
All three pieces were printed on an Ultimaker 3 using transparent PLA filament and took about 50 hours on 20% infill. The files are on Thingiverse for those who want to just download them.
The circuitThe circuitry for this tree is pretty straight forward. The LEDs are separated by color into 4 groups, 2 with 6 LEDs and 2 with 4 LEDs. Each group has its LEDs wired in Parallel and connected to ground through a 220 Ohm resistor. Looking back, I wish I had used a potentiometer to find out the ideal resistor values because to me the green LEDs seem brighter than the red ones. All the LEDs are connected directly to power. However, if plugged into the piezo's power line the LED's will blink on and off to the sound of the music.
The piezo is connected to pin D02 on the Dash which is a PWM pin. This is required to use the tone() function. The other end of the piezo is connected in series to a 10 ohm resistor which is connected to ground. Originally, I used a fairly small buzzer so it was hard to hear it when inside the 3D printed structure. I thought about possibly adding a transistor and powering the piezo with a 9v battery, but instead I found a bigger buzzer in my toolbox which was plenty loud.
Assembling this was straight forward, but required fitting LED's in tight spots so you definitely have to be strategic about it. I first soldered a power and ground wire to all the LEDs. I quickly designed and 3D printer an LED holder to help with this task.
Once I was done with that, I started placing the LED's in their holes working from top to bottom. Once in their hole, I super glued the LEDs in place to make my life easier. After I glued every group in, I soldered the leads onto a piece of copper tape. Each group got its own piece of copper tape for ground, but I used one for all the positive leads. I did this so I could then wire all the ground copper pads to one main one, through their resistors, and end up with a single positive lead and a single negative lead for the whole tree. For a second iteration I might consider separating the power lines so I can power the LED groups separately and make animations.
You definitely want to make sure you test for shorts often given that there is a ton of wiring in a small space.
Once you have done this a few times it gets a bit easier and faster. Just make sure to avoid super gluing your fingers to the tree like I did.
Once I was done with the tree's wiring, I hot glued the Hologram logo tree topper. Finally, I tested everything wired together and got some pretty mesmerizing shots.
The code was written with Arduino's IDE and uses Hologram's API to abstract all the cellular connectivity portion. Currently it allows users to play 10 different songs and has a help function to list the songs. There is no need to copy it in chunks as I have the whole thing in one box at the end of this section.
The first part declares the variables and establishes the names of the pins on the Dash.
//Dash Pins
int buzzer = D02;
//variables
int messageSuccess = 0;
The setup loop sets our Dash's buzzer pin to output mode, initializes serial communication for debugging, goes through a test sequence which I will talk about later, and finally uses Hologram's API to send a message to the Hologram Cloud and attach an SMS handler function which is described further down in this section.
void setup() {
//set pin to output
pinMode(buzzer, OUTPUT);
// initialize Serial communication for debugging
Serial.begin(9600);
Serial.println("Starting");
//Trigger the test sequence
test_sequence();
//send a starting messge to the Hologram cloud
HologramCloud.sendMessage("Starting", "Holiday");
//attach the SMS Handler Funciton
HologramCloud.attachHandlerSMS(cloud_sms);
}
The loop in this sketch simply prints out a "loop" debug message every 3 seconds. This is because our jingles and the code to play them will be their own functions and because the SMS handler will do most of the heavy lifting.
void loop() {
//print loop to let us know we are running in serial
Serial.println("loop");
delay(3000);
}
The SMS handler was borrowed from Hologram's REPL sketch and modified to fit this project (Shout-out to Hologram and Erik). Basically, it gets the sender, timestamp, and message fields from incoming SMS messages and prints them to serial. The part I added converted the message into a string called message2, which was then set to all lower case and passed through a battery of if-else if statements that printed the message name and triggered the specific jingle function. You'll notice that each if statement has two or'd conditions. I did this because I'm lazy and didn't want to have to type the whole jingle's name every time. I also added a helper condition that sends a list of the text queues to both the Hologram Cloud and Serial. This was aptly named "Plz Help" . The last condition, the else without the if, is a catch all for any input that is not tied to a function. The last bit of this function echos the text message to the Hologram Cloud and prints a success or failure message. This will then allow us to implement routes on Holograms Data Engine to add even more functionality.
//On received SMS, print the sender, timestamp and message
//Send message to the cloud that an SMS was received with the sender number
//as the content and the tag SMSRX
void cloud_sms(const String &sender, const rtc_datetime_t ×tamp, const String &message) {
Serial.println("CLOUD SMS RECEIVED:");
Serial.print("SMS SENDER: ");
Serial.println(sender);
Serial.print("SMS TIMESTAMP: ");
Serial.println(timestamp);
Serial.println("SMS TEXT: ");
Serial.println(message);
String message2 = message;
message2.toLowerCase();
if (message2 == "jb" || message2 == "jingle bells") {
jingle_bells();
Serial.println("Jingle Bells");
}
else if (message2 == "ww" || message2 == "we wish you a merry xmas") {
we_wish_you_a_merry_xmas();
Serial.println("We Wish You a Merry Christmas");
}
else if (message2 == "dh" || message2 == "deck the halls") {
deck_the_halls();
Serial.println("Deck the Halls");
}
else if (message2 == "sn" || message2 == "silent night") {
silent_night();
Serial.println("Silent Night");
}
else if (message2 == "ot" || message2 == "o tannenbaum") {
o_tannenbaum();
Serial.println("O Tannenbaum");
}
else if (message2 == "ftsm" || message2 == "frosty the snowman") {
frosty_the_snowman();
Serial.println("Frosty the Snowman");
}
else if (message2 == "jbr" || message2 == "jingle bell rock") {
jingle_bell_rock();
Serial.println("Jingle Bell Rock");
}
else if (message2 == "rtrnrd" || message2 == "rudolph the red nosed raindeer") {
rudolph_the_red_nosed_rainder();
Serial.println("Rudolph the Red Nosed Raindeer");
}
else if (message2 == "lc" || message2 == "surprize") {
la_cucaracha();
Serial.println("La Cucaracha");
}
else if (message2 == "plz help") {
HologramCloud.sendMessage("Jingle Bells, We Wish You a Merry Xmas, Deck the Halls, Silent Night, O Tannenbaum, Frosty the Snowman, Jingle Bell Rock, Rudolph the Red Nosed Raindeer, Surprize", "Help");
Serial.println("Jingle Bells");
Serial.println("We Wish You a Merry Xmas");
Serial.println("Deck the Halls");
Serial.println("Silent Night");
Serial.println("O Tannenbaum");
Serial.println("Frosty the Snowman");
Serial.println("Jingle Bell Rock");
Serial.println("Rudolph the Red Nosed Raindeer");
Serial.println("Surprize");
Serial.println();
}
else {
Serial.println("Invalid Input");
}
if (HologramCloud.sendMessage(message, "SMSRX")) {
Serial.println("SMS received message sent to cloud.");
} else {
Serial.println("Notification send failed.");
}
}
Next we have three distinct functions nested together that make up the musical part of this project. These are playTone(), playNote(), and PlaySong(). I borrowed the original code from Arduino's tutorial page for Melody and made some modifications which I will talk about here.
The first function we want to look at is playNote(). Here we define the notes we will have and their corresponding tone. Arduino is kind enough to provide us with one full octave, but I had two issues here. First, it used alphabetical letters instead of the Fixed Do naming convention which I am used to. Second one octave is not enough for most of the songs we need, so using two basic conversions we were able to solve these issues. Problem one was solved by switching letters for the note names in the following order
A=La B=Ti C=Do D=Re E=Mi F=Fa G=Sol
However, we need these to be a single character long so I simply used the first letter of each name.
For the second issue I looked up a chart with all the Frequencies of musical notes and used the equation provided on the Arduino tutorial
timeHigh = period / 2 = 1/(2*toneFrequency)
to extend our range. A quick Python script made this process a breeze. One thing I wish I had done was made a quick side project to tie my buzzer to potentiometer to map out its range and get as many notes out of it as possible...maybe next time...
Anyway, after this process I had something that looked like this:
void playNote(char note, int duration) {
char names[] = { '+', 'D', 'R', 'M', 'F', 'Z', 'S', 'L', 'P', 'T', 'd', 'r', 'm', 'f', 'z', 's', 'l', 't'};
int tones[] = { 4050, 3822, 3405, 3034, 2863, 2703, 2551, 2272, 2145, 2024, 1915, 1700, 1519, 1432, 1352, 1275, 1136, 1014};
// play the tone corresponding to the note name
for (int i = 0; i < 17; i++) {
if (names[i] == note) {
playTone(tones[i], duration);
}
}
}
Here I added the a whole other octave and then some. Capital letters are the lower octave, + is the lowest Ti, Z and z are the first Sharps (Fa) in their respective octaves, and P is the first flat (Ti) in the lower octave. I'll explain the rest of this function after I go through the other two and the songs as that is what makes most sense to me.
The next function is playTone(). I left this function pretty much intact outside of changing variable names.
void playTone(int tone, int duration) {
for (long i = 0; i < duration * 1000L; i += tone * 2) {
digitalWrite(buzzer, HIGH);
delayMicroseconds(tone);
digitalWrite(buzzer, LOW);
delayMicroseconds(tone);
}
}
Lastly, we have the playSong() function. This is actually the loop() function in the Arduino tutorial, but since I needed to play more than one song I had to transform this into a function.
void playSong(char notes[], int beats[], int tempo, int length) {
for (int i = 0; i < length; i++) {
if (notes[i] == ' ') {
delay(beats[i] * tempo); // rest
} else {
playNote(notes[i], beats[i] * tempo);
}
// pause between notes
delay(tempo / 2);
}
}
That amounted to adding the variables that would characterize each song. Which segways nicely into the songs.
Each song was its own function that consisted of the song's length (how many notes), the notes themselves, the length of each note, the song's tempo, and finally the playSong() function that takes in all of these. Here is a list of all the songs I coded. I describe that process in more detail in the "The Songs" section of this tutorial.
void test_sequence() {
int length = 15;
char notes[] = "+DRMFSLTdrmfslt";
int beats[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
int tempo = 300;
playSong(notes, beats, tempo, length);
}
void jingle_bells() {
int length = 105;
char notes[] = "RTLSDDDDTLSM MdTLZ rrdLTDTLSR RTLSMMMdTLrrrrmrdLS TTTTTTTrSLT dddddTTTTTLLTLr TTTTTTTrSLT dddddTTTTrrdLS ";
int beats[] = {2, 2, 2, 2, 6, 1, 1, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 8, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 2, 2, 4, 2, 2, 4, 2, 2, 3, 1, 6, 2, 2, 2, 3, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, 2, 4, 2, 2, 3, 1, 6, 2, 2, 2, 3, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 6, 8};
int tempo = 100;
playSong(notes, beats, tempo, length);
}
void we_wish_you_a_merry_xmas() {
int length = 53;
char notes[] = "RSSLSZMMMLLTLSZRRTTdTLSMRRMLZSRSSSZZSZMRLTLSrRRRMLZS ";
int beats[] = {2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 4, 4};
int tempo = 150;
playSong(notes, beats, tempo, length);
}
void deck_the_halls() {
int length = 69;
char notes[] = "SFMRDRMDRMFRMRD+DSFMRDRMDRMFRMRD+DRMFRMFSRMZSLTdTLSSFMRDRMDLLLLSFMRD ";
int beats[] = {3, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 3, 1, 2, 2, 4, 3, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 3, 1, 2, 2, 4, 3, 1, 2, 2, 3, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2, 2, 4, 3, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 3, 1, 2, 2, 4};
int tempo = 150;
playSong(notes, beats, tempo, length);
}
void silent_night() {
int length = 50;
char notes[] = "SLSMSLSMrrT ddSLLdTLSLSMLLdRLSLSMrrfrTdmdSMSFRDD ";
int beats[] = {3, 1, 2, 6, 3, 1, 2, 6, 4, 2, 4, 2, 4, 2, 6, 4, 2, 3, 1, 2, 3, 1, 2, 6, 4, 2, 3, 1, 2, 3, 1, 2, 6, 4, 2, 3, 1, 2, 6, 6, 2, 2, 2, 3, 1, 2, 6, 4, 2, 4};
int tempo = 150;
playSong(notes, beats, tempo, length);
}
void o_tannenbaum() {
int length = 49;
char notes[] = "SdddrmmmmrmfTrd ssmlssffffrsrfmm SdddrmmmmrmfTrd ";
int beats[] = {4, 3, 2, 3, 2, 3, 2, 3, 2, 2, 2, 4, 4, 4, 4, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 4, 2, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 2, 4, 4, 4, 4, 8};
int tempo = 125;
playSong(notes, beats, tempo, length);
}
void frosty_the_snowman() {
int length = 30;
char notes[] = "SMFSdTdrdTLSTdrdTLLSdMSLSFMRD ";
int beats[] = {4, 3, 1, 2, 4, 1, 1, 2, 2, 2, 2, 6, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 8, 8};
int tempo = 160;
playSong(notes, beats, tempo, length);
}
void rudolph_the_red_nosed_rainder() {
int length = 112;
char notes[] = "SLSMdLSSLSLSdTFSFRTLSSLSLSLMSLSMdLSSLSLSdTFSFRTLSSLSLSrdLLdLSMSFLSFMRMSLTTTddTLSFRSLSMdLSSLSLSdTFSFRTLSSLSLSrd ";
int beats[] = {1, 2, 1, 2, 2, 2, 6, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 4, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 3, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 3, 1, 1, 1, 1, 2, 2, 8, 2, 2, 2, 2, 2, 1, 5, 2, 2, 2, 2, 8, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 1, 5, 1, 2, 1, 2, 2, 2, 5, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 5, 1, 1, 1, 1, 2, 2, 8, 8};
int tempo = 160;
playSong(notes, beats, tempo, length);
}
void jingle_bell_rock() {
int length = 74;
char notes[] = "dddTTTLTLMLTLMSLTLFRMFSLSRMFS LSLSLLRRdddTTTLTLMLTLMSLTLFRMFSLSRMFS LLTSd ";
int beats[] = {1, 1, 2, 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 2, 2, 1, 1, 2, 4, 1, 2, 1, 1, 2, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 2, 2, 2, 4, 1, 1, 2, 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 2, 2, 1, 1, 2, 4, 1, 2, 1, 1, 2, 1, 1, 1, 2, 4, 1, 1, 1, 2, 2, 4, 8};
int tempo = 160;
playSong(notes, beats, tempo, length);
}
void la_cucaracha() {
int length = 38;
char notes[] = "DDDFLDDDFL FFMMRRD DDDMSDDDMS drdPLSF ";
int beats[] = {1, 1, 1, 3, 2, 1, 1, 1, 3, 5, 1, 2, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 3, 2, 1, 1, 1, 3, 5, 1, 2, 1, 1, 1, 1, 1, 4, 4};
int tempo = 100;
playSong(notes, beats, tempo, length);
}
Now that I have gone through each of the musical parts individually I can explain how it all comes together. Basically when you call one of the song functions you create an array with that song's notes and one with its beats. These are fed into the playSong() function along with the song's length and the tempo. In the playSong() function, we loop the length of the song which is essentially the equivalent to playing each note. In this loop we have an if statement that checks what the note at that iteration is. If its a blank space or a rest it simply sets a delay for the length of that rest which is the beat array's value at that iteration times the song's tempo. Otherwise, this function calls the playNote() function and passes the value of the notes array at that iteration and the length of the note which is the beats array value at that iteration multiplied by the tempo just like for rests.
In the playNote() function we have all our possible notes in an array called names and the note's corresponding tone in an array called tones. This function iterates through all of its possible notes and checks if the note value we passed from the playSong() function is in our "library" of notes. If it is, it then calls the playTone() function and passes along the tone value for the specific note as they have the same index, and the duration of the note which we got from the playSong() function. Finally, playTone() makes the note by pushing the buzzer high and low very quickly. The specific tone is achieved by delaying the interval between highs and lows by the note's tone. This is all done in a loop that lasts the duration of the note. One of the parts that I love about this function is how the loops increment is twice the tone as that is the time that has elapsed between highs. Whoever wrote this code definitely writes code that is more elegant than mine, but in my defense I'm a hardware guy ones and zeros are the protagonists of my nightmares.
Here is a post of all the code in one chunk for your copy+pasting pleasure:
// Maiky Iberkleid Dec 2017
// Holo-day Text to Jingle XMas Tree V1.0
// Mandatory shout-out to Pranavacus Finchelberry and Frapancho "Feta" Romero
//Dash Pins
int buzzer = D02;
//variables
int messageSuccess = 0;
void setup() {
//set pin to output
pinMode(buzzer, OUTPUT);
// initialize Serial communication for debugging
Serial.begin(9600);
Serial.println("Starting");
//test sequence
test_sequence();
//send a starting messge to the Hologram cloud
HologramCloud.sendMessage("Starting", "Holiday");
//attach the SMS Handler Funciton
HologramCloud.attachHandlerSMS(cloud_sms);
}
void loop() {
//print loop to let us know we are running in serial
Serial.println("loop");
delay(3000);
}
void test_sequence() {
int length = 15;
char notes[] = "+DRMFSLTdrmfslt";
int beats[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
int tempo = 300;
playSong(notes, beats, tempo, length);
}
void jingle_bells() {
int length = 105;
char notes[] = "RTLSDDDDTLSM MdTLZ rrdLTDTLSR RTLSMMMdTLrrrrmrdLS TTTTTTTrSLT dddddTTTTTLLTLr TTTTTTTrSLT dddddTTTTrrdLS ";
int beats[] = {2, 2, 2, 2, 6, 1, 1, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 8, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 2, 2, 4, 2, 2, 4, 2, 2, 3, 1, 6, 2, 2, 2, 3, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, 2, 4, 2, 2, 3, 1, 6, 2, 2, 2, 3, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 6, 8};
int tempo = 100;
playSong(notes, beats, tempo, length);
}
void we_wish_you_a_merry_xmas() {
int length = 53;
char notes[] = "RSSLSZMMMLLTLSZRRTTdTLSMRRMLZSRSSSZZSZMRLTLSrRRRMLZS ";
int beats[] = {2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 4, 4};
int tempo = 150;
playSong(notes, beats, tempo, length);
}
void deck_the_halls() {
int length = 69;
char notes[] = "SFMRDRMDRMFRMRD+DSFMRDRMDRMFRMRD+DRMFRMFSRMZSLTdTLSSFMRDRMDLLLLSFMRD ";
int beats[] = {3, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 3, 1, 2, 2, 4, 3, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 3, 1, 2, 2, 4, 3, 1, 2, 2, 3, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2, 2, 4, 3, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 3, 1, 2, 2, 4};
int tempo = 150;
playSong(notes, beats, tempo, length);
}
void silent_night() {
int length = 50;
char notes[] = "SLSMSLSMrrT ddSLLdTLSLSMLLdRLSLSMrrfrTdmdSMSFRDD ";
int beats[] = {3, 1, 2, 6, 3, 1, 2, 6, 4, 2, 4, 2, 4, 2, 6, 4, 2, 3, 1, 2, 3, 1, 2, 6, 4, 2, 3, 1, 2, 3, 1, 2, 6, 4, 2, 3, 1, 2, 6, 6, 2, 2, 2, 3, 1, 2, 6, 4, 2, 4};
int tempo = 150;
playSong(notes, beats, tempo, length);
}
void o_tannenbaum() {
int length = 49;
char notes[] = "SdddrmmmmrmfTrd ssmlssffffrsrfmm SdddrmmmmrmfTrd ";
int beats[] = {4, 3, 2, 3, 2, 3, 2, 3, 2, 2, 2, 4, 4, 4, 4, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 4, 2, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 2, 4, 4, 4, 4, 8};
int tempo = 125;
playSong(notes, beats, tempo, length);
}
void frosty_the_snowman() {
int length = 30;
char notes[] = "SMFSdTdrdTLSTdrdTLLSdMSLSFMRD ";
int beats[] = {4, 3, 1, 2, 4, 1, 1, 2, 2, 2, 2, 6, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 8, 8};
int tempo = 160;
playSong(notes, beats, tempo, length);
}
void rudolph_the_red_nosed_rainder() {
int length = 112;
char notes[] = "SLSMdLSSLSLSdTFSFRTLSSLSLSLMSLSMdLSSLSLSdTFSFRTLSSLSLSrdLLdLSMSFLSFMRMSLTTTddTLSFRSLSMdLSSLSLSdTFSFRTLSSLSLSrd ";
int beats[] = {1, 2, 1, 2, 2, 2, 6, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 4, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 3, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 3, 1, 1, 1, 1, 2, 2, 8, 2, 2, 2, 2, 2, 1, 5, 2, 2, 2, 2, 8, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 1, 5, 1, 2, 1, 2, 2, 2, 5, 1, 1, 1, 1, 2, 2, 8, 1, 2, 1, 2, 2, 2, 5, 1, 1, 1, 1, 2, 2, 8, 8};
int tempo = 160;
playSong(notes, beats, tempo, length);
}
void jingle_bell_rock() {
int length = 74;
char notes[] = "dddTTTLTLMLTLMSLTLFRMFSLSRMFS LSLSLLRRdddTTTLTLMLTLMSLTLFRMFSLSRMFS LLTSd ";
int beats[] = {1, 1, 2, 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 2, 2, 1, 1, 2, 4, 1, 2, 1, 1, 2, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 2, 2, 2, 4, 1, 1, 2, 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 2, 2, 1, 1, 2, 4, 1, 2, 1, 1, 2, 1, 1, 1, 2, 4, 1, 1, 1, 2, 2, 4, 8};
int tempo = 160;
playSong(notes, beats, tempo, length);
}
void la_cucaracha() {
int length = 38;
char notes[] = "DDDFLDDDFL FFMMRRD DDDMSDDDMS drdPLSF ";
int beats[] = {1, 1, 1, 3, 2, 1, 1, 1, 3, 5, 1, 2, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 3, 2, 1, 1, 1, 3, 5, 1, 2, 1, 1, 1, 1, 1, 4, 4};
int tempo = 100;
playSong(notes, beats, tempo, length);
}
void playTone(int tone, int duration) {
for (long i = 0; i < duration * 1000L; i += tone * 2) {
digitalWrite(buzzer, HIGH);
delayMicroseconds(tone);
digitalWrite(buzzer, LOW);
delayMicroseconds(tone);
}
}
void playNote(char note, int duration) {
char names[] = { '+', 'D', 'R', 'M', 'F', 'Z', 'S', 'L', 'P', 'T', 'd', 'r', 'm', 'f', 'z', 's', 'l', 't'};
int tones[] = { 4050, 3822, 3405, 3034, 2863, 2703, 2551, 2272, 2145, 2024, 1915, 1700, 1519, 1432, 1352, 1275, 1136, 1014};
// play the tone corresponding to the note name
for (int i = 0; i < 17; i++) {
if (names[i] == note) {
playTone(tones[i], duration);
}
}
}
void playSong(char notes[], int beats[], int tempo, int length) {
for (int i = 0; i < length; i++) {
if (notes[i] == ' ') {
delay(beats[i] * tempo); // rest
} else {
playNote(notes[i], beats[i] * tempo);
}
// pause between notes
delay(tempo / 2);
}
}
//On received SMS, print the sender, timestamp and message
//Send message to the cloud that an SMS was received with the sender number
//as the content and the tag SMSRX
void cloud_sms(const String &sender, const rtc_datetime_t ×tamp, const String &message) {
Serial.println("CLOUD SMS RECEIVED:");
Serial.print("SMS SENDER: ");
Serial.println(sender);
Serial.print("SMS TIMESTAMP: ");
Serial.println(timestamp);
Serial.println("SMS TEXT: ");
Serial.println(message);
String message2 = message;
message2.toLowerCase();
if (message2 == "jb" || message2 == "jingle bells") {
jingle_bells();
Serial.println("Jingle Bells");
}
else if (message2 == "ww" || message2 == "we wish you a merry xmas") {
we_wish_you_a_merry_xmas();
Serial.println("We Wish You a Merry Christmas");
}
else if (message2 == "dh" || message2 == "deck the halls") {
deck_the_halls();
Serial.println("Deck the Halls");
}
else if (message2 == "sn" || message2 == "silent night") {
silent_night();
Serial.println("Silent Night");
}
else if (message2 == "ot" || message2 == "o tannenbaum") {
o_tannenbaum();
Serial.println("O Tannenbaum");
}
else if (message2 == "ftsm" || message2 == "frosty the snowman") {
frosty_the_snowman();
Serial.println("Frosty the Snowman");
}
else if (message2 == "jbr" || message2 == "jingle bell rock") {
jingle_bell_rock();
Serial.println("Jingle Bell Rock");
}
else if (message2 == "rtrnrd" || message2 == "rudolph the red nosed raindeer") {
rudolph_the_red_nosed_rainder();
Serial.println("Rudolph the Red Nosed Raindeer");
}
else if (message2 == "lc" || message2 == "surprize") {
la_cucaracha();
Serial.println("La Cucaracha");
}
else if (message2 == "plz help") {
HologramCloud.sendMessage("Jingle Bells, We Wish You a Merry Xmas, Deck the Halls, Silent Night, O Tannenbaum, Frosty the Snowman, Jingle Bell Rock, Rudolph the Red Nosed Raindeer, Surprize", "Help");
Serial.println("Jingle Bells");
Serial.println("We Wish You a Merry Xmas");
Serial.println("Deck the Halls");
Serial.println("Silent Night");
Serial.println("O Tannenbaum");
Serial.println("Frosty the Snowman");
Serial.println("Jingle Bell Rock");
Serial.println("Rudolph the Red Nosed Raindeer");
Serial.println("Surprize");
Serial.println();
}
else {
Serial.println("Invalid Input");
}
if (HologramCloud.sendMessage(message, "SMSRX")) {
Serial.println("SMS received message sent to cloud.");
} else {
Serial.println("Notification send failed.");
}
}
The Songs
Getting the songs was one of the most fun, yet tedious parts of this project. What I ended up doing was printing the sheet music for the songs I wanted and writing down every note and its length down. I then locked myself in our office's snack room and translated my hand written values into the two arrays for each song. This obviously led to some mistakes which I had to debug in the wonderful world of audio debugging and while I'm sure there is probably a better way to do this, nothing compares to the euphoria of finally getting a song right after almost an hour of listening to a screechy buzzer play the wrong note or mess up the song's timing. One pro tip I would recommend is using python to quickly get the array's length to avoid having to count characters. Also, I found that breaking songs up into chunks made debugging more manageable.
I won't walk you through the Hologram on-boarding process as you can find it all here. I assume that at this point you have an activated SIM and have purchased a phone number for it through Hologram's Dashboard. This is mostly to show you all of this in action and walk you through my quick route. As can be seen in the picture below, your Dash should be echoing all messages to you Dashboard.
As an extra step for convenience I created a route to "boomerang" all the SMS cloud messages to my phone number. To do this I simply created a new route, added the topic that I wanted to trigger it ,in this case its "SMSRX", and chose the SMS action. Then, I entered my phone number, clicked on the "add route" button and voilá!
The Result
Comments