Kyanit Core

The kyanit package is referred to as Kyanit Core.

Kyanit Core is a user code supervisor built for MicroPython. Development is currently focused on the ESP8266 port of MicroPython. Together with an ESP8266-based board, Kyanit Core is indended to be a foundation for home automation systems. A board with Kyanit Core installed is referred to simply as Kyanit or Kyanit board. An official board design for Kyanit can be found at https://kyanit.eu/kyanit-board.

For information about MicroPython head to https://micropython.org/

Being a supervisor, Kyanit Core is responsible for starting and stopping user code, as well as handling any uncaught exceptions in the user code.

Kyanit Core provides an HTTP API through which the user code can be changed and controlled. Additionally it provides an addressing mechanism called the "Color ID," which maps to the IP address of the board. The Color ID consists of 3 colors, each either red, gree, blue, cyan, magenta, yellow or white, and they are displayed using 3 WS2812B (Neopixel) LEDs.

The official board design already has these LEDs built in, as well as a button, which when pressed, will make the Color ID be displayed on the LEDs.

The Kyanit Ecosystem

The core Kyanit package, flashed into an ESP8266.

A stylish, triangle-shaped ESP8266-based board built around an ESP-12F module, the official Kyanit board.

Command-line utility for interfacing and interacting with a Kyanit board.

Python API for interfacing and interacting with a Kyanit board from Python code. (Kyanit CTL is an application of this API.)

Getting Started with Kyanit Core

Setting Up

The Kyanit Core package is intended to be compiled into the MicroPython firmware, and flashed into an ESP8266. A board with at least 1 MB of flash is recommended, leaving approximately 380 kB free for user code. The Kyanit Board, or any other board with an ESP-12E/F is a good candidate. If you will not be working with the Kyanit Board, wire 3 WS2812B LEDs to Pin(4), and an active-low button to Pin(14) on your board of choice. Other pin numbers are possible, in that case, some configuration is required, for changing the pin numbers, see controls().

If you're new to the WS2812B LEDs, you can refer to this extensive tutorial on SparkFun: https://learn.sparkfun.com/tutorials/ws2812-breakout-hookup-guide/all

NOTE: It is possible to use the core package without flashing it into the firmware, but substantially less RAM will be available (~5k versus ~20k).

To flash a released version into an ESP8266, see the Flashing a Released version of Kyanit Core section further below. Alternatively you can build the current development version, in that case, see Building Kyanit Core from Source (Linux).

The first time you power up the board, it will set up an AP with an SSID starting with Kyanit and ending with a unique hexadecimal number. When the AP is active, the LEDs will flash blue.

A command-line utility called kyanitctl is provided for managing a Kyanit board. To start interacting with the board, install it from PyPI with:

pip install kyanitctl

Make sure you don't have a 192.168.4.0/24 network currently active in your system, as this is the network address Kyanit will use while setting up. If you have an existing network with this address, disconnect it first.

Connect to Kyanit's SSID, with the password myKyanit. Then start setting up Kyanit to connect to your home wireless LAN, with DHCP enabled by executing:

kyanitctl -setup

Follow the instructions and enter your home network SSID and password. You can review your settings before it gets uploaded to the board.

Alternatively you can set up a static IP configuration by running the setup with:

kyanitctl -setupstatic

Choose an IP address that's outside of the DHCP pool of your router. If you're unsure, use -setup instead.

Kyanit will then reboot and try to connect to your wireless router. If this doesn't work, it will fall back to AP, and start flashing the LEDs again. You can retry the above steps, if this happens.

On successful connection, the LEDs will light up in a blueish color, with a slow "breathing" animation. If you were originally connected to your home network through wireless on your computer, you can now reconnect.

The Color ID

Addressing a Kyanit board can be done in two ways. Either by knowing its IP address, or by means of the Color ID. The Color ID is a sequence of 3 colors, each color being one of red, green, blue, cyan, magenta, yellow or white:

All Color ID Colors

If you know the Color ID of the Kyanit, you don't need to know its IP address. To find out the Color ID, press the button on the Kyanit. This will cause the Color ID to be shown on the LEDs for 10 seconds.

On a Kyanit Board, the Color ID must be read starting from the bottom (where the USB connector is), and going counter-clockwise, as the notches on the enclosure suggest. Here are some Color ID examples:

Color ID Examples

The last octet of the IP address maps to the Color ID under the hood. For this reason only networks with a subnet mask of 255.255.255.0 are supported. Most home and small business routers create such a network, so you should be fine.

In the unlikely scenario where your home wireless network is configured with a netmask different from 255.255.255.0, you'll need to provide the IP address of the Kyanit when running kyanitctl. In that case it's easier if Kyanit is set up with a static IP.

