Introduction: Sine-ese Dragon

Sine-ese Dragon is an ambient home decor piece that employs mechanical movements and lights to tell you the weather forecast for the next three three-hours intervals. By definition, ambient describes the immediate surroundings of something; hence it was decided to be appropriate to incorporate weather data into an ambient display. Weather is an aspect that unintentionally changes people’s day and is a piece of information that is constantly changing every minute, or even down to the second.

The Chinese Dragon is “a symbol of power, strength, and good luck” and is frequently held at a high cultural and traditional value across the Asian subcontinent. In addition to bringing good fortune, the Chinese Dragon is also said to have potent powers that control “water, rainfall, typhoons, and floods.” Ultimately, the Chinese Dragon was deemed appropriate to represent weather data.

Visualization

The Sine-ese Dragon is manipulated at six main points at three separate sections representing the weather forecast for three 3 hours intervals. For each 3-hour interval, the following information will be included:

  • Weather description - determines the color of the current weather information.
  • Temperature - specifies the height of the body
  • Humidity - blinking of LED segments
  • Wind speed - controls the speed of the body moving left and right.

Materials Required

  1. 3 mm plywood/Cardboard
  2. 5 mm wooden dowels or chopsticks
  3. 2 Particle Photons
  4. 3 Slinky toys
  5. 6 servo motors
  6. NeoPixel lights (either a strand or individual lights sewn together)
  7. Lots of super Glue
  8. Conductive thread
  9. Acrylic paint
  10. Decorative fabric
  11. Laser cutter
  12. 3D printer

Step 1: Up and Down!

Your very first step to building the Sine-ese Dragon is to construct the component that controls the up and down movement of the body. How exciting!

  1. Download the Adobe Illustrator files (.ai) and print them out using a laser cutter machine.
    1. upDownBoxWithPlatform.ai should be printed on a cardboard.
  2. Download the 3D printing files (.stl) and use your favorite 3D printer to print them out.
    1. The color does not matter for the disk or the disk turner. In the second image, the disk turner has been inserted inside the hole of the disk.
  3. Assemble the first two components and glue them together as shown in pictures 3 to 5.
    1. The platform
    2. The grooves for the disc
  4. Now, put together the box following the tips below.
    1. The servo's wires should go through the rectangular opening on the side of the box.
    2. The shortest end of the disk turner gets attached to the servo head and the longer end goes through the hole of the other side of the box with a circular hole on it. This is demonstrated in picture 6.
  5. Now, we need something to ensure the platform stays leveled when the disk gets turned. Cut the chopstick into 75 mm long sticks (picture 7) and glue them through the top of the box into the top of the platform using hot glue. Make sure that the sticks are leveled down at 90 degrees to the platform.
  6. Insert a 212 mm long stick into the middle hole on the top of the box onto the platform.

Sweet! Now you have a complete box (picture 8) for the up and down movement of the dragon. Now, repeat the steps above two more times!

Step 2: What About Left and Right?!

Now, we cannot be forgetting about the left and right movement of the Sine-ese Dragon, can we? Let's jump into the second step!

  1. Download the Adobe Illustrator files (.ai) and print them out using a laser cutter machine.
    1. leftRightBoxWithPlatforms.ai should be printed on a cardboard.
    2. armTurner.ai file should be printed on a material that is 3 mm thick.
  2. Download the 3D printing files (.stl) and use your favorite 3D printer to print them out.
    1. Make sure you print two of the arms! The color does not matter here.
  3. Assemble the two platforms together as shown in picture 3 using hot glue.
  4. Put together the box. While it may be tricky to do so, it is easier to achieve by:
    1. Inserting the two platforms between the two large slits on the either side of the box.
    2. Placing the first arm on the top of the upper platform.
    3. Threading the arm turner through the arm and then the upper platform.
    4. Placing the second arm on the top of the bottom platform.
    5. Threading the arm turner through the second arm and then the bottom platform.
    6. Sticking the arm turner through the rectangular opening of the 3D printed arm turner.
    7. The other end of the turner goes on top of the servo motor.
    8. Add the top, bottom, and back pieces to the box.

Your final assembled box should look like the sixth picture. Now, you get to repeat that two more times!

By the end of this step, you should have six boxes with three each of the up/down and left/right movement systems.

Step 3: Holding Up the Body...HOW?

Good question! That is when those 3D printed slinky holders come in. Download the included .stl file and print it using a 3D printer. Be sure to print 6 holders in total for the 6 different boxes.

