Skip to content

Badge hardware overview

LEDs

You can use the board's LEDs with the tildagonos package:

Example

This example app lights up the LEDs closest to a button when a button is pressed.

import app

from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES
from tildagonos import tildagonos
from system.eventbus import eventbus
from system.patterndisplay.events import PatternDisable


class LEDExampleApp(app.App):
    def __init__(self):
        self.button_states = Buttons(self)

        # This disables the patterndisplay system module, which does the
        # default colour spinny thing
        eventbus.emit(PatternDisable())

    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["RIGHT"]):
            tildagonos.leds[2] = (255, 0, 0)
            tildagonos.leds[3] = (255, 0, 0)
        elif self.button_states.get(BUTTON_TYPES["LEFT"]):
            tildagonos.leds[8] = (0, 255, 0)
            tildagonos.leds[9] = (0, 255, 0)
        elif self.button_states.get(BUTTON_TYPES["UP"]):
            tildagonos.leds[12] = (0, 0, 255)
            tildagonos.leds[1] = (0, 0, 255)
        elif self.button_states.get(BUTTON_TYPES["DOWN"]):
            tildagonos.leds[6] = (255, 255, 0)
            tildagonos.leds[7] = (255, 255, 0)
        elif self.button_states.get(BUTTON_TYPES["CANCEL"]):
            tildagonos.leds[10] = (0, 255, 255)
            tildagonos.leds[11] = (0, 255, 255)
        elif self.button_states.get(BUTTON_TYPES["CONFIRM"]):
            tildagonos.leds[4] = (255, 0, 255)
            tildagonos.leds[5] = (255, 0, 255)
        else:
            for i in range(0, 12):
                tildagonos.leds[i+1] = (0, 0, 0)

        tildagonos.leds.write()

    def draw(self, ctx):
        clear_background(ctx)


__app_export__ = LEDExampleApp

You can see a more comprehensive example in the intro_app.py.

Usage

To use the LEDs:

  1. Import the tildagonos package:
from tildagonos import tildagonos
  1. Enable the LEDs (this step is generally optional, but needed if running outside of an app or from repl):
tildagonos.set_led_power(True)
  1. Set the LEDs by assigning a colour tuple to one of the 12 LEDs:
tildagonos.leds[2] = (255, 0, 0)
  1. Write the updated values to the LEDs
tildagonos.leds.write()

Buttons

You can use the board's Buttons with the events.input package:

Example

This example app lights up the LEDs closest to a button when a button is pressed.

import app

from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES
from tildagonos import tildagonos


class LEDExampleApp(app.App):
    def __init__(self):
        self.button_states = Buttons(self)

    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["RIGHT"]):
            tildagonos.leds[2] = (255, 0, 0)
            tildagonos.leds[3] = (255, 0, 0)
        elif self.button_states.get(BUTTON_TYPES["LEFT"]):
            tildagonos.leds[8] = (0, 255, 0)
            tildagonos.leds[9] = (0, 255, 0)
        elif self.button_states.get(BUTTON_TYPES["UP"]):
            tildagonos.leds[12] = (0, 0, 255)
            tildagonos.leds[1] = (0, 0, 255)
        elif self.button_states.get(BUTTON_TYPES["DOWN"]):
            tildagonos.leds[6] = (255, 255, 0)
            tildagonos.leds[7] = (255, 255, 0)
        elif self.button_states.get(BUTTON_TYPES["CANCEL"]):
            tildagonos.leds[10] = (0, 255, 255)
            tildagonos.leds[11] = (0, 255, 255)
        elif self.button_states.get(BUTTON_TYPES["CONFIRM"]):
            tildagonos.leds[4] = (255, 0, 255)
            tildagonos.leds[5] = (255, 0, 255)
        else:
            for i in range(0, 12):
                tildagonos.leds[i+1] = (0, 0, 0)

        tildagonos.leds.write()

    def draw(self, ctx):
        clear_background(ctx)


__app_export__ = LEDExampleApp

Usage

To use the buttons:

  1. Import the events.input package:
from events.input import Buttons, BUTTON_TYPES
  1. Initialize a variable to hold the button_states in the __init__ method of your app:
