A Simple Clock
This post is a continuation of a previous article on getting started with the Notecard on the Raspberry Pi Pico, where I showed how to set the time on the Raspberry Pi Pico Real Time Clock from the Notecard. If you haven't already, you'll want to read that post before continuing.
In this post we will take this further, to build a clock. Using web APIs we can augment the display further, ensuring the correct timezone information, and displaying local weather. The project is based on the Galactic Unicorn by Pimoroni, an interesting LED Matrix with a Raspberry Pi Pico W built in. Whilst a display with 583 RGB LEDs would not be your first thought when building a project with an ultra-low-power cellular IoT connectivity device like the Blues Notecard, it does provide a mesmerising example to demonstrate the capabilities.
To start, combining the code from last time, and the clock example provided by Pimoroni, we can get a basic clock going very quickly with the following code snippet:
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
last_second = second
# Check whether the RTC time has changed and if so redraw the display
def redraw_display_if_reqd():
global year, month, day, wd, hour, minute, second, last_second
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
if second != last_second:
clock = "{:02}:{:02}:{:02}".format(hour, minute, second)
graphics.text(clock, 0, 0)
last_second = second
while True:
redraw_display_if_reqd()
# update the display
gu.update(graphics)
time.sleep(0.01)
Not shown we initialize the Notecard and RTC as in the previous example, and initialize the Graphics library.
Then we configure the display and set up some colours and the font before going into the main loop. Here in the main loop we call a function to update the clock display, which simply checks to only update the display once a second.
The first issue that you will see, unless you happen to be on UTC time currently, is that the time displayed is incorrect. We can get timezone offset information from the Notecard, but this would mean we needed to check frequently to account for Daylight saving time. Instead, we will query a web API to determine both the current offset and the next daylight saving changeover time.
Web Requests with Notecard
To access web APIs through the Notecard we need to set up a Proxy Route on Notehub. This allows us to avoid hardcoding URLs, keys, and certificates in the application on the Pico while relying on Notehub's secure authentication mechanisms for performing requests.
In our Notehub project, we add a new route to access the Time API:
Once set up, we can access this from the Notecard with the web.get
request. To use this the Notecard needs to be in continuous mode. If you recall from the previous post though, we have the Notecard in periodic mode as a power-saving feature, however, this is simple to work around. The hub.set
request has a parameter of on
and off
for temporary enablement of continuous mode. The time to leave it in continuous mode is set with the parameter seconds
and defaults to five minutes. The off
parameter to the request will disconnect immediately.
Combining this, we can define a function allowing us to enable the mode, wait until the Notecard is connected, make the web request, and disconnect.
def webRequest(route,path):
nCard.Transaction({'req': 'hub.set', 'on': True})
connected = False
while connected == False:
res = card.status(nCard)
connected = res.get('connected',False)
time.sleep(1)
res = nCard.Transaction({'req': 'web.get', 'route': route, 'name': path})
nCard.Transaction({'req': 'hub.set', 'off': True})
return res
When this is returned, the json structure from the API is returned as the value to the body
parameter, which Python helpfully converts to a dict for our use.
With this, we can now use the data provided to add the correct offset to our RTC before we set it. The get_tz_info
function call here makes the call to webRequest, and manipulates the response into the return structure.
tzInfo = get_tz_info()
celltime = card.time(nCard)
epochtime = celltime["time"]
epochtime += tzInfo['offset']
if tzInfo['dstactive']:
epochtime += tzInfo['dstoffset']
dstupdate = tzInfo['dstchange']
tm = time.gmtime(epochtime)
rtc = machine.RTC()
rtc.datetime((tm[0], tm[1], tm[2], tm[6], tm[3], tm[4], 0, 0))
Now we have both the correct time, and with the dstupdate
parameter holding the date and time of the next DST change, we can set a check in our main loop to update accordingly.
This is great, and allows us to have a clock that will display the correct time anywhere in the world.
Using a similar technique, with another proxy route setup in Notehub for Open-Meteo we can get weather information every 15 minutes.
In our main loop, we can add some code like this:
if (second != last_second and second == 0 and minute == 0):
weather = update_weather(latitude,longitude)
current_weather=weather['body']['current_weather']
draw_weather(current_weather['weathercode'],math.floor(current_weather['temperature']),'c')
Here we are checking that we are at the top of the hour, (and that the second value has changed, so we don't run the code 100 times), then call a couple of functions, one that wraps accessing the weather API, passing in the latitude and longitude (this is available from the Notecard either with the GPS, or getting the cell tower information for a less accurate location), and then calling another function to draw the resultant information to the screen.
The astute of you will realise there is a problem here though, our main loop is responsible for updating the clock display every second, and our webRequest
function has a loop waiting for the notecard to connect, which itself has a 1-second delay every time round the loop. What will happen in practice is there will be a several-second delay as the Notecard comes out of low-power mode and reconnects to the cell network, and then the Notehub, during which time the main loop is blocked.
This sort of blocking is important to handle in many Notecard applications, for example you don't want to stop collecting sensor information just because you have a need to power up and make a web request. There are multiple ways to handle this, for example rather than blocking on waiting for the Notecard to connect, you could make the request to connect, and then check periodically in the main loop until it was ready, whilst still handling the other functions of the loop, but Micropython on the Pico gives us a more elegant solution.
Micropython Timer Interrupts
Micropython on the Raspberry Pi Pico gives you software based timer interrupts, only limited by memory; using these, we can trigger our display updates, removing them from our main loop.
Timers are created with just one function call:
timer = Timer(mode=Timer.PERIODIC, period=1000, callback=function)
The timer will operate in two modes, PERIODIC
as above, will call the function repeatedly with the period
, specified in milliseconds, as the interval. ONESHOT
will wait the specified period
, and then call the function once. The callback
can either be a function, or a lambda inline, as shown in the example:
Timer(period=5000, mode=Timer.ONE_SHOT, callback=lambda t:print(1))
For our case, we define a function update_clock
and pass it as the callback function
def update_clock(timer = None):
global second, minute
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
x = 0
y = 0
draw_clock(x, y, hour, minute, second)
timer = Timer(mode=Timer.PERIODIC, period=1000, callback=upfate_clock)
This updates the time part variables from the RTC, then calls the function that sets up the drawing on the screen. This doesn't however call the function to actually draw onto the screen, for this we define another function and timer:
def update_gfx(timer = None):
# update the display
gu.update(graphics)
gfx_timer = Timer(mode=Timer.PERIODIC, period=100, callback=update_gfx)
Finally, we adjust our main loop, removing these lines as they are now handled by timers:
redraw_display_if_reqd()
gu.update(graphics)
Conclusion
I hope this helps you see the power of the Notecard, and some tricks with using Micropython to work with it. There is of course more than one way to achieve what I have here, but hopefully this will provide inspiration.
Check out the full (evolving) source code at Github, to see how it all comes together.
In the next article on this series, we will show how you can use Notehub to set up changeable settings for the Notecard application, and how you can send data on demand to the Notecard. In the meantime please check out the Notecard Starter Kit and the Quickstart on blues.dev.