Keith Tanner
Keith Tanner MegaDork
2/15/22 7:59 p.m.

I posted this in the "something you made" thread and received emails asking for the recipe. Like, more than one! Obviously, there is deep demand from multiples of people. So I'm going to post a how-to.

What it is:

My wife and I share cat feeding duties on a very ad-hoc basis - whomever happens to be closest at roughly the right time puts some food out. But if you live with cats, you know they cannot be trusted. I have been led to believe that dogs are no better.

So I put this together. When you press the "feed" button, it wakes up, checks the time, puts the timestamp on the eink display and goes back into hibernation. It sticks to the fridge with magnets. I could have it do more things, but sometimes you just need something simple.

 

 

If you're interested in getting started with little microcontrollers, this is a good way to start. It's fairly simple and doesn't require any soldering or messing around with wires. Because it's got a display and Wifi, it's a little more complex than the usual "make an LED blink" but we'll try to walk through it.

Parts list:

Everything you need

Here's what's in there, piece by piece and for a little more money:

Adafruit MagTag. This is the basis of the whole thing. Like everything else with chips in it, prices have gone up recently but not dramatically. 
Battery. You can also attach the MagTag to a USB cable and power it that way. I'm still working on power management (I have an idea there that we might explore later) but this one should be good for a week and a half or so between recharges. Because eink displays don't require power to maintain the display, you'll know it needs charging when it doesn't respond to button pushes. Adafruit sells a range of batteries, our device should suck down about 40 mAh per day with the current programming so you can size accordingly.
Magnetic feet. Allows you to stick the MagTag to your fridge.
Some sort of case if you don't like the bare aesthetic. Mine has a 3D printed case. You could make your own faceplate out of wood or cardboard or decorate it with Swarovski crystals, whatever.

One great thing about Adafruit is that they have guides of all sorts. Here are the ones for the MagTag. You'll notice there's one in there for a cat feeding timer, but the difference is that one is unable to run off batteries as shows the current time so it's constantly updating. The big advantage of that eink display is ridiculously low power use, and I don't like having to deal with cords on something stuck on my fridge. So I decided to do something different.

I'll post some programming stuff later tonight.

Appleseed
Appleseed MegaDork
2/15/22 8:08 p.m.

Did you ask your cat a question ?

The answer is a lie.

 

Simple as that. It's a cat after all.

Steve_Jones
Steve_Jones Dork
2/15/22 8:37 p.m.

Keith Tanner
Keith Tanner MegaDork
2/15/22 9:37 p.m.

This is true. This tool will only confirm one specific untruth. All other statements by the cats should still be treated with suspicion.

Step 1 in programming is to set up CircuitPython in the MagTag. This is well documented by Adafruit and the first version should work: https://learn.adafruit.com/adafruit-magtag/circuitpython

Now you should see a drive called CIRCUITPY on your computer, and you can edit the files on it using a text editor. There's also a piece of software called Mu Editor you can install but seriously, Notepad works.

You'll also need to install some libraries, you do this by simply dragging and dropping into a directory called "lib" on the MagTag. These are basically pre-defined programs that allow you do to things. Download this zip file and unzip it, then copy over the directories or files you need.
https://circuitpython.org/libraries

You'll need the following libraries from the bundle. Actually, I'm almost positive there's extra stuff in here left over from previous experiments. But it'll work with this :)

adafruit_bitmap_font
adafruit_bus_device

adafruit_display_text
adafruit_fakerequests.mpy
adafruit_magtag

adafruit_requests.mpy
adafruit_io
adafruit_portalbase
neopixel.mpy
simpleio.mpy

Now you can set up internet access for this thing. It's only used to get the time in this instance. Follow the instructions on this page to set it up and test it. You already have the libraries on your board, so it's just a matter of filling out secrets.py and then trying the test code. When it says to "load up" the example code, that just means copy/pasting the code with a text editor. When you save, the MagTag will restart and run the code.

https://learn.adafruit.com/adafruit-magtag/internet-connect

Okay, we have basic function. Let's do the cat thing.

Keith Tanner
Keith Tanner MegaDork
2/15/22 10:03 p.m.

The basic plan is to have this thing wake up when you press a button, log on to wifi, check the time on the internet, display it and then go to sleep until the next time. Here's the simplified code that uses a standard font. You can just copy all this into code.py and call it done if you'd like.

Lines beginning with # are comments, they're ignored by the program. I'll walk through all of this piece by piece because 1) it's good practice for me to lay this out and 2) hopefully someone will learn something and be able to work from here. I apologize to all the real programmers for the inevitable bad practices that follow.

# import all the libraries we'll use

import terminalio

import board

import alarm

from adafruit_magtag.magtag import MagTag

import time

 

# Get wifi details and more from a secrets.py file

try:

    from secrets import secrets

