Poststation User Guide

This is a user guide for the Poststation tool.

What is Poststation?

Poststation is a tool for connecting embedded devices and applications running on a PC, laptop, or embedded linux system. It works with any device speaking the postcard-rpc protocol.

Poststation is used to:

  • Discover and connect to externally connected embedded devices over USB, I2C, SPI, etc.
  • Retrieve information about each of the connected devices, e.g. "Service Discovery"
  • Provide API access for locally running user programs to interact with connected devices
  • Maintain historical information about devices, including a history of messages sent to/from each device

A typical setup looks something like this:

┌───────────────────────────────────────────────────────────────────────────────┐
│ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐                               │
│   ┌───────────────┬───┐       ┌─────────────┐                                 │
│ │ │ User Program  │SDK│◀──┐   │             │ │                               │
│   └───────────────┴───┘   │   │             │                                 │
│ │ ┌───────────────┬───┐   │   │             │ │                USB            │
│   │ User Program  │SDK│◀──┼──▶│ Poststation │────────┬──────┬──────┬──────┐   │
│ │ └───────────────┴───┘   │   │             │ │      ▼      ▼      ▼      ▼   │
│   ┌───────────────┬───┐   │   │             │     ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ │ User Program  │SDK│◀──┘   │             │ │   │SDK │ │SDK │ │SDK │ │SDK │ │
│   └───────────────┴───┘       └─────────────┘     ├────┤ ├────┤ ├────┤ ├────┤ │
│ │                                             │   │MCU │ │MCU │ │MCU │ │MCU │ │
│                                                   └────┘ └────┘ └────┘ └────┘ │
│ │  ┌ ─ ─ ─                                    │                               │
│  ─ ─  PC  │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                                │
│    └ ─ ─ ─                                                                    │
│ ┌───────────────┐                                                             │
└─┤ Typical Setup ├─────────────────────────────────────────────────────────────┘
  └───────────────┘

Poststation for prototyping

If you are prototyping or building experimental projects such as demos, proof of concepts, or one-offs, Poststation is intended to make your life easier by:

  • Handling the communications, enumeration, and service discovery for you automatically
  • Providing tools, like poststation-cli, that make it quick to work with devices interactively or via scripts on your PC
  • Providing PC-side libraries, REST APIs, and socket interfaces for writing applications that speak with your devices
  • Providing MCU-side libraries and templates, to make starting a project painless

Poststation is offered as a single user license, intended for prototyping and development.

Poststation for production

Poststation is also intended to be a component you can use all the way through production.

It is lightweight, and can run on small Embedded Linux devices, as a background service managing connections to microcontrollers on your product. It can serve as a single interface for monitoring logs, sending commands, or triggering firmware updates, which you can orchestrate using your existing application stack, whether that is in Rust, Python, Node, or whatever else.

If you are looking to ship Poststation as part of your product, or use it internally on more than one machine, please Contact OneVariable for pricing.

Installation

There are two things you will need to install:

  1. The poststation server
  2. The poststation-cli tool

Install Poststation

Currently, the only way to obtain the Poststation executables is to Contact OneVariable for early access.

MacOS

You should have received a file named poststation-aarch64-apple-darwin-signed.zip.

You can unzip this file in the Finder, or run:

unzip poststation-aarch64-apple-darwin-signed.zip

This contains an application package called Poststation.app. This folder contains the following contents:

Poststation.app/
Poststation.app/Contents/
Poststation.app/Contents/_CodeSignature/
Poststation.app/Contents/MacOS/
Poststation.app/Contents/Info.plist
Poststation.app/Contents/_CodeSignature/CodeResources
Poststation.app/Contents/MacOS/poststation

This is a signed package, and is a command line executable. You can launch the application on the command line:

$ ./Poststation.app/Contents/MacOS/poststation

MacOS does not require any additional steps for permissions.

Linux

You should have received one of the following files:

  • poststation-aarch64-unknown-linux-musl.zip
    • This will work on 64-bit ARM linux targets, including the newer Raspberry Pi devices
    • You can extract the contents with unzip poststation-aarch64-unknown-linux-musl.zip
  • poststation-x86_64-unknown-linux-gnu.tar.xz
    • This will work on 64-bit AMD/Intel linux targets, including most desktop/laptop systems
    • You can extract the contents with tar xf poststation-x86_64-unknown-linux-gnu.tar.xz

