Skip to content

Commit

Permalink
Add Magic Mirror examples
Browse files Browse the repository at this point in the history
add Magic Mirror examples

adjust paths and tweak wi-fi code

add magic mirror readme, tweak adafruit example

Remove Adafruit Magic Mirror example for now
  • Loading branch information
helgibbons committed Oct 6, 2023
1 parent 86e832b commit 4e59152
Show file tree
Hide file tree
Showing 11 changed files with 817 additions and 0 deletions.
52 changes: 52 additions & 0 deletions examples/magic_mirror/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
- [Micro Magic Mirror](#micro-magic-mirror)
- [About](#about)
- [Icons](#icons)
- [Variants](#variants)
- [Magic Mirror](#magic-mirror)
- [Magic Mirror Without Wifi](#magic-mirror-without-wifi)
- [Magic Mirror (Home Assistant)](#magic-mirror-home-assistant)

# Micro Magic Mirror

## About

Micro Magic Mirror is a home dashboard for showing you useful information. We're working on an associated learn guide to show you how we built our Magic Mirror.

To get online data, create a file called secrets.py containing your WiFi SSID and password, like this:
```
WIFI_SSID = "ssid_goes_here"
WIFI_PASSWORD = "password_goes_here"
```
Press the Y button to update the online data.

## Icons

Weather/wifi icons are from [Icons8](https://icons8.com).

## Variants

There are three variants of this example in this directory, showing various different functions. For an example that will do something out of the box without any configuring, go for `magic_mirror.py`.

### Magic Mirror

[magic_mirror.py](magic_mirror.py)

This example connects to wifi and shows you the time (from an NTP server), the current weather conditions (from the OpenMeteo API) and an inspirational quote (from quotable.io).

### Magic Mirror Without Wifi

[magic_mirror_without_wifi.py](magic_mirror_without_wifi.py)

This stripped down version shows how we're drawing the basic framework of our Magic Mirror display, before we start adding in the wifi functions. It has a graph showing the temperature from the internal sensor on the Pico W.

### Magic Mirror (Home Assistant)

[magic_mirror_home_assistant.py](magic_mirror_home_assistant.py)

Do you automate your home with [Home Assistant](https://www.home-assistant.io/)? (if not you should try it, it's great fun). This version of Magic Mirror displays sunrise/sunset data it gets from a local Home Assistant server.

To connect to your Home Assistant, you'll need to create a token at http://homeassistant.local:8123/profile > Long Lived Access Tokens and add it to your `secrets.py` like this:
```
HOME_ASSISTANT_TOKEN = "token_goes_here"
```
Solar data is provided by the Home Assistant Sun integration, which should be installed by default. The example assumes your Pico W is connected to the same network as your Home Assistant server, and that your Home Assistant server is located at http://homeassistant.local:8123
Binary file added examples/magic_mirror/icons/cloud.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/magic_mirror/icons/no-wifi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/magic_mirror/icons/rain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/magic_mirror/icons/snow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/magic_mirror/icons/storm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/magic_mirror/icons/sun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/magic_mirror/icons/wifi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
316 changes: 316 additions & 0 deletions examples/magic_mirror/magic_mirror.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
"""
MicroMagicMirror - a home dashboard that shows the time, weather and an inspirational quote.
To get online data, create a file called secrets.py containing your WiFi SSID and password, like this:
WIFI_SSID = "ssid_goes_here"
WIFI_PASSWORD = "password_goes_here"
Press the Y button to update the data.
"""

from modes import VGA
from pimoroni import Button
import pngdec
import urequests
import ntptime
import time
import machine
import gc

# General settings:
# set this to True if you don't want your mirror to display your WiFi password
REDACT_WIFI = True

# Adjust the clock to show the correct timezone
UTC_OFFSET = 1.0

# how often to poll the APIs for data, in minutes
UPDATE_INTERVAL = 15

# Weather settings:
# Weather from OpenMeteo - find out more at https://open-meteo.com/

# Set your latitude/longitude here (find yours by right clicking in Google Maps!)
LAT = 53.38609085276884
LNG = -1.4239983439328177
TIMEZONE = "auto" # determines time zone from lat/long
WEATHER_URL = "http://api.open-meteo.com/v1/forecast?latitude=" + str(LAT) + "&longitude=" + str(LNG) + "&current_weather=true&timezone=" + TIMEZONE

# Quote settings:
# Quotes are from quotable.io - API documentation here: https://github.com/lukePeavey/quotable
QUOTE_URL = "https://api.quotable.io/quotes/random?tags=technology|education|future&maxLength=155"

# Constants for drawing the display
TITLE = "MicroMagicMirror"
TITLE_POSITION = 20
ROW_1_POSITION = 72
ROW_2_POSITION = 110
ROW_3_POSITION = 200
ROW_4_POSITION = 330
CLOCK_POSITION = 430
COLUMN_2_POSITION = 80
COLUMN_3_POSITION = 530
PADDING = 10
ICON_OFFSET = 20

# set up our display and some useful constants
graphics = VGA()

WIDTH, HEIGHT = graphics.get_bounds()

BLACK = graphics.create_pen(0, 0, 0)
RED = graphics.create_pen(255, 0, 0)
ORANGE = graphics.create_pen(246, 138, 30)
YELLOW = graphics.create_pen(255, 216, 0)
GREEN = graphics.create_pen(0, 121, 64)
BLUE = graphics.create_pen(0, 0, 255)
INDIGO = graphics.create_pen(36, 64, 142)
VIOLET = graphics.create_pen(115, 41, 130)
WHITE = graphics.create_pen(255, 255, 255)

# Create a new PNG decoder for our PicoGraphics
png = pngdec.PNG(graphics)

# create the rtc object
rtc = machine.RTC()

sensor_temp = machine.ADC(4)
conversion_factor = 3.3 / (65535) # used for calculating a temperature from the raw sensor reading

button_y = Button(9)

# some other variables for keeping track of stuff
timer = 0
second = 0
last_second = 0
temperature = None
quote = None
wifi_problem = False


# Connect to wifi
def connect_to_wifi():
global WIFI_SSID, WIFI_PASSWORD
import network
try:
from secrets import WIFI_SSID, WIFI_PASSWORD
except ImportError:
print("Create secrets.py with your WiFi credentials")
return False

wlan = network.WLAN(network.STA_IF)
if wlan.isconnected():
return True

wlan.active(True)
wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs
retries = 0

MAX_RETRIES = 5 # How many times to retry connecting to WiFi before giving up

while retries < MAX_RETRIES:
print(f"Attempt {retries + 1} to connect to WiFi...")
wlan.connect(WIFI_SSID, WIFI_PASSWORD)

max_wait = 100
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
time.sleep(0.2)

if wlan.isconnected():
print("Connected to WiFi successfully.")
return True
else:
print("Failed to connect to WiFi. Retrying...")
retries += 1
time.sleep(2) # Wait for 2 seconds before retrying

print("Failed to connect to WiFi after maximum retries.")
return False


# Synchronize the RTC time from NTP and download data from our APIs
def get_data():
global wifi_problem
if connect_to_wifi() is False:
wifi_problem = True
return
else:
wifi_problem = False

try:
print("Setting time from NTP")
ntptime.settime()
print(f"Time set (UTC): {rtc.datetime()}")
except Exception as e:
print(e)

get_weather_data()

get_quote_data()


def get_weather_data():
global temperature, windspeed, winddirection, weathercode, date, now
try:
print("Requesting weather data")
r = urequests.get(WEATHER_URL, timeout=5)
# open the json data
j = r.json()
print("Weather data obtained:")
print(j)
# parse relevant data from JSON
current = j["current_weather"]
temperature = current["temperature"]
windspeed = current["windspeed"]
winddirection = calculate_bearing(current["winddirection"])
weathercode = current["weathercode"]
date, now = current["time"].split("T")
r.close()
except Exception as e:
print(e)


def get_quote_data():
global quote, author
try:
print("Requesting quote data")
r = urequests.get(QUOTE_URL, timeout=5)
# open the json data
j = r.json()
print("Quote data obtained:")
print(j)
# parse relevant data from JSON
quote = j[0]["content"]
author = j[0]["author"]
r.close()
except Exception as e:
print(e)


def calculate_bearing(d):
# calculates a compass direction from the wind direction in degrees
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
ix = round(d / (360. / len(dirs)))
return dirs[ix % len(dirs)]


def redraw_display_if_reqd():
global year, month, day, wd, hour, minute, second, last_second
if second != last_second:
# clear the screen
graphics.set_pen(BLACK)
graphics.clear()

# draw a lovely rainbow
graphics.set_pen(VIOLET)
graphics.circle(0, 0, 140)
graphics.set_pen(BLUE)
graphics.circle(0, 0, 120)
graphics.set_pen(GREEN)
graphics.circle(0, 0, 100)
graphics.set_pen(YELLOW)
graphics.circle(0, 0, 80)
graphics.set_pen(ORANGE)
graphics.circle(0, 0, 60)
graphics.set_pen(RED)
graphics.circle(0, 0, 40)

# draw the title
graphics.set_pen(WHITE)
graphics.text(TITLE, COLUMN_2_POSITION, TITLE_POSITION, scale=7)

# show system temperature
# the following two lines do some maths to convert the number from the temp sensor into celsius
reading = sensor_temp.read_u16() * conversion_factor
internal_temperature = 27 - (reading - 0.706) / 0.001721
graphics.text(f"uMM is running at {internal_temperature:.1f}°C.", COLUMN_2_POSITION, ROW_1_POSITION, WIDTH - COLUMN_2_POSITION, scale=2)

# lets have some weather
graphics.set_pen(WHITE)
if temperature is not None:
# Choose an appropriate icon based on the weather code
# Weather codes from https://open-meteo.com/en/docs
# Weather icons from https://icons8.com/
if weathercode in [71, 73, 75, 77, 85, 86]: # codes for snow
png.open_file("/magic_mirror/icons/snow.png")
elif weathercode in [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82]: # codes for rain
png.open_file("/magic_mirror/icons/rain.png")
elif weathercode in [1, 2, 3, 45, 48]: # codes for cloud
png.open_file("/magic_mirror/icons/cloud.png")
elif weathercode in [0]: # codes for sun
png.open_file("/magic_mirror/icons/sun.png")
elif weathercode in [95, 96, 99]: # codes for storm
png.open_file("/magic_mirror/icons/storm.png")
png.decode(COLUMN_3_POSITION, ROW_2_POSITION - ICON_OFFSET)
# draw the weather text
graphics.set_pen(WHITE)
graphics.text(f"Outside, the temperature is {temperature}°C. The wind speed is {windspeed}kmph {winddirection}.", PADDING, ROW_2_POSITION, COLUMN_3_POSITION - PADDING, scale=3)
graphics.text(f"Last OpenMeteo data: {now}, {date}", PADDING, ROW_2_POSITION + 55, scale=2)
else:
graphics.text("Weather data not available.", PADDING, ROW_2_POSITION, scale=3)

# and a quote
graphics.set_pen(YELLOW)
if quote is not None:
graphics.text(f"'{quote}' - {author}", PADDING, ROW_3_POSITION, WIDTH - PADDING, scale=3)
else:
graphics.text("Quote data not available.", PADDING, ROW_3_POSITION, WIDTH - PADDING, scale=3)

# show wifi details
graphics.set_pen(WHITE)
if wifi_problem is True:
png.open_file("/magic_mirror/icons/no-wifi.png")
graphics.text("WiFi not available. Create secrets.py to connect to WiFi!", PADDING, ROW_4_POSITION, COLUMN_3_POSITION - PADDING, scale=3)
else:
png.open_file("/magic_mirror/icons/wifi.png")
if REDACT_WIFI is True:
graphics.text(f"Our WiFi network is '{WIFI_SSID}'.", PADDING, ROW_4_POSITION, COLUMN_3_POSITION - PADDING, scale=3)
else:
graphics.text(f"Our WiFi network is '{WIFI_SSID}' and the password is '{WIFI_PASSWORD}'.", PADDING, ROW_4_POSITION, COLUMN_3_POSITION - PADDING, scale=3)
png.decode(COLUMN_3_POSITION, ROW_4_POSITION - ICON_OFFSET)

# draw clock
graphics.set_pen(WHITE)
graphics.text(f"{hour:02}:{minute:02}:{second:02}", PADDING, CLOCK_POSITION, scale=7)
text_width = graphics.measure_text(f"{day}/{month}/{year}", scale=7)
graphics.text(f"{day}/{month}/{year}", WIDTH - text_width, CLOCK_POSITION, scale=7)

last_second = second
graphics.update()


# Display 'updating' message when first started up
graphics.set_font("bitmap8")
graphics.set_pen(BLACK)
graphics.clear()
graphics.set_pen(WHITE)
graphics.text("Updating!", PADDING, CLOCK_POSITION, scale=6)
graphics.update()

get_data()
redraw_display_if_reqd()
last_updated = time.ticks_ms()

while True:
# get new data when button Y is pressed
if button_y.is_pressed:
get_data()
last_updated = time.ticks_ms()

ticks_now = time.ticks_ms()
# get new data after UPDATE_INTERVAL has passed
if time.ticks_diff(ticks_now, last_updated) > UPDATE_INTERVAL * 1000 * 60:
connect_to_wifi()
last_updated = time.ticks_ms()

year, month, day, wd, hour, minute, second, _ = rtc.datetime()
# apply timezone offset
hour = (hour + UTC_OFFSET) % 24

redraw_display_if_reqd()

# clean up
gc.collect()
Loading

0 comments on commit 4e59152

Please sign in to comment.