def __init__(self):
    self.button_states = Buttons(self)
  1. Check for button_state. You can access the different BUTTON_TYPES with the names "UP", "RIGHT", "CONFIRM", "DOWN", "LEFT", "CANCEL":
 if self.button_states.get(BUTTON_TYPES["RIGHT"]):
     # do something
  1. The button_state will continue returning true while the button is pressed. If you want to only do something once you can clear the button_state once the event has fired once:
 if self.button_states.get(BUTTON_TYPES["RIGHT"]):
     self.button_states.clear()
     # do something

Custom usage

You can also use the ButtonDownEvent and the ButtonUpEvent directly with an event handler that you register on the eventbus.

  1. Import the events.input package:

    from events.input import \
        Button, BUTTON_TYPES, ButtonDownEvent, ButtonUpEvent
    from system.eventbus import eventbus
    
  2. Add a method to handle the event:

    def _handle_buttondown(self, event: ButtonDownEvent):
        if BUTTON_TYPES["CANCEL"] in event.button:
            self._cleanup()
            # perform other actions as needed
    
        if BUTTON_TYPES["CONFIRM"] in event.button:
            self._cleanup()
            # perform other actions as needed
    
  3. Add an event handler in the __init__ method of your app with the event (ButtonDownEvent or ButtonUpEvent) and a function that should be called when the event happens. Depending on whether the event handler is a synchronous or asynchronous method call on() or on_async:

    def __init__(self):
        eventbus.on(ButtonDownEvent, self._handle_buttondown, self.app)
        # eventbus.on_async(ButtonDownEvent, self._handle_buttondown, self.app)
    
  4. Remove the event handler when the app is minimised or closed:

    def _cleanup(self):
        eventbus.remove(ButtonDownEvent, self._handle_buttondown, self.app)
    

    Warning

    Make sure you remove the event handler when the app is minimised or closed!

You can see a more comprehensive example in dialog.py.

Pins

Each hexpansion has:

  • 5 low speed (LS) external GPIO (eGPIO) pins which you can use with the tildagon.Pin
  • 4 high speed (HS) GPIO pins which you can use with the machine.Pin library (12, 13, 18, 19)
  • 6 GND pins (1, 10, 11, 14, 17, 20)
  • 1 pin that detects insertion (6)
  • 2 3.3V Power pins (15, 16)
  • 1 SDA pin (Data) (4)
  • 1 SCL pin (Clock) (5)

Warning

eGPIO does not work correctly in version 1.6.0.

Example

Select a hexpansion port, then press the UP button to toggle the eGPIO value ls_1 or the DOWN button to toggle the GPIO value hs_1. You can see how to access an toggle the Pins in the update methods:

import app

from system.hexpansion.config import HexpansionConfig
from app_components import clear_background, Menu
from app_components.tokens import colors
from events.input import Buttons, BUTTON_TYPES
from math import pi

menu_items = ["1", "2", "3", "4", "5", "6"]