Kyanit CTL will discover the networks available on your computer the first time you run it with a Color ID. If one supported network is present on your system, it will be used by default, otherwise you will be presented with a list of networks, and you'll need to select the network, the Kyanit is connected to. The selection will be saved and used for all further connection attempts. The network selection can be re-run with kyanitctl -reset_network if desired later.

Checking the status of the Kyanit

Try running kyanitctl <Color ID> -status (or -stat for short) to see the current status of the board. Pass in the Color ID of your board to <Color ID> after checking it by pressing the button.

Here's an example output for a Kyanit Board with a Color ID of BCG (Blue-Cyan-Green) on a system where two supported networks are present:

> kyanitctl BCG -stat

=== Network Setup ===

Connecting to Kyanit with the Color ID works on networks with a netmask of 255.255.255.0
(most home wireless networks). Multiple such networks detected. Select the one Kyanit is
connected to:

0: 192.168.137.0   Ethernet
1: 192.168.1.0     Wi-Fi

Select network [0, 1]: 1
Saved. You may re-run this setup with -reset_network.

=== Kyanit BCG (192.168.1.9 through 'Wi-Fi') ===

Retrieving system status...

    Firmware version: 0.1.0
          Free flash: 3514368
         Free memory: 15776
           Run state: CODE.PY MAIN

Having the network saved, further runs will provide a much concise output:

=== Kyanit BCG (192.168.1.9 through 'Wi-Fi') ===

Retrieving system status...

    Firmware version: 0.1.0
          Free flash: 3514368
         Free memory: 18800
           Run state: CODE.PY MAIN

The same as above with IP address instead of the Color ID would be:

kyanitctl -ip 192.168.1.9 -stat

...

Downloading and Uploading Files

List the files currently on the board with the -files option:

> kyanitctl BCG -files

=== Kyanit BCG (192.168.1.9 through 'Wi-Fi') ===

Retrieving file list done.
Files on Kyanit:

wlan.json
code.py

With a fresh install, you should see wlan.json, which includes your wireless credentials and IP settings, and code.py, which is the user code that was uploaded as part of the initial setup. This is the user code entry point. On startup, Kyanit Core will try to import it and call the main function inside. The code currently only controls the LEDs and monitors the button.

Download code.py from the board using kyanitctl with the option -get code.py. (Be aware, that this will overwrite any existing code.py file that you may have in your local directory.)

The code.py initially contains the following:

# This code is imported on startup, then main is called, if it exists. Neither main, nor
# cleanup should block for too long. Use coroutines through
# kyanit.runner.create_task('name', coro) for continuous or longer tasks. Any errors
# (including from coroutines) will be passed to cleanup.
# The @kyanit.controls() decorator adds functionality to the LEDs and button. It can be
# removed if this is not required, to save ~1k of RAM.

# To get started, read more at https://kyanit.eu


import kyanit


@kyanit.controls()
def main():
    # Put startup code here.
    pass


@kyanit.controls()
def cleanup(exception):
    # Put error-handling code here, as well as code that needs to be run when stopped,
    # or before reboot.
    pass

The controls() decorator on the main and cleanup functions should be left there, unless you don't need feedback on run state (discussed in the next section) and the Color ID. This means, that if you remove those, the Kyanit will be unresponsive to button press, and the LEDs will not be controlled automatically.

Uploading a new code.py can be done with -put code.py. Other files can also be uploaded, but keep in mind, that directories are not supported on Kyanit. If you pass a directory to -put, every file from that directory will be uploaded (and it will not recurse to further directories inside).

NOTE: The board will need to be rebooted with -reboot for the new code to become active. This can be done with a single command, for example:

kyanitctl BCG -put code.py -reboot

For a full list of what Kyanit CTL can do, refer to the command-line help with kyanitctl -h.

Run States

There are 5 different run states: CODE.PY MISSING, CODE.PY IMPORTED, CODE.PY MAIN, STOPPED and ERROR.

  • CODE.PY MISSING

There's no code.py file to import and run.

  • CODE.PY IMPORTED

There is a code.py, that was imported, but there's no main function in it to call.

  • CODE.PY MAIN

code.py was imported, and main was called. This is what can be considred a "running" state.

Having the controls() decorator on main will make the LEDs "breathe" in a bluish color, indicating that main was called, and that Kyanit is running the user code.

  • STOPPED

The code was stopped either from within the code itself, or by outside means, ex. with Kyanit CTL's -stop option. The code may be restarted with Kyanit CTL, using the -start option.

Before enterint this state, the runner will call the cleanup function. Having the controls() decorator on cleanup will change the LED colors to a deep orange, and the animation will slow down, indicating that it entered the STOPPED state.

  • ERROR ExceptionName