If you have seen the picture of the slinky holder above, the surprise has been ruined - that is the color of our Sine-ese Dragon!

Step 4: But Those Boxes Aren't That Pretty...

And I agree! This is why we are going to use a laser cutter to cut a much more attractive box to contain all of those boxes and conceal them.

Download those Adobe Illustrator files and cut them out using the laser cutter. The clouds design was hand drawn by one of the contributors. Feel free to modify them by removing them inside the illustrator file and adding your own design as you see fit! Below are the suggested steps to putting everything together.

  1. Assemble and glue all three pieces from the first file (outerBoxFinal_1) together.
  2. Do not add the piece from the second file (outerBoxFinal_2) yet.
  3. Put the piece from the third file (outerBoxFinal_3) to the bottom of the box and it should close at the top. Glue ONLY at the bottom of the box.
  4. Print innerBoxesPlatform twice. Glue the two pieces that have large rectangle holes in them together. Then, glue three of the remaining pieces together. Finally, glue it to the other glued set with holes in them.
  5. Place the platform at the bottom of the large box.
  6. Insert all 6 smaller boxes into their corresponding spots on the platform.
  7. Now, place the piece from the second file (outerBoxFinal_2) on the top of the box and glue around the edge. The holes on the top piece should align with the holes on the smaller boxes. If not, rearrange your smaller boxes. Do not add glue to the smaller boxes at all.
  8. If you are using a breadboard that has a sticky piece at the bottom, place this near the center of the bottom piece in a place that when you close the box, the breadboard along with the Photons should disappear. There are small slits on the bottom piece making easier for you to connect to the Photons from the outside.

Step 5: Slinky Toys?? Oh, My!

Body of the dragon:

1. Combine three slinkies together using hot glue or tape.

2. Measure the length and diameter of the slinkies and cut a piece of decorative fabric.

3. Bring the two ends of the fabric and sew them together.

4. Once you are done sewing them, slide the slinkies in like a sock.

5. Sew the ends of the slinky to the sewn fabric.

Step 6: Print Your Dragon!

3D printed parts of the dragon:

1. The parts were taken from https://www.thingiverse.com/thing:854575.

2. We used only the head, legs and the eyes.

3. After 3D printing the part, smoothen it using sandpaper and acetone.

4. Paint the parts the way you wish to decorate it.

Step 7: Time to Step Up Your Dragon With NeoPixels!

Light segment:

1.You can simply use a neopixel strand to create the lights if you wish.(We ran out of the strands).

2. We used 20 neopixel lights and connected them using wires. These wires were soldered onto them and connected to the photon using red wiring so that it matches the theme of the dragon.

3. You can also sew your neopixel lights on a long piece of cloth, but we didn't use them because we had a slinky made out of metal.

Assembly of the parts: Secure the light segment inside the body of the dragon using threads or wires. Make sure you are able to connect the lights to the photon inside the base box. Attach the head, legs and the tail to the body using glue. Once they are in place, secure the body into the slinky holders we printed before. Now the body is ready to be programmed.

Step 8: Programming Time!

Since we will be using two Particle Photons to work with six separate servo motors (one Photon can only work with four), we will be writing two separate but similar codes to be flashed on the microcontrollers.

Now, for the first microcontroller...

In an Arduino file (.ino), include the following libraries and defines:

<p>#include "neopixel.h" </p><p>#include "ArduinoJson.h" </p><p>#define PIXEL_PIN D4 </p><p>#define PIXEL_COUNT 18</p>

Next, declare the following variables:

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN);
Servo servoLeftRight_1;
Servo servoUpDown_1;
Servo servoLeftRight_2;
Servo servoUpDown_2;


int positionLeftRight_1 = 0;
int positionUpDown_1 = 0;
int leftRight_1 = 1; 
int upDown_1 = 1;

int positionLeftRight_2 = 100; // has to be between 0 and 180 (in degrees)
int positionUpDown_2 = 180; // has to be between 0 and 180 (in degrees)
int leftRight_2 = 1; //0=left, 1=right
int upDown_2 = 1; //0=up, 1=down

const size_t bufferSizeCurrent = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(12) + 390;
const size_t bufferSizeForecast = 38*JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(38) + 2*JSON_OBJECT_SIZE(0) + 112*JSON_OBJECT_SIZE(1) + 39*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 38*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 76*JSON_OBJECT_SIZE(8) + 12490;