class ExampleApp(app.App):
    def __init__(self):
        self.menu = Menu(self, menu_items, select_handler=self.select_handler,
                         back_handler=self.back_handler)
        self.hexpansion_config = None
        self.button_states = Buttons(self)
        self.pins = None

    def select_handler(self, item, idx):
        self.hexpansion_config = HexpansionConfig(idx+1)

    def back_handler(self):
        if self.hexpansion_config:
            self.hexpansion_config = None
            self.pins = None
        else:
            self.minimise()

    def update(self, delta):
        if self.hexpansion_config is None:
            self.menu.update(delta)

        if self.hexpansion_config and not self.pins:
            self.pins = {}
            # eGPIO pins
            self.pins["ls_1"] = self.hexpansion_config.ls_pin[0]
            self.pins["ls_1"].init(self.pins["ls_1"].OUT)
            # GPIO pins
            self.pins["hs_1"] = self.hexpansion_config.pin[0]
            # All HS pins start in low mode. Initialize them as follows:
            self.pins["hs_1"].init(self.pins["hs_1"].OUT)

        if self.pins and self.button_states.get(BUTTON_TYPES["UP"]):
            self.button_states.clear()
            # Toggle pin ls_1
            if self.pins["ls_1"].value():
                self.pins["ls_1"].off()
            else:
                self.pins["ls_1"].on()
        if self.pins and self.button_states.get(BUTTON_TYPES["DOWN"]):
            self.button_states.clear()
            # Toggle pin hs_1
            if self.pins["hs_1"].value():
                self.pins["hs_1"].off()
            else:
                self.pins["hs_1"].on()

    def draw(self, ctx):
        clear_background(ctx)

        if self.hexpansion_config is None:
            self.menu.draw(ctx)

            # Drawing a shape as a port indicator.
            ctx.save()
            ctx.font_size = 22
            ctx.rgb(*colors["dark_green"]).rectangle(
                -120, -120, 240, 100).fill()
            ctx.rgb(*colors["dark_green"]).rectangle(
                -120, 20, 240, 100).fill()
            rotation_angle = (self.menu.position - 1) * pi/3
            ctx.rgb(*colors["mid_green"]).rotate(rotation_angle).rectangle(
                80, -120, 40, 240).fill()
            prompt_message = "Select hexpansion port:"
            ctx.rgb(1, 1, 1).rotate(-rotation_angle).move_to(
                0, -45).text(prompt_message)
            ctx.restore()

        elif self.pins:
            ctx.save()
            ctx.font_size = 24
            msg = "Hexpansion in port " + str(self.hexpansion_config.port)
            msg_width = ctx.text_width(msg)
            ctx.rgb(1, 1, 1).move_to(-msg_width/2, 0).text(msg)

            # draw pin values
            pin_ls_1 = "LS_1: " + str(self.pins["ls_1"].value())
            msg_width = ctx.text_width(pin_ls_1)
            ctx.rgb(1, 1, 1).move_to(-msg_width/2, -90).text(pin_ls_1)

            pin_hs_1 = "HS_1: " + str(self.pins["hs_1"].value())
            msg_width = ctx.text_width(pin_hs_1)
            ctx.rgb(1, 1, 1).move_to(-msg_width/2, 90).text(pin_hs_1)

            ctx.restore()


__app_export__ = ExampleApp

A more elaborate example is this breadboard tester app which allows you to toggle all GPIO and eGPIO pins.

Methods

GPIO pins support the standard machine.Pin methods. These are referred to as high speed pins as they are connected directly to the ESP32.

eGPIO pins are referred to as low speed pins as they are connected to a port expander by I2C and support the following methods:

Method Description Arguments Returns
on() Drive the pin high. None None
off() Drive the pin low. None None
value() If provided with a value, sets the Pin value. If called without value, gets the Pin value. None value: The pin value. If called without a value.
duty() When in PWM mode, sets the duty cycle of the open collector output duty: The duty cycle, 0-255 None

Usage

To use the Pins:

  1. Access the pins from the HexpansionConfig object and for GPIO pins, initialize the pins:
# eGPIO pins
self.pins["ls_1"] = self.hexpansion_config.ls_pin[0]
self.pins["ls_2"] = self.hexpansion_config.ls_pin[1]

# GPIO pins
self.pins["hs_1"] = self.hexpansion_config.pin[0]

# All pins start in inputs mode. Initialize them as follows:
self.pins["hs_1"].init(self.pins["hs_1"].OUT)
self.pins["ls_1"].init(self.pins["ls_1"].OUT)

# Only LS pins support the PWM function directly.
self.pins["ls_2"].init(self.pins["ls_2"].PWM)
  1. Call one of the methods, for example off(), on() or for LS pins duty().
self.pins["hs_1"].off()
self.pins["ls_1"].on()
self.pins["ls_2"].duty()

IMU

The IMU device is a highly integrated, low power inertial measurement unit (IMU) that combines precise acceleration and angular rate (gyroscopic) measurement. The triple axis device has been configured to measure 2g and 2 degree per second ranges. It also has a step count function intended for wrist mounted applications.

More information about the sensor

For more information see Inertial Measurement Unit BMI270 | Bosch Sensortec (bosch-sensortec.com).

import app
import imu

from events.input import Buttons, BUTTON_TYPES


class ExampleApp(app.App):
    def __init__(self):
        self.button_states = Buttons(self)
        self.acc_read = None

    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["CANCEL"]):
            self.button_states.clear()
            self.minimise()
        else:
            self.acc_read = imu.acc_read()

    def draw(self, ctx):
        ctx.save()
        ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
        if self.acc_read:
            ctx.rgb(1, 0, 0).move_to(-80, -40).text(
                "accel x,y,z:\n{},\n{},\n{}".format(
                    self.acc_read[0], self.acc_read[1], self.acc_read[2]))
        else:
            ctx.rgb(1, 0, 0).move_to(-80, 0).text("no readings yet")
        ctx.restore()