These archives will contain a single binary called poststation. This can be executed on the command line:

./poststation

udev rules

Depending on the devices you are using, you may need to add udev rules for the VID and PID of connected devices, in order to allow access to connected USB devices. It is NOT recommended to run poststation as a root user or with sudo.

You may need to repeat this process for each new device!

The following udev rules file is usable for the examples provided in the poststation-util repository, which uses the USB VID 16c0, and the USB PID 27dd:

# These rules are based on the udev rules from the OpenOCD + probe.rs projects
#
# This file is available under the GNU General Public License v2.0
#
# SETUP INSTRUCTIONS:
#
# 1. Copy/write/update this file to `/etc/udev/rules.d/60-poststation.rules`
# 2. Run `sudo udevadm control --reload` to ensure the new rules are used
# 3. Run `sudo udevadm trigger` to ensure the new rules are applied to already added devices.

ACTION!="add|change", GOTO="poststation_rules_end"
SUBSYSTEM!="usb|tty|hidraw", GOTO="poststation_rules_end"

# Default demos from poststation - 16c0:27dd
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="27dd", MODE="660", GROUP="plugdev", TAG+="uaccess"

# You can add addtional rules here if your devices use different VID:PID pairs
# ATTRS{idVendor}=="xxxx", ATTRS{idProduct}=="xxxx", MODE="660", GROUP="plugdev", TAG+="uaccess"

LABEL="poststation_rules_end"

Windows

You should have received a file called poststation-x86_64-pc-windows-msvc.zip.

You can unzip this in the file explorer. This contains a single file, poststation.exe.

You can run this on the command line:

poststation.exe

Permissions

TODO: I don't think that windows needs any additional permissions for this? If you have issues then please open an issue!

Install Tools

The poststation-cli tool is an open source component, and can be found in the poststation-util repository.

Install with cargo

poststation-cli is published on crates.io. You can install it by running:

cargo install poststation-cli

Build from source

git clone https://github.com/OneVariable/poststation-util
cd poststation-util/tools/poststation-cli
cargo install --path=.

Configuration

On first run, poststation will create a configuration file in the default data storage directory. This path will be printed on first run.

Additionally, you can use the poststation-cli folder to show the location of the data storage directory, including the path to the configuration file.

$ poststation-cli folder
Poststation Folder Information:
===============================
Folder:         "/Users/username/Library/Application Support/com.onevariable.onevariable.poststation"
CA Certificate: "/Users/username/Library/Application Support/com.onevariable.onevariable.poststation/ca-cert.pem"
Configuration:  "/Users/username/Library/Application Support/com.onevariable.onevariable.poststation/poststation-config.toml"

For certain security settings, you may need to use the "CA Certificate" listed here in order to establish secure connections. It is NOT recommended to add this certificate to your operating system's certificate store or the one used by your web browser.

Default Contents

The default configuration file currently contains the following:

##
## Poststation Configuration File
##

# # `apis.sdk`
#
# This section is used for the binary SDK. This section is required.
[apis.sdk]
## API SDK Security options - pick ONE:

# Insecure, no encryption, only local connections will be allowed
# security.insecure        = {}

# Self-signed CA certificate. Global connections will be allowed, clients
#   on other machines will need a copy of the generated CA Certificate
#
# This is the default and recommended option.
# security.tls-self-signed = {}

## API SDK Listener options - pick ONE

# Listen only to connections on `localhost`
#
# This is the default option.
# listener.local-only = { port = 51837 }

# Listen to the given ipv4/ipv6 socket address. Use `0.0.0.0` to listen
#   on all interfaces. This option is not allowed when "Insecure" security
#   is selected.
# listener.global     = { socket_addr = "0.0.0.0:51837" }

# # `apis.http`
#
# This section is used for the REST API. This section is optional,
# and when omitted the REST API is disabled.
#
# When `insecure` security is selected, only `local-only` mode
# is allowed.
# [apis.http]
# ## REST API Security options - pick ONE:

# Insecure, no encryption, only local connections will be allowed
# security.insecure        = {}