String weatherArray[3];
float temperatureArray[3];
float humidityArray[3];
float windSpeedArray[3];
String timestampArray[3];
int upDownMaxDegree[3];
int leftRightSpeed[3];

String allData5DaysForecast;

Click here to learn how to set up webhooks. When you are done, add the following declarations and functions and make appropriate changes if necessary:

void getWeather5DayForecast() <br>{
  Particle.publish("get_weather5DayForecast");
  allData5DaysForecast = "";
}
Timer timerWeatherForecast(60000, getWeather5DayForecast);


void getCurrentWeather() 
{
	Particle.publish("get_currentWeather");
}
Timer timerWeatherCurrent(60000, getCurrentWeather);

The following functions controls the up/down and left/right movements of the dragon:

void changeLeftRight1() {<br>	if (leftRight_1)
	{
		positionLeftRight_1 = positionLeftRight_1 + leftRightSpeed[0];
		if (positionLeftRight_1 > 100)
		{
			leftRight_1 = 0;
		}
	}
	else
	{
		positionLeftRight_1 = positionLeftRight_1 - leftRightSpeed[0];
		if (positionLeftRight_1 < 0)
		{
			leftRight_1 = 1;
		}
	}	
	servoLeftRight_1.write(positionLeftRight_1);  
}
void changeLeftRight2() {
	if (leftRight_2)
	{
		positionLeftRight_2 = positionLeftRight_2 + leftRightSpeed[1];
		if (positionLeftRight_2 > 100)
		{
			leftRight_2 = 0;
		}
	}
	else
	{
		positionLeftRight_2 = positionLeftRight_2 - leftRightSpeed[1];
		if (positionLeftRight_2 < 0)
		{
			leftRight_2 = 1;
		}
	}
	servoLeftRight_2.write(positionLeftRight_2);   
}
void changeUpDown1() {
	if (upDown_1)
	{
		positionUpDown_1++;
		if (positionUpDown_1 > upDownMaxDegree[0])
		{
			upDown_1 = 0;
		}
	}
	else
	{
		positionUpDown_1--;
		if (positionUpDown_1 < 1)
		{
			upDown_1 = 1;
		}
	}
	servoUpDown_1.write(positionUpDown_1);   
}
void changeUpDown2() {
	if (upDown_2)
	{
		positionUpDown_2++;
		if (positionUpDown_2 > upDownMaxDegree[1])
		{
			upDown_2 = 0;
		}
	}
	else
	{
		positionUpDown_2--;
		if (positionUpDown_2 < 1)
		{
			upDown_2 = 1;
		}
	}
	servoUpDown_2.write(positionUpDown_2);   
}

To be able to change the movements in an interval, timers are created.

Timer timerLeftRight1(100, changeLeftRight1);
Timer timerLeftRight2(100, changeLeftRight2);
Timer timerUpDown1(10, changeUpDown1); 
Timer timerUpDown2(10, changeUpDown2);

The setup function is finally added next. Be sure to make appropriate changes to the lines of code that deals with webhooks.

void setup() {<br>	// start the weather timers
	timerWeatherForecast.start();
	timerWeatherCurrent.start();
	
	//Neopixels
	strip.begin();
	
	// Put initialization like pinMode and begin functions here.
	// Setup the Micro Servo
  	servoLeftRight_1.attach(D1);
  	servoUpDown_1.attach(D0);
	
  	servoLeftRight_2.attach(D3);
  	servoUpDown_2.attach(D2);

	servoLeftRight_1.write(positionLeftRight_1); //initialize servo position
	servoUpDown_1.write(positionUpDown_1); //initialize servo position 

	servoLeftRight_2.write(positionLeftRight_2); //initialize servo position
	servoUpDown_2.write(positionUpDown_2); //initialize servo position

	timerLeftRight1.start();
	timerLeftRight2.start();
	timerUpDown1.start();
	timerUpDown2.start();

	// Open a console
	Serial.begin(9600);
	delay(2000);
	Serial.println("Hello!");

	// Subscribe to the get_weather5DayForecast and get_currentWeather webhooks
	Particle.subscribe("hook-response/get_weather5DayForecast", gotWeather5DayForecast, MY_DEVICES); 
	Particle.subscribe("hook-response/get_currentWeather/0", gotCurrentWeatherData, MY_DEVICES);

	getCurrentWeather();
	getWeather5DayForecast();
}

A loop function is not used for this project. We cannot forget the functions to handle the data received from the webhooks!