__app_export__ = ExampleApp

The following example app measures steps as you walk around with your badge:

import app
import imu

from events.input import Buttons, BUTTON_TYPES


class ExampleApp(app.App):
    def __init__(self):
        self.button_states = Buttons(self)
        self.acc_read = None
        self.steps_read = None

    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["CANCEL"]):
            self.button_states.clear()
            self.minimise()
        else:
            self.steps_read = imu.step_counter_read()

    def draw(self, ctx):
        ctx.save()
        ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
        if self.steps_read:
            ctx.rgb(1, 0, 0).move_to(-80, -40).text(
                "steps:\n{}\n".format(self.steps_read))
        else:
            ctx.rgb(1, 0,  0).move_to(-80, 0).text("no readings yet")
        ctx.restore()


__app_export__ = ExampleApp

Methods

In order to support alternative imu devices a base set of functionality is provided along with i2c access to the device for apps to add functionality. An identify function is provided to determine which device is on the badge. Only the prototype badges have a different imu, but this may change in the future.

Method Description Arguments Returns
acc_read() Get the accelerometer data. None (x,y,z): The accelerometer data as a tuple of floats (m/s^2).
gyro_read() Get the gyro data. None (x,y,z): The gyro data as a tuple of floats (d/s).
step_counter_read() Get the step count None count: The step count
temperature_read() Get the temperature None temerature: Temperature (°).
id() Get the device id None id: string id of device.
readfrom() Read from a device register (register address, length): address to start read from, length of read data: bytes of the data or -ve error code.
writeto() Write data to a device register (register address, bytes): address to start write to, data. error: or None if ok.

Usage

To use the imu package:

  1. Import the power package:
import power
  1. Call one of the methods, for example imu.acc_read().
imu.acc_read()

Power

The power package allows you to perform multiple battery related functions, like powering off the badge or getting the battery level.

Example

This example shows you how to power off the badge when the UP button is pressed. Note that your badge will only power off if it is not connected to power through USB. It technically only disconnects the battery.

import power
import app

from events.input import Buttons, BUTTON_TYPES


class ExampleApp(app.App):
    def __init__(self):
        self.button_states = Buttons(self)

    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["CANCEL"]):
            self.minimise()
        if self.button_states.get(BUTTON_TYPES["UP"]):
            power.Off()

    def draw(self, ctx):
        ctx.save()
        ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
        ctx.rgb(1, 0, 0).move_to(-80, 0).text("Press up to\npower off")
        ctx.restore()


__app_export__ = ExampleApp

Usage

To use the power package:

  1. Import the power package:
import power
  1. Call one of the methods, for example power.Off().
power.Off()

Methods

Method Description Arguments Returns
Off() Turn off the battery. When the usb is disconnected the badge will turn off. None None
BatteryChargeState() Status of the Battery charing cycle. None status (string): "Not Charging", "Pre-Charging", "Fast Charging", "Terminated".
BatteryLevel() Return the battery charge level. None. level (float): Battery charge level as a float representing the charge percentage.
Enable5V() Enable the usb out 5V supply. enable (Boolean): whether to enable or disable the 5V supply. None.
Fault() Get the PMIC fault status. None. - fault: The battery fault. Battery: Normal, Over Voltage; Boost: Normal, Overloaded or low battery; Charge: Normal, Input Fault, Safety Timer expired
SupplyCapabilities() Read the capabilities of the power supply. None. capabilities (List): List of tuples containing supply type, voltage (V) and current (mA).
Icharge() Get the battery charge current None. current (float): The charge current in A.
Vbat() Get the battery voltage. None. voltage (float): The battery voltage in V.
Vin() Get the input voltage. None. voltage (float): The input voltage in V.
Vsys() (float) Get the system voltage. None. voltage (float): Get the system voltage in V.

Events

