embeint.htf
Open HTF
Get started

Your first station run

Create a station, install its certificate and keys, add a demo secret, and run a DUT.

This walkthrough takes you from an empty project to a completed test. It uses the station's built-in print stage and one small custom stage to prove that a station can read a value from Station secrets. Use a disposable value such as Hello from HTF for this exercise. Stage logs appear in the station terminal and in HTF.

You need an HTF organisation and project, permission to create stations, uv, and a macOS or Linux machine that can reach app.embeint-htf.com and mqtt.app.embeint-htf.com:8883. The commands below use a shell on that station machine.

1. Create the station

  1. Sign in at app.embeint-htf.com, open your project, and select Stations.
  2. Select Add station. Choose the project and group, and name the station, for example Quickstart bench.
  3. Select Continue to reach Station runtime YAML.

Replace the editor contents with this complete first-run plan. The first stage prints the DUT ID. The second uses the small Python stage added in step 5.

stages:
  - name: Print DUT ID
    kind: print
    message: 'Testing ${dut_id}'
    wait_seconds: 0

  - name: Print demo secret
    kind: print_demo_secret

Select Create. The Station keys dialog opens after creation.

2. Save the one-time keys and certificate

In Station keys, select Copy .env and save the copied text in a temporary protected location. It contains the station ID, organisation ID, MQTT username and password, API key, server address, and certificate paths. The API key and MQTT password are shown only once.

In the same dialog, under Station TLS certificates, select Issue and download. Save the ZIP bundle, then close the dialog. The bundle contains client.pem and client.key; the private key is also delivered only once. If you need to find the certificate controls later, open the station and select Station settings.

Keep the bundle outside the Git checkout. For example, after replacing the filename below with the file you downloaded:

install_dir="$HOME/.config/embeint-htf/quickstart"
bundle="$HOME/Downloads/PASTE_DOWNLOADED_FILENAME.zip"
mkdir -p "$install_dir"
chmod 700 "$install_dir"
unzip "$bundle" -d "$install_dir"
chmod 600 "$install_dir/client.key"

Check that client.pem and client.key are in that directory. The certificate proves this station's identity to the public MQTT broker; the MQTT username and password are still required.

3. Install the station sample and its credentials

Clone the station repository and install its dependencies:

git clone https://github.com/Embeint/embeint-htf-station.git
cd embeint-htf-station
uv sync --all-groups
cp samples/basic-station/.env.example samples/basic-station/.env

Open samples/basic-station/.env and replace its example values with the text from Copy .env. Change these two lines to the private certificate location used above:

HTF_MQTT_CLIENT_CERT=~/.config/embeint-htf/quickstart/client.pem
HTF_MQTT_CLIENT_KEY=~/.config/embeint-htf/quickstart/client.key

Confirm the file also has non-empty HTF_ORG_ID, HTF_STATION_ID, HTF_MQTT_USERNAME, HTF_MQTT_PASSWORD, and HTF_API_KEY. The production values should be HTF_MQTT_TRANSPORT=mtls, HTF_MQTT_HOST=mqtt.app.embeint-htf.com, HTF_MQTT_PORT=8883, and HTF_API_BASE_URL=https://app.embeint-htf.com. The .env file is ignored by the station repository; do not add it or the certificate bundle to Git.

4. Copy the local station YAML

Replace samples/basic-station/config.yaml with the following complete example. This local file tells the Python process how to connect and identifies the station. The stages section is a fallback for a direct run; HTF supplies the saved runtime plan from step 1 when the station connects.

mqtt:
  transport: mtls
  host: mqtt.app.embeint-htf.com
  port: 8883
  client_cert: ${HTF_MQTT_CLIENT_CERT}
  client_key: ${HTF_MQTT_CLIENT_KEY}

server:
  api_base_url: https://app.embeint-htf.com

station:
  org_id: ${HTF_ORG_ID}
  station_id: ${HTF_STATION_ID}

stages:
  - name: Print DUT ID
    kind: print
    message: 'Testing ${dut_id}'
    wait_seconds: 0

  - name: Print demo secret
    kind: print_demo_secret

