Getting Started
Adafruit Playground is a wonderful and safe place to share your interests with Adafruit's vibrant community of makers and doers. Have a cool project you are working on? Have a bit of code that you think others will find useful? Want to show off your electronics workbench? You have come to the right place.
The goal of Adafruit Playground is to make it as simple as possible to share your work. On the Adafruit Playground users can create Notes. A note is a single-page space where you can document your topic using Adafruit's easy-to-use editor. Notes are like Guides on the Adafruit Learning System but guides are high-fidelity content curated and maintained by Adafuit. Notes are whatever you want them to be. Have fun and be kind.
Click here to learn more about Adafruit Playground and how to get started.
-
Learn mayan calendars with CircuitPython
What mayan calendars mean?
It's common for people and scholars to day that Mayan calendars are complicated. Or that they had a vision of time that was cyclical. Let's clear this up before learning some mayan calendars.
Our calendar seems simple in comparison, because it only does one of two things: count the says, and kinda tell us how is Earth moving in relation to the Sun, so that we can have a sense of what season show we be in, and what is the next season to come.
Mayan calendars do more than that. They paint a picture of how the sky looked on any given date, during the night and during the day. And yes, I said "calendars" because they didn't just had one, but many calendars. They followed lots of the planets (Venus, Mars, Jupiter, Saturn, etc), the Moon, the movement of the Earth around the Sun, solar and moon eclypses, and for the long-count, even the date their mythology said it was the creation date... and 13 creations before that.
If you look at the moon for many days, you will notice how it will receive more or less sunlight (phases of the moon), but also rising on the horizon earlier and then later. And after some days, those observations will start repeating, over and over. This is why their calendars feel as cyclical, because that is what the planets and the moons doing as they go through the sky.
Another short example is looking at Venus. Sometimes this planet will appear at sunrise, sometimes at sundown, and sometimes it will be too close to the Sun to observe it. And here we can realize something very important. All civilizations, through all of the ages have named "the morning star" and "the evening star" as two separate objects, with no relation between them. The maya were the only civilization to understand that it was Venus. And this tells us that they were incredible astronomers, with nothing that we could compare from their time or before. It would take 100's of years of arab precise observations with astrolabe and sexants, and advanzed mathematicians like Copernicus for the rest of the planet to realize this.
So, mayan calendars are not just ways to count the days, but ways for us to understand the sky and to keep track of where everything is on a certain date. Or, the other way around, to paint with numbers and words how the sky would look like when certain event happened. Think of it like Stellarium, but instead of being a piece of software it was the base for a culture that loved the sky.
Why learn them?
Sure, if we already have Stellarium, why go through the trouble of learning them?
As we talked about, they are an astronomical tool that has modern uses. If you know the date of the Moon Landing, what does that date tells you about the sky when that happened? Or, what would you know about the sky if you translated that date to mayan calendars?
We also talked how is the base for an entire culture that was almost completely erased. If they knew so much, what could we still learn from them?
Would you even look at the sky in the same way, if by "today" you can relate to where are all the planets in the sky and where they are in their cycles just by looking at the date?
Not just that, but mayans scribes and builders were some of the most extreme graphical artists you can think of. Imaging having artist and area specific, anime or graphic-novel style art, filled with astronomy and culture in every important building of your town! The styles of how you draw a specific calendar glyph are quite beautiful and intricate.
Tzolk'in
This will be our first calendar, used in lots of parts of mesoamerica, not only by the mayan but by many related cultures. It has many names, but
Tzolk'inis one that we use often, also called "sacred round" or "sacred calendar". It's not based on a solar year, but on aprox 9 months. Some scholars point that it could be length of time of a human pregnancy. It sort of makes sense, they believed that the date you were born influenced the type of person you would become, and it's also used to name a child.Others point to agricultural calendars, since if you plant maize on a certain day, you should have a harvest when the calendar is about to repeat. Since their culture uses a lot of maize, there is no doubt this would be useful as well.
My favorite idea, is that as we have discussed, the calendars paint a picture of the sky. In one of the few codices that still exist, called the Desden codex, we have an amazing section that we call "The tables of Venus", and it's a series of events in the cycle of Venus through the sky (from our view point) that are computed by using the
Tzolk'in. -
Meshtastic-compatible full stack (in 🦀) for CircuitPython
If you want to get a Meshtastic-compatible library that works great in CircuitPython, of course there is no better place to start than the MeshFruit library!
Yet, there is another library in town, that might be a have some extra steps to install it, but because it has been written in Rust, and for CircuitPython, it bridges the two things we like the most: an efficient and fast full stack library with the ease of use of CircuitPython.
The way that it has been designed, you can connect to REPL and have the library already talking with the Meshtastic network, while you send messages or read your inbox using simple commands. But, if you power on the device and to manually take it to REPL, it will start running
code.pyacting as a Meshtastic client that you can connect with your phone via the official Meshtastic application, or you can interact with it over the network by using the official Meshtastic Python API.So, you can use the
REPLfor simplicity, learning how to use the library, quick serial console or web-workflow testing, use the defaultcode.pythat comes with the library to connect your phone with the same app everyone else is using, and of course you can call the library from your own CircuitPython code to let your project start talking with the amazing Meshstastic network.Compatible hardware
Since me, the author of the library, only has a handful of hardware that is both capable of running CircuitPython and that has a LoRa radio, so far the list of hardware is a bit limited to a couple of new and shiny boards that Meshstastic enthusiasts are gravitating to, because of very useful features. With the library released, now I will work on adding support for Adafruit hardware, and later for popular Meshtastic hardware. So far, we got:
- Muzi Works' Base Duo: nRF52840 with LR1121 radio. Cool features: 915MHz + 2.4GHz LoRa, BLE, integrated solar charger.
- Meshnology W12: ESP32-S3R8 (same S3 we know and love, but with 8M of PSRAM!) with LR2021 radio. Cool features: 915MHz + 2.4GHz LoRa, FLRC! (more on this, below), WiFi, multi-net navigation device, OLED screen, comes with case, a very decent 915MHz antenna and battery.
Note: This boards are not official CircuitPython supported hardware, yet. I'm working on it, but we found an external issue that Adafruit is trying to solve.
Integrated devices
- The latest generation of LoRa radios from Semtech: LR1121 and LR2021.
- A navigation device (GNSS or "GPS")
- An OLED screen
How to install
Any device
- Use a CircuitPython build that has
CIRCUITPY_ENABLE_MPY_NATIVEenabled. This might require a manual rebuild, but it's required for Rust support. - Copy the
mpyfiles for the radio used in your board to theCIRCUITPY/libon your board. Current support is for: LR1121 and LR2021 - Copy the
mpyfiles from the cpy-meshtastic library to theCIRCUITPY/libon your board. Note: Since Rust is compiled, in a similar way to Turbo, you need to use the mpy files compiled for your board's arch. - Copy the
code.py,repl.py, andsettings.tomlto the root ofCIRCUITPYon your board. Editsettings.tomlto configure the LoRa region, client short and long name, as well as any board specific features like WiFi connection, OLED, navigation device, and to enable how you want to connect to the Phone app.
This is a short example of a
settings.tomlfile:MESH_RADIO = "lr2021" MESH_GPS = "gps" MESH_PHONE = "tcp" CIRCUITPY_WIFI_SSID = "your access point" CIRCUITPY_WIFI_PASSWORD = "4d4fru1t-ru13z"
Board specific install
Meshtastic folks are used to flashing from a web browser, and using a single file to install the firmware.
Look for the
UF2file that includes both CircuitPython, the radio, and the cpy-meshtastic library, for either the Muzi Works Base Duo, or the Meshnology W12.Notes:
- For the Base Duo board, embedding the library in the UF2 is needed anyway, to save memory.
- The W12 board is so new that they don't have UF2 support yet. Install TinyUF2 on it, first.
Usage
REPLIf you copied the
repl.pyfile to the root of yourCIRCUITPYfolder on your board, useCTRL+Cas usual to enter theREPL. The power of havingrepl.pyon your board, means that all of the init that has to happen to use cpy-meshtastic is already happening there; so the board is already listening to incomming packets from the Meshtastic network, and if you want to send a message, just ahead and do it. This is an example of what you will see:Press any key to enter the REPL. Use CTRL-D to reload. repl.py output: LR1121 fw 1.1, hw 0x22 # meshtastic node, firmware v2.7.26.54e0d8d # US/LongFast ch 20/104 @ 906.8750 MHz SF11 BW250.00kHz CR4/5 # node 0x0b9dbde1 relay 0xe1 channel 0x08, key from nvm # +22 dBm, duty cycle 0.00% of 100% over 3600s # keystore: node_num=4 bytes, channel_key=16 bytes, channel_name=0 bytes, region=2 bytes, preset=8 bytes, channel_num=4 bytes, tx_power_dbm=4 bytes, duty_cycle_pct=4 bytes, nodeinfo_s=4 bytes, long_name=13 bytes, short_name=4 bytes # cache: 32 nodes, 25 messages restored # read() peek() unread() channels() nodes() listen(30) # send("text") introduce() poll() status() save() stop() # ask(num) ask(num, "environment") node() release() # phone() serve() # settings() configure(region="EU_868") Adafruit CircuitPython 10.3.1-rust on 2026-09-14; muzi works Base Duo with nRF52840 >>> nodes() # 32 nodes heard # number short hops pkts snr last long # 0xb2a73a5c 3h 117 10.5 56s # 0xaca9f8e8 1h 549 10.5 1m # 0xe277a008 4h 1 10.5 2m (...) >>> send("Hi from Costa Rica, using cpy-meshtastic") # learned 0xcXXXXX is Long-Name (short-name), hw 101 1 (id=0xAAAA fr=0xBBBBB to=0xffffffff, WantAck=0, HopLim=2 Ch=0x8 Portnum=4 rxSNR=10.25 rxRSSI=-65 signalRSSI=-65 hopStart=7 relay=0xbd) node=Hopp port=nodeinfo bytes=73 # clock set from 0xCCCCCC: 2026-09-25T17:31:49Z UTC 2 17:31:49 (id=0xDDDDD fr=EEEEEE to=0xffffffff, WantAck=0, HopLim=1 Ch=0x8 Portnum=67 rxSNR=10.5 rxRSSI=-52 signalRSSI=-52 hopStart=3 relay=0xbd) node=0xGGGGGG port=telemetry bytes=22code.pywith Phone appInstall the Meshtastic phone app, power the CircuitPython device (remember to configure
settings.toml), and in the settings of the phone app scan for either bluetooth or network devices, depending on the capabilities of the board you are using and it's configuration, and connect to that device.The list of nodes, configuration, channels and other settings will be shared from the phone app to the CircuitPython device, and now you can use the phone app as you would if you had the original Meshtastic firmware on your LoRa device.
It's optional, but if you decide to open up a serial console to the device while running in
code.py"mode", it sill show something like this:Connected to /dev/tty.usbmodem441B67EA618F1 # code.py: last reset POWER_ON # code.py: bringing the node up, Ctrl-C stops it here # no GNSS module 'gps'; the clock will come from elsewhere LR2021 fw 1.24 # meshtastic node, firmware v2.7.26.54e0d8d # US/LongFast ch 20/104 @ 906.8750 MHz SF11 BW250.00kHz CR4/5 # node 0x0f61ea67 relay 0x67 channel 0x08, key from nvm # +22 dBm, duty cycle 0.00% of 100% over 3600s # keystore: node_num=4 bytes, channel_key=16 bytes, region=2 bytes # cache: 24 nodes, 32 messages restored # no interrupt support in this firmware, use listen() # (cannot watch that pin: no port support, already in use, or no slot left) # code.py: announcing, which keys the sub-GHz amplifier # nodeinfo: Meshtastic ea67 (ea67), asking for replies # sending 59 bytes, 681 ms on air SENT (id=0x4cba0c09 fr=0x0f61ea67 to=0xffffffff, WantAck=0, HopLim=3 Ch=0x8 Portnum=4 WANTRESP hopStart=3 relay=0x67) # phone: tcp starting, 8165120 bytes free # phone: tcp on 192.168.123.123:4403 (meshtastic-0f61ea67.local), 8140624 bytes free # code.py: node up, panel running -- Ctrl-C for the prompt # phone: 192.168.123.123:4403 (meshtastic-0f61ea67.local) connected from 192.168.123.123 # phone: 12 bytes in, 1 queued, at 0.0s # phone: 8 bytes in, 32 queued, at 0.2s # clock set from the phone: 2026-09-25T17:54:29Z UTC
And if you keep looking you should see one printed line for every message that is received or sent on the Meshtastic network.
In this screenshot, you can see a connected device, which is shown as a full-compatible to a device that would be using the original Meshtastic firmware!
Orchids in Costa Rica with a cpy-meshtastic on Meshnology radio -
Failing with CircuitPython
How to fail with CircuitPython
All code will have errors. You might find it sooner or later, or the error might be in your code, in a library that you are using or even in CircuitPython itself!
It is also very common to write some code on your lab environment, test it a bunch of times, then take it out into the field and watch it fail in lots of ways you would never though of. Exactly the same happens when you give your device to a user or a kid who will test it in ways that will make your coding skills feel silly.
In my case this is happening for a weather station. This should be a simple project, right? Grab some data from a sensor like a Stemma QT thermometer, then send that data to Adafruit.IO and watch it get graphed in a lovely dashboard. Or at least that was my expectation for this project.
As you can see, I started getting missing data for days, and only when I went to the semi-remote location where I am testing my weather station, and manually rebooted the devices, I would get start getting data again. But now the graph is ruined, it has a large gap in the data and to all whom I showed the graphs, the first thing they mentioned is 'what happened to the missing data?'. Now what do I do?
Look at the last hours of the graph. Even if I am missing only a little bit of data, it looks terrible right? You, the reader have no idea how hot it is during the day, how cold it is during the night, or what is the coldest hour. Your eyes go straight to the missing data at the end, with the graphs looking like a shark tooth and being jumpy. What a disaster.
I thought about blaming Adafruit.IO or maybe CircuitPython or maybe it's the fault of the ESP32 that is reading the data. Then I thought about adding extra hardware to it like a TPL5110 power timer but that would require some soldering and would break the wifi workflow that I love so much from CircuitPython 8.
I cloud also get my code full of
tryandexceptPython blocks on all of the parts of my code. To be honest, I did tried to do it this way, but things would still keep failing so I kept adding moretry/exceptblocks until the code looked terrible. I was this close to having a project that was not fun for me, and that I would not like to share how it works with this amazing community that makes CircuitPython what it is. Not sharing? That doesn't sound like me at all. Time to do things a different way.Loving to fail
There is a bit of a hidden gem in CircuitPython and it's the
supervisormodule. I was only able to find a little bit of technical documentation without a simple example that I could just copy and paste into my project, and I also found another example which was close to what I wanted to do in Tod's CircuitPython tricks.After staring at the code I had available for a couple of minutes, I realized that this is what I had to do. I needed to let go of my impulse of keep the code failure free, and embrace CircuitPython failing. I would let if fail any time it wanted, and then I would ask it to try again.
With two simple lines, my weather station code can now fail because the wifi router is down, because the Stemma QT cables had a problem with talking to the sensor, because it was running for so long that it was finding errors in the CircuitPython base code or the request library. It doesn't matter, the board will accept that something weird happened, and would try again. Try this in your code and love to fail as much as I do now!
import supervisorsupervisor.set_next_code_file(filename='code.py', reload_on_error=True)It should not be this simple, right? Here's how it works:
In the first line of code we are calling a library called
supervisor. Don't worry, you don't have to install anything, it comes as a standard CircuitPython library and will probably work on any board that you have CircuitPython running on. What it does, is talk to one of the internal parts of CircuitPython and modify how it will behave.The second like, is the one that changes how it works. It's using a function called
set_next_code_file. It's normally used so that you can run the well knowncode.pyfile and if something happens, you can define a second file with another name and that code would do something like recover things, blink a light or beep to indicate that it will not behave as it would normally do. I am not calling a second file, instead I'm telling it to go tocode.pyso that it can try again.And my favorite part comes with the code that says
reload_on_error=True. The way CircuitPython works is that when it fails it will take you to the REPL console, and show you why it failed. This is a good thing as that is the way that we learn, we make mistakes, the REPL shows us what those mistakes are, and we go and fix them.But for my weather station, once the code was ready and it was doing what I wanted, if there is a failure like the wifi is down, it would go to the console and wait for me to do something. This weather station is located on a dry forest in Costa Rica and it doesn't know that is not next to my computer where it's easy for me to tell it to try again. So what
reload_on_error=Truetells it to do, is to change what it would have usually do and take me to the console, and instead it will do something similar as if I was close so the board, pushing the RESET button every time something unexpected happens.This way, CircuitPython will keep on trying over and over until wifi comes back, and hopefully the graphs will have smaller gaps in the data, and they will look much better and should be able to work like a weather station should. Read the thermometer. Send it to Adafruit.IO. Go to sleep to save some battery.
I love you CircuitPython! You and me can fail as many times as we need to, as long as we do it together and without feeling bad. <3.