Smart room controller designed to introduce teens to programming and IoT. It was inspired by "Why's Poignant Guide to Ruby".
Things used in this project
Hardware components
Hand tools and fabrication machines
Laser cutter (generic)
3D Printer (generic)
Story
The purpose of this project was to program a microprocessor controller to control different devices in the room.
I modeled my project after the first book that I read on programming called "why's (poignant) Guide to Ruby"; where it follows a cat through the process of learning Ruby.
I wanted my project to be a project for a middle or high school student wanting to learn how to program and build smart devices.
I chose to use images from the book because of how artistic the book is. Ruby is an easy language to learn (but has limited application), while C++ is a difficult language wide a verity amount of applications.
Around the middle and high school age range is when life can get dark very quickly for teens. I wish that I was able to create a physical manifestation of my code when I was younger and going through a difficult time myself.
If my nephews can do some coding and can artistically express themselves then I believe that my project is successful.
The different items I created are used to teach my nephews (and teens) how to use tools and designs to gain control over their lives and the environment through the use of 3D printing
Laser cutting and microcontroller programming are valuable skills to learn and I believe that those skills will allow them to manifest their desires positively.
How does this concept work?
The encoder and potentiometer move electrically through different states/cases and "mental rooms". Some of these rooms are paired with cat images to demonstrate the concept of the programmer controlling the mental, physical, and emotional rooms around them. The NeoPixel color is blue to demonstrate the ability for teens to control their mental, spiritual, electrical methods of expression. I believe these concepts are important to develop during this point in their lives. Each switch case is labeled to allow them to easily understand basic programming and electrical concepts.
Each concept is labeled in the code and on the LED to easily tie together difficult concepts.
The 3D print was designed to show how to connect the ideas that bring my nephew's interests to the physical world through a simple form of physical manifestation.
The cats were placed as a continuation of the Ruby concepts and implemented as bosses for conquering programming levels. I believe that this concept will help them to tie previous concepts in with new programming concepts.
Schematics
Fritzing
The encoder is grounded by 2 capacitors to help reduce jitter
Code
Smart Room Controller
C/C++
Use a microprocessor to control various devices in the room.
/*
* Project: Smart Room Controller
* Description: Uses keypad/breadboard components to control different
* objects in the room.
*
* Authors: Daniel Mills
* Date: 03-Mar-2022
*/
//Header Files
#include <SPI.h> //Library to use for the SPI modual
#include <Wire.h> //Imports the wire library for connecting
#include <Adafruit_GFX.h> //Ada graphics library
#include <Adafruit_SSD1306.h> //Imports the library for the OLED display
#include <OneButton.h> //includes the OneButton library
#include <SPI.h> //includes the header for the serial bus
#include <Ethernet.h> //includes the Ethernet header for internet
#include <mac.h> //Includes the mac header
#include <hue.h> //Includes the hue header for adjusting the hue
#include <colors.h> //includes the colors library
#include <Encoder.h> //includes the library for the encoder
#include <SD.h> //includes the SD card library
#include "wemo.h" //includes the wemo library
#include <Adafruit_NeoPixel.h> //includes the NeoPixel library
#include <Adafruit_BME280.h> //Calls library for Adafruit display
//Image Header Files - For doubleClick in Switch Cases
#include "kittyHeader.h" //Includes the kitty image
#include "Cat1.h"
#include "Cat2.h"
#include "Cat3.h"
#include "cat4.h"
#include "cat5.h"
//Var for OLED Screen
const int SCREEN_WIDTH = 128; //sets the screen width for the OLED
const int SCREEN_HEIGHT = 64; //sets the screen height
const int OLED_RESET = 4;
const int SCREEN_ADDRESS = 0x3C; //Registeres the screen address to the SPI
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); //defines the Adafruit screen
//Declaring vars for encoder
Encoder myEnc(3, 2); //pins the encoder is connected to
int encoderOutput; //var to hold encoder output
int encoderLastPosition; //var to hold last encoder position
bool ONOFF; //bool to turn on and off the hue
//OneButton Vars
OneButton button1(20, false); //OneButton to pin 23 and sets value to false
bool buttonState = 0;
bool blinker = 0; //Creates a bool var for buttonState and for blinker
int iButton = 0; //Creates var to hold the button press outcome
//Creates the SD card object
File dataFile;
//Creates the vars for the SD Card
const int chipSelect = 4; //Registers the SD card to pin 4
//int object; //Creates an int var for saving data from the SD Card
bool status;
//Wemo Vars
int wemoPorts = 0; //starts the wemo ports on zero
byte thisbyte; //used to get IP address
int i;
//NeoPixel Var
const int PIXELPIN = 17;
const int PIXELCOUNT = 16;
int currentNeoPixel;
//Vars for BME - Temp Sensor
Adafruit_BME280 bme; //Defines BME sensor data
int temp;
int humid;
float tempF; //set temperature variable
float pressPA; //sets pressure var
float humidRH; //sets RH var
Adafruit_NeoPixel pixel(PIXELCOUNT,PIXELPIN, NEO_GRB + NEO_KHZ800); //declares the NeoPixel Object
//Ints for potent
int potentPin = 14; //set potent to port 14
int lastPotentValue; //creates int for last potent value
int potentMap;
void setup() {
//##SetUp block for OneButton
Serial.begin(9600); //Checks serial monitor
button1.attachClick(click1); //initialized button1 click1
button1.attachDoubleClick(doubleClick1);
button1.setClickTicks(700);
button1.setPressTicks(2000);
buttonState = false;
blinker = false;
//##Ethernet Start Up Block
Ethernet.begin(mac); //starts Ethernet
delay(200); //ensure Serial Monitor is up and running
printIP();
Serial.printf("LinkStatus: %i \n",Ethernet.linkStatus());
//##Setup Information for the OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.printf("SSD1306 allocation failed");
for(;;); //looks for an end to the loop
}
display.display(); //Shows the Adafruit splah
delay(100); //delay
display.clearDisplay(); //Clears the screen
display.display(); //Only use this command when needed instead of repeating
delay(100);
//##Setup Block for the SD Card
Serial.printf("Initializing SD card...");
// initialize uSD card module CS to off
pinMode(chipSelect,OUTPUT);
digitalWrite(chipSelect,HIGH);
// see if the card is present and can be initialized:
status = SD.begin(chipSelect);
if (!status) { // if status is false
Serial.printf("Card failed, or not present\n");
//while(true); // pause the code indefinately //commented out in case SD card isn't present
}
else {
Serial.printf("card initialized.\n");
}
//##Set Up block for Wemo Ports - Print your local IP address
Serial.print("My IP address: "); //Lets user know that the ip is being printed to the screen
for (thisbyte = 0; thisbyte < 3; thisbyte++) { //Start of loop for identifing the IP
//print value of each byte of the IP address
Serial.printf("%i.",Ethernet.localIP()[thisbyte]); //Command for printing the Ethernet local IP
}
Serial.printf("%i\n",Ethernet.localIP()[thisbyte]); //Prints the Final ethernet local ip to the monitor
switchOFF(0); //Turns off the wemo port 0
//Setup Block for NeoPixels
pixel.setBrightness(150); //sets the brightness of the NeoPixels to 150
pixel.begin(); //Sets the Neopixel and starts listening for commands
//Sets the pinMode for the potent values
pinMode(potentPin, INPUT); // set pin modes
Serial.begin(9600); //listen to Serial monitor
status = bme.begin(0x76); //looks for the temp sensor
//NeoPixel Setup Block
pixel.setBrightness(150); //brightness value for the Neopixels display
pixel.begin(); //sets Neopixels
pixel.show(); //shows Neopixels
}
//////////////////////////////
/////////////////////////////
void loop() {
button1.tick();
Serial.printf("Button State %i \n", buttonState);
encoderOutput = myEnc.read();
int potentValue = analogRead(potentPin); // read potentPin and divide by 255 to give 5 possible readings
potentMap = map(potentValue, 0, 1023, 0, 4);
//Serial.printf("Potent Map %i \n, Potent Values %i \n", potentMap, potentValue);
Serial.printf("Potent Map %i \n", potentMap);
//sp-Space or serial.print output testing
if(potentMap != lastPotentValue){ //Start of Switch loop
buttonState = false;
blinker = false;
lastPotentValue = potentMap;
}
// enter switch case
switch(potentMap)
{
case 0:
if (buttonState) {
pixel.fill(blue, i, 16);
pixel.show();
Serial.printf("Case 0 Button Check %i \n", buttonState);
}
else {
Serial.printf("Case 0 Button Check Else Stat %i \n", buttonState);
pixel.clear();
pixel.show();
}
if(blinker){
Cat1display();
}
else {
display.clearDisplay(); //clears the display
display.setCursor(0,0); // Start at top-left corner
display.setTextColor(SSD1306_WHITE);
display.printf("Squire, you are in: \n Case0-NeoPixels < \n Case1 \n Case2 \n Case3 \n Case4 \n "); //Outputs Switch Case
display.display();
}
break;
case 1:
encoderOutput = myEnc.read();
if (encoderOutput != encoderLastPosition) {
Serial.println(encoderOutput);
buttonState = false;
encoderLastPosition = encoderOutput;
if (encoderOutput > 4) {
myEnc.write(4);
display.clearDisplay(); //clears the display
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0); // Start at top-left corner
display.printf("Encoder %i, ", encoderOutput); //Outputs Switch Case
display.display();
}
if (encoderOutput < 0) {
myEnc.write(0);
}
}
if (buttonState) {
setHue(encoderOutput,true,HueBlue,255,255); //lights the bulb the color blue
Serial.printf("Case 1 Button Check %i \n", buttonState);
}
else {
Serial.printf("Case 1 Button Check Else Stat %i \n", buttonState);
setHue(encoderOutput,false,HueBlue,0,0); //lights the bulb the color blue
}
if(blinker){
catDisplay2();
}
else {
//display.setTextSize(1); // Draw 2X-scale text (too large for screen)
display.clearDisplay(); //clears the display
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0); // Start at top-left corner
display.printf("Squire you are in: \n Case0 \n Case1-HueLights < \n Case2 \n Case3 \n Case4 "); //Outputs Switch Case
display.display();
}
Serial.printf("Encoder Value %i \n", encoderOutput);
break;
//Start of case 2
case 2:
encoderOutput = myEnc.read();
if (encoderOutput != encoderLastPosition) {
Serial.println(encoderOutput);
buttonState = false;
encoderLastPosition = encoderOutput;
if (encoderOutput > 5) {
myEnc.write(5);
}
if (encoderOutput < 0) {
myEnc.write(0);
}
}
if (buttonState){
switchON(encoderOutput);
Serial.printf("Wemo is on \n");
}
else{
switchOFF(encoderOutput);
}
if(blinker){
catDisplay3();
}
else {
//display.setTextSize(1); // Draw 2X-scale text (too large for screen)
display.clearDisplay(); //clears the display
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0); // Start at top-left corner
display.printf("Squire you are in: \n Case0 \n Case1 \n Case2-Wemo< \n Case3 \n Case4 "); //Outputs Switch Case
display.display();
}
Serial.println("Switch Case 2");
break;
//Start of case 3
case 3:
display.clearDisplay(); //clears the display
if (buttonState) {
tempF = (bme.readTemperature()*1.8)+32; //converting F to C
pressPA = bme.readPressure()*0.00030; //converting pascal pressure to inches of mercury
humidRH = bme.readHumidity(); //read humidity
Serial.printf("temp %.02f \n", tempF); //Serial.print temp
Serial.printf("pressPA %.02f \n", pressPA); //serial.print pressure
Serial.printf("humid %.02f \n", humidRH); //Serial print humidity
display.setTextSize(1); // Sets 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draws black text on white (inverse text)
display.setCursor(0,0); // Start at top-left corner
display.printf("Temp: %.02f%cF \n Press: %.02f \n RH: %.02f%cF \n", tempF, 248, pressPA, humidRH, 248); //outputs sensor values
display.display();
//#Calls the write to datafile and read from file
writeToSD(tempF, pressPA, humidRH); //Calls the writeToSD function and writes these vars to the file
Serial.printf("Case 3 Button Check %i \n", buttonState);
if (tempF > 70){
switchON(1);
switchON(3);
Serial.printf("Wemo is on \n");
}
else{
switchOFF(1);
switchOFF(3);
}
}
else {
Serial.printf("Case 3 Button Check Else Stat %i \n", buttonState);
}
if(blinker){
catDisplay4();
}
else {
display.clearDisplay(); //clears the display
display.setTextColor(SSD1306_WHITE); //sets the display color to white
display.setCursor(0,0); // Start at top-left corner
display.printf("Squire, you are in: \n Case0 \n Case1 \n Case2 \n Case3-FanandTemp < \n Case4 \n "); //Outputs Switch Case
display.display(); //shows the display
}
Serial.println("Switch Case 3");
break;
//Start of Case 4
case 4:
if (buttonState) {
setHue(1,true,random(0,60000),255,255); //lights the bulb the color random
setHue(2,true,random(0,60000),255,255); //lights the bulb the color random
setHue(3,true,random(0,60000),255,255); //lights the bulb the color random
setHue(4,true,random(0,60000),255,255); //lights the bulb the color random
setHue(5,true,random(0,60000),255,255); //lights the bulb the color random
Serial.printf("Case 4 Button Check %i \n", buttonState);
}
else {
Serial.printf("Case 1 Button Check Else Stat %i \n", buttonState);
setHue(1,false,random(0,60000),0,0); //lights the bulb the color random
setHue(2,false,random(0,60000),0,0); //lights the bulb the color random
setHue(3,false,random(0,60000),0,0); //lights the bulb the color random
setHue(4,false,random(0,60000),0,0); //lights the bulb the color random
setHue(5,false,random(0,60000),0,0); //lights the bulb the color random
Serial.printf("Case 4 Button Check %i \n", buttonState);
}
if(blinker){
catDisplay5();
}
else {
display.clearDisplay(); //clears the display
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0); // Start at top-left corner
display.printf("Squire, you are in: \n Case0 \n Case1 \n Case2 \n Case3 \n Case4-HueLightsRandom< \n "); //Outputs Switch Case
display.display();
}
Serial.println("Switch Case 4");
break;
default:
display.clearDisplay(); //clears the display
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0); // Start at top-left corner
display.printf("Error"); //Outputs Switch Case
display.display();
delay(2000); //delays the clear display for 2 seconds
Serial.println("error!");
break;
lastPotentValue = potentMap;
}
}
/////////////////////////////////////////////
////////////////////////////////////////////
//Void Functions block
//Creates the void functions for OneButton
void click1() { //when input is received (button is pressed)..
buttonState = !buttonState; //when button is pressed it changes the bool value of the buttonState
Serial.printf("Single button press \n"); //can do the same thing as different data types on the same line
}
void doubleClick1() { //looks for the doubleclick function
blinker = !blinker; //if current state is not equal to previous state
Serial.printf("Double button press \n"); //outputs that a doublepress occured to the screen
}
//#Void Function for Starting of the printIP function
void printIP() {
Serial.printf("My IP address: "); //outputs this to the screen if the connection was successful
for (byte thisByte = 0; thisByte < 3; thisByte++) {
Serial.printf("%i.",Ethernet.localIP()[thisByte]); //prints IP to serial monitor
}
Serial.printf("%i\n",Ethernet.localIP()[3]); //prints IP to serial monitor
}
void writeToSD(float tempF, float pressPA, float humidRH ) { //looks to write these variable to the SD card
dataFile = SD.open("datalog.csv", FILE_WRITE); //file the information was written to
// if the file is available, write to it:
if (dataFile) {
dataFile.printf("Temp: %.02f%cF \n Press: %.02f \n RH: %.02f%c \n", tempF, 248, pressPA, humidRH, 248); //writes these var to the SD card
dataFile.close(); //closes the SD when done
Serial.printf("Temp: %.02f%cF \n Press: %.02f \n RH: %.02f%c \n", tempF, 248, pressPA, humidRH, 248); //prints these values to the screen
}
else {
Serial.printf("error opening datalog.csv \n"); //returns this message if information couldn't be read from the card
}
return; //exits function
}
//##Start of the read from SD card function
void readFromSD(){
// re-open the file for reading:
dataFile = SD.open("datalog.csv"); //opens the datalog.csv file
if (dataFile) {
Serial.printf("datalog.csv: \n"); //if it was able to open that file it will output those values to the serial monitor
// read from the file until there's nothing else in it:
while (dataFile.available()) {
Serial.write(dataFile.read()); //will continue to read from card as long as data is present
}
dataFile.close(); //closes the data file when it is finished reading
}
else {
Serial.printf("error opening datalog.csv \n"); //will output this message if there was a problem reading from the file.
}
return; //exits function
}
//////////////////////////////////////////////
/////////////////////////////////////////////
//Images Block
void Cat1display(void) {
int centerV = (display.height()-64)/2; //(display.height()-128)/2;
int centerH = (display.width()-128)/2; //(display.width()-64)/2;
display.clearDisplay();
display.drawBitmap(centerH, centerV, Cat1, 128, 64, 1);
display.display();
delay(2000);
}
void catDisplay2(void) {
int centerV = (display.height()-64)/2; //(display.height()-128)/2;
int centerH = (display.width()-128)/2; //(display.width()-64)/2;
display.clearDisplay();
display.drawBitmap(centerH, centerV, Cat2, 128, 64, 1);
display.display();
delay(2000);
}
void catDisplay3(void) {
int centerV = (display.height()-64)/2; //(display.height()-128)/2;
int centerH = (display.width()-128)/2; //(display.width()-64)/2;
display.clearDisplay();
display.drawBitmap(centerH, centerV, Cat3, 128, 64, 1);
display.display();
delay(2000);
}
void catDisplay4(void) {
int centerV = (display.height()-64)/2; //(display.height()-128)/2;
int centerH = (display.width()-128)/2; //(display.width()-64)/2;
display.clearDisplay();
display.drawBitmap(centerH, centerV, Cat4, 128, 64, 1);
display.display();
delay(2000);
}
void catDisplay5(void) {
int centerV = (display.height()-64)/2; //(display.height()-128)/2;
int centerH = (display.width()-128)/2; //(display.width()-64)/2;
display.clearDisplay();
display.drawBitmap(centerH, centerV, Cat5, 128, 64, 1);
display.display();
delay(2000);
}
The article was first published in hackster, March 11 2022
cr: https://www.hackster.io/daniel-mills/super-sentai-cat-smart-room-controller-2a79da
author: Daniel Mills