If you want to use the EventBus, you can use it with the following events:

  • RequestChargeEvent
  • RequestBatFaultEvent
  • RequestBoostFaultEvent
  • RequestChargeFaultEvent
  • RequestTimeoutFaultEvent
  • RequestHostAttachEvent
  • RequestHostDetachEvent
  • RequestDeviceAttachEvent
  • RequestDeviceDetachEvent
  • RequestLanyardAttachEvent
  • RequestLanyardDetachEvent

To use events with the EventBus, import the following package:

from system.power import events

You can also use the following hexpansion-related events

  • HexpansionRemovalEvent
  • HexpansionInsertionEvent
  • HexpansionFormattedEvent
  • HexpansionMountedEvent

To use these events with the EventBus, import the following package:

from system.hexpansion.events import \
    HexpansionRemovalEvent, HexpansionInsertionEvent

Then attach a function to the event as following:

class ChargeApp(app.App):
    def __init__(self):
        eventbus.on(events.RequestChargeEvent, self._handle_charge_event, self)

    def _handle_charge_event(self, event: RequestChargeEvent):
        # do something
        return None

Power Delivery (PD)

The PD module allows sending and receiving PD messages on the USB in and out ports. The message header is handled entirely by the badge for Vendor messages and will automatically fill the fields of the header relating to the physical layer. This leaves the user to only fill the message type and number of objects for the Prime messages used to communicate with the cables plugs. The fusb302b part that the badge uses supports USB PD 2.0, Version 1.1 and uses the number of objects field of the header to determine message length, preventing us from sending extended messages. Each message that it sends must have a 2 byte header followed by 0-7 4 byte data objects. To assist with this the badge will pad the messages sent to the next 4 byte boundary, so messages received from another badge may have additional 0s appended to the end.

Example

import settings
from app import App
from app_components import Menu, clear_background
from system.patterndisplay.events import PatternReload
from system.eventbus import eventbus
from system.power import events
from pd import Host, Device

main_menu_items = ["rainbow", "cylon", "flash", "off"]


class LedSyncApp(App):
    def __init__(self):
        self.usb_in = Device()
        self.usb_out = Host()
        self.menu = Menu(
            self,
            main_menu_items,
            select_handler=self.select_handler,
            back_handler=self.back_handler,
        )
        eventbus.on(events.VendorMsgDevRxEvent, self._handle_dev_msg, self)
        eventbus.on(events.VendorMsgHostRxEvent, self._handle_host_msg, self)
        eventbus.on(
            events.BadgeAsHostAttachEvent, self._handle_host_detect, self
        )
        self.state = main_menu_items[0]

    def _handle_dev_msg(self, event: VendorMsgDevRxEvent):
        msg = self.usb_in.get_vendor_msg()
        if msg is not None:
            settings.set("pattern", main_menu_items[msg[0]])
            eventbus.emit(PatternReload())
            self.state = main_menu_items[msg[0]]
            if self.usb_out.badge_connected():
                self.usb_out.send_vendor_msg(msg)

    def _handle_host_msg(self, event: VendorMsgHostRxEvent):
        msg = self.usb_out.get_vendor_msg()
        if msg is not None:
            settings.set("pattern", main_menu_items[msg[0]])
            eventbus.emit(PatternReload())
            self.state = main_menu_items[msg[0]]
            if self.usb_in.badge_connected():
                self.usb_in.send_vendor_msg(msg)

    def _handle_host_detect(self, event: BadgeAsHostAttachEvent):
        idx = main_menu_items.index(self.state)
        self.usb_out.send_vendor_msg(bytearray([idx]))

    def select_handler(self, item, item_idx):
        self.state = main_menu_items[item_idx]
        settings.set("pattern", main_menu_items[item_idx])
        eventbus.emit(PatternReload())
        if self.usb_in.badge_connected():
            self.usb_in.send_vendor_msg(bytearray([item_idx]))
        if self.usb_out.badge_connected():
            self.usb_out.send_vendor_msg(bytearray([item_idx]))

    def back_handler(self):
        self.minimise()

    def draw(self, ctx):
        clear_background(ctx)
        self.menu.draw(ctx)

    def update(self, delta):
        self.menu.update(delta)


__app_export__ = LedSyncApp

Usage

To use either of the USB ports for PD communications, import the Host or Device classes. To assist with the creation of a message import the helper.

from pd import Host, Device

Then create the objects:

usb_in = Device()
usb_out = Host()

