Skip to content

Integration Guide

Overview

This guide covers the integration of the miro EdgeCard mioty into a host system. It is intended for system integrators embedding the card into a product or gateway device.

Current firmware revisions are fully locked down. There is no SSH or serial console access. All configuration and management is performed through an HTTP API exposed by the card over its USB network interface.

Legacy firmware

If you are working with an older firmware revision that provides SSH access, refer to the legacy integration guide.

Host System Networking

The miro EdgeCard mioty exposes a USB CDC ECM network interface with a static IP address of 172.30.1.2/24. The host system must be configured with an address in the same subnet (e.g. 172.30.1.1) and must forward traffic on behalf of the card so it can reach the mioty service center.

The host network stack SHALL restrict access to 172.30.1.2 to localhost only. The Config API is unauthenticated; network-level isolation is the only access control in place.

The card requires the following outbound connections. The host SHALL permit these through its firewall or routing policy:

Service Protocol Notes
BSSCI or MQTT TCP Port configurable via Config API
NTP UDP 123 Time synchronisation
Firmware updates HTTPS or HTTP RAUC bundle download

The card does not require any inbound connections from the internet.

Replace eth1 with the host's uplink interface and eth2 with the USB CDC ECM interface.

ip addr add 172.30.1.1/24 dev <usb-interface>
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables --flush
iptables --table nat --flush
iptables --delete-chain
iptables --table nat --delete-chain
iptables --table nat --append POSTROUTING --out-interface eth1 -j MASQUERADE
iptables --append FORWARD --in-interface eth2 -j ACCEPT

Save to /etc/iptables/iptables.rules to survive reboots:

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o eth1 -j MASQUERADE
COMMIT

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A FORWARD -i eth2 -j ACCEPT
COMMIT

All subsequent API calls use the base address http://172.30.1.2/cgi-bin.

Security Requirements

The following requirements apply to all deployments. They are mandatory for compliance with EN 18031-1:2024 under EU Radio Equipment Directive 2022/30, Article 3(3)(d). Requirements use normative language: SHALL denotes a mandatory requirement; SHOULD denotes a strong recommendation.

Config API Access Control

The Config API at 172.30.1.2 is unauthenticated by design. The host network stack SHALL restrict access to 172.30.1.2 to localhost only. The Config API SHALL NOT be exposed to any external network interface, including any interface reachable from the internet or from other devices on the local network.

The iptables configuration in Host System Networking provides a forwarding baseline. The integrator is responsible for ensuring no rule exposes the Config API beyond the host.

BSSCI Transport Security

Unencrypted BSSCI connections are permitted only to a mioty Service Center running on the host system (loopback or isolated LAN segment). For any BSSCI connection to a Service Center reachable over an untrusted network, TLS SHALL be enabled. mTLS is strongly recommended. Certificate and key management is the responsibility of the system integrator.

See TLS Configuration for setup instructions.

MQTT Transport Security (NM Mode)

This requirement applies to the GWC-62-MY-868-NM variant only.

Unencrypted MQTT connections are permitted only to a broker running on the host system. TLS SHALL be enabled for any connection to an external broker. mTLS is strongly recommended.

Physical Access Control

The host system enclosure SHALL prevent unauthorised physical access to the installed gateway card. Physical access to the card provides direct access to the Config API. See the Overview for mechanical requirements.

Firmware Updates

The system integrator SHALL apply Miromico security firmware updates within a timeframe appropriate to the severity of the vulnerability, as stated in the Miromico security advisory. Miromico provides security updates for a minimum of 5 years from the last date of manufacture.

System Time at Boot

The host system SHOULD set the card system time via /cgi-bin/time after each boot, before NTP synchronisation is established. This ensures correct certificate validation and license expiry checking from first startup. See Set System Time.

Initial Setup

Boot Sequence

Complete the following steps in order on boot:

  1. Verify the card is reachable: GET /cgi-bin/status
  2. Set system time: POST /cgi-bin/time
  3. Configure BSSCI or MQTT endpoint: POST /cgi-bin/config
  4. Verify operation: GET /cgi-bin/status

Check Device Status

Before configuring the card, verify it is reachable and note the device ID. The device ID is required when requesting a license.

curl -s http://172.30.1.2/cgi-bin/status | jq .
{
  "version": "1.2.0",
  "date": "2026-04-07T14:00:00",
  "uptime_seconds": "3600.12",
  "device_id": "01010053343030303401001109142C00",
  "device_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjAN...\n-----END PUBLIC KEY-----\n"
}