void gotWeather5DayForecast(const char *event, const char *data)<br>{		
	allData5DaysForecast += data; // saves all the data to one string.
	
	int allData5DaysForecastLen = allData5DaysForecast.length();
	char buffer[allData5DaysForecastLen + 1];
	allData5DaysForecast.toCharArray(buffer, allData5DaysForecastLen + 1); // create a buffer for the string
	
	int bufferLength = sizeof(buffer);
	DynamicJsonBuffer jsonBufferWeather(bufferLength);
	JsonObject& root = jsonBufferWeather.parseObject(buffer);
	
	// Test if parsing succeeds.
	if (!root.success()) {
	  //Serial.println("Parsing for weather 5 days forecast...ERROR!");
	  return;
	}
	
	int i = 1;
	JsonArray& list = root["list"];
	for (JsonObject& currentObject : list){
		if (i < 3){
			JsonObject& main = currentObject["main"];
			float temperature = main["temp"];
			int humidity = main["humidity"];
			
			JsonObject& weather = currentObject["weather"][0];
			const char* weatherInfo = weather["main"];
			
			float windSpeed = currentObject["wind"]["speed"];
			
			const char* timestamp = currentObject["dt_txt"];
			
			int tempFah = convertToFahrenheit(temperature);
			int servoMaxDegree = updateUpDown(tempFah);
			upDownMaxDegree[i] = servoMaxDegree;
			
			int servoIncrement = updateleftRight(windSpeed);
			leftRightSpeed[i] = servoIncrement;
			
			setColor(weatherInfo, i);
			
			temperatureArray[i] = tempFah;
			humidityArray[i] = humidity;
			weatherArray[i] = weatherInfo;
			windSpeedArray[i] = windSpeed;
			timestampArray[i] = timestamp;
			i++;
			
		}
		else{
			break;
		}
	}
}
void gotCurrentWeatherData(const char *event, const char *data)<br>{	
	DynamicJsonBuffer jsonBufferWeather(bufferSizeCurrent);
	JsonObject& root = jsonBufferWeather.parseObject(data);

	// Test if parsing succeeds.
	if (!root.success()) {
	  //Serial.println("Parsing for current weather...ERROR!");
	  return;
	}
	
	JsonObject& weather = root["weather"][0];
	const char* weather_main = weather["main"];
	
	JsonObject& main = root["main"];
	float main_temp = main["temp"]; 
	int main_humidity = main["humidity"]; 
	float wind_speed = root["wind"]["speed"];
	
	const char* timestamp = root["dt_txt"];
	
	int tempFah = convertToFahrenheit(main_temp);
	int servoMaxDegree = updateUpDown(tempFah);
	upDownMaxDegree[0] = servoMaxDegree;
	
	int servoIncrement = updateleftRight(wind_speed);
	leftRightSpeed[0] = servoIncrement;
	
	setColor(weather_main, 0);
	
	weatherArray[0] = weather_main;
	temperatureArray[0] = tempFah;
	humidityArray[0]= main_humidity;
	windSpeedArray[0] = wind_speed;
	timestampArray[0] = timestamp;
}

Below, you can find additional functions that control the updating of the servo motors' positions, the conversion of temperature from Kelvin to Fahrenheit and setting the colors of the LEDs.

int updateUpDown(float temp){<br>	//Map the degree to a range of [0, 180]
	float servoMaxDegree = temp * 45 / 31 + (990 / 31);
	Serial.print("new servo degree: ");
	Serial.println(servoMaxDegree);
	return servoMaxDegree;
		
}
int updateleftRight(float windSpeed){
	//Map the wind speed to a range of [1, 100]
	float servoIncrement = windSpeed * 99 / 26 + 1;
	Serial.print("new servo increment value: ");
	Serial.println(servoIncrement);
	return servoIncrement;
}
int convertToFahrenheit(float tempKel){
	int tempFah = tempKel * 9.0 / 5.0  - 459.67;
	return tempFah;
}
void setColor(String weatherDesc, int index){
	int ledIndex = 0;
	if (index == 0){
		ledIndex = 0;
	}
	else if (index == 1){
		ledIndex = 6;
	}
	else if (index == 2){
		ledIndex = 12;
	}
	else{
		return;
	}
	
	if(weatherDesc == "Clear") { //yellow
		for (int j = ledIndex; j < ledIndex+6; j++){ 
			strip.setPixelColor(j, strip.Color(253, 219, 62));//yellow
			strip.show();
			delay(20);
		}
	}
	else if(weatherDesc == "Clouds"){ //grey
		for (int j = ledIndex; j < ledIndex+6; j++){ 
			strip.setPixelColor(j, strip.Color(223, 229, 237));//grey
			strip.show();
			delay(20);
		}
	}
	else if (weatherDesc == "Snow"){ //white
		for (int j = ledIndex; j < ledIndex+6; j++){ 
			strip.setPixelColor(j, strip.Color(255, 225, 225));//white
			strip.show();
			delay(20);
		}
	}
	else if (weatherDesc == "Rain"){//blue
		for (int j = ledIndex; j < ledIndex+6; j++){ 
			strip.setPixelColor(j, strip.Color(119, 191, 246));//blue
			strip.show();
			delay(20);
		}
	} 
	else{ //red
		for (int j = ledIndex; j < ledIndex+6; j++){ 
			strip.setPixelColor(j, strip.Color(254, 11, 5));//red
			strip.show();
			delay(20);
		}
	}
}

