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.

MAC ID mode

The current firmware supports fixed MAC ID only. Variable MAC ID mode is available for testing purposes on request.

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.

Configure the host interface and enable IP forwarding:

ip addr add 172.30.1.1/24 dev <usb-interface>
echo 1 > /proc/sys/net/ipv4/ip_forward

Set up NAT so outbound traffic from the card is masqueraded through the host's WAN interface. Replace eth1 with the host's uplink interface and eth2 with the USB CDC ECM interface:

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

To make this configuration persistent across reboots, save the rules to /etc/iptables/iptables.rules:

*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",
  "kernel": "6.12.43-ti-gaf3896fca24b",
  "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 .
{
  "status": "ok",
  "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.

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 contains the configuration as a JSON object. Configuration can be applied as persistent (survives reboot) or temporary (lost on reboot).

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

The 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.

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 remove temporary configuration and revert to the 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 set to true, the connection to the service center is authenticated and encrypted. The following certificates and keys must be provided:

  • root_ca.cer -- TLS CA certificate
  • bstation.cer -- TLS client certificate
  • bstation.key -- TLS client private key

The certificates are passed as JSON string values in the bs config block. PEM files must be converted to single-line strings with literal \n sequences. Use the following awk command for each file:

awk '{printf "%s\\n", $0}' root_ca.cer

Include the converted values in the configuration request:

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 .

The resulting JSON will contain the certificate values in the following form (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 system has internet access and NAT is configured as described above, the bundle URL can point directly to the download server:

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 from the host system. Place the bundle file in a directory and start an HTTP server:

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

Then use the host's address on the USB interface as the bundle URL.

Trigger the Update

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

# Set time
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 .

# Trigger update (adjust 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.

# Fetch last 50 log lines (default)
curl -s http://172.30.1.2/cgi-bin/errors | jq .

# Fetch last 100 lines, show only errors (priority 0-3)
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.

Read

GET /cgi-bin/antsel

No request body required.

Field Type Description
status string ok or error
rxant integer Antenna selected for RX (0 or 1)
txant integer 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.

Field Type Description
status string ok or error
curl -s http://172.30.1.2/cgi-bin/antsel | jq .
{
    "status": "ok",
    "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 .
{
    "status": "ok"
}