except ImportError:

    print("WiFi secrets are kept in secrets.py, please add them there!")

    raise

 

# this function turns the timestamp into a form people can read.

def hh_mm(time_struct):

    postfix = ""

    if time_struct.tm_hour > 12:

        hour_string = str(time_struct.tm_hour - 12) # 13-23 -> 1-11 (pm)

        postfix = "pm"

    elif time_struct.tm_hour > 0:

        hour_string = str(time_struct.tm_hour) # 1-12

        postfix = "am"

    else:

        hour_string = '12' # 0 -> 12 (am)

        postfix = "am"

    return (hour_string + ':{mm:02d}'.format(mm=time_struct.tm_min),postfix)

 

#create the MagTag object

magtag = MagTag()

#turn on lights so it looks like we're doing something

# light stuff could be ignored for better battery life but it's good to have some feedback.

magtag.peripherals.neopixels.fill((100,0,0))

#wait for half a second

time.sleep(0.5)

#different color lights just to be cool

magtag.peripherals.neopixels.fill((100,0,150))

 

#set up the various text fields that we'll use

 

#"cats fed"

magtag.add_text(

    text_font=terminalio.FONT,

    text_scale=2,

    text_position=(148,5),

    text_anchor_point=(0.5, 0),

    text_color=0x666666

 

)

# time (h/m)

magtag.add_text(

    text_font=terminalio.FONT,

    text_scale=5,

    text_position=(195,28),

    text_anchor_point=(1,0)

 

)

#feed label

magtag.add_text(

    text_font=terminalio.FONT,

    text_scale=2,

    text_position=(5,120),

    text_anchor_point=(0,1),

    text_color=0x999999

 

)

#am/pm

magtag.add_text(

    text_font=terminalio.FONT,

    text_scale=3,

    text_position=(208,28),

    text_anchor_point=(0,0),

    text_color=0x666666,

 

)

#updating label

magtag.add_text(

    text_font=terminalio.FONT,

    text_scale=2,

    text_position=(290,120),

    text_anchor_point=(1,1)

 

)

 

#put the "updating" label on the screen and refresh the display

magtag.set_text("updating...", 4,auto_refresh=False)

magtag.refresh()

#blinkylight

magtag.peripherals.neopixels.fill((0,00,100))

 

#log on to the network and get the time, this takes a while which is why we do the "updating" screen

magtag.network.get_local_time()

now = time.localtime()

formattedTime = hh_mm(now)

 

#display the result

magtag.set_text("cats were fed at", 0,auto_refresh=False)

magtag.set_text("feed", 2,auto_refresh=False)

magtag.set_text("", 4,auto_refresh=False)

magtag.set_text(formattedTime[0], 1,auto_refresh=False)

magtag.set_text(formattedTime[1], 3,auto_refresh=False)

magtag.peripherals.neopixels.fill((0,100,0))

magtag.refresh()

 

# now get ready to go to sleep

# the first line is a bug workaround

magtag.peripherals.buttons[0].deinit()

a_alarm = alarm.pin.PinAlarm(pin=board.BUTTON_A, value=False, pull=True) 

#turn off the lights

magtag.peripherals.neopixels.fill((0,0,0))

#go to sleep until someone pushes your button

alarm.exit_and_deep_sleep_until_alarms(a_alarm)

Karacticus
Karacticus Dork
2/15/22 10:11 p.m.

Keith Tanner
Keith Tanner MegaDork
2/15/22 10:22 p.m.

OK, from the top.

This section imports the libraries we're going to use. The "secrets" import also brings in the wifi info we put in our secrets.py file. The "try" section will check that it has wifi and then shut down if not.

# import all the libraries we'll use

import terminalio

import board

import alarm

from adafruit_magtag.magtag import MagTag

import time

# Get wifi details and more from a secrets.py file

try:

    from secrets import secrets

except ImportError:

    print("WiFi secrets are kept in secrets.py, please add them there!")

    raise

Now we define a function, a little sub-program that will take a timestamp and return a time and AM/PM. This wouldn't really be necessary if we used 24 hour time. Also, time is a pain in the butt.

# this function turns the timestamp into a form people can read.

def hh_mm(time_struct):

    postfix = ""

    if time_struct.tm_hour > 12:

        hour_string = str(time_struct.tm_hour - 12) # 13-23 -> 1-11 (pm)

        postfix = "pm"

    elif time_struct.tm_hour > 0:

        hour_string = str(time_struct.tm_hour) # 1-12

        postfix = "am"

    else:

        hour_string = '12' # 0 -> 12 (am)

        postfix = "am"

    return (hour_string + ':{mm:02d}'.format(mm=time_struct.tm_min),postfix)

This next section creates the MagTag object. Basically, it's a virtual version of the MagTag that we can tell to do things. This is one of the libraries we imported.

Then, because the next section will take time and eink screens take a moment to refresh, we flash some LEDs just so you know that you really did push a button.