There was an uncaught exception within code.py. An exception detail, with traceback will also be available through Kyanit CTL's -status option. At this point, the code may be restarted. (Although debugging is probably required.)

Before enterint this state, the runner will call the cleanup function and it will pass it the exception, so handling it is possible in cleanup. Having the controls() decorator on cleanup will change the LED colors to a deep orange, and the animation will be a blinking "attention" animation, indicating that an uncaught error occurred.

Coding on Kyanit (Using Coroutines)

Kyanit Core is built around MicroPython's uasyncio. (CPython's asyncio implementation for MicroPython.) This means Kyanit is all about coroutines. Albeit it's advisable to learn about coroutines before coding on Kyanit, it's not entirely required, if you abide by some basic rules.

A coroutine is just a function defined with async:

async def my_func():
    # This is a coroutine
    pass

Coroutines are not multithreaded, but they can be understood as if they were running alongside each other in parallel.

Every internal functionality of Kyanit is built using coroutines. This means, that your code in code.py must also use coroutines to achieve continuous functionality. Nothing within code.py should block for too long, because this essentially freezes the whole system, preventing Kyanit from running its internal tasks, rendering it unresponsive.

In short, this means no continuous loops (with while True) within any non-async functions, which includes main and cleanup.

main is intended to set up your own code and potentially start coroutines, which will run alongside Kyanit's internal tasks.

Likewise, cleanup is intended to run quickly after some exception, potentially acting on them.

cleanup is also called when code is stopped or rebooted, with StoppedError and RebootError passed respectively. This lets you do stuff on these two events, if desired.

Here's a simple example of a continuosly running task in code.py:

from kyanit import controls, runner


async def my_task():
    while True:
        # do some stuff
        await runner.sleep(1)


@controls()
def main():
    runner.create_task('my_task_name', my_task)


@controls()
def cleanup(exception):
    pass

The above code will immediately start my_task. Note the await statement, which will give CPU time for other coroutines to run. In this case, the uasyncio scheduler will return to my_task after 1 second. You must have an await statement inside your coroutines to yield CPU time to other tasks. You may also await other coroutines that you defined.

You may await a sleep time of 0, which basically means "return to this function as soon as possible." The scheduler will give time to other tasks that are not awaiting a sleep in a round-robin fashion, and will return to the function as soon as possible.

HINT: Everything from uasyncio is available in kyanit.runner (it has everything imported from uasyncio).

About create_task()

The kyanit.runner module is responsible for running the code and keeping tabs on the tasks.

Always create tasks with create_task() from kyanit.runner and not uasyncio.get_event_loop().create_task(), because Kyanit can not catch errors in tasks created on the event loop directly. Uncaught errors will cause the coroutine that's raising them to stop silently, which may result in unexpected behavior.

On the other hand, errors in tasks created on the runner will be handled. In case of an error, all tasks will be stopped in a controlled fashion, and Kyanit will go into ERROR state. (See Run States above.)

The same applies when running is stopped with stop() or by ex. Kyanit CTL, using -stop. Kyanit will stop all tasks created on runner, but it cannot stop tasks directly created on the event loop.

Stop a single task from running with runner.destroy_task('task_name').

The things to keep in mind:

  • If you want a long-running code or continuous loop, implement it in an async function and use await runner.sleep() where pauses can be accepted.
  • Never use time.sleep() for long delays, use await runner.sleep() instead.
  • If timing is critical (ex. for bit-banging), you may use time.sleep(), but keep in mind that this prevents other coroutines to run in the meantime, so keep it short.
  • Always create tasks with create_task() from kyanit.runner.

More on coroutines in MicroPython: https://github.com/peterhinch/micropython-async/blob/master/TUTORIAL.md

Read documentation of kyanit.runner to find out more about tasks and how to control the runner.

Additional sub-modules

Kyanit Core has the following sub-modules available:

Module to monitor a button and catch button events.

A minimal HTTP server module.

Module to control the wireless network interfaces of the ESP8266.

Module to control WS2812B LEDs (NeoPixels) with animations and different color display options.

The code executor and supervisor module.

Color ID helper functions.

All of these modules are available for the user code. Check out their documentation page to see what they can do.

Kyanit Netvar

Kyanit Core has a notion of a "network variable"which is available to read and write through the network connection by Kyanit CTL or Kyanit API.

The Netvar can be used to issue custom commands to your board, such as switching or reading some data from your board (like sensor data).

Check out the Netvar class for details.

Kyanit API

So far it was demonstrated how Kyanit CTL can be used to control a Kyanit board. If you want to control your board from Python code, check out Kyanit API at https://kyanit-project.github.io/kyanit-api/kyanitapi/.