Check the connection state and send a message:

if usb_out.pd_enabled():
    usb_out.send_vendor_msg(data, length)

Methods

Both the Host and Device ports support the following methods.

Method Description Arguments Returns
connected Returns whether the badge is connected as a device. None. state(bool): Connection state
pd_enabled Returns whether PD is enabled on this port None state(bool): PD enabled
badge_connected Returns whether the badge is connected to another badge as a device None. state(bool): Connection state
get_vendor_msg Gets vendor message from the in port None. message(tuple): vendor header(int) message header, length(int)message length, buffer(list) list of data
send_vendor_msg Sends a vendor message on the in port data(bytearray): data, contents (VDOs) left to the user and the helper. the data will be padded to the 4 byte VDO boundary, max 28 bytes None.

The Host port also supports sending messages to the cable plug at either end, known as prime and double prime messages:

Method Description Arguments Returns
send_prime_msg Sends a prime message data(bytearray): data, must include 2 byte header None
send_dbl_prime_msg Sends a double prime message data(bytearray): data, must include 2 byte header None
get_prime_msg Gets a received prime message None data(bytearray): data, includes 2 byte header
get_dbl_prime_msg Gets a received double prime message None data(bytearray): data, includes 2 byte header

Events

Method Description
BadgeAsDeviceAttachEvent The badge has detected another badge and connected as a device
BadgeAsDeviceDetachEvent The badge has disconnected
BadgeAsHostAttachEvent The badge has detected another badge and connected as a host
BadgeAsHostDetachEvent The badge has disconnected
VendorMsgDevRxEvent The badge has received a message on the USB in port
VendorMsgHostRxEvent The badge has received a message on the USB out port
PrimeMsgHostRxEvent The badge has received a prime message on the USB out port
DblPrimeMsgHostRxEvent The badge has received a double prime message on the USB out port

PD helper API

You can use the PD helper API to create structured and unstructured vendor headers and send the badge ID message. The badge ID message is sent automatically on connection to the usb out port. If this is detected by another badge on the usb in port it will respond with the same message and set the badge connected state to true. The vendor header is only required when not communicating with another badge.

Example

from system.power.pd_helper import pdHelper

pdh = pdHelper()
pdh.device_send_badge_id()

Usage

You can use the helper as follows to create the headers required for a discover identity command:

from system.power.pd_helper import pdHelper, vdmCmd

pdh = pdHelper()
header = pdh.header(dataType.VENDOR_DEFINED, 1)
vendor_header = pdh.vdm_structured_header(
    vdmCmd.DISCOVER_IDENTITY, 0xFF00, 0, 0
)
usb_out = Host()
usb_out.send_prime_msg(
    header.to_bytes(2, "little") + vendor_header.to_bytes(4, "little")
)

Methods

Method Description Arguments Returns
device_send_badge_id Sends the id used to detect another badge on the USB in port None None
host_disc_id_dbl_prime Sends the discover identity command to a cables plug None None
host_disc_id_prime Sends the discover identity command to a cables plug None None
host_send_badge_id Sends the id used to detect another badge on the USB out port None None
pd_header Create a message header with space for the badge to fill in physical layer info message_type(int): see dataType and cmdType below, no_objects(int): number of 4 byte objects, optional, default 0 header(int): 16 bit header
vdm_structured_header Creates a structured vendor header command(int): see vdmCmd below, SVID(int): Standard or Vendor ID, optional, default 0xFF00, obj_pos(int):For the Enter Mode, Exit Mode and Attention Commands, optional, default 0, version(int): Structured VDM Version, optional, default 0, vendor_header(int): 32 bit vendor header
vdm_unstructured_header Creates an unstructured vendor header SVID(int): optional, default 0xFF00, data(int): 15 bits of vendor defined data vendor_header(int): 32 bit vendor header
vdm_header_extract extract the fields of a vendor header vendor_header(int): 32 bit vendor header vendor_header(dict): dictionary containing each field of the header

Constants

The following constants are available for use in the two headers.

Class Constant
vdmCmd DISCOVER_IDENTITY
vdmCmd DISCOVER_SVIDS
vdmCmd DISCOVER_MODES
vdmCmd ENTER_MODE
vdmCmd EXIT_MODE
vdmCmd ATTENTION
dataType BIST
dataType VENDOR_DEFINED
cmdType ACCEPT
cmdType REJECT
cmdType SOFT_RESET