# Self-signed CA certificate. Global connections will be allowed, clients
#   on other machines will need a copy of the generated CA Certificate
#
# This is the default and recommended option.
# security.tls-self-signed = {}

# ## Listener options
# listener.local-only = { port = 4444 } # default
# listener.global     = { socket_addr = "0.0.0.0:1235" }

# # `storage`
#
# This section is used to control local storage options. This section
# is optional.
# [storage]

# There are no configuration options for this yet.

# # `experimental`
#
# This section is used to control experimental, unstable features. This
# section is subject to change without stability guarantees
# [experimental]

# There are no configuration options for this yet.

The apis section

The apis section contains public interfaces presented by the poststation server. These are used to interact with the attached devices from your host PC(s).

The apis.sdk subsection

This section controls the SDK API. This API is used by the poststation-sdk crate, as well as the poststation-cli tool.

By default, this API will be configured to:

  • Serve ONLY to the local machine, on port 51837
  • Serve using TLS encryption

Note that poststation will refuse to serve outside of the local machine, unless TLS encryption is enabled.

If you wanted to serve on any interface, using TLS encryption, you could use the following configuration:

[apis.sdk]
security.tls-self-signed = {}
listener.global          = { socket_addr = "0.0.0.0:51837" }

If you wanted to serve only locally, with no encryption, you could use the following configuration:

[apis.sdk]
security.insecure   = {}
listener.local-only = { port = 51837 }

The apis.http section

This section controls the HTTP/REST API. This API can be used by other languages such as Python to communicate with the poststation server.

For example requests using curl, please see the poststation-api-icd docs.

By default, this API will be configured to:

  • Serve ONLY to the local machine, on port 4444
  • Serve using TLS encryption

Note that poststation will refuse to serve outside of the local machine, unless TLS encryption is enabled.

If you wanted to serve on any interface, using TLS encryption, you could use the following configuration:

[apis.http]
security.tls-self-signed = {}
listener.global          = { socket_addr = "0.0.0.0:4444" }

If you wanted to serve only locally, with no encryption, you could use the following configuration:

[apis.http]
security.insecure   = {}
listener.local-only = { port = 4444 }

Command Line Options

The Poststation server contains a couple of options:

$ poststation --help

A tool for making it easy to talk to devices

Usage: poststation [OPTIONS]

Options:
      --simulator-devices <SIM_DEVICES>
          How many virtual simulator devices to spawn. Defaults to zero
      --interface-testers <INTERFACE_TEST_DEVICES>
          How many virtual simulator devices to spawn. Defaults to zero
      --headless
          Run the device in headless mode, instead of launching the TUI
      --config-path <CONFIG_PATH>

  -h, --help
          Print help
  -V, --version
          Print version

--simulator-devices <SIM_DEVICES>

This can be used to spawn a few simple simulated devices that can be used for testing interactions with the SDK or REST APIs.

--interface-testers <INTERFACE_TEST_DEVICES>

This can be used to spawn simulated devices that exercise a larger portion of possible postcard-schema types. This can be useful to ensure that your application can correctly handle a variety of different kinds of messages.

--headless

This option disables the TUI interface, and instead prints logs to stdout. This is intended to be used when installing poststation as a background service.

When run in --headless mode, the RUST_LOG environment variable can be used to control the log level of the system. See the EnvFilter docs for examples of how to fine tune the logging.

--config-path

This controls the path used for the configuration file used by the poststation server. This overrides the default, and can be used for testing.

Getting Started Tutorial

This tutorial will show you how to use Poststation and the parts you'll use as a developer.

This tutorial is broken up into two main stages:

The first stage uses simulated devices to get familiar with the poststation application and tools.

The second stage uses a Raspberry Pi 2040 as a connected device, as you typically would for connected USB devices.

Using Simulated Devices

This tutorial is focused on getting familiar with the "host", or PC side of poststation.

Make sure you've completed the Installation Steps first, including installing poststation-cli before continuing.

Starting Simulated Devices

We'll need to start poststation with simulated devices for this step. This will create virtual devices we can interact with, before we are ready to use real external devices.

If the poststation server is running, you will need to stop it first. We'll then launch it with the --simulator-devices=1 flag as shown below, to start a single simulator device.

