This project is a continuation of the Solar LED project, it will display the energy status in W or kW from on a small display using a Pico W.

Although the Solar LED project provides an easy to read status (from miles away due to the bright LED’s), this little display will show you the exact details.
The display shows:
- Solar generation in Green
- Grid consumption in Red
- Return to Grid in White
- Diverted power in Blue
- House consumption in Orange
- Time of last measurement
- % energy independance
When there is no change in readings for a while, the display will turn off and will turn back on once new data arrives (to cope with sunset, nothing to display …).
What will you need:
- Pico WH (WiFi version with header) – Approx. 9 EUR
- Pimoroni Pico Display Pack unit – Approx. 18 EUR
As the hard work has already been done, we only need to expose the JSON file we downloaded on the Pi Zero W to the local internet, on the Pi Zero W enter the following:
sudo ln -s /tmp/eddi.json eddi.json
cd /var/www/html/
sudo ln -s /tmp/eddi.json eddi.json
cd /var/www/html/
sudo ln -s /tmp/eddi.json eddi.json
To prepare the Pico you can press the display on the header, do it gently and make sure the USB side is indeed on the USB side of the Pico.
Now we can connect the Pico W with Display and with an official USB Pico cable (most cables will not work!) to your Mac. Install Thonny for macOS and you will be able to connect to the Pico.
Download & install the Pimoroni custom MicroPython for the “Pico W” version (keep the boot select button pressed and then plug it into the Mac) – copy the file over and unplug & replug the Pico in the Mac. Then use Thonny to create two files on the Pico. The first one is named “secrets.py” and will hold your WiFi networks name and password:
SSID = "Your Wi-Fi Network"
SSID = "Your Wi-Fi Network"
PASS = "Your Password"
SSID = "Your Wi-Fi Network"
PASS = "Your Password"
The next file should be named “main.py” and will run every time the Pico has power and boots up:
# Pico W + Pico Display Pack
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY
from pimoroni import RGBLED
from machine import Timer, WDT
# URL to local json file using http
# See https://mywebmymail.com/solar-level-indicator-for-myenergi-eddi/
url = "http://192.168.68.110/eddi.json"
pollinterval = 30 # Interval in seconds for a new JSON poll
thresholdw = 10 # Ignore solar power when below this value (W)
brightness = 0.5 # Brightness of the display
timeoffset = 120 # Time offset in minutes from UTC
sleepafter = 10 # Number of JSON polls without change
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY, rotate=0)
display.set_font("bitmap8")
display.set_backlight(brightness)
BLACK = display.create_pen(0, 0, 0)
GREEN = display.create_pen(50, 205, 50)
RED = display.create_pen(200, 0, 0)
BLUE = display.create_pen(0, 191, 255)
WHITE = display.create_pen(255, 255, 255)
ORANGE = display.create_pen(255, 140, 0)
# Power data, animation coordinates, time helpers
def house(color, happy, line):
display.line(90, 90, 150, 90, line)
display.line(150, 90, 150, 50, line)
display.line(150, 50, 120, 30, line)
display.line(120, 30, 90, 50, line)
display.line(90, 50, 90, 90, line)
display.circle(120, 67, 15)
display.circle(120, 64, 15)
display.triangle(103, 59, 117, 59, 110, 50)
display.triangle(123, 59, 137, 59, 130, 50)
display.rectangle(105, 54, 10, 5)
display.rectangle(125, 54, 10, 5)
display.rectangle(105, 74, 30, 3)
# Format power in W or kW
return (str(power) + " W")
return (str(floor(power / 100) / 10) + " kW")
# Write text centered or right aligned
def writetext(text, y, center, size, color):
width = display.measure_text(text, size)
display.text(text, floor((240 - width) / 2), y, 240, size)
display.text(text, 240 - width, y, 240, size)
global diverted_power, solar_power, grid_power, total
global url, timestamp, oldstamp, timedout
led.set_rgb(138, 43, 226)
myenergi = urequests.get(url).json()
data = myenergi[0]['eddi'][0]
writetext("No WiFi", 122, True, 1, RED)
diverted_power = data['div']
solar_power = data['gen']
if timestamp != oldstamp:
total = solar_power + grid_power
if solar_power < thresholdw:
def resetwdTimer(wdTimer):
def update_display(myTimer):
writetext("Version 1.2", 102, True, 2, WHITE)
wlan = network.WLAN(network.STA_IF)
wlan.connect(secrets.SSID, secrets.PASS)
text = "No WiFi - Waiting 10s"
width = display.measure_text(text, 2)
display.text(text, floor((240 - width) / 2), 3, 240, 2)
myTimer.init(period = pollinterval * 1000, mode = Timer.PERIODIC, callback = update_display)
wdTimer.init(period = 5000, mode = Timer.PERIODIC, callback = resetwdTimer)
# Automatic reboot after 6 seconds of inactivity
# Sleep Mode when no new data is received
if timedout == sleepafter:
display.set_backlight(0.0)
if timedout >= sleepafter:
time.sleep(pollinterval * 10)
display.set_backlight(brightness)
if ((in_y > 69) and (in_x < 82)):
if ((in_x > 74) and (in_z > 1)):
if ((out_x > 219) and (out_y < 101)):
if ((out_y > 95) and (out_z > 1)):
if ((diverted_power > 0 ) or (grid_power < 0)):
display.text(formatpower(solar_power), 0, 0, 240, 3)
display.line(20, 30, 20, 70, 2)
display.line(20, 70, 90, 70, 2)
display.circle(in_x, in_y, in_z)
# Draw energy independance
perc = floor(min(100, (solar_power / max(1, total)) * 100))
text = str(int(perc)) + " %"
writetext(text, 102, True, 2, WHITE)
text = formatpower(abs(grid_power))
writetext(text, 0, False, 3, RED)
display.line(220, 30, 220, 60, 2)
display.line(220, 60, 150, 60, 2)
display.circle(240 - in_x, min(in_y, 60), in_z)
house(RED, False, linewidth)
writetext(text, 0, False, 3, WHITE)
display.line(220, 30, 220, 60, 2)
display.line(220, 60, 150, 60, 2)
display.circle(out_x, 135 - out_y, out_z)
if ((diverted_power > 0) and (out_y > 98)):
house(BLUE, True, linewidth)
house(GREEN, True, linewidth)
text = formatpower(diverted_power)
writetext(text, 111, False, 3, BLUE)
display.line(220, 105, 220, 75, 2)
display.line(220, 75, 150, 75, 2)
display.circle(out_x, out_y, out_z)
if (total - diverted_power) > 0:
display.text(formatpower(total - diverted_power), 0, 111, 240, 3)
# Draw time from last JSON data
tmp = timestamp.split(':')
if ((len(tmp) == 3) and (timestamp !='00:00:00')):
mininday = (int(tmp[0]) * 60) + int(tmp[1]) + timeoffset
hours = floor(mininday / 60)
minutes = mininday - (hours * 60)
text = "0" * (2 - len(str(hours))) + str(hours) + '.' + "0" * (2 - len(str(minutes))) + str(minutes)
writetext(text, 3, True, 2, WHITE)
# Update everything to the screen
# (c) JF Nutbroek
# 08/10/2023
# Pico W + Pico Display Pack
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY
from pimoroni import RGBLED
import time
import network
import secrets
import urequests
from math import floor
from machine import Timer, WDT
# User defined input
# URL to local json file using http
# See https://mywebmymail.com/solar-level-indicator-for-myenergi-eddi/
url = "http://192.168.68.110/eddi.json"
pollinterval = 30 # Interval in seconds for a new JSON poll
thresholdw = 10 # Ignore solar power when below this value (W)
brightness = 0.5 # Brightness of the display
timeoffset = 120 # Time offset in minutes from UTC
sleepafter = 10 # Number of JSON polls without change
# ********* Start
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY, rotate=0)
display.set_font("bitmap8")
display.set_backlight(brightness)
# Colors
BLACK = display.create_pen(0, 0, 0)
GREEN = display.create_pen(50, 205, 50)
RED = display.create_pen(200, 0, 0)
BLUE = display.create_pen(0, 191, 255)
WHITE = display.create_pen(255, 255, 255)
ORANGE = display.create_pen(255, 140, 0)
# Switch LED off
led = RGBLED(6, 7, 8)
led.set_rgb(0, 0, 0)
# Power data, animation coordinates, time helpers
diverted_power = 0
solar_power = 0
grid_power = 0
total = 0
in_x = 20
in_y = 34
in_z = 2
out_x = 158
out_y = 75
out_z = 2
timestamp = '00:00:00'
oldstamp = '00:00:00'
timedout = 0
# Draw the house
def house(color, happy, line):
display.set_pen(color)
display.line(90, 90, 150, 90, line)
display.line(150, 90, 150, 50, line)
display.line(150, 50, 120, 30, line)
display.line(120, 30, 90, 50, line)
display.line(90, 50, 90, 90, line)
# Draw mouth
if happy:
display.circle(120, 67, 15)
display.set_pen(BLACK)
display.circle(120, 64, 15)
display.set_pen(color)
display.triangle(103, 59, 117, 59, 110, 50)
display.triangle(123, 59, 137, 59, 130, 50)
else:
display.rectangle(105, 54, 10, 5)
display.rectangle(125, 54, 10, 5)
display.rectangle(105, 74, 30, 3)
# Format power in W or kW
def formatpower(power):
if power < 1000:
return (str(power) + " W")
else:
return (str(floor(power / 100) / 10) + " kW")
# Write text centered or right aligned
def writetext(text, y, center, size, color):
width = display.measure_text(text, size)
display.set_pen(color)
if center:
display.text(text, floor((240 - width) / 2), y, 240, size)
else:
display.text(text, 240 - width, y, 240, size)
# Read JSON
def readjson():
global diverted_power, solar_power, grid_power, total
global url, timestamp, oldstamp, timedout
led.set_rgb(138, 43, 226)
try:
if wlan.isconnected():
myenergi = urequests.get(url).json()
data = myenergi[0]['eddi'][0]
diverted_power = 0
solar_power = 0
grid_power = 0
total = 0
else:
led.set_rgb(255, 0, 0)
writetext("No WiFi", 122, True, 1, RED)
return
except:
led.set_rgb(255, 0, 0)
return
if 'div' in data:
diverted_power = data['div']
if 'gen' in data:
solar_power = data['gen']
if 'grd' in data:
grid_power = data['grd']
if 'tim' in data:
timestamp = data['tim']
if timestamp != oldstamp:
oldstamp = timestamp
timedout = 0
else:
timedout += 1
# Threshold conditions
total = solar_power + grid_power
if solar_power < thresholdw:
solar_power = 0
total = grid_power
led.set_rgb(0, 0, 0)
def resetwdTimer(wdTimer):
global wd
wd.feed()
# Read JSON
def update_display(myTimer):
readjson()
# Startup
house(WHITE, True, 2)
writetext("Version 1.2", 102, True, 2, WHITE)
display.update()
time.sleep(2)
# Connect to WiFi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(secrets.SSID, secrets.PASS)
time.sleep(1)
# First JSON download
if wlan.isconnected():
readjson()
else:
display.set_pen(RED)
text = "No WiFi - Waiting 10s"
width = display.measure_text(text, 2)
display.text(text, floor((240 - width) / 2), 3, 240, 2)
display.update()
time.sleep(10)
readjson()
# Start the timers
myTimer = Timer()
myTimer.init(period = pollinterval * 1000, mode = Timer.PERIODIC, callback = update_display)
wdTimer = Timer()
wdTimer.init(period = 5000, mode = Timer.PERIODIC, callback = resetwdTimer)
# Automatic reboot after 6 seconds of inactivity
wd = WDT(timeout = 6000)
while True:
# Sleep Mode when no new data is received
if timedout == sleepafter:
display.set_pen(BLACK)
display.clear()
display.update()
display.set_backlight(0.0)
if timedout >= sleepafter:
time.sleep(pollinterval * 10)
readjson()
if timedout == 0:
display.set_backlight(brightness)
else:
continue
# Start drawing
display.set_pen(BLACK)
display.clear()
# Animations
linewidth = 2
if in_y < 70:
in_y += 1
if in_z < 8:
in_z += 1
if ((in_y > 69) and (in_x < 82)):
in_x += 1
if ((in_x > 74) and (in_z > 1)):
in_z -= 1
if in_x > 81:
in_x = 20
in_y = 34
in_z = 2
linewidth = 6
if out_x < 220:
out_x += 1
if out_z < 8:
out_z += 1
if ((out_x > 219) and (out_y < 101)):
out_y += 1
if ((out_y > 95) and (out_z > 1)):
out_z -= 1
if out_y > 100:
out_x = 158
out_y = 75
out_z = 2
if ((diverted_power > 0 ) or (grid_power < 0)):
linewidth = 6
# Draw solar power
if solar_power > 0:
display.set_pen(GREEN)
display.text(formatpower(solar_power), 0, 0, 240, 3)
display.line(20, 30, 20, 70, 2)
display.line(20, 70, 90, 70, 2)
display.circle(in_x, in_y, in_z)
# Draw energy independance
perc = floor(min(100, (solar_power / max(1, total)) * 100))
text = str(int(perc)) + " %"
writetext(text, 102, True, 2, WHITE)
# Draw grid power
text = formatpower(abs(grid_power))
if grid_power > 0:
writetext(text, 0, False, 3, RED)
display.line(220, 30, 220, 60, 2)
display.line(220, 60, 150, 60, 2)
display.circle(240 - in_x, min(in_y, 60), in_z)
house(RED, False, linewidth)
if grid_power < 0:
# Draw returned power
writetext(text, 0, False, 3, WHITE)
display.line(220, 30, 220, 60, 2)
display.line(220, 60, 150, 60, 2)
display.circle(out_x, 135 - out_y, out_z)
if ((diverted_power > 0) and (out_y > 98)):
house(BLUE, True, linewidth)
else:
house(GREEN, True, linewidth)
# Draw diverted power
text = formatpower(diverted_power)
if diverted_power > 0:
writetext(text, 111, False, 3, BLUE)
display.line(220, 105, 220, 75, 2)
display.line(220, 75, 150, 75, 2)
display.circle(out_x, out_y, out_z)
# Draw house consumption
if (total - diverted_power) > 0:
display.set_pen(ORANGE)
display.text(formatpower(total - diverted_power), 0, 111, 240, 3)
# Draw time from last JSON data
tmp = timestamp.split(':')
if ((len(tmp) == 3) and (timestamp !='00:00:00')):
mininday = (int(tmp[0]) * 60) + int(tmp[1]) + timeoffset
hours = floor(mininday / 60)
minutes = mininday - (hours * 60)
text = "0" * (2 - len(str(hours))) + str(hours) + '.' + "0" * (2 - len(str(minutes))) + str(minutes)
writetext(text, 3, True, 2, WHITE)
# Update everything to the screen
display.update()
time.sleep(0.01)
# (c) JF Nutbroek
# 08/10/2023
# Pico W + Pico Display Pack
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY
from pimoroni import RGBLED
import time
import network
import secrets
import urequests
from math import floor
from machine import Timer, WDT
# User defined input
# URL to local json file using http
# See https://mywebmymail.com/solar-level-indicator-for-myenergi-eddi/
url = "http://192.168.68.110/eddi.json"
pollinterval = 30 # Interval in seconds for a new JSON poll
thresholdw = 10 # Ignore solar power when below this value (W)
brightness = 0.5 # Brightness of the display
timeoffset = 120 # Time offset in minutes from UTC
sleepafter = 10 # Number of JSON polls without change
# ********* Start
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY, rotate=0)
display.set_font("bitmap8")
display.set_backlight(brightness)
# Colors
BLACK = display.create_pen(0, 0, 0)
GREEN = display.create_pen(50, 205, 50)
RED = display.create_pen(200, 0, 0)
BLUE = display.create_pen(0, 191, 255)
WHITE = display.create_pen(255, 255, 255)
ORANGE = display.create_pen(255, 140, 0)
# Switch LED off
led = RGBLED(6, 7, 8)
led.set_rgb(0, 0, 0)
# Power data, animation coordinates, time helpers
diverted_power = 0
solar_power = 0
grid_power = 0
total = 0
in_x = 20
in_y = 34
in_z = 2
out_x = 158
out_y = 75
out_z = 2
timestamp = '00:00:00'
oldstamp = '00:00:00'
timedout = 0
# Draw the house
def house(color, happy, line):
display.set_pen(color)
display.line(90, 90, 150, 90, line)
display.line(150, 90, 150, 50, line)
display.line(150, 50, 120, 30, line)
display.line(120, 30, 90, 50, line)
display.line(90, 50, 90, 90, line)
# Draw mouth
if happy:
display.circle(120, 67, 15)
display.set_pen(BLACK)
display.circle(120, 64, 15)
display.set_pen(color)
display.triangle(103, 59, 117, 59, 110, 50)
display.triangle(123, 59, 137, 59, 130, 50)
else:
display.rectangle(105, 54, 10, 5)
display.rectangle(125, 54, 10, 5)
display.rectangle(105, 74, 30, 3)
# Format power in W or kW
def formatpower(power):
if power < 1000:
return (str(power) + " W")
else:
return (str(floor(power / 100) / 10) + " kW")
# Write text centered or right aligned
def writetext(text, y, center, size, color):
width = display.measure_text(text, size)
display.set_pen(color)
if center:
display.text(text, floor((240 - width) / 2), y, 240, size)
else:
display.text(text, 240 - width, y, 240, size)
# Read JSON
def readjson():
global diverted_power, solar_power, grid_power, total
global url, timestamp, oldstamp, timedout
led.set_rgb(138, 43, 226)
try:
if wlan.isconnected():
myenergi = urequests.get(url).json()
data = myenergi[0]['eddi'][0]
diverted_power = 0
solar_power = 0
grid_power = 0
total = 0
else:
led.set_rgb(255, 0, 0)
writetext("No WiFi", 122, True, 1, RED)
return
except:
led.set_rgb(255, 0, 0)
return
if 'div' in data:
diverted_power = data['div']
if 'gen' in data:
solar_power = data['gen']
if 'grd' in data:
grid_power = data['grd']
if 'tim' in data:
timestamp = data['tim']
if timestamp != oldstamp:
oldstamp = timestamp
timedout = 0
else:
timedout += 1
# Threshold conditions
total = solar_power + grid_power
if solar_power < thresholdw:
solar_power = 0
total = grid_power
led.set_rgb(0, 0, 0)
def resetwdTimer(wdTimer):
global wd
wd.feed()
# Read JSON
def update_display(myTimer):
readjson()
# Startup
house(WHITE, True, 2)
writetext("Version 1.2", 102, True, 2, WHITE)
display.update()
time.sleep(2)
# Connect to WiFi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(secrets.SSID, secrets.PASS)
time.sleep(1)
# First JSON download
if wlan.isconnected():
readjson()
else:
display.set_pen(RED)
text = "No WiFi - Waiting 10s"
width = display.measure_text(text, 2)
display.text(text, floor((240 - width) / 2), 3, 240, 2)
display.update()
time.sleep(10)
readjson()
# Start the timers
myTimer = Timer()
myTimer.init(period = pollinterval * 1000, mode = Timer.PERIODIC, callback = update_display)
wdTimer = Timer()
wdTimer.init(period = 5000, mode = Timer.PERIODIC, callback = resetwdTimer)
# Automatic reboot after 6 seconds of inactivity
wd = WDT(timeout = 6000)
while True:
# Sleep Mode when no new data is received
if timedout == sleepafter:
display.set_pen(BLACK)
display.clear()
display.update()
display.set_backlight(0.0)
if timedout >= sleepafter:
time.sleep(pollinterval * 10)
readjson()
if timedout == 0:
display.set_backlight(brightness)
else:
continue
# Start drawing
display.set_pen(BLACK)
display.clear()
# Animations
linewidth = 2
if in_y < 70:
in_y += 1
if in_z < 8:
in_z += 1
if ((in_y > 69) and (in_x < 82)):
in_x += 1
if ((in_x > 74) and (in_z > 1)):
in_z -= 1
if in_x > 81:
in_x = 20
in_y = 34
in_z = 2
linewidth = 6
if out_x < 220:
out_x += 1
if out_z < 8:
out_z += 1
if ((out_x > 219) and (out_y < 101)):
out_y += 1
if ((out_y > 95) and (out_z > 1)):
out_z -= 1
if out_y > 100:
out_x = 158
out_y = 75
out_z = 2
if ((diverted_power > 0 ) or (grid_power < 0)):
linewidth = 6
# Draw solar power
if solar_power > 0:
display.set_pen(GREEN)
display.text(formatpower(solar_power), 0, 0, 240, 3)
display.line(20, 30, 20, 70, 2)
display.line(20, 70, 90, 70, 2)
display.circle(in_x, in_y, in_z)
# Draw energy independance
perc = floor(min(100, (solar_power / max(1, total)) * 100))
text = str(int(perc)) + " %"
writetext(text, 102, True, 2, WHITE)
# Draw grid power
text = formatpower(abs(grid_power))
if grid_power > 0:
writetext(text, 0, False, 3, RED)
display.line(220, 30, 220, 60, 2)
display.line(220, 60, 150, 60, 2)
display.circle(240 - in_x, min(in_y, 60), in_z)
house(RED, False, linewidth)
if grid_power < 0:
# Draw returned power
writetext(text, 0, False, 3, WHITE)
display.line(220, 30, 220, 60, 2)
display.line(220, 60, 150, 60, 2)
display.circle(out_x, 135 - out_y, out_z)
if ((diverted_power > 0) and (out_y > 98)):
house(BLUE, True, linewidth)
else:
house(GREEN, True, linewidth)
# Draw diverted power
text = formatpower(diverted_power)
if diverted_power > 0:
writetext(text, 111, False, 3, BLUE)
display.line(220, 105, 220, 75, 2)
display.line(220, 75, 150, 75, 2)
display.circle(out_x, out_y, out_z)
# Draw house consumption
if (total - diverted_power) > 0:
display.set_pen(ORANGE)
display.text(formatpower(total - diverted_power), 0, 111, 240, 3)
# Draw time from last JSON data
tmp = timestamp.split(':')
if ((len(tmp) == 3) and (timestamp !='00:00:00')):
mininday = (int(tmp[0]) * 60) + int(tmp[1]) + timeoffset
hours = floor(mininday / 60)
minutes = mininday - (hours * 60)
text = "0" * (2 - len(str(hours))) + str(hours) + '.' + "0" * (2 - len(str(minutes))) + str(minutes)
writetext(text, 3, True, 2, WHITE)
# Update everything to the screen
display.update()
time.sleep(0.01)
That’s it! Find a good spot for the display and you have all the info you need at a glance.