The certificate paths and identity values are filled from .env when the sample starts. For a different HTF deployment, use its API and MQTT endpoints.

5. Add a demo secret and the stage that reads it

Open the station in HTF and select Station secrets. Enter DEMO_MESSAGE as the name and Hello from HTF as the value, then select Save secret. The value is shown only during entry. The station fetches current secrets when it starts or reconnects, so restart it after changing a value.

The built-in print stage does not substitute station secrets into YAML. To prove the value reached the station, replace samples/basic-station/main.py with this small sample. It registers print_demo_secret, reads DEMO_MESSAGE through the stage context, and prints it using the normal stage logger.

from __future__ import annotations

import argparse
import asyncio
from datetime import UTC, datetime
from pathlib import Path

from embeint_htf_station.config import StageSettings, load_settings_from_yaml
from embeint_htf_station.stages.base import StageContext, StageLogger, StageResult
from embeint_htf_station.stations import BasicStation


class PrintDemoSecretStage:
    def __init__(self, settings: StageSettings) -> None:
        self.settings = settings

    async def run(self, logger: StageLogger, context: StageContext) -> StageResult:
        started_at = datetime.now(UTC)
        value = context.require_secret("DEMO_MESSAGE")
        await logger.log("info", f"Demo secret: {value}")
        return StageResult(
            name=self.settings.name,
            outcome="passed",
            started_at=started_at,
            finished_at=datetime.now(UTC),
        )


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("dut_id", nargs="?")
    parser.add_argument("--listen", action="store_true")
    parser.add_argument(
        "--config",
        type=Path,
        default=Path(__file__).with_name("config.yaml"),
    )
    args = parser.parse_args()

    settings = load_settings_from_yaml(args.config)
    station = BasicStation(
        settings,
        stage_factories={"print_demo_secret": PrintDemoSecretStage},
    )
    if args.listen:
        asyncio.run(station.serve_forever())
    else:
        if not args.dut_id:
            parser.error("Enter a DUT ID, or use --listen")
        result = asyncio.run(station.run_once(args.dut_id))
        print(f"test finished: {result.outcome}")


if __name__ == "__main__":
    main()
This stage intentionally prints a disposable demo value. Stage logs are sent to HTF. Do not use this stage to print an API key, password, production token, or other real secret. In a production stage, call context.require_secret("NAME") and use the result without logging it.

6. Run a DUT and check the output

From the station repository root, run:

uv run python samples/basic-station/main.py DUT-001

The process connects with its certificate and MQTT credentials, pulls the runtime YAML and secret from HTF, runs both stages, and exits. The useful lines should look like this (timestamps are omitted here):

=======Print DUT ID=======
[Print DUT ID] - Testing DUT-001
=======Print demo secret=======
[Print demo secret] - Demo secret: Hello from HTF
test finished: passed

A missing DEMO_MESSAGE causes the demo stage to fail. Check the secret name in HTF, the station API key in .env, and whether the process restarted after the secret was saved.

To accept runs from HTF, start the station in listening mode:

uv run python samples/basic-station/main.py --listen

Leave that process running. In HTF, open the station and wait for it to show Online. Select Open kiosk view, enter a DUT ID such as DUT-002, and select Start test. Watch the stage progress and Console output in the kiosk, then open the station's Run logs to inspect the saved result. Stop the listener with Ctrl+C when you are finished.

If it does not connect

SymptomFirst check
Certificate or TLS error before connectingConfirm client.pem and client.key exist at the paths in .env, the key is readable by the station account, and the system clock is correct.
MQTT authentication failsCheck the copied MQTT username and password and the broker host and port.
Secret is missingCheck HTF_API_KEY, the DEMO_MESSAGE name, and restart the station to fetch the latest secret.
Station stays offlineKeep --listen running and check access to the MQTT broker.
Kiosk cannot start a testWait for the station to be online and verify its saved runtime YAML has both stages.

See Troubleshooting for more checks and Credentials and secrets for rotation and production use. When you are ready to add hardware flashing, follow Applications and firmware to upload and download a HEX archive and reference it from the station plan.