Poststation will "reuse" simulator devices, so if you exit and restart poststation, the same simulator devices will re-appear.

$ poststation --simulator-devices=1

If this is the first time you have run poststation, you will need to enter your license key, or press "escape" to continue. You should see a screen similar to this:

First Launch

We'll look at the information presented on this menu in the next section.

The Poststation User Interface

This section explores the Poststation "Text User Interface", or TUI.

You saw this screen when we launched poststation:

First Launch

This screen contains some important information:

The [ DEVICES ] pane

A list of known devices is shown in the leftmost pane. Devices are divided by whether they are currently connected or not. Connected devices are shown above the --- divider, and disconnected devices are shown below.

We can see that we have one connected device, called "QUIRKY-344". This name is a shortened, human readable version of the device's serial number. We use the twoten crate to generate these names from the serial number of the device.

We can also see a summary of devices below the [ DEVICES ] pane, showing that we have one ACTIVE (or connected) device, and zero INACTIVE (or disconnected) devices.

The main pane

To the right of the [ DEVICES ] pane, we have the main pane. The main pane starts with the [ INFO ] page selected, which is highlighted at the bottom of the pane.

These pages can be navigated by using the left and right arrow keys.

The [ INFO ] page

This page shows some basic metadata about the device, including it's generated short name, as well as the SERIAL number of the device. Poststation uses a 64-bit SERIAL number as a globally unique ID. You'll need this when you want to interact with this device.

The [ LOGS ] page

You can move from the [ INFO ] page to the [ LOGS ] page by pressing the right arrow key. You should see a page that looks like this:

Logs page

This page shows all logs from the device. The simulator will send a log message once per second with the same text. You can see the logs scrolling upwards as new logs are received.

The [ ENDPOINTS ] page

You can move from the [ LOGS ] page to the [ ENDPOINTS ] page by pressing the right arrow key. You should see a page that looks like this:

Endpoints page

Endpoints are the main way that we will interact with a connected device. Endpoints are made up of a "request", sent from a client to a server, and a "response", which is sent from the server to the client.

Poststation will automatically communicate with the connected device to determine what endpoints it offers, and the types used for each of those endpoints. This process is called Schema Discovery. This occurs any time that poststation connects to the device, which means that if it changes as you are developing, poststation will always update this information.

Endpoints also have a "path", a human readable string that is used to identify them.

The Endpoints page is broken into two parts, the Types section, and the Endpoints section. We'll start with the bottom section.

The "Endpoints (by path)" section

The current page shows that the simulator device supports six endpoints.

The first endpoint, postcard-rpc/ping is an endpoint built in to all postcard-rpc devices. This endpoint takes a u32, or 32-bit unsigned integer, and also returns a u32. This endpoint will always "echo" back the received value, and is often used to ensure the connection is working correctly.

The second endpoint, postcard-rpc/schemas/get, is used for the Schema Discovery process described above, and will trigger the device to send information about itself. This is handled automatically by poststation.

The third endpoint, poststation/unique_id/get is used to obtain the serial number of the device. We can see that this endpoint takes no data, or () as the request parameter, and returns a u64, or a 64-bit unsigned integer as a response.

The fourth endpoint, simulator/picoboot/reset mimics the "reset to bootloader" command used on physical RP2040 devices. In the simulator, calling this endpoint will cause the simulator device to disconnect, as if it was told to reboot into a bootloader that does not talk with poststation. If you call this and would like the device to come back, you will need to exit and restart poststation.

The fifth endpoint, simulator/status_led/set, takes an Rgb8 value, which can be used to set a red, green, and blue value, mimicing a device that has an attached color LED. We can see that this endpoint does not return any data, though we will still get an "empty" response from the device as a confirmation.

The sixth endpoint, simulator/status_led/get, takes no data, but returns the currently set value. In the simulator, this starts with all zero values, and will retain the last value set by the simulator/status_led/set endpoint.

We can see some more information about the types mentioned in these endpoints in the section above.

The "Types" section

This section shows the reported schema of all endpoints. If we see a type like Rgb8 in the "Endpoints" table, we can refer to the "Types" section to see more.

For example, the Rgb8 type is reported as:

struct Rgb8 { r: u8, g: u8, b: u8 }

This means we can provide a one byte value (0-255) for each color "r", "g", and "b".

The [ TOPICS ] page

You can move from the [ ENDPOINTS ] page to the [ TOPICS ] page by pressing the right arrow key. You should see a page that looks like this:

Topics page

Topics are the other way that we communicate with devices through Poststation. While Endpoints always consist of a Request and Response, Topics are messages that can be sent either client to server, or server to client at any time, but do not have a response.

The client or server can "publish" a topic message, and the other device will "subscribe" to topic messages.

Topics also have a human readable "path", but only have a single type associated with them.

NOTE: As of poststation v0.14, the [ TOPICS ] page ONLY shows "topics out", or topics from server to client. In the future, it will also show "topics in", or topics from the client to server.

We can see that our simulator device has three topics.

The first is postcard-rpc/schema/data, which is used as part of the Schema Discovery process. This is handled automatically by Poststation.

The second is postcard-rpc/logging. This topic is used to fill in the data on the [ LOGS ] page, and contains text data, or a String.

The third is simulator/temperature, which the simulator will publish data to. If we look at the "Types" table, we can see that the Temperature type contains the following data:

struct Temperature { temp: f64 }

Meaning that this temperature will be sent as a 64-bit floating point value

The [ HISTORY ] page

This page shows a history of all messages (excluding LOGS) sent TO or FROM the device. This can be used to monitor communication between clients and servers.

It should currently look something like this:

History Page

Each line is broken up into four main parts:

First is the timestamp, which is shown in local time, for example 13:52:38.097.

Second is the "kind" of message, as well as the direction. This can be:

  • <-T - or a "Topic" message sent TO the client FROM the server
  • T-> - or a "Topic" message sent TO the server FROM the client
  • E-> - or an "Endpoint" Request sent TO the server FROM the client
  • <-E - or an "Endpoint" Response sent TO the client FROM the server

As we are not currently interacting with the device, we only see topic messages sent from server to client.

The [ TRACING ] page

This page shows the tracing logs of poststation itself. This is not specific to the currently connected device, and is the same information you would see on the command line if you ran poststation with the --headless mode enabled.

NOTE: This page is likely to be moved in the future, likely behind a separate diagnostics menu. It is here mostly as a development artifact of poststation.

This page should look like this:

Tracing page

The poststation-cli tool

For this next section, we'll use the Command Line Interface tool, poststation-cli. Make sure that:

  1. You've installed the CLI tool
  2. Your poststation server is still running with a simulator device

In a new window, we can verify that we can connect to the server by running the "list devices" command:

$ poststation-cli ls

# Devices

| serial           | name       | interface | connected |
| :--------------- | ---------: | :-------- | :-------- |
| 563BF78B6A56DF04 | QUIRKY-344 | usb       | yes       |

This prints out a table of the devices currently known by poststation.

We can see the same simulator device from the previous chapter, and it is also showing as currently connected.

Top level commands

You can print help with poststation-cli --help:

$ poststation-cli --help
A CLI tool for poststation

Usage: poststation-cli [OPTIONS] <COMMAND>

Commands:
  ls      List devices
  folder  Show paths for configuration, database storage, and the CA certificate for external usage
  device  Interact with a specific device
  help    Print this message or the help of the given subcommand(s)

Options:
  -s, --server <SERVER_ADDR>  A path to the server. Defaults to `127.0.0.1:51837`
      --insecure              When set, a plaintext connection will be made with the server
      --timings               Print timing information
  -h, --help                  Print help
  -V, --version               Print version

We've seen the output of ls already, we'll now focus on the device command, which is the primary way of interacting with connected devices.

The device command

We can see all of the device subcommands with --help:

$ poststation-cli device --help
Interact with a specific device

Usage: poststation-cli device [SERIAL] <COMMAND>

Commands:
  types       View all types used for communicating with a given device
  endpoints   View all endpoints available for communicating with a given device
  topics-out  View all topics published by a given device
  topics-in   View all topics handled by a given device
  logs        View the most recent logs from a given device
  logs-range  View the selected range of logs from a given device
  proxy       Proxy message to device endpoint
  publish     Publish a topic message to a device
  listen      Listen to a given "topic-out" path from a device
  help        Print this message or the help of the given subcommand(s)