I2C

The badge supports the I2C communication protocol on a bus for each hexpansion slot. Hexpansion slots are numbered 1-6, with 1 being the top right slot and the numbers increasing clockwise. To create an I2C bus for a given slot, use:

from machine import I2C

bus = I2C(1)

If your app is loaded from EEPROM on an hexpansion, you can get the slot in the hexpansion config object that is passed to your app to select the correct slot.

Untested

This code is currently untested and may not work. If you work on this, please let us know what you find or what you have to fix at issue.

import app

from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES

class ExampleApp(app.App):
    def __init__(self, config=None):
        self.button_states = Buttons(self)
        self.hexpansion_config = config

    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["CANCEL"]):
            self.minimise()

        if self.hexpansion_config:
            print(self.hexpansion_config.i2c)

    def draw(self, ctx):
        ctx.save()
        clear_background(ctx)
        ctx.rgb(0, 1, 0).move_to(-90, -40).text("Hello from your\nhexpansion!")
        ctx.restore()

        return None

__app_export__ = ExampleApp

If it's an app loaded from the badge, you'll need to check each port:

import app

from machine import I2C

from app_components import clear_background
from events.input import Buttons, BUTTON_TYPES
from system.eventbus import eventbus
from system.hexpansion.events import HexpansionRemovalEvent, HexpansionInsertionEvent
from system.hexpansion.config import HexpansionConfig
from system.hexpansion.util import read_hexpansion_header, detect_eeprom_addr

class ExampleApp(app.App):
    def __init__(self):
        self.button_states = Buttons(self)
        self.text = "No hexpansion found."
        self.hexpansion_config = self.scan_for_hexpansion()

        eventbus.on(HexpansionInsertionEvent, self.handle_hexpansion_insertion, self)
        eventbus.on(HexpansionRemovalEvent, self.handle_hexpansion_removal, self)

    def handle_hexpansion_insertion(self, event):
        self.hexpansion_config = self.scan_for_hexpansion()


    def handle_hexpansion_removal(self, event):
        self.hexpansion_config = self.scan_for_hexpansion()


    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["CANCEL"]):
            self.minimise()

        if self.hexpansion_config:
            print(self.hexpansion_config.i2c)

    def draw(self, ctx):
        ctx.save()
        clear_background(ctx)
        ctx.rgb(0, 1, 0).move_to(-90, -40).text(self.text)
        ctx.restore()

    def scan_for_hexpansion(self):
        for port in range(1, 7):
            print(f"Searching for hexpansion on port: {port}")
            i2c = I2C(port)
            addr, addr_len = detect_eeprom_addr(i2c)

            if addr is None:
                continue
            else:
                print("Found EEPROM at addr " + hex(addr))

            header = read_hexpansion_header(i2c, addr, addr_len=addr_len)
            if header is None:
                continue
            else:
                print("Read header: " + str(header))
            self.text = "Hexp. found.\nvid: {}\npid: {}\nat port: {}".format(hex(header.vid), hex(header.pid), port)
            return HexpansionConfig(port)

        self.color = (1, 0, 0)
        self.text = "No hexpansion found."

        return None

    __app_export__ = ExampleApp

You can then use the standard MicroPython I2C API to communicate with devices on the bus.

Example usage from the MicroPython I2C docs:

from machine import I2C

i2c = I2C(freq=400000)

# scan for peripherals, returning a list of 7-bit addresses
i2c.scan()

# write 3 bytes to peripheral with 7-bit address 42
i2c.writeto(42, b'123')
# read 4 bytes from peripheral with 7-bit address 42
i2c.readfrom(42, 4)
# read 3 bytes from memory of peripheral 42, starting at memory-address 8 in
# the peripheral
i2c.readfrom_mem(42, 8, 3)
# write 1 byte to memory of peripheral 42 starting at address 2 in the
# peripheral
i2c.writeto_mem(42, 2, b'\x10')

For more information, see MicroPython I2C docs.

Version

You can use the ota package to obtain the installed TildagonOS version:

Usage

  1. Import the ota package:
import ota
  1. Use the get_version() method:
ota.get_version()