/      日本語

TWELITE Wings API / MWings for Python

Latest Edition
By using the TWELITE Wings API (MWings), you can control TWELITE from Python scripts on your PC.

Library Overview

The TWELITE Wings API (hereafter MWings) is a library for handling TWELITE from Python scripts.

Features

You can communicate with TWELITE child nodes through a TWELITE parent node connected to the host.

  • Interpret received data and convert it to a dictionary, JSON, or pandas DataFrame*
  • Send commands generated from dictionaries to the parent node
  • The Lite version does not support pandas.

Example Use Cases

For example, you can implement systems like the following:

  • Send temperature and humidity data received by MONOSTICK to a cloud server as JSON
  • Record acceleration data received by MONOSTICK to a CSV or Excel file*
  • Control an LED connected to TWELITE DIP via MONOSTICK from a PC
  • The Lite version cannot output directly to CSV or Excel files.

Characteristics

This is a module for modern Python, by modern Python, for modern Python.

  • With exceptions. Details explained later.

Installation

Available from PyPI.

pip の場合


pip install mwings

poetry の場合


poetry add mwings

The Simplest Sample Script

In just 6 lines, you can output received data from the Extremely Simple! Standard App (App_Twelite) in JSON format.

import mwings as mw
twelite = mw.Twelite(mw.utils.ask_user_for_port())
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
    print(packet.to_json())
twelite.start()

Environment Setup and Operation Check

What You Need

  • PC
  • MONOSTICK (Parent and Repeater App / default settings)
  • TWELITE DIP (Extremely Simple! Standard App / default settings)
    • Connect peripherals such as switches (example: connect a tact switch between DI1 port and GND)

Environment Setup

Installing pyenv

To manage the Python interpreter version, install pyenv.

Linux

curl https://pyenv.run | bash
macOS

brew update
brew install pyenv
Windows

There is no pyenv for Windows. Instead, use pyenv-win.

Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"

Installing Python with pyenv / pyenv-win

Install Python 3.12 or later (3.11 or later for Lite version), as required by MWings.

To list available versions, run:

pyenv install -l

For example, to install Python 3.12.4 and apply it system-wide:

pyenv install 3.12.4
pyenv global 3.12.4

To list installed versions, run:

pyenv versions

Installing pipx

To manage command-line tools such as poetry in an isolated environment, install pipx.

Linux

Debian-based


sudo apt update
sudo apt install pipx
pipx ensurepath

From pip (recommended for Raspberry Pi)


python3 -m pip install --user pipx
python3 -m pipx ensurepath

Fedora-based


sudo dnf install pipx
pipx ensurepath

Reference: Installation - pipx

macOS

brew install pipx
pipx ensurepath
Windows
scoop install pipx
pipx ensurepath

Installing poetry

To manage the Python interpreter version and module dependencies for your project (similar to Node.js), install poetry.

pipx install poetry

Creating a Project

Here, we will use mwtest as the project name.

Move to the directory where you want to create the project and run:

poetry new mwtest

This will generate the mwtest directory.

Project Setup

Move into the project directory and link it to the Python version you installed earlier with pyenv.

poetry env use 3.12.4

Install MWings.

poetry add mwings

Creating the Simplest Sample Script

First, let’s try running the script introduced earlier.

import mwings as mw
twelite = mw.Twelite(mw.utils.ask_user_for_port())
@twelite.on(mw.common.PacketType.APP_TWELITE)
def on_app_twelite(packet):
    print(packet.to_json())
twelite.start()

上記の内容で poetry が生成した __init__.py と同じ階層に simple.py を作成します。

📁 mwtest
└ 📁 mwtest
  ├ 📄 __init__.py
  └ 📄 simple.py

Running the Simplest Sample Script

Connect MONOSTICK and run:

poetry run python simple.py

If there are multiple serial ports, please select the serial port.

If you get output in JSON string format, you have succeeded. An example of actual output is shown below.

{
  "time_parsed": "2024-02-20T03:16:50.150386+00:00",
  "packet_type": "APP_TWELITE",
  "sequence_number": 13699,
  "source_serial_id": "810E0E23",
  "source_logical_id": 120,
  "lqi": 84,
  "supply_voltage": 3249,
  "destination_logical_id": 0,
  "relay_count": 0,
  "periodic": true,
  "di_changed": [
    true,
    true,
    false,
    false
  ],
  "di_state": [
    false,
    false,
    false,
    false
  ],
  "ai_voltage": [
    8,
    272,
    1032,
    112
  ],
  "mwings_implementation": "python",
  "mwings_version": "1.0.0",
  "hostname": "silverstone.local",
  "system_type": "Darwin"
}

Creating a Practical Script

simple.py is just an explanatory sample. It is not a practical script.

This is because twelite.start() creates a thread to receive data, but does not provide a way to terminate it. It is also a very hard-to-read script.

Next, let’s create a more practical script. After running it, we will explain its contents.

This time, we will set the following three conditions:

Below is an example applying these points.

# -*- coding:utf-8 -*-
# Written for Python 3.12
# Formatted with Black

# MWings example: Receive data, print JSON, typed

from zoneinfo import ZoneInfo

import mwings as mw


# Main function
def main() -> None:
    # Create a twelite object
    twelite = mw.Twelite(mw.utils.ask_user_for_port())

    # Use JST for received data
    twelite.set_timezone(ZoneInfo("Asia/Tokyo"))

    # Register an event handler
    @twelite.on(mw.common.PacketType.APP_TWELITE)
    def on_app_twelite(packet: mw.parsers.app_twelite.ParsedPacket) -> None:
        print(packet.to_json(verbose=True, spread=False))

    # Start receiving
    try:
        # Set as daemon thread
        twelite.daemon = True
        # Start the thread, Join to the main thread
        twelite.start()
        print("Started receiving")
        while True:
            twelite.join(0.5)
    except KeyboardInterrupt:
        # Stop the thread
        print("Flushing...")
        twelite.stop()
        print("Completed")