Once you get everything added to your Arduino file, compile it. If there are no errors, go ahead and flash the code to the first Photon. The next step will provide you with similar code to be flashed on the second Photon.

Step 9: Programming Continues

Because the code for the second Photon is nearly identical to the one for the first one, the entire code is copied and pasted below:

#include "ArduinoJson.h"
Servo servoLeftRight_3;
Servo servoUpDown_3;
int positionLeftRight_3 = 45;
int positionUpDown_3 = 0;
int leftRight_3 = 1;
int upDown_3 = 1;
const size_t bufferSizeCurrent = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(12) + 390;
const size_t bufferSizeForecast = 38*JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(38) + 2*JSON_OBJECT_SIZE(0) + 112*JSON_OBJECT_SIZE(1) + 39*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 38*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 76*JSON_OBJECT_SIZE(8) + 12490;
String weatherArray[3];
float temperatureArray[3];
float humidityArray[3];
float windSpeedArray[3];
String timestampArray[3];
int upDownMaxDegree[3];
int leftRightSpeed[3];
String allData5DaysForecast;
void getWeather5DayForecast() 
{
  Particle.publish("get_weather5DayForecast2");
  allData5DaysForecast = "";
}
Timer timerWeatherForecast(60000, getWeather5DayForecast); //10,800,000 ms = 3 days
void getCurrentWeather() 
{
  Particle.publish("get_currentWeather2");
}
Timer timerWeatherCurrent(60000, getCurrentWeather);
void changeLeftRight3() {
	if (leftRight_3)
	{
		positionLeftRight_3 = positionLeftRight_3 + leftRightSpeed[2];
		if (positionLeftRight_3 > 100)
		{
			leftRight_3 = 0;
		}
	}
	else
	{
		positionLeftRight_3 = positionLeftRight_3 - leftRightSpeed[2];
		if (positionLeftRight_3 < 0)
		{
			leftRight_3 = 1;
		}
	}	
	servoLeftRight_3.write(positionLeftRight_3);  
}
void changeUpDown3() {
	if (upDown_3)
	{
		positionUpDown_3++;
		if (positionUpDown_3 >  upDownMaxDegree[2])
		{
			upDown_3 = 0;
		}
	}
	else
	{
		positionUpDown_3--;
		if (positionUpDown_3 < 1)
		{
			upDown_3 = 1;
		}
	}
	servoUpDown_3.write(positionUpDown_3);   
}
Timer timerLeftRight3(100, changeLeftRight3);
Timer timerUpDown3(10, changeUpDown3);
void setup() {
	// start the weather timers
	timerWeatherForecast.start();
	timerWeatherCurrent.start();
	
	// Put initialization like pinMode and begin functions here.
	// Setup the Micro Servo	
  	servoLeftRight_3.attach(D1);
  	servoUpDown_3.attach(D0);
	servoLeftRight_3.write(positionLeftRight_3); //initialize servo position
  	servoUpDown_3.write(positionUpDown_3); //initialize servo position
  	timerLeftRight3.start();
 	timerUpDown3.start();
	
	// Open a console
	Serial.begin(9600);
	delay(2000);
	Serial.println("Hello!");
		
	// Subscribe to the get_weather5DayForecast and get_currentWeather webhooks
	Particle.subscribe("hook-response/get_weather5DayForecast2", gotWeather5DayForecast, MY_DEVICES); 
	Particle.subscribe("hook-response/get_currentWeather2/0", gotCurrentWeatherData, MY_DEVICES);
	
	getCurrentWeather();
	getWeather5DayForecast();
}
void gotWeather5DayForecast(const char *event, const char *data)
{		
	allData5DaysForecast += data; // saves all the data to one string.
	
	int allData5DaysForecastLen = allData5DaysForecast.length();
	char buffer[allData5DaysForecastLen + 1];
	allData5DaysForecast.toCharArray(buffer, allData5DaysForecastLen + 1); // create a buffer for the string
	
	int bufferLength = sizeof(buffer);
	DynamicJsonBuffer jsonBufferWeather(bufferLength);
	JsonObject& root = jsonBufferWeather.parseObject(buffer);
	
	//Serial.println(allData5DaysForecast);
	
	// Test if parsing succeeds.
	if (!root.success()) {
	  //Serial.println("Parsing for weather 5 days forecast...ERROR!");
	  return;
	}
	
	int i = 1;
	JsonArray& list = root["list"];
	for (JsonObject& currentObject : list){
		if (i < 3){
			JsonObject& main = currentObject["main"];
			float temperature = main["temp"];
			int humidity = main["humidity"];
			
			JsonObject& weather = currentObject["weather"][0];
			const char* weatherInfo = weather["main"];
			
			float windSpeed = currentObject["wind"]["speed"];
			
			const char* timestamp = currentObject["dt_txt"];
			
			int tempFah = convertToFahrenheit(temperature);
			int servoMaxDegree = updateUpDown(tempFah);
			upDownMaxDegree[i] = servoMaxDegree;
			
			int servoIncrement = updateleftRight(windSpeed);
			leftRightSpeed[i] = servoIncrement;
			
			temperatureArray[i] = tempFah;
			humidityArray[i] = humidity;
			weatherArray[i] = weatherInfo;
			windSpeedArray[i] = windSpeed;
			timestampArray[i] = timestamp;
			i++;
			
		}
		else{
			break;
		}
	}
}
void gotCurrentWeatherData(const char *event, const char *data)
{	
	DynamicJsonBuffer jsonBufferWeather(bufferSizeCurrent);
	JsonObject& root = jsonBufferWeather.parseObject(data);
	
	//Serial.println(data);
	
	// Test if parsing succeeds.
	if (!root.success()) {
	  //Serial.println("Parsing for current weather...ERROR!");
	  return;
	}
	
	JsonObject& weather = root["weather"][0];
	const char* weather_main = weather["main"];
	
	JsonObject& main = root["main"];
	float main_temp = main["temp"]; 
	int main_humidity = main["humidity"]; 
	float wind_speed = root["wind"]["speed"];
	
	const char* timestamp = root["dt_txt"];
	
	int tempFah = convertToFahrenheit(main_temp);
	int servoMaxDegree = updateUpDown(tempFah);
	upDownMaxDegree[0] = servoMaxDegree;
	
	int servoIncrement = updateleftRight(wind_speed);
	leftRightSpeed[0] = servoIncrement;
	
	weatherArray[0] = weather_main;
	temperatureArray[0] = tempFah;
	humidityArray[0]= main_humidity;
	windSpeedArray[0] = wind_speed;
	timestampArray[0] = timestamp;
}
int updateUpDown(float temp){
	//Map the degree to a range of [0, 180]
	float servoMaxDegree = temp * 45 / 31 + (990 / 31);
	Serial.print("new servo degree: ");
	Serial.println(servoMaxDegree);
	return servoMaxDegree;
		
}
int updateleftRight(float windSpeed){
	//Map the wind speed to a range of [1, 100]
	float servoIncrement = windSpeed * 99 / 26 + 1;
	Serial.print("new servo increment value: ");
	Serial.println(servoIncrement);
	return servoIncrement;
}
int convertToFahrenheit(float tempKel){
	int tempFah = tempKel * 9.0 / 5.0  - 459.67;
	return tempFah;
} 

You did it! You've made it through the programming section of the project! Now, be sure to make all the wiring and connections from the servo motors and neopixels to the breadboard and microcontrollers.

NOTE: insert the extra dowels/chopsticks through the vertical slits on the boxes for the left and right movements of the body. The other end should be connected to the body of the dragon.

Step 10: Enjoy Your Dragon!

Congratulations! You've built a Sine-ese Dragon from scratch! Now all you have to do is to sit back and enjoy your ambient display!

NOTE: This project was built as a part of a coursework by Joan Bempong and Soundarya Muthuvel. The course page can be found here.