Set System Time

The card does not have a hardware real-time clock. Set the system time on every boot before performing any operation that involves certificate validation (TLS connections, firmware updates).

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d "{\"datetime\": \"$(date -u +%Y-%m-%dT%H:%M:%S)\"}" \
    http://172.30.1.2/cgi-bin/time | jq .
{
  "datetime": "2026-04-07T14:00:00"
}

License

The card ships with a license for the BS (Base Station) feature pre-installed. No license installation is required for initial operation.

Check License Status

Use GET /cgi-bin/license to query the installed licenses and their current feature validity.

curl -s http://172.30.1.2/cgi-bin/license | jq .
{
  "licenses": [
    {
      "signer": "miromico",
      "issued": "2026-01-15",
      "valid": true,
      "features": {
        "bs": { "expiry": null, "valid": true }
      }
    }
  ]
}

expiry: null means the feature has no expiry date. valid: false on a feature means it has expired. An empty licenses array means no license is installed.

Install License

The license API (POST /cgi-bin/license) allows field license upgrades. The license file is provided as a signed JSON object by Miromico. Install it with:

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d "{\"license\": $LICENSE}" \
    http://172.30.1.2/cgi-bin/license | jq .

BS Configuration

The BS is configured via POST /cgi-bin/config. The request body is a JSON object with a bs block containing the configuration and top-level control flags.

Configuration can be applied as persistent (stored on the card, survives reboot) or temporary (active until the next reboot). Omitting a parameter leaves its current value unchanged.

Parameter Reference

Top-level request fields

Field Type Default Description
bs object Configuration block. Contains bsi, mps, and optional TLS fields.
persistent boolean false true stores the configuration to flash; false applies it only until the next reboot.
restart boolean true true restarts the BS service immediately after applying the configuration. Required for changes to take effect without a full reboot.
reset boolean false true discards any active temporary configuration and reverts to the stored persistent configuration. Cannot be combined with persistent.

bs.bsi Base Station Identity

Field Type Default Description
uniqueBaseStationId string 00-00-00-00-00-00-00-00 EUI-64 identifier, hyphen-separated. Must be unique across all base stations connected to the same service center. See Base Station ID.
baseStationName string mioty-bsm Human-readable name reported to the service center. Used for display and logging.
baseStationInfo string "" Optional free-text description of the installation site or deployment context.
baseStationVendor string "" Optional vendor identifier reported to the service center.
baseStationModel string "" Optional model identifier reported to the service center.
serviceCenterAddr string 172.30.1.1 Hostname or IP address of the mioty Service Center (BSSCI endpoint).
serviceCenterPort string 16017 TCP port of the mioty Service Center.
tlsAuthRequired string "false" Set to "true" to enable TLS for the BSSCI connection. Requires tlsCaCert, tlsCert, and tlsKey. See TLS Configuration.

bs.mps Modulation and Physical Layer Settings

Field Type Default Description
profile string eu868 Radio frequency profile. Determines the operating band and channel plan. Supported value: eu868 (863-870 MHz, EU).

bs TLS fields (required when tlsAuthRequired is "true")

Field Type Description
tlsCaCert string PEM-encoded CA certificate used to verify the service center's server certificate.
tlsCert string PEM-encoded client certificate presented by the base station during mTLS handshake.
tlsKey string PEM-encoded private key corresponding to tlsCert.

PEM values must be passed as single-line strings with literal \n escape sequences. See TLS Configuration for the conversion procedure.

bs Feature Flags

These flags enable optional BS capabilities. Each flag is omitted from the default configuration (equivalent to disabled). Set to true to enable. Each POST rewrites all flags from scratch; omitting a flag or setting it to false clears any previously-set value.

Field Type Description
varmac boolean Enable variable MAC ID mode. When enabled, the BS accepts endpoint frames with variable MAC addresses.
ulp boolean Enable ULP (Ultra Low Power) physical layer.
hdr boolean Enable HDR (High Data Rate) physical layer.
emu boolean Enable emulator mode with virtual endpoints for testing without physical devices.
plmli boolean Enable PHY MAC layer interface (PLMLI).
recon boolean Enable debug and measurement interfaces. Use during integration and diagnostics only.

Factory Default Configuration

The default persistent configuration applied at the factory is equivalent to:

<module name="root">
  <module name="bsi">
    <parameter name="uniqueBaseStationId">00-00-00-00-00-00-00-00</parameter>
    <parameter name="baseStationName">mioty-bsm</parameter>
    <parameter name="baseStationInfo"/>
    <parameter name="baseStationVendor"></parameter>
    <parameter name="baseStationModel"></parameter>
    <parameter name="serviceCenterAddr">172.30.1.1</parameter>
    <parameter name="serviceCenterPort">16017</parameter>
    <parameter name="tlsAuthRequired">false</parameter>
  </module>
  <module name="mps">
    <parameter name="profile">eu868</parameter>
  </module>
</module>

Base Station ID

uniqueBaseStationId must be unique across all base stations connected to the service center. The recommended approach is to derive it from the host system's MAC address by inserting ff-fe after the third octet.

For example, if the host MAC address is 9c:65:f9:61:07:c7, the base station ID becomes 9c-65-f9-ff-fe-61-07-c7.

Read Current Configuration

Use GET /cgi-bin/config to read back the active BS configuration. Returns the temporary config if one is active, otherwise the persistent config. Returns HTTP 400 if no config has been applied yet.

curl -s http://172.30.1.2/cgi-bin/config | jq .
{
  "bs": {
    "bsi": {
      "uniqueBaseStationId": "9c-65-f9-ff-fe-61-07-c7",
      "serviceCenterAddr": "eu3.loriot.io",
      "serviceCenterPort": "727",
      "tlsAuthRequired": "false"
    },
    "mps": {
      "profile": "eu868"
    }
  },
  "recon": false,
  "ulp": false,
  "hdr": false,
  "emu": false,
  "plmli": false,
  "varmac": false
}

Persistent Configuration

Persistent configuration is stored on the card and survives reboots. This is the appropriate choice for production deployments.

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{
        "bs": {
            "bsi": {
                "uniqueBaseStationId": "9c-65-f9-ff-fe-61-07-c7",
                "baseStationName": "my-gateway-01",
                "serviceCenterAddr": "eu3.loriot.io",
                "serviceCenterPort": "727",
                "tlsAuthRequired": "false"
            },
            "mps": {
                "profile": "eu868"
            }
        },
        "persistent": true,
        "restart": true
    }' \
    http://172.30.1.2/cgi-bin/config | jq .

Temporary Configuration

Temporary configuration is lost when the card reboots. It is useful for testing or for dynamic configuration applied by the host at runtime.

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{
        "bs": {
            "bsi": {
                "uniqueBaseStationId": "9c-65-f9-ff-fe-61-07-c7",
                "serviceCenterAddr": "eu3.loriot.io",
                "serviceCenterPort": "727",
                "tlsAuthRequired": "false"
            },
            "mps": {
                "profile": "eu868"
            }
        },
        "persistent": false,
        "restart": true
    }' \
    http://172.30.1.2/cgi-bin/config | jq .

Note

Temporary configuration must be re-applied after every reboot.

Resetting Configuration

To discard any active temporary configuration and revert to the stored persistent configuration:

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{"bs": {}, "reset": true}' \
    http://172.30.1.2/cgi-bin/config | jq .

TLS Configuration

When tlsAuthRequired is "true", the BSSCI connection to the service center is authenticated and encrypted using TLS. mTLS is strongly recommended; provide all three of the following files:

File Description
root_ca.cer CA certificate used to verify the service center
bstation.cer Client certificate presented by the base station
bstation.key Private key corresponding to the client certificate

PEM files must be converted to single-line strings with literal \n sequences before embedding in JSON.

Use awk to convert each PEM file to a single-line string:

awk '{printf "%s\\n", $0}' root_ca.cer
CA=$(awk '{printf "%s\\n", $0}' root_ca.cer)
CERT=$(awk '{printf "%s\\n", $0}' bstation.cer)
KEY=$(awk '{printf "%s\\n", $0}' bstation.key)

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d "{
        \"bs\": {
            \"bsi\": {
                \"uniqueBaseStationId\": \"9c-65-f9-ff-fe-61-07-c7\",
                \"serviceCenterAddr\": \"eu3.loriot.io\",
                \"serviceCenterPort\": \"727\",
                \"tlsAuthRequired\": \"true\"
            },
            \"mps\": {
                \"profile\": \"eu868\"
            },
            \"tlsCaCert\": \"$CA\",
            \"tlsCert\": \"$CERT\",
            \"tlsKey\": \"$KEY\"
        },
        \"persistent\": true,
        \"restart\": true
    }" \
    http://172.30.1.2/cgi-bin/config | jq .

Certificate values truncated for readability:

{
  "bs": {
    "bsi": {
      "uniqueBaseStationId": "9c-65-f9-ff-fe-61-07-c7",
      "serviceCenterAddr": "eu3.loriot.io",
      "serviceCenterPort": "727",
      "tlsAuthRequired": "true"
    },
    "mps": {
      "profile": "eu868"
    },
    "tlsCaCert": "-----BEGIN CERTIFICATE-----\nMIIB....\n-----END CERTIFICATE-----\n",
    "tlsCert": "-----BEGIN CERTIFICATE-----\nMIIB....\n-----END CERTIFICATE-----\n",
    "tlsKey": "-----BEGIN PRIVATE KEY-----\nMIIE....\n-----END PRIVATE KEY-----\n"
  },
  "persistent": true,
  "restart": true
}

Important

The system time must be valid before applying TLS configuration. Set the time using /cgi-bin/time as described in Initial Setup.

Firmware Update

The card firmware can be updated in the field. The card downloads a RAUC bundle from an HTTP server and installs it. The bundle URL is passed in the update request and can point to any server reachable by the card.

If the host has internet access and NAT is configured as described above:

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d "{\"url\": \"https://miromico.ch/firmware/latest/gwc-62/raucb\", \"timeout\": 300}" \
    http://172.30.1.2/cgi-bin/update | jq .

If internet access is not available, serve the bundle file from the host system and use the host's USB interface address as the bundle URL:

cd /path/to/bundles/
python3 -m http.server 8080

Trigger the Update

Set the system time before triggering the update, then start the install:

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d "{\"datetime\": \"$(date -u +%Y-%m-%dT%H:%M:%S)\"}" \
    http://172.30.1.2/cgi-bin/time | jq .

Adjust the bundle URL as needed:

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{
        "url": "http://172.30.1.1:8080/mioty-bundle-am62xx-mioty-emmc.raucb",
        "timeout": 300
    }' \
    http://172.30.1.2/cgi-bin/update | jq .

Monitor Progress

The install runs in the background. Poll the status endpoint until the state is no longer installing:

curl -s http://172.30.1.2/cgi-bin/update-status | jq .
{
  "state": "installing",
  "progress": "63%",
  "step": "Copying image to rootfs.0",
  "error": "",
  "log": ""
}

Possible states are idle, installing, success, failed, and timeout. On failure, the error and log fields contain details from the installer.

Reboot

After a successful update, reboot the card to activate the new firmware:

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{"delay": 5}' \
    http://172.30.1.2/cgi-bin/reboot | jq .

The USB connection will drop during reboot. Wait 30-60 seconds, then use GET /cgi-bin/status to confirm the card is back online.

Diagnostics

The card exposes recent system journal entries via the errors endpoint. This is useful for verifying that the BS service has started and connected to the service center, or for diagnosing connection issues.

curl -s http://172.30.1.2/cgi-bin/errors | jq .

Fetch last 100 lines and show only entries with priority 0-3 (error and above):

curl -s "http://172.30.1.2/cgi-bin/errors?lines=100" | \
    jq '.entries[] | select(.priority | tonumber <= 3) | {unit, message}'
{
  "lines": 50,
  "entries": [
    {
      "time": "1742738400000000",
      "unit": "mioty-bsm.service",
      "priority": "6",
      "message": "Connected to eu3.loriot.io:727"
    }
  ]
}

Antenna Recommendation

The miro EdgeCard mioty has been certified using the 2JW1115-C952B 868/915 MHz ISM antenna from 2J Antennas. This antenna is ground-plane independent and suitable for enclosure mounting without a metal backplane.

The card exposes a U.FL connector. Connect the antenna using a U.FL to SMA adapter cable.

Antenna Selection

Reads or sets the antenna selection for RX and TX.

Note

This endpoint requires the recon interface to be enabled. Set "recon": true in the POST /cgi-bin/config request before using this endpoint.

Read

GET /cgi-bin/antsel

No request body required.

Field Type Description
rxant string Antenna selected for RX ("0" or "1")
txant string Antenna selected for TX ("0" or "1")

Set

POST /cgi-bin/antsel
Content-Type: application/json
Field Type Description
rxant integer Antenna selected for RX (0 or 1)
txant integer Antenna selected for TX (0 or 1)

At least one of rxant or txant must be provided.

Returns HTTP 200 with an empty JSON object {} on success.

Example Command

curl -s http://172.30.1.2/cgi-bin/antsel | jq .
{
    "rxant": "0",
    "txant": "1"
}
curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{"rxant": 0, "txant": 1}' \
    http://172.30.1.2/cgi-bin/antsel | jq .
{}