Smart Cities - Tiny House at Home (Alternate way)
Required Items
- Raspberry pi pico wh (wireless with headers)
- OneWire temperature sensor (x2)
- Plugable Terminal V2 (x2)
- Power timer for Pico
- Battery box to go with Pico power timer
- High quality breadboard
- Precision temperature sensor
- RTC - Real Time Clock
- PiicoDev cables (x2)
- Optional:
+ Any other USB cables, screws/standoffs, jumper wires etc.
Installing Thonny IDE
Follow installation steps for your os
For Windows/Mac
visit the thonny website and download the latest windows installer https://thonny.org/
For Linux
Installer (installs private Python 3.10 on x86_64, uses existing python3 elsewhere)
bash <(wget -O - https://thonny.org/installer-for-linux)
Re-using an existing Python installation
pip3 install thonny
Connect Sensors To Pico W
The final assembly should look something like this:
Daisy chain the piicodev modules together then plug into 3V3, SDA/SCL and GND
Connect the onewire temperature sensor into the raspberry pi pico (GND, PWR and SIG (I chose pin 28 for my SIG pin and for my second OneWire sensor I chose pin 12))
OneWire adapter boards (there are two)
Thonny IDE Setup
Steps to setup Thonny for use with pico w
Connect Raspberry Pi Pico
Connect the pico to your computer using a micro-USB to USB-A cable
Open Thonny IDE
Click 'Configure Interpreter' or the hamburger menu on the bottom right
Select 'MicroPython (Raspberry Pi Pico)'
Install Required Packages
Ensure Raspberry pi pico is connected
Select 'Tools' -> 'Manage packages...'
Search for and install 'network' 'onewire' 'piicodev' 'smbus2'
This should install the required libraries and put them in a folder called 'lib'
Programming The Pico W
Code and explanation of some sections of the code
main.py
import json, onewire, ds18x20
from os import stat
from utime import sleep
from machine import Pin, reset
from urequests import get, post
from network import WLAN, STA_IF
from PiicoDev_TMP117 import PiicoDev_TMP117
# Initialize pins for controlling power and the on-board LED
DONE = Pin(22, Pin.OUT)
LED = Pin("LED", Pin.OUT); LED.off()
# Initialize temperature sensors
tempSensor = PiicoDev_TMP117(asw=[1, 0, 0, 0])
onewire_sensor = ds18x20.DS18X20(onewire.OneWire(Pin(28)))
onewire_sensor2 = ds18x20.DS18X20(onewire.OneWire(Pin(12)))
onewire_scan = [onewire_sensor.scan(), onewire_sensor2.scan()]
# Setup WLAN
wlan = WLAN(STA_IF)
wlan.active(True)
# Initialize the time dictionary
time: dict[str, any] = {}
# Function to set the current time
def setTime():
try:
print("Fetching time from API...")
response = get("https://worldtimeapi.org/api/timezone/Australia/Melbourne", timeout=10)
data = response.json()["datetime"]
time.update({
"Date": f"{int(data[8:10])}/{int(data[5:7])}/{int(data[:4])}",
"Hour": (int(data[11:13]) if int(data[11:13]) <= 12 else int(data[11:13]) - 12),
"AM/PM": "PM" if int(data[11:13]) > 12 else "AM",
"Minute": int(data[14:16]),
"Second": int(data[17:19])
})
response.close()
print("Time successfully set.")
return time
except Exception as e:
log_error("Time Fetching Error", e)
print("Failed to fetch time.")
return None
# Function to read the file content, optionally returning the last line
def getFileContent(filename, last_line=False):
try:
file_size = stat(filename)[6]
if file_size == 0:
return None
with open(filename) as f:
return f.readlines()[-1].strip() if last_line else f.read()
except OSError:
return False
# Function to connect to WiFi network
def connect_to_wifi():
wlan.connect("yourSSID", "yourPASSWD")
for _ in range(21):
if wlan.isconnected():
print('Network config:', wlan.ifconfig())
return
print('Waiting for connection...')
sleep(1)
log_error("WiFi Connection Error", "Timeout")
DONE.on()
# Function to log errors with a timestamp
def log_error(context, exception):
with open("time.txt", "w") as t, open("errors.txt", "a") as f:
t.write(f'Error time: {time}')
f.write(f'({context} at time: {time}) Containing: {exception}\n')
# Function to get temperature data based on sensor index
def getData(whichOne):
if whichOne == 0:
onewire_sensor.convert_temp()
sleep(1)
return onewire_sensor.read_temp(onewire_scan[0][0])
elif whichOne == 1:
return tempSensor.readTempC()
elif whichOne == 2:
onewire_sensor2.convert_temp()
sleep(1)
return onewire_sensor2.read_temp(onewire_scan[1][0])
else:
log_error("getData Error", f"incorrect input: {whichOne}")
return None
# Function to post data to a remote server
def postData():
data = {
'outdoor_temp': getData(0),
'in_house_temp': getData(2),
'cont_box_temp': tempSensor.readTempC(),
'timestamp': str(time),
'last_error_time': getFileContent("time.txt"),
'last_error': getFileContent("errors.txt", last_line=True)
}
headers = {'Content-Type': 'application/json'}
try:
response = post('https://dweet.io/dweet/for/TinyHouse', data=json.dumps(data), headers=headers)
out = response.text
response.close()
return out
except Exception as e:
log_error("Posting Error", e)
return None
# Main execution block
try:
connect_to_wifi() # Connect to WiFi
LED.on() # Turn on the LED to indicate activity
print('Getting time...')
setTime() # Sync time dict with online time
print('Starting data posting...')
print(postData()) # Post temperature data
LED.off() # Turn off the LED after completing data posting
DONE.on() # Set DONE pin high to remove power
except Exception as e:
log_error("Main Execution Error", e)
LED.off()
DONE.on() # Set DONE pin high to remove power
Explanations
1. Initial Setup:
- The code starts by setting up four sensors:
- An LED light that can be turned on and off
- A real-time clock (RTC) to keep track of time
- Two temperature sensors: one precision sensor inside (TMP117) and one waterproof sensor outside (DS18X20)
2. WiFi Connection:
- The code tries to connect to WiFi using a username (ssid) and password
- If it can't connect within 20 seconds, it will restart the device
- Once connected, it shows the network information
3. getData Function:
- This function can read from either temperature sensor
- If you call it with 0, it reads the outdoor temperature
- If you call it with 1, it reads the indoor temperature
- If you call it with 2, it reads the second outdoor temperature sensor
- If you use any other number, it gives an error message
4. postData Function:
- This function collects four pieces of information:
- The outdoor temperature
- The indoor temperature
- The second outdoor temperature
- The current time from the clock
- It packages this information together
- It sends this data to a website called "dweet.io" where it can be stored and viewed
- The data is sent to a specific channel called "TinyHouse"
5. Try/Except:
- The program runs the Trys to:
- Turns the LED on
- Sends the temperature data
- Turns the LED off
- Turns the Pico off for 1-hour
- Repeats
- If it can't do any of these it will log the error to a file
Think of it like a weather station that:
- Takes temperature readings from inside and outside your house
- Adds a timestamp to know when the readings were taken
- Sends this information to the internet every minute
- Turns an LED on when it is sending data, so you know it's working
- Turns the LED off when it has finished sending data
Testing
If everything is setup correctly you should se a similar output at https://dweet.io/follow/TinyHouse in the 'RAW' tab
{
"outdoor_temp2": 25.3125,
"timestamp": "2024-12-07 01:05:57 PM",
"outdoor_temp": 25.5,
"indoor_temp": 25.97656
}
Saving Data With Node-RED
Install Node-RED then import the flow.
Install Node-RED
Run the following commands in the Raspberry pi terminal:
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
sudo systemctl enable nodered.service
node-red-start
You can then access the flows menu at localhost:1880
Import the flow
[
{
"id": "16a6d7b3b7d65baf",
"type": "tab",
"label": "TechSchool TinyHouse (balcony)",
"disabled": false,
"info": "",
"env": []
},
{
"id": "1f6e3812c0e0c41b",
"type": "inject",
"z": "16a6d7b3b7d65baf",
"name": "",
"props": [],
"repeat": "3600",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"x": 150,
"y": 400,
"wires": [
[
"c1646b9e6d680755"
]
]
},
{
"id": "cfb974a7e537d86f",
"type": "http request",
"z": "16a6d7b3b7d65baf",
"name": "",
"method": "GET",
"ret": "obj",
"paytoqs": "ignore",
"url": "https://dweet.io/get/latest/dweet/for/TinyHouse",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 450,
"y": 380,
"wires": [
[
"c9eaaaa486180cad"
]
]
},
{
"id": "3e8484dcd165936c",
"type": "function",
"z": "16a6d7b3b7d65baf",
"name": "function 1",
"func": "// Create a dictionary to hold all the variables\nvar payload = {\n outdoor_temp: msg.payload.with[0].content.outdoor_temp,\n in_house_temp: msg.payload.with[0].content.in_house_temp,\n last_error_time: msg.payload.with[0].content.last_error_time,\n last_error: msg.payload.with[0].content.last_error,\n cont_box_temp: msg.payload.with[0].content.cont_box_temp,\n timestamp: msg.payload.with[0].content.timestamp\n};\n\n// Assign the dictionary to msg.payload\nmsg.payload = payload;\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 720,
"y": 360,
"wires": [
[
"e5672fb79ce78898",
"65ab310cd48fc227",
"278bf827fb413c13"
]
]
},
{
"id": "e5672fb79ce78898",
"type": "file",
"z": "16a6d7b3b7d65baf",
"name": "",
"filename": "/home/admin/TinyHouse-Data/data.txt",
"filenameType": "str",
"appendNewline": true,
"createDir": false,
"overwriteFile": "false",
"encoding": "none",
"x": 990,
"y": 400,
"wires": [
[
"0d98891e68f96021",
"9fe81e1427c3b3f4",
"0c4082018898d6d3"
]
]
},
{
"id": "d0158bd8fdb0e410",
"type": "inject",
"z": "16a6d7b3b7d65baf",
"name": "",
"props": [],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 150,
"y": 360,
"wires": [
[
"c1646b9e6d680755"
]
]
},
{
"id": "488d2b8e0879420e",
"type": "ui_text",
"z": "16a6d7b3b7d65baf",
"group": "40710314a2a7b8c6",
"order": 1,
"width": 0,
"height": 0,
"name": "",
"label": "Outdoor Temp",
"format": "{{msg.payload}}",
"layout": "row-spread",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 1480,
"y": 240,
"wires": []
},
{
"id": "74a9d5dbf20e1b23",
"type": "ui_gauge",
"z": "16a6d7b3b7d65baf",
"name": "",
"group": "40710314a2a7b8c6",
"order": 2,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Outdoor Temp",
"label": "°C",
"format": "{{msg.payload}}",
"min": 0,
"max": "50",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1480,
"y": 280,
"wires": []
},
{
"id": "fabb4071a67bbe96",
"type": "ui_gauge",
"z": "16a6d7b3b7d65baf",
"name": "",
"group": "6bf64c6ebecdd8a4",
"order": 2,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "In House Temp",
"label": "°C",
"format": "{{value}}",
"min": 0,
"max": "50",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1480,
"y": 400,
"wires": []
},
{
"id": "2a06f008a3efac15",
"type": "ui_chart",
"z": "16a6d7b3b7d65baf",
"name": "",
"group": "40710314a2a7b8c6",
"order": 3,
"width": 0,
"height": 0,
"label": "Outdoor Temp",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "",
"dot": false,
"ymin": "0",
"ymax": "50",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "86400",
"cutout": 0,
"useOneColor": false,
"useUTC": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"outputs": 1,
"useDifferentColor": false,
"className": "",
"x": 1480,
"y": 320,
"wires": [
[]
]
},
{
"id": "11f200d35a47323d",
"type": "ui_gauge",
"z": "16a6d7b3b7d65baf",
"name": "",
"group": "a81cd77a8bf32b4d",
"order": 2,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Control Box Temp",
"label": "°C",
"format": "{{value}}",
"min": 0,
"max": "50",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1490,
"y": 520,
"wires": []
},
{
"id": "492dd0d2535dc611",
"type": "ui_text",
"z": "16a6d7b3b7d65baf",
"group": "6bf64c6ebecdd8a4",
"order": 1,
"width": 0,
"height": 0,
"name": "",
"label": "In House Temp",
"format": "{{msg.payload}}",
"layout": "row-spread",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 1480,
"y": 360,
"wires": []
},
{
"id": "f0e92fc90ddf1ec3",
"type": "ui_text",
"z": "16a6d7b3b7d65baf",
"group": "a81cd77a8bf32b4d",
"order": 1,
"width": 0,
"height": 0,
"name": "",
"label": "Control Box Temp",
"format": "{{msg.payload}}",
"layout": "row-spread",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 1490,
"y": 480,
"wires": []
},
{
"id": "be65b487c1c28224",
"type": "ui_chart",
"z": "16a6d7b3b7d65baf",
"name": "",
"group": "6bf64c6ebecdd8a4",
"order": 3,
"width": 0,
"height": 0,
"label": "In House Temp",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "",
"dot": false,
"ymin": "0",
"ymax": "50",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "86400",
"cutout": 0,
"useOneColor": false,
"useUTC": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"outputs": 1,
"useDifferentColor": false,
"className": "",
"x": 1480,
"y": 440,
"wires": [
[]
]
},
{
"id": "9653a2a329f451d3",
"type": "ui_chart",
"z": "16a6d7b3b7d65baf",
"name": "",
"group": "a81cd77a8bf32b4d",
"order": 3,
"width": 0,
"height": 0,
"label": "Control Box Temp",
"chartType": "line",
"legend": "false",
"xformat": "HH:mm:ss",
"interpolate": "linear",
"nodata": "",
"dot": false,
"ymin": "0",
"ymax": "50",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "86400",
"cutout": 0,
"useOneColor": false,
"useUTC": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"outputs": 1,
"useDifferentColor": false,
"className": "",
"x": 1490,
"y": 560,
"wires": [
[]
]
},
{
"id": "0d98891e68f96021",
"type": "change",
"z": "16a6d7b3b7d65baf",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "payload.in_house_temp",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1300,
"y": 400,
"wires": [
[
"be65b487c1c28224",
"fabb4071a67bbe96",
"492dd0d2535dc611"
]
]
},
{
"id": "9fe81e1427c3b3f4",
"type": "change",
"z": "16a6d7b3b7d65baf",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "payload.cont_box_temp",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1300,
"y": 520,
"wires": [
[
"f0e92fc90ddf1ec3",
"11f200d35a47323d",
"9653a2a329f451d3"
]
]
},
{
"id": "c9eaaaa486180cad",
"type": "rbe",
"z": "16a6d7b3b7d65baf",
"name": "",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 590,
"y": 380,
"wires": [
[
"3e8484dcd165936c",
"6073ca1837c5d125"
]
]
},
{
"id": "6073ca1837c5d125",
"type": "ui_text",
"z": "16a6d7b3b7d65baf",
"group": "8852e72e5635321d",
"order": 1,
"width": 0,
"height": 0,
"name": "",
"label": "Timestamp",
"format": "{{msg.mydate}} {{msg.mytimes}}",
"layout": "row-spread",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 730,
"y": 400,
"wires": []
},
{
"id": "c1646b9e6d680755",
"type": "simpletime",
"z": "16a6d7b3b7d65baf",
"name": "",
"mydate": true,
"myymd": true,
"myyear": true,
"mymonth": true,
"mymonthn": true,
"mymonthf": true,
"mydom": true,
"mydoy": true,
"myday": true,
"mydayf": true,
"myhourpm": true,
"myhour": true,
"mytime": true,
"mytimes": true,
"myminute": true,
"myminutes": true,
"mysecond": true,
"mymillis": true,
"myepoch": true,
"myrawdate": true,
"mypm": true,
"x": 290,
"y": 380,
"wires": [
[
"cfb974a7e537d86f"
]
]
},
{
"id": "278bf827fb413c13",
"type": "ui_text",
"z": "16a6d7b3b7d65baf",
"group": "8852e72e5635321d",
"order": 3,
"width": 0,
"height": 0,
"name": "",
"label": "Last Error Time",
"format": "{{msg.payload.last_error_time}}",
"layout": "row-spread",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 920,
"y": 360,
"wires": []
},
{
"id": "65ab310cd48fc227",
"type": "ui_text",
"z": "16a6d7b3b7d65baf",
"group": "8852e72e5635321d",
"order": 2,
"width": 0,
"height": 0,
"name": "",
"label": "Last Error",
"format": "{{msg.payload.last_error}}",
"layout": "row-spread",
"className": "",
"style": false,
"font": "",
"fontSize": 16,
"color": "#000000",
"x": 900,
"y": 320,
"wires": []
},
{
"id": "0c4082018898d6d3",
"type": "change",
"z": "16a6d7b3b7d65baf",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "payload.outdoor_temp",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1300,
"y": 280,
"wires": [
[
"2a06f008a3efac15",
"488d2b8e0879420e",
"74a9d5dbf20e1b23"
]
]
},
{
"id": "40710314a2a7b8c6",
"type": "ui_group",
"name": "Outdoor Temp",
"tab": "0649c51a78a7619d",
"order": 1,
"disp": true,
"width": 6,
"collapse": false,
"className": ""
},
{
"id": "6bf64c6ebecdd8a4",
"type": "ui_group",
"name": "In House Temp",
"tab": "0649c51a78a7619d",
"order": 2,
"disp": true,
"width": 6,
"collapse": false,
"className": ""
},
{
"id": "a81cd77a8bf32b4d",
"type": "ui_group",
"name": "Control Box Temp",
"tab": "0649c51a78a7619d",
"order": 3,
"disp": true,
"width": 6,
"collapse": false,
"className": ""
},
{
"id": "8852e72e5635321d",
"type": "ui_group",
"name": "Misc.",
"tab": "0649c51a78a7619d",
"order": 4,
"disp": true,
"width": 6,
"collapse": false,
"className": ""
},
{
"id": "0649c51a78a7619d",
"type": "ui_tab",
"name": "TinyHouse",
"icon": "dashboard",
"disabled": false,
"hidden": false
}
]