Flashing a Released version of Kyanit Core

Download the latest kyanit-core-<version>.bin from https://github.com/kyanit-project/kyanit/releases/latest , then refer to MicroPython documentation on flashing firmware onto an ESP8266 here: https://docs.micropython.org/en/latest/esp8266/tutorial/intro.html

Building Kyanit Core from Source (Linux)

To find out how to build from source, head to https://github.com/kyanit-project/kyanit#building-from-source-linux

License Notice

Copyright (C) 2020 Zsolt Nagy

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

The above license notice applies to all parts of this package.

Kyanit Core Package Documentation

Sub-modules

kyanit.button

kyanit.colorid

kyanit.httpsrv

kyanit.interfaces

kyanit.neoleds

kyanit.runner

Functions

def controls(kyanit_leds=None, kyanit_button=None, active_colors=((0, 50, 250), (0, 50, 250), (0, 50, 250)), idle_colors=((250, 50, 0), (250, 50, 0), (250, 50, 0)), brightness=1)

Optional decorator for code.main and code.cleanup.

Having this decorator on code.main and code.cleanup gives visual feedback on run state, as well as showing the Color ID on the LEDs, when the button is pressed.

When user code is running, the LEDs will continuously show the active_colors with a "breathing" animation. When code is stopped, the idle_colors will be shown with a slower "breathing" animation.

Default colors may be changed by passing a list of 3 colors to active_colors and idle_colors. Each color must be an RGB tuple.

On code error, the idle_colors will be shown, with an "attention" animation.

kyanit_leds may be instance of NeoLeds to override default LEDs pin, and to provide additional custom functionality to the LEDs.

kyanit_button may be instance of Button to override default button pin. As of now, the button can not be used for any other functionality, if this decorator is used.

The brightness of the LEDs can be adjusted with the brightness parameter, which must be a float between 0 (completely dark) and 1 (full brightness).

def get_color_id()

Return the Color ID string of the Kyanit, which is derived from the current IP address.

Returned value will be 'BBB' (address of 0) if Kyanit can not connect to the wireless network, or if it loses connection.

def run()

Classes

class Netvar

The Netvar is a notion of a "network variable", which is available to read and write through the network connection. Netvar is a static class, therefore it's not intended to be instantiated.

There are 2 variables within Netvar, which can be understood as "channels".

Netvar.inbound() accesses the variable that is written from "outside", such as by Kyanit CTL or Kyanit API.

Netvar.outbound() on the other hand is what Kyanit publishes, and is available for reading by Kyanit CTL or Kyanit API.

Both methods have the same usage. Having no parameters passed gets the actual value of the variables:

Netvar.inbound()  # gets the value of the inbound variable
Netvar.outbound()  # gets the value of the outbound variable

Passing an object sets the value:

Netvar.outbound('some_value')  # the outbound variable is now equal to 'some_value'

Any object may be passed, which are JSON serializable.

The inbound variable may also be written:

Netvar.inbound('processed')  # the new value is now 'processed'

As suggested by the example, the inbound variable might be overwritten when the data (or part of it) has been processed.

It is also possible to clear the variables with:

Netvar.inbound(clear=True)  # inbound now equals to None
Netvar.outbound(clear=True)  # outbound now equals to None

Kyanit API example:

from kyanitapi import Kyanit

my_kyanit = Kyanit('BCG', network_addr='192.168.1.0')
netvar = my_kyanit.netvar()  # netvar now equals to the value of Netvar.outbound()
my_kyanit.netvar('some_value')  # 'some_value' will be sent to Kyanit, after which
                                # Netvar.inbound() will return 'some_value'

Setting and getting the netvar is realized through HTTP, so keep in mind that there's an order of a second time lag between every network operation. Netvar is not intended to be accessed continuously, because the network overhead would substantially slow down the operation of the Kyanit board.

Instead use it for user commands, which depending on the project can be things like switching, reading sensor data, changing the LED colors, etc.

Static methods

def inbound(obj=None, clear=False)

Get, set or clear the inbound variable.

See class documentation for details.

def outbound(obj=None, clear=False)

Get, set or clear the outbound variable.

See class documentation for details.

class RebootError (...)

This exception is passed to code.cleanup on a reboot request through Kyanit API or Kyanit CTL.

Ancestors

  • builtins.Exception
  • builtins.BaseException
class StoppedError (...)

This exception is passed to code.cleanup on a stop request through Kyanit API or Kyanit CTL.

NOTE: Calling runner.stop() directly will not cause this exception to be passed to cleanup.

Ancestors

  • builtins.Exception
  • builtins.BaseException