Skip to content

About

LED matrix info sign coded in Circuit Python, intended for maker and hackerspaces.

Resources

Code of conduct

Stars

4 stars

Watchers

2 watching

Forks

Repository files navigation

Give Me A Sign!

Build CI Code style: black Documentation Status License: MIT CircuitPython PyPI

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.

clock weather Air Quality Index UV Index

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.

Design Philosophy

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:

  1. Is the device intended to be stand-alone, with no additional support? If so then obviously the complexity has to be in the device.
  2. 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.

Hardware

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.

Larger displays

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/CPU

Content 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.

Software

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 --py

CircUp 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.

Install from a GitHub release

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):

  1. Download give-me-a-sign-10.x-mpy-*.zip from the latest release.
  2. Unzip and copy lib/give_me_a_sign/ to CIRCUITPY/lib/give_me_a_sign/.
  3. Copy examples/give-me-a-sign/code.py from the same zip (or from give-me-a-sign-examples-*.zip) to CIRCUITPY/code.py.
  4. Install bundle dependencies with circup (they are not inside the release zip):
circup install -r requirements-circuitpython.txt --upgrade

Install 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 --upgrade

Circup 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.

Development vs release installs

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 --upgrade

On every push and pull request, Build CI runs circuitpython-build-bundles and uploads a bundles artifact with the same zip names as GitHub releases.

CircuitPython Community Bundle

This repo follows the CircuitPython Community Bundle library layout and CI (circuitpython-build-tools). To get into the bundle:

  1. Land a SemVer-tagged GitHub Release (for example 0.5.2) so release assets include stamped .mpy zips.
  2. Fork adafruit/CircuitPython_Community_Bundle, add this repo as a submodule under libraries/helpers/give_me_a_sign (or another fitting category), and update circuitpython_community_library_list.md.
  3. 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.

Preparing The Sign

Update CircuitPython

Install the latest CircuitPython for your board, for example:

https://circuitpython.org/board/adafruit_matrixportal_s3/

Mounting

#4-40 3/8" screws

Installation

  1. Install CircuitPython on the board and mount CIRCUITPY.
  2. 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
  1. Copy examples/code.py to the root of CIRCUITPY (or merge into your own code.py), and add settings.toml / environment as needed.
  2. Optionally copy examples/config.json to /config.json on CIRCUITPY to customize which modules rotate and for how long.

Modules

Give Me A Sign uses a module registry. Each module:

  • Declares the MQTT ENDPOINTS it 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).

External modules

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".

Configuration (/config.json)

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.

API

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.

Displaying Images

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.

Working with fonts

adafruit_bitmap_font.py

https://github.com/adafruit/Adafruit_CircuitPython_Bitmap_Font

Weather Icons

Home Assistant

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

Timezones

About

LED matrix info sign coded in Circuit Python, intended for maker and hackerspaces.

Resources

Code of conduct

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages