Documentation: https://give-me-a-sign.readthedocs.io/
Give Me A Sign! is an info sign designed for hacker and makerspaces, although you could use it in your home or office as well.
The sign will mostly look like a clock, while sometimes displaying other useful information like messages, weather, air quality information or whatever you push to it.
The sign is intended to be deployed and left aone. It doesn't pull data (other than syncing its clock via NTP), data gets pushed to it. The project includes examples of how to set up Home Assistant to push data to the sign.
In the age of connected devices one of the big questions becomes "where should we do what?". For instance, should a smart sign that shows the weather fetch the weather information itself, or would it be better to push that information to it from a more capable computer?
Two important questions arise:
- Is the device intended to be stand-alone, with no additional support? If so then obviously the complexity has to be in the device.
- If the device is not intended to be stand-alone then where is the most appropriate place to put different functions?
Give Me A Sign! is not intended to run stand-alone. Its clock function can but the biggest design decision here is to use an external source (or sources) to push information to the sign rather than have it pull information.
This allows much greater flexibility. For instance, PDX Hackerspace has its own weather station. Rather than use weather conditions from a weather service, we can use the ones that our weather station reports. We still need weather forecasts, though, and we it's convenient to pull the "current conditions" (like rain, snow, volcanos, etc) from an external source.
Pushing the data allows us to easily adapt to a mix of local and remote sources and change that as we wish without having to update the signs distributed around the space.
There are also questions about security. While it's difficult to correctly protect secrets and sensitive information like passwords and API keys on capable systems like Linux, it's even more difficult or even impossible to protect them on embedded devices, especially devices that people have physical access to. Not having them there at all also means that you don't have to update certificates, change API keys or keep other credentials up to date on devices where it's inherently more difficult to do so.
This code targets boards with native WiFi and enough RAM for the full application, such as the Adafruit Matrix Portal S3 connected to a 64x32 LED matrix. The older Matrix Portal M4 (SAMD51 + AirLift ESP32 co-processor) is no longer supported — there is not enough RAM to run the sign reliably.
Other native-WiFi matrix setups may work but have not been tested.
Displays built from multiples of the standard 64x32 panel are supported:
chain panels for more width, stack ("tile") them for more height, or use
64-row panels (which need the ADDR E jumper closed and a board that
provides the fifth address line, like the MatrixPortal S3). Configure the
geometry in settings.toml:
MATRIX_WIDTH = 128 # total pixels across (chained panels)
MATRIX_PANEL_HEIGHT = 32 # rows per panel: 32, or 64 (needs ADDR E)
MATRIX_TILE = 2 # rows of panels stacked vertically
MATRIX_SERPENTINE = true # alternate panel rows rotated 180 degrees
MATRIX_BIT_DEPTH = 2 # color depth; higher uses more RAM/CPUContent is laid out on a virtual 64x32 canvas and integer-scaled to fit
the display, centered. Sizes that aren't proportional to 64x32 (say
128x32) scale by the common factor and letterbox the rest. Images pushed
to the image module via MQTT that are larger than 64x32 are shown unscaled at
the display's native resolution.
This project is written in CircuitPython. It depends on several libraries that help manage the LED matrix, WiFi, the buttons, and the RTC modules.
The application ships as a CircuitPython library package named
give_me_a_sign (import GiveMeASign from it). The package loads the
other modules, connects to MQTT, and drives the main display loop.
Fonts, splash images, and weather BMPs live in give_me_a_sign/assets/
(next to the .py files under /lib/give_me_a_sign/ on the device).
Install the library on CIRCUITPY under lib/give_me_a_sign/ using
CircUp
(Adafruit’s recommended tool), for example from a clone of this repo:
circup install -r requirements.txt ./give_me_a_sign --pyCircUp reads requirements.txt for bundle library names and copies the
local give_me_a_sign package; use --upgrade if copies already exist on
the device. Bundle entries use import names (for example
adafruit_minimqtt, adafruit_ntp) as in
the CircuitPython library bundle.
Each release publishes several zip files. Pick the one that matches how you want to install:
| Asset | Contents | Use when |
|---|---|---|
give-me-a-sign-10.x-mpy-*.zip |
Precompiled .mpy under lib/ |
Matrix Portal S3 and other CircuitPython 10.x boards (recommended) |
give-me-a-sign-9.x-mpy-*.zip |
Precompiled .mpy under lib/ |
CircuitPython 9.x boards |
give-me-a-sign-py-*.zip |
Source .py under lib/ |
Debugging or boards where you want readable source on device |
give-me-a-sign-examples-*.zip |
examples/give-me-a-sign/code.py and sample settings |
Example app only (not the library) |
Do not use GitHub’s auto-generated “Source code (zip)” at the bottom of
the release page — that is the git tree (.py source), not the built library
bundle.
Manual install (mpy):
- Download
give-me-a-sign-10.x-mpy-*.zipfrom the latest release. - Unzip and copy
lib/give_me_a_sign/toCIRCUITPY/lib/give_me_a_sign/. - Copy
examples/give-me-a-sign/code.pyfrom the same zip (or fromgive-me-a-sign-examples-*.zip) toCIRCUITPY/code.py. - Install bundle dependencies with circup (they are not inside the release zip):
circup install -r requirements-circuitpython.txt --upgradeInstall with circup (mpy release zip):
unzip give-me-a-sign-10.x-mpy-0.5.2.zip
circup install -r requirements-circuitpython.txt \
./give-me-a-sign-10.x-mpy-0.5.2/lib/give_me_a_sign --upgradeCircup copies the prebuilt .mpy package from the path you give it and pulls
Adafruit bundle libraries from the official bundles. Omit --py when using an
mpy release zip; --py forces source copies.
For local development from a git clone, install the package as .py so code
remains readable on the device:
circup install -r requirements-circuitpython.txt ./give_me_a_sign --py --upgradeOn every push and pull request, Build CI runs circuitpython-build-bundles and
uploads a bundles artifact with the same zip names as GitHub releases.
This repo follows the
CircuitPython Community Bundle
library layout and CI (circuitpython-build-tools). To get into the bundle:
- Land a SemVer-tagged GitHub Release (for example
0.5.2) so release assets include stamped.mpyzips. - Fork
adafruit/CircuitPython_Community_Bundle, add this repo as a submodule underlibraries/helpers/give_me_a_sign(or another fitting category), and updatecircuitpython_community_library_list.md. - Open a PR against the Community Bundle. After merge, daily bundle releases pick up new tags from this repo automatically.
See Adafruit’s guide: Sharing in the Community Bundle.
Install the latest CircuitPython for your board, for example:
https://circuitpython.org/board/adafruit_matrixportal_s3/
#4-40 3/8" screws
- Install CircuitPython on the board and mount
CIRCUITPY. - Install the library from a release (see Install from a GitHub release above) or, for development, from a clone:
circup install -r requirements-circuitpython.txt ./give_me_a_sign --py --upgrade- Copy
examples/code.pyto the root ofCIRCUITPY(or merge into your owncode.py), and addsettings.toml/ environment as needed. - Optionally copy
examples/config.jsonto/config.jsononCIRCUITPYto customize which modules rotate and for how long.
Give Me A Sign uses a module registry. Each module:
- Declares the MQTT
ENDPOINTSit listens to - Owns a private data store (
ModuleStore) - Renders its own screens via
show()/loop() - May declare Home Assistant entities via
ha_entities() - May publish complications (fractional screen layouts) for composed screens
Built-in modules: clock, weather, aqi, uv, pollen, greet, message,
image, tones, and store-only endpoints (debug, lunar).
Copy a package named gmas_* into CIRCUITPY/lib/ (for example
lib/gmas_example/ from examples/gmas_example/). The package must export:
MODULES = [YourModuleClass]Each class subclasses SignModule from give_me_a_sign.module. You can also
list extra import names in /config.json under "modules".
If /config.json is missing, the sign shows the default rotation:
clock (20s) → weather (10s) → aqi (10s) → uv (10s) → pollen (10s).
Example:
{
"modules": ["gmas_stocks"],
"rotation": [
{"module": "clock", "duration": 20},
{"screen": {
"name": "combo",
"duration": 10,
"complications": [
{"ref": "clock.mini", "x": 32, "y": 0},
{"ref": "aqi.eighth", "x": 0, "y": 0}
]
}}
]
}Complication refs use the form module_name.complication_name. Slot sizes on the
64×32 canvas: full (64×32), half-wide (64×16), half-tall (32×32), quarter
(32×16), eighth (32×8).
See examples/config.json for a full example.
Data is pushed to the sign over MQTT, not HTTP. Each sign subscribes to
topics under MQTT_TOPIC_PREFIX (default givemeasign):
{prefix}/all/module/{endpoint}— broadcast to every sign{prefix}/sign/{mac}/module/{endpoint}— per-device (used by Home Assistant)
Supported {endpoint} values include weather, message, greet, aqi, uv,
pollen, forecast, lunar, tones, image, timezone, solar, trimet,
and debug. Payloads are JSON objects (plain text is accepted for message and
greet).
Per-device command topics (under {prefix}/sign/{mac}/):
| Topic | Payload | Effect |
|---|---|---|
reboot |
any | MCU reset |
display/set |
ON / OFF |
Blank or show the matrix |
time/set |
ISO 8601 UTC, epoch, or {"epoch": …} |
Set the device RTC |
data/publish |
any | Publish all module data stores to data/state |
Home Assistant autodiscovery is built in when MQTT is configured. See
give_me_a_sign/home_assistant.py for entity definitions.
Store the image as in indexed BMP format. Follow the code in nyan.py to display it. Can also load some GIFs and PNGs.
magick original.bmp -colors power-of-two output.bmp
Adafruit has a good discussion of how to deal with indexed BMPs.
adafruit_bitmap_font.py
https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font
Configure MQTT in settings.toml and set SIGN_NAME if you want a friendly
device name in Home Assistant. The sign publishes MQTT autodiscovery configs
for text entities, a display switch, a device-time datetime, reboot and
publish-data buttons, and diagnostic sensors.
Example — publish a message via MQTT (mosquitto_pub):
mosquitto_pub -h broker -t 'givemeasign/all/module/message' \
-m '{"text": "Hello!", "color": 16711680}'
Set the clock (ISO 8601 UTC, as Home Assistant's datetime entity sends):
mosquitto_pub -h broker -t 'givemeasign/sign/aa_bb_cc_dd_ee_ff/time/set' \
-m '2026-07-10T12:00:00+00:00'
Dump the in-memory Data store (also available as the Publish Data Store button in Home Assistant):
mosquitto_pub -h broker -t 'givemeasign/sign/aa_bb_cc_dd_ee_ff/data/publish' \
-m publish
# then: mosquitto_sub -h broker -t 'givemeasign/sign/aa_bb_cc_dd_ee_ff/data/state' -C 1