if __name__ == "__main__":
    # Call the main function
    main()

Please save the above as practical.py.

📁 mwtest
└ 📁 mwtest
  ├ 📄 __init__.py
  ├ 📄 simple.py
  └ 📄 practical.py

Running the Practical Script

Run the following command to get output in JSON format, just like with simple.py.

poetry run python practical.py

However, this time you can exit with Ctrl+C without causing an error, and time_parsed should be in Japan Standard Time.

Explanation of the Practical Script

Code Explanation

Let’s explain practical.py.

import Statements

In practical.py, two modules are imported:

from zoneinfo import ZoneInfo

import mwings as mw
  • zoneinfo.ZoneInfo is used to specify the time zone for the received timestamp.
  • mwings is the MWings library. It is imported as mw for brevity. For the Lite version, use mwingslite.
Creating the Object

The mw.Twelite object serves as the interface to access the TWELITE parent node connected to the host.

    # Create a twelite object
    twelite = mw.Twelite(mw.utils.ask_user_for_port())

The mw.utils.ask_user_for_port() function gets a list of available serial ports on the host and returns the file descriptor path or COM port name selected by the user.

Setting the Time Zone

By default, the reception time of data is treated as UTC.

In practical.py, this is set to JST.

    # Use JST for received data
    twelite.set_timezone(ZoneInfo("Asia/Tokyo"))

Pass an IANA time zone identifier to ZoneInfo.

Registering a Receive Handler

To process data sent from TWELITE child nodes, register a receive handler. Here, in the receive handler for the Extremely Simple! Standard App, the received data is converted to JSON format and output.

    # Register an event handler
    @twelite.on(mw.common.PacketType.APP_TWELITE)
    def on_app_twelite(packet: mw.parsers.app_twelite.ParsedPacket) -> None:
        print(packet.to_json(verbose=True, spread=False))

You can register a receive handler by applying the twelite.on() decorator ? to any function.

The data class ParsedPacket received by the handler has methods such as to_json() to convert to a JSON string, to_dict() to convert to a dictionary, and to_df() to convert to a pandas DataFrame.

Here, the to_json() method is used to convert to a JSON string.

        print(packet.to_json(verbose=True, spread=False))
Starting and Stopping Reception

mw.Twelite inherits from threading.Thread. In practical.py, twelite.start() starts the receive process in a separate thread, and twelite.stop() stops it.

    # Start receiving
    try:
        # Set as daemon thread
        twelite.daemon = True
        # Start the thread, Join to the main thread
        twelite.start()
        print("Started receiving")
        while True:
            twelite.join(0.5)
    except KeyboardInterrupt:
        # Stop the thread
        print("Flushing...")
        twelite.stop()
        print("Completed")

Setting twelite.daemon to True makes the receiving subthread a daemon. When all non-daemon threads have exited, the whole Python program will also exit. Here, the main thread is kept waiting by repeatedly calling twelite.join().

When the main thread detects Ctrl+C input, it calls twelite.stop() in the except block to stop the receive process. twelite.stop() waits until the subthread finishes calling the receive handler.

Supplementary Explanation

PEP8 Compliance

practical.py and the MWings source code are formatted using the Black code formatter, which is compatible with PEP8.

Except for the maximum line length, Black does not accept any configuration, making it a stubborn formatter, but it saves the trouble of formulating and sharing coding standards. Coding rules are often contentious, but spending resources on such trivial matters rather than on productive work goes against the Python spirit.

To add Black to your project, run:

poetry add --group dev black

You can run it on specific files or directories:

poetry run black mwtest

You can also check without formatting:

poetry run black --check mwtest
Type Hint Support

practical.py and the MWings source code support type hints.

The MWings library uses mypy, the official static type checker for Python.

To add mypy to your project, run:

poetry add --group dev mypy

Like Black, you can specify files or directories to check:

poetry run mypy mwtest

Practical Script Applications

We provide scripts that further develop practical.py.

mwings_python/examples at main

log_export.py

Reads a text file containing output from the parent node, interprets it, saves the results as a pandas DataFrame, and finally outputs to a CSV or Excel file. Not supported in the Lite version.

rx_export.py

Receives output from the connected parent node, interprets the results, saves them as a pandas DataFrame, and finally outputs to a CSV or Excel file. Not supported in the Lite version.

  • If you select a CSV file, all results are saved in a single file.
  • If you select an Excel file instead, the results are saved in separate sheets by packet type.

rx_export_csv_durable.py

Receives output from the connected parent node, interprets the results, and appends them to CSV files by source serial ID. Not supported in the Lite version.

rx_print_df.py

Receives output from the connected parent node, simply converts the interpreted results to a pandas DataFrame, and outputs them as a string. Not supported in the Lite version.

rx_print_dict.py

Receives output from the connected parent node, simply converts the interpreted results to a dictionary, and outputs them. Also supported in the Lite version.

rx_print_json.py

Receives output from the connected parent node, simply converts the interpreted results to a JSON string, and outputs them. Also supported in the Lite version.

tx_binary_uart.py

Sends binary data [0xBE, 0xEF] to TWELITE UART via the connected parent node running the Serial Communication App. Also supported in the Lite version.

Blinks the LED connected to the DO1 port of TWELITE DIP via the connected Parent and Repeater App parent node. Also supported in the Lite version.

Turns on the LED of the Notification PAL in each color via the connected Parent and Repeater App parent node. Also supported in the Lite version.

API Reference