#create the MagTag object

magtag = MagTag()

#turn on lights so it looks like we're doing something, the LEDs are called neopixels

# light stuff could be ignored for better battery life but it's good to have some feedback.

magtag.peripherals.neopixels.fill((100,0,0))

#wait for half a second

time.sleep(0.5)

#different color lights just to be cool

magtag.peripherals.neopixels.fill((100,0,150))

I'll only quote one of these, but this is the display setup. There are five text fields: the "updating" one, "cats were fed at", hour:minute, AM/PM and the "feed". They're all in different places and different greys, so this is just telling the display where they go, how big they are and what color they are. You could use graphical icons for some of these as well, but we're not gonna go there today.

#set up the various text fields that we'll use

 

#"cats fed"

magtag.add_text(

    text_font=terminalio.FONT,

    text_scale=2,

    text_position=(148,5),

    text_anchor_point=(0.5, 0),

    text_color=0x666666

 

)

Now we put "updating" on screen so again you know you actually pushed a button and we change the light color when we're done. This is just to amuse the user.

Next step is to ask the wifi what time it is. Most routers know this. This takes a little while which is why there's the "updating" text. You'll see the last line is calling that formatting function we defined earlier.

#put the "updating" label on the screen and refresh the display

magtag.set_text("updating...", 4,auto_refresh=False)

magtag.refresh()

#blinkylight

magtag.peripherals.neopixels.fill((0,00,100))

 

#log on to the network and get the time, this takes a while which is why we do the "updating" screen

magtag.network.get_local_time()

now = time.localtime()

formattedTime = hh_mm(now)

Now we display the result. If you have dogs, you'll make the obvious change here. The formattedTime[0] and [1] pull out the first and second parts of the formattedTime result, which are hours:minutes and then AM/PM. Again, this would be a lot easier with 24 hour time. Time is a pain. It also changes the color of the LEDs again just because. The color is red, blue,green in values from 0-254. 

#display the result

magtag.set_text("cats were fed at", 0,auto_refresh=False)

magtag.set_text("feed", 2,auto_refresh=False)

magtag.set_text("", 4,auto_refresh=False)

magtag.set_text(formattedTime[0], 1,auto_refresh=False)

magtag.set_text(formattedTime[1], 3,auto_refresh=False)

magtag.peripherals.neopixels.fill((0,100,0))

magtag.refresh()

Now it's time to go to sleep until someone presses the "feed" button. 

# now get ready to go to sleep

# the first line is a bug workaround

magtag.peripherals.buttons[0].deinit()

a_alarm = alarm.pin.PinAlarm(pin=board.BUTTON_A, value=False, pull=True) 

#turn off the lights

magtag.peripherals.neopixels.fill((0,0,0))

#go to sleep until someone pushes your button

alarm.exit_and_deep_sleep_until_alarms(a_alarm)

And voila.

The version in my house uses a different font which is a bit of a pain to create, so I'll dive deeper into some customizations in a later post. Also figure out how to identify what bodily functions a dog has performed during a walk, as dogs lie about if they've been walked.

Keith Tanner
Keith Tanner MegaDork
2/15/22 10:35 p.m.

Okay, maybe not THAT simple :) But at least you have something you can use at the end.

VolvoHeretic
VolvoHeretic Reader
2/15/22 11:25 p.m.

LOL, you lost me at "So, lets begin." Does it remember the time if the battery goes dead? I can't even manage to build the simple crossover for my super tweeters I accumulated the parts for about 4 years ago.

Keith Tanner
Keith Tanner MegaDork
2/16/22 12:43 a.m.

The display remains the same until it's changed, so the time stays even with a dead battery. It just doesn't get updated. Eink displays are very cool that way, absolutely no power required to maintain them.

codrus (Forum Supporter)
codrus (Forum Supporter) PowerDork
2/16/22 12:53 a.m.

I like it :)

One suggestion:  datetime.strftime is a fairly powerful library for generating text time strings in various ways, you can probably replace hh_mm() with a couple lines of code by using it.

Toyman!
Toyman! MegaDork
2/16/22 7:45 a.m.

Neat project.

Our cats are fat so we apparently believe their lies. 

 

Keith Tanner
Keith Tanner MegaDork
2/16/22 4:15 p.m.
codrus (Forum Supporter) said:

I like it :)

One suggestion:  datetime.strftime is a fairly powerful library for generating text time strings in various ways, you can probably replace hh_mm() with a couple lines of code by using it.

Good point. CircuitPython doesn't have all the libraries that normal Python does, but there is a version of datetime and I'll take a look at it. That section was borrowed from elsewhere and it worked, so I didn't bother chasing much further :)

Our Preferred Partners
vg67d4AoQMmu5KNOVfi14TwMIV8PpMiyKkAcBmFRuJVWEtDd74ql4c05HiM49608