The underlying objective of this project is to build us a personal library of circuit blocks for high power motors and actuators and a suite of microcontroller web socket (IoT) code that will serve us for every future build to come. A build video will air on Element14 Presents in 2019.
Visit us at the YouTube Channel Raising Awesome for deep dive videos that support the build.
4D Game Engine Project Introduction
My boy, Connor, burned all his lawnmowing money this summer on an Oculus Rift. It is very cool and its even fun just to watch him use it. It's just like scenes from the movie Ready Player One. The experience gave us an idea - what if instead of us going into the game world, the game comes to our world!
So, we set out on our most ambitious IoT build to date. Here are the project stats so far:
- Fully Playable 3D Game - built from the ground up: Son.Light.Sleepwalker
- Inspired by Five Nights at Freddy's, it is a jump scare game based on a security camera system emulator.
- The premise of the game is that a 9 year old boy sleep walks and it terrifies his father for some reason only to be understood at the end of the game.
- If his son wakes while sleepwalking, a jump scare will occur.
- To get the boy back to the bed, you turn on lights along the path back to his room from your PC or Tablet, but, you can't let him walk fully into any given light - or he'll wake up!!
- The game is set over 6 nights, each with progressively harder scenarios to navigate him through to get back to the bed.
- When the user hits Escape to pause the game, they then can hit the letter "m" on the keyboard to enable maker mode. This allows the game to talk to a Microcontroller that will control the real, physical world around you. For us, the microcontroller is the Arduino MKR1000.
- 7 Freeware Software Applications Showcased
- Audacity
- Autodesk Fusion 360
- Blender
- Eagle
- Make Human
- Paint.Net
- Unity
- 3 Free Helpful Websites Showcased
- IFTTT.com
- Freesound.org
- TurboSquid.com
- 4 Programming Languages
- 2700+ lines of C#
- 100 lines Python
- 100 lines of Arduino C
- 300 lines of C++
- 5 Custom Built Circuit Boards
- ESP8266 5V-24V, 3A IoT Device
- Any MC 5V Powered, High Power Relay IoT Device
- ESP8266 5V Powered, High Power Relay IoT Device
- Two 5V Motor, Forward/Reverse Driver for Any 3.3 Microcontroller
- Matrix Creator driven sounds and "Boss" jump scare for the finale
- 4D Game Engine
- The 4D Game Engine is all of the above working in concert.
- The 3D Game uses serial to communicate to a Arduino MKR 1000 Microcontroller.
- The Arduino MKR1000 then uses wires, Web Sockets, and IFTTT.com to trigger the circuits powering physical mechanical devices to blend the 3D Game world with the physical world such as:
- lights in our house turn on and off
- a door opena and closea
- lightening and thunder
- a fan turns on for wind
- the gamer's chair rumbles
- a final "Boss" jump scare is triggered
The 3D Game IoT Code
The first challenge was getting the 3D game to talk to the microcontroller. The game was made in Unity, so the first thing to do was change the Unity game engine to not just use a subset of .Net. This is done from the main screen of unity under the Configuration settings typically located on the right side of the screen. Below is how the settings should look to enable COM port access:
Once you have done this, you can now access the COM port. Here is example code that I attached to the default light source in the Scene that will attach to a com port and push out serial digits to an attached Microcontroller:
using UnityEngine; using System.IO.Ports; public class SeanSerialTesterScript : MonoBehaviour { SerialPort sp; float next_time; int ii = 0; // Use this for initialization void Start () { string the_com=""; next_time = Time.time; foreach (string mysps in SerialPort.GetPortNames()) { print(mysps); if (mysps != "COM1") { the_com = mysps; break; } } sp = new SerialPort("\\\\.\\" + the_com, 9600); if (!sp.IsOpen) { print("Opening " + the_com + ", baud 9600"); sp.Open(); sp.ReadTimeout = 100; sp.Handshake = Handshake.None; if (sp.IsOpen) { print("Open"); } } } // Update is called once per frame void Update() { if (Time.time > next_time) { if (!sp.IsOpen) { sp.Open(); print("opened sp"); } if (sp.IsOpen) { print("Writing " + ii); sp.Write((ii.ToString())); } next_time = Time.time + 5; if (++ii > 9) ii = 0; } } }
To test it, program an Arduino with the following code:
int inByte; void setup() { // start serial port at 9600 bps: Serial.begin(9600); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); } void loop() { // if we get a valid byte, read analog ins: if (Serial.available() > 0) { // get incoming byte: inByte = Serial.read(); if ((inByte-48)==0) { //after pressing m in pause screen and connected blink_it (10); } if ((inByte-48)==1) { //Saw kid blink_it(1); } if (inByte-48)==2) { //Lightening/Thunder blink_it(2); } if ((inByte-48)==3) { //Spooky Noises blink_it(3); } if ((inByte-48)==4) { //Sudden sound like the door being beat on blink_it(4); } if ((inByte-48)==5) { //Chilling Ambient Sound blink_it(5); } if ((inByte-48)==6) { //door creak blink_it(6); } if ((inByte-48)==7) { //voices talking blink_it(7); } if ((inByte-48)==8) { //drone woke kid blink_it(8); } if ((inByte-48)==9) { //next chapter blink_it(9); } } delay(500); } void blink_it(int the_count){ for (int ii=0;ii<the_count;ii++){ digitalWrite(LED_BUILTIN, HIGH); delay(150); digitalWrite(LED_BUILTIN, LOW); delay(150); } }
Hit play in Unity and you will see the code write to the debug console and your Arduino will blink its LED accordingly. Once you can get an LED to blink, you can control the world!!! Or at least this game world.
Instead of just blinking the LED, we coded our Arduino MKR1000 to send web client requests to the circuits below as well as IFTTT.com to trigger WebHooks that triggered our WeMo switches throughout our house.
Here is the MKR1000 code with the additional methods to connect to other devices through web sockets:
#include <WiFi101.h> int inByte; void setup() { // start serial port at 9600 bps: Serial.begin(9600); pinMode(LED_BUILTIN, OUTPUT); pinMode(0,OUTPUT); digitalWrite(LED_BUILTIN, LOW); connectToWiFI(); } void loop() { // if we get a valid byte, read analog ins: if (Serial.available() > 0) { // get incoming byte: inByte = Serial.read(); if ((inByte-48)==0) { //after pressing m in pause screen and connected sendWebSocketCommand("hello.mp3" , "192.168.1.27", 4000);//Matrix to play hello sound blink_it (10); } if ((inByte-48)==1) { //Saw kid sendWebSocketCommand("heartbeat.mp3" , "192.168.1.27", 4000);//matrix to play heartbeat sound blink_it(1); } if ((inByte-48)==2) { //Lightening/Thunder digitalWrite(0,HIGH); delay(200); digitalWrite(0,LOW); delay(50); digitalWrite(0,HIGH); delay(100); digitalWrite(0,LOW); delay(200); digitalWrite(0,HIGH); delay(300); digitalWrite(0,LOW); blink_it(2); } if ((inByte-48)==3) { //Spooky Noises sendWebSocketCommand("spookynoise.mp3" , "192.168.1.27", 4000);//matrix to play heartbeat sound blink_it(3); } if ((inByte-48)==4) { //Sudden sound like the door being beat on //drill under seat sendWebSocketCommand("/" , "192.168.1.102", 4000); blink_it(4); } if ((inByte-48)==5) { //Chilling Ambient Sound sendWebSocketCommand("spookynoise2.mp3" , "192.168.1.27", 4000);//matrix to play heartbeat sound blink_it(5); } if ((inByte-48)==6) { //door creak //linear actuator sendHTTPCommand("/" , "192.168.1.100", 4000); blink_it(6); } if ((inByte-48)==7) { //voices sendWebSocketCommand("spookynoise3.mp3" , "192.168.1.27", 4000);//matrix to play heartbeat sound blink_it(7); } if ((inByte-48)==8) { //light on blink_it(8); triggerIFTTT("/trigger/SleepWalkerLightOn/with/key/-xxxxx");//use your own trigger key instead of xxxxx } if ((inByte-48)==9) { //light off triggerIFTTT("/trigger/SleepWalkerLightOff/with/key/-xxxxx");//use your own trigger key instead of xxxxxx blink_it(9); } } delay(500); } void sendHTTPCommand(char* message, char* host, int port){ WiFiClient client; client.connect(host, port); client.print(String("GET ") + message + " HTTP/1.1\r\nHost: " + host + "\r\nUser-Agent: RaisingAwesome\r\nConnection: close\r\n\r\n"); while (client.connected()) { String line = client.readStringUntil('\n'); //Serial.print("\nServer Response: " + line + '\n'); if (line == "\r") { break; } } String line = client.readStringUntil('\n'); } void sendWebSocketCommand(char* message, char* host, int port){ WiFiClient client; client.connect(host, port); client.print(message); while (client.connected()) { String line = client.readStringUntil('\n'); //Serial.print("\nServer Response: " + line + '\n'); if (line == "\r") { break; } } String line = client.readStringUntil('\n'); } void triggerIFTTT(String url){ const char* host = "maker.ifttt.com"; int status = WL_IDLE_STATUS; WiFiClient client; client.connect(host, 80); client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: BuildFailureDetectorESP8266\r\n" + "Connection: close\r\n\r\n"); while (client.connected()) { String line = client.readStringUntil('\n'); //Serial.print(line + '\n'); if (line == "\r") { break; } } String line = client.readStringUntil('\n'); //Serial.print("done\n"); } void connectToWiFI() { char ssid[] = "SSID"; char pass[] = "passwd"; int status=WL_IDLE_STATUS; // attempt to connect to Wifi network: while ( status != WL_CONNECTED) { // Connect to WPA/WPA2 network. Change this line if using open or WEP network: status = WiFi.begin(ssid, pass); // wait 1 second for connection: delay(1000); } } void blink_it(int the_count) { for (int ii=0;ii<the_count;ii++){ digitalWrite(LED_BUILTIN, HIGH); delay(150); digitalWrite(LED_BUILTIN, LOW); delay(150); } }
For example, when lightening strikes in the game, a value of 0 is sent over serial. The Arduino MKR1000 hits the .8A circuit to run a routine that flickers GPIO0 to simulate lightening. A string of LED strip lighting is used along the wall to give the illusion of lightening flashing outside.
Another example is when we click the "Memo Switch", it turns on a light in our house.
After a few hundred hours of game development using the applications listed above(3D Asset creation, voice acting, sound effect creation, and 2700+ lines of code), the triggering side of our 4D Game Engine was complete:
Son.Light.Sleepwalker
The 3D Game Making Software
Below are 5 of our top 10 software that one most teach their kids to be awesome! They are what we used to build our game.
Paint.Net has been my go-to paint program for over a decade. It has a home right on every task bar in the house. It is freeware from a university that original was making it as a MSPaint replacement, but Microsoft went another direction. The features are lean and intuitive - the direct opposite of GIMP, but not as powerful. However, it does do layering and PNG's - just about everything you need for 3D game textures.
Audacity is another great free program. It will even handle mp3's with a free addon. It can handle everything you would ever need for sound effects for a 3D game. We used it to put the dad's voice in the left ear and the mom's voice in the right ear to give an immersive experience with headphones.
Make Human is a must if you want humans in your 3D game. It has a full body, face, and hair editor to make fully rigged humoids. It's also 100% free! The exports will import into Blender perfectly.
Blender is by far the most difficult software I've learned. The payoff is huge though as it can be a 3D mesh builder, animator, video editor/compositor, and even a game engine itself. It can make Toy Story quality animated movies from scratch and is used more and more in Hollywood. Again, fully free. We used it to animate the sleepwalker and the psychologist in Son.Light.Sleepwalker.
Unity has made many, many great games. They have a collaborative business model that allows an ambitious indy developer to take something to market if they have the heart to do it. If it hits big, Unity still doesn't want anything. However, if you continue to develop with the tool after making at least $100K, you then have to pay. It would be worth it.
If you think Unity games suck, it's not the engine, it's the developer. It's all about the time they put into making good textures for realism.
The Circuits
- ESP8266 5V-24V, 3A, IoT Device
This circuit is designed to use a single large battery source to power some higher voltage DC motors and actuators. Here are some key features:
- Battery Source: any battery from 5V to 24V
- Power reduced to 3.3V to power the ESP8266
- ESP8266 monitors its port 4000 for web requests and then triggers GPIO0 (pin 3) high when hit.
- Can be used to turn on higher power DC motors and actuators via the web.
- We will use it to rumble Connor's chair using a drill motor.
- Any MC 5V Powered, Photocoupled High Voltage IoT Device
- This circuit is designed to allow a high signal from any Microcontroller to trigger a 0.8A independent circuit up to a 40 Volt power supply.
- An optocoupler is used to isolate the two circuits.
- For the 4D Game Engine, it will be used by the MKR1000 to trigger 9Vs to flicker an LED light strip to simulate lightening in the room.
- ESP8266 5V Powered, High Power Relay IoT Device
- This circuit is designed to electrically isolate the ESP8266 from the high power source using a relay.
- This allows to switch in AC driven appliances such as tape players and lights.
- Note the diode to prevent the coil of the relay from generating >200V when the transistor switches off.
- For our 4D game engine, it will turn on a fan.
- Two Motor, Forward/Reverse Driver for Any Microcontroller
- This circuit will allow to drive two motors simultaneously in either direction.
- It utilizes an H-Bridge chip and is made to accept input from any microcontroller using a 4 pin header. Note the diodes required to ensure the chip isn't damaged if there is a quick stop or reverse direction.This is only needed for the L493. The L493D has them integrated into the chip.
- For our 4D game engine, it will drive a linear actuator to open and close a closet door
The Microcontrollers' IoT Code:
The ESP8266 Server Code:
Each ESP8266 has a variation of the following code. It connects to your home WiFi and triggers GPIO0 and the onboard LED high when a web request is made.
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> const char* ssid = "your ssid"; const char* password = "your password"; ESP8266WebServer server(4000); const int led = 13; void handleRoot() { digitalWrite(led, 1); digitalWrite(0, 1); server.send(200, "text/plain", "Visit Raising Awesome on YouTube for more cool stuff!"); delay(3000); digitalWrite(led, 0); digitalWrite(0, 0); } void handleNotFound(){ digitalWrite(led, 1); String message = "File Not Found\n\n"; message += "URI: "; message += server.uri(); message += "\nMethod: "; message += (server.method() == HTTP_GET)?"GET":"POST"; message += "\nArguments: "; message += server.args(); message += "\n"; for (uint8_t i=0; i<server.args(); i++){ message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } server.send(404, "text/plain", message); digitalWrite(led, 0); } void setup(void){ pinMode(led, OUTPUT); digitalWrite(led, 0); Serial.begin(115200); // config static IP IPAddress ip(192, 168, 1, 100); IPAddress gateway(192, 168, 1, 1); // set gateway to match your network Serial.print(F("Setting static ip to : ")); Serial.println(ip); IPAddress subnet(255, 255, 255, 0); // set subnet mask to match your network WiFi.config(ip, gateway, subnet); WiFi.begin(ssid, password); Serial.println(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connected to "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); if (MDNS.begin("esp8266")) { Serial.println("MDNS responder started"); } server.on("/", handleRoot); server.on("/inline", [](){ server.send(200, "text/plain", "this works as well"); }); server.onNotFound(handleNotFound); server.begin(); Serial.println("HTTP server started"); } void loop(void){ server.handleClient(); }
The ESP8266 can programmed using the Arduino IDE. However, it is a little tricky because you have to set one of the pins high first and reset it each time you are ready to program. On a side project, we built a programmer for it. This video shows a tutorial on KiCad as well as explains how the ESP8266 programmer circuit is put together:
Raspberry Pi Server Code
Below is the C++ code for a Raspberry PI web socket server. Another microcontroller or app can send it text commands to control GPIO pins. We altered it to use with the Matrix Creator:
/* Creates a datagram server. The port number is passed as an argument. This server runs until the client sends exit. */ #include <sys/types.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <netdb.h> #include <stdio.h> #include <wiringPi.h> void error(const char *msg) { perror(msg); exit(0); } void stop() { digitalWrite(21,LOW); digitalWrite(22,LOW); digitalWrite(23,LOW); digitalWrite(24,LOW); } void left() { digitalWrite(21,HIGH); digitalWrite(22,LOW); digitalWrite(23,LOW); digitalWrite(24,HIGH); } void right() { digitalWrite(21,LOW); digitalWrite(22,HIGH); digitalWrite(23,HIGH); digitalWrite(24,LOW); } void forward() { digitalWrite(21,HIGH); digitalWrite(22,LOW); digitalWrite(23,HIGH); digitalWrite(24,LOW); } void back() { digitalWrite(21,LOW); digitalWrite(22,HIGH); digitalWrite(23,LOW); digitalWrite(24,HIGH); } int main(int argc, char *argv[]) { int sock, length, n; socklen_t fromlen; struct sockaddr_in server; struct sockaddr_in from; char buf[1024]; wiringPiSetup(); pinMode(21,OUTPUT); pinMode(22,OUTPUT); pinMode(23,OUTPUT); pinMode(24, OUTPUT); stop(); if (argc < 2) { fprintf(stderr, "ERROR, no port provided\n"); exit(0); } sock=socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) error("Opening socket"); length = sizeof(server); bzero(&server,length); server.sin_family=AF_INET; server.sin_addr.s_addr=INADDR_ANY; server.sin_port=htons(atoi(argv[1])); if (bind(sock,(struct sockaddr *)&server,length)<0) error("binding"); fromlen = sizeof(struct sockaddr_in); while (1) { n = recvfrom(sock,buf,1024,0,(struct sockaddr *)&from,&fromlen); if (n < 0) error("recvfrom"); n = sendto(sock,buf,n,0,(struct sockaddr *)&from,fromlen); if (strcmp("exit\n",buf)==0) break; if (n < 0) error("sendto"); if (strcmp(buf,"right\n")==0) right(); if (strcmp(buf,"left\n")==0) left(); if (strcmp(buf,"forward\n")==0) forward(); if (strcmp(buf,"back\n")==0) back(); if (strcmp(buf,"stop\n")==0) stop(); memset(buf, 0, sizeof(buf)); } stop(); // write(1,"Server: Exiting\n",16); return 0; }
To compile, just type the following in the raspberry pi terminal:
g++ -omyfile myfile.cpp -lwiringPi
To run it, type the following:
sudo myfile 4000 &
The 4000 will be your port. You can make it whatever you like actually as long as its not already in use.
Completed Circuit Boards
Making the boards was a great demonstration of practice make perfect. With our projects, I make one circuit board every other month or so. Typically, I start out okay, but then end with a rat nest, never really improving on my final presentation between projects. With this project, I made six boards and the last one took three tries due to complications with how the ESP8266 goes into bootloader mode if a GPIO is pulled down. Using a clockface for orientation in the picture below, 2:00 o'clock was my first for this project and 6:00 o'clock was my last. Definitely an improvement!
Here are the board descriptions pictured below
Center: Matrix Creator/Raspberry PI (purchased, of course). Under that is the Arduino MKR1000, also purchased.
12:00: Relay circuit Relay circuit for ESP8266 for switching AC or DC up to 10A
02:00: H-bridge to drive two motors from any microcontroller up to 1.5A at 5V
04:00: Another H-Bridge to drive two motors from any microcontroller up to 1.5A at 5V
06:00: Another relay circuit for ESP8266 for switching AC or DC up to 10A
08:00 Photocoupled circuit for switching up to .8A @ 40V DC
10:00 Another relay circuit for ESP8266 for switching AC or DC up to 10A
This is the close up of the last board made - much better than the first five:
3D Printed Parts
The prototyped board enclosures were designed using Autodesk Fusion 3D. It is in our top 5 favorite software packages (if not the top).
Our printer is from XYZPrinting. It is a DaVinci 1.0 Pro, which is much improved over the original Davinci 1.0. It now allows for third party filament and has an aluminum bed. No more glue stick and broken glass!
Build and Demo Video
Trailer: Full video to be released on the Youtube Channel Element14 Presents in 2019.
Connor's cousin Sammy was our Betatester. Here she is about to feel the chair rumble just after lightening flashed in the room.
Full Build Video:
Bill of Materials
Each | Description | Newark Item | Farnell |
1 | MATRIX.C1.US/EU - Daughter Board, Matrix Creator IoT, Raspberry Pi 3, Loaded with Sensors, Mic Array, US | 05AC854805AC8548 | 2675819 |
1 | RPI3-MODBP - Single Board Computer, Raspberry Pi 3 Model B+, BCM2837B0 SoC, IoT, PoE Enabled | 49AC763749AC7637 | 2842228 |
1 | ABX00004 - Development Board, Arduino MKR1000 Without Pin Headers, IoT Development | 47AC416447AC4164 | 2830991 |
5 | 83-16992 - Breakout Board, ESP8266 Wifi Module, Serial Transceiver, 1MB Flash | 68Y015668Y0156 | 2801859 |
3 | SRM-1C-SL-5VDCSRM-1C-SL-5VDC General Purpose Relay, SRM Series, Power, Non Latching, SPDT, 5 VDC, 10 A | 74R153974R1539 | 1094018 |
5 | LM340AT-5.0LM340AT-5.0 Linear Voltage Regulator, Fixed, Positive, 7.5V To 35V In, 5V And 1A Out, TO-220-3 | 41K476241K4762 | 9490183 |
5 | UA78M33CKCSUA78M33CKCS Linear Voltage Regulators, Fixed, 5.3V To 25V In, 3.3V And 0.5A Out, TO-220-3 | 14P758114P7581 | 2075447 |
1 | HCPL-817-000EHCPL-817-000E Optocoupler, Transistor Output, 1 Channel, DIP, 4 Pins, 50 mA, 5 kV, 50 % | 36J961036J9610 | 1244544 |
5 | 2N37042N3704 Bipolar (BJT) Single Transistor, NPN, 30 V, 100 MHz, 625 mW, 800 mA, 100 hFE | 08N770008N7700 | 1574393 |
5 | 1N4004 - Standard Recovery Diode, General Purpose, 400 V, 1 A, Single, 1.1 V, 30 A | 10M293810M2938 | 2295710 |
5 | FQP7P06FQP7P06 MOSFET Transistor, P Channel, 7 A, -60 V, 0.41 ohm, -10 V, -4 V | 97K019697K0196 | 9846557 |
2 | MCM - Prototyping Board, Small PC, 25 Rows | 38C910038C9100 | 2855013 |
2 | MULTICOMP - Prototype Board, Phenolic, 1.6 mm, 72 mm, 47 mm | 22AC832222AC8322 | 2768277 |
2 | KEMO ELECTRONIC - STRIPBOARD, FR2, 100X160MM | 71Y906071Y9060 | 2503760 |
2 | MC001811 - Jumper Wire Kit, Multicolor, 2 mm - 125 mm, 22 AWG, 350 Piece | 24AC505424AC5054 | 2770339 |
2 | L293DNE - Motor Driver/Controller, Half-H, 4.5V to 36V supply, 36V/600 mA/4 Outputs, DIP-16 | 06F952306F9523 | 1470423 |
Project Conclusion
We definitely achieved what we set out to do, which was to establish circuit blocks and code that we can forever leverage forward in our projects. With web socket code and high power relays, we can now control any appliance from any where in the world. Future projects quickly come to mind:
- Lawn Irrigation
- Safety switches for appliances such as our hot water heater and stove
- Retractable Garage Shelving
- Improved Hot Tub Controller
- Laundry Dumbwaiter
We now have a great appreciation for the independent 3D game developer. Hundreds of hours, potentially thousands with the research required, went into making our game and it still has a lot more to go to attract to the mass consumer. It even costs us to publish it on Steam, so the Indy gamer has to also take on financial risk. However, the software we learned along the way will serve us well for work, school, and even leisure projects for the rest of our lives.
As for the 4D experiment, we actually found that the game pulls one's mind in on its own. The rumble chair helped with the jump scares, but the lightning effects being in-game as well as in the real world appeared to go unnoticed as the in-game experience was where she was focused. Perhaps this is a sign of really good immersion. Without an MRI comparison with and without, we'll never know. We'll save the MRI for another build. :-)
If you have any questions about this build, please comment below.
Also, please check us out at Raising Awesome on YouTube! We are a father and son Maker team making a chronicle of the boy's maker development from age 9 to 18.
See ya'!
Sean and Connor
Top Comments