Arguments:
  [SERIAL]  Device Serial Number or Name. Can be set via POSTSTATION_SERIAL env var

Options:
  -h, --help  Print help

Here we can fully interact with our device:

  • types will print the types known to the device on all endpoints/topics
  • endpoints will print information about the device's endpoints
  • topics-out will print all topics sent TO the client FROM the server
  • topics-in will print all topics sent TO the server FROM the client
  • logs will print the most recent logs, and logs-range can be used to print a specific range of logs
  • proxy is used to send an endpoint request and get the response from the device
  • publish is used to send a topics-in message to the device
  • listen is used to receive topics-out messages from the device

Common patterns

These device commands each take a common "kinds" of arguments.

Serial Number

We will need to specify what device we want to interact with. We can use the full serial number of our device, in hex format:

$ poststation-cli device 563BF78B6A56DF04 endpoints

Endpoints offered by device 563BF78B6A56DF04

* 'postcard-rpc/ping' => async fn(u32) -> u32
* 'postcard-rpc/schemas/get' => async fn(()) -> SchemaTotals
* 'poststation/unique_id/get' => async fn(()) -> u64
* 'simulator/picoboot/reset' => async fn(())
* 'simulator/status_led/set' => async fn(Rgb8)
* 'simulator/status_led/get' => async fn(()) -> Rgb8

However for convenience, the CLI also supports "fuzzy" matching, on part of the serial number, or on part of the short name.

For example, we can also use the last four digits of the serial number:

$ poststation-cli device DF04 types

Types used by device 563BF78B6A56DF04

* struct Key([u8; 8])
* struct Rgb8 { r: u8, g: u8, b: u8 }
* enum OwnedSchemaData { Type(Schema), Endpoint{ path: String, request_key: Key, response_key: Key}, Topic{ path: String, key: Key, direction: TopicDirection} }
* [u8; 8]
* struct Temperature { temp: f64 }
* enum TopicDirection { ToServer, ToClient }
* struct SchemaTotals { types_sent: u32, endpoints_sent: u32, topics_in_sent: u32, topics_out_sent: u32, errors: u32 }

Or we can use part of the short name, "QUIRKY-344":

$ poststation-cli device quirky topics-out

Topics offered by device 563BF78B6A56DF04

* 'postcard-rpc/schema/data' => Channel<OwnedSchemaData>
* 'postcard-rpc/logging' => Channel<String>
* 'simulator/temperature' => Channel<Temperature>

Path

When we need to specify the path, we can also use "fuzzy" matching. For example, instead of using simulator/status_led/get, we can just say led/get:

$ poststation-cli device quirky proxy led/get '{}'
Response: '{"b":30,"g":20,"r":10}'

However if we aren't specific enough, then we will get an error instead:

$ poststation-cli device quirky proxy led
Given 'led', found:

* 'simulator/status_led/set' => async fn(Rgb8)
* 'simulator/status_led/get' => async fn(()) -> Rgb8

Error: Too many matches, be more specific!

Values

Since postcard is a binary format, the CLI will automatically translate all messages to and from JSON, to make it possible to type on the command line.

As we saw above with device quirky proxy led/get, the CLI printed out:

{"b":30,"g":20,"r":10}

If we want to send a command, we will also need to provide JSON. You may want to use single quotes on your shell, to avoid needing to escape " double quotes.

$ poststation-cli device quirky proxy led/set '{"r": 20, "g": 30, "b": 40}'
Response: 'null'

If an endpoint or topic-in takes () as a value, we can also omit the value entirely. For example, these two commands do the same thing:

$ poststation-cli device quirky proxy led/get '{}'
Response: '{"b":30,"g":20,"r":10}'

$ poststation-cli device quirky proxy led/get
Response: '{"b":30,"g":20,"r":10}'

Don't forget that JSON requires keys to be strings! If you forget this, you'll get an error when poststation trys to convert this:

$ poststation-cli device quirky proxy led/set '{r: 20, g: 30, b: 40}'
Error: 'Dynamic("provided JSON does not match the expected schema for this endpoint")'

Next up

In the next section, we'll explore using the SDK crate, poststation-sdk.

Using an RP2040