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:
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:
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:
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:
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 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 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:
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 serverT->
- or a "Topic" message sent TO the server FROM the clientE->
- 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:
The poststation-cli tool
For this next section, we'll use the Command Line Interface tool,
poststation-cli
. Make sure that:
- You've installed the CLI tool
- 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/topicsendpoints
will print information about the device's endpointstopics-out
will print all topics sent TO the client FROM the servertopics-in
will print all topics sent TO the server FROM the clientlogs
will print the most recent logs, andlogs-range
can be used to print a specific range of logsproxy
is used to send an endpoint request and get the response from the devicepublish
is used to send atopics-in
message to the devicelisten
is used to receivetopics-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
.