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",
  "data_disk": { "total_kb": 5976896, "used_kb": 123456, "free_kb": 5853440 }
}

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.

In-field license changes are provided by Miromico as a separate process; the integrator does not call the license API directly to install or upgrade a license.

Operating Modes

The card operates in exactly one of three mutually exclusive modes at a time: BS, SC, or IQ. POST /cgi-bin/config selects the active mode by which top-level block (bs, sc, or iq) is present in the request body; applying one mode's configuration stops the previously active mode's services and starts the new one. Each mode requires the corresponding feature to be licensed (see License).

Mode Block Description
BS bs Base Station only. Connects to an external mioty Service Center over BSSCI.
SC sc Service Center + co-located Base Station, connected over loopback. This is the mode referenced as "NM Mode" in MQTT Transport Security.
IQ iq IQ stream tunneling server only. No Base Station or Service Center runs in this mode.

GET /cgi-bin/config returns the configuration for whichever mode is currently active.

Configuration Merge Behavior

Configuration is always persistent (survives reboot) and is merged field-by-field into whatever is already stored, rather than being replaced wholesale:

  • A parameter omitted from the request keeps its last stored value.
  • A parameter present with a value overwrites the stored value.
  • A parameter explicitly set to "" (empty string) clears it.
  • An empty mode block (e.g. "bs": {}) is therefore a no-op on stored configuration -- it just activates that mode without changing anything already configured.

This applies to every mode block (bs, sc, iq) and to the feature flags described below.

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 is merged into whatever is already stored -- see Configuration Merge Behavior. Omitting a parameter leaves its current value unchanged; submit only the fields you want to change.

Parameter Reference

Top-level request fields

Field Type Default Description
bs object Configuration block. Contains bsi, mps, and optional TLS fields. Merged into whatever is already stored.
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 erases the stored BS configuration back to factory defaults and activates BS mode. Mode block must be empty ("bs": {}).

bs.bsi Base Station Identity

Field Type Default Description
uniqueBaseStationId string 00-00-00-00-00-00-00-00 EUI-64 identifier. Accepts 16 plain hex digits, hyphen-separated, or colon-separated (case-insensitive) -- normalized to lowercase hyphen-separated form. 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. Flags merge the same way as everything else: omitting a flag keeps its last stored value, and only an explicitly-present flag changes it. There is no way to "unset" a flag back to a default state distinct from false -- send false to turn it off.

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. It may be submitted as 16 plain hex digits, hyphen-separated, or colon-separated octets (case-insensitive) -- the device normalizes it to lowercase hyphen-separated form before storing it. Any other form (wrong length, non-hex characters) is rejected with HTTP 400.

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 (any of 9c65f9fffe6107c7, 9C:65:F9:FF:FE:61:07:C7, or 9c-65-f9-ff-fe-61-07-c7 are equally accepted).

Read Current Configuration

Use GET /cgi-bin/config to read back the active BS configuration -- either what's been explicitly configured, or the factory default if BS has never been configured.

Warning

If bsi.tlsKey is set, GET never returns it in full -- it's replaced with a short prefix...suffix fingerprint (e.g. MIIJQQ...VUmZncz), enough to confirm the right key is loaded without exposing it over the API. Re-POSTing a GET response verbatim without editing it would therefore overwrite the real stored key with that fingerprint -- omit tlsKey from a request entirely unless you are actually setting a new one.

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
}

Apply Configuration

Configuration is stored on the card and survives reboots. Submit only the fields you want to set or change -- anything omitted keeps its current value (see Configuration Merge Behavior).

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"
            }
        },
        "restart": true
    }' \
    http://172.30.1.2/cgi-bin/config | jq .

Changes only serviceCenterPort; every other already-stored field is left untouched.

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

Resetting Configuration

To erase the stored BS configuration and revert to factory defaults:

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\",
                \"tlsCaCert\": \"$CA\",
                \"tlsCert\": \"$CERT\",
                \"tlsKey\": \"$KEY\"
            },
            \"mps\": {
                \"profile\": \"eu868\"
            }
        },
        \"restart\": true
    }" \
    http://172.30.1.2/cgi-bin/config | jq .
{
  "message": "BSM config applied"
}

Important

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

SC Configuration

SC mode runs a mioty Service Center together with a co-located Base Station that connects to it over loopback, forwarding endpoint data to an application via MQTT. Selecting SC mode stops BS mode (and vice versa) -- see Operating Modes.

The SC is configured via POST /cgi-bin/config. The request body is a JSON object with an sc block containing the configuration and the same top-level control flags (restart, reset) as BS Configuration. The bs feature flags (varmac, ulp, hdr, emu, plmli, recon) also apply here, controlling the co-located BS instance -- they share storage with standalone BS mode, so setting a flag from either mode affects the other.

Parameter Reference

sc.ac Application Center

Field Type Default Description
mqttBrokerAddr string 127.0.0.1 Hostname or IP address of the MQTT broker.
mqttBrokerPort string 1883 TCP port of the MQTT broker.
uniqueAppCenterId string 00-00-00-00-00-00-00-00 EUI-64 identifier for the application center, hyphen-separated.

sc.sc Service Center Settings

Field Type Default Description
tlsAuthRequired string "false" Set to "true" to enable TLS for the MQTT connection.

Local vs. Remote MQTT Broker

Whether the card runs its own MQTT broker or connects to an external one is inferred from the merged sc.ac.mqttBrokerAddr -- i.e. after the request is merged into whatever is already stored -- not from whether the request's sc block happens to be empty:

  • Empty or loopback (mqttBrokerAddr unset, "", or 127.0.0.1): the card runs a local MQTT broker, reachable at 172.30.1.2:1883 from the host. Use this when the application consuming endpoint data runs on the host system.
  • Any other address: the card connects to the external broker configured under sc.ac. Use this to forward endpoint data directly to a broker elsewhere on the network.

Because this is based on the merged result, "sc": {} after a remote broker has already been configured does not revert to local -- it's a no-op that leaves the remote configuration in place (see Configuration Merge Behavior). To revert to a local broker, explicitly clear the ac fields:

{ "sc": { "ac": { "mqttBrokerAddr": "", "mqttBrokerPort": "", "uniqueAppCenterId": "" } } }

Read Current Configuration

Use GET /cgi-bin/config while SC mode is active to read back the current configuration.

curl -s http://172.30.1.2/cgi-bin/config | jq .
{
  "sc": {
    "ac": {
      "mqttBrokerAddr": "127.0.0.1",
      "mqttBrokerPort": "1883",
      "uniqueAppCenterId": "00-00-00-00-00-00-00-00"
    },
    "sc": {
      "tlsAuthRequired": "false"
    }
  },
  "recon": false,
  "ulp": false,
  "hdr": false,
  "emu": false,
  "plmli": false,
  "varmac": false
}

Local Broker

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

Remote Broker

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{
        "sc": {
            "ac": {
                "mqttBrokerAddr": "mqtt.example.com",
                "mqttBrokerPort": "8883",
                "uniqueAppCenterId": "9c-65-f9-ff-fe-61-07-c7"
            }
        },
        "restart": true
    }' \
    http://172.30.1.2/cgi-bin/config | jq .

Revert to Local Broker

After a remote broker has been configured, "sc": {} alone is a no-op (see Local vs. Remote MQTT Broker) -- explicitly clear the ac fields to revert:

curl -s -X POST \
    -H "Content-Type: application/json" \
    -d '{
        "sc": {
            "ac": {
                "mqttBrokerAddr": "",
                "mqttBrokerPort": "",
                "uniqueAppCenterId": ""
            }
        }
    }' \
    http://172.30.1.2/cgi-bin/config | jq .

Resetting Configuration

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

IQ Configuration

IQ mode runs only the IQ stream tunneling server -- no Base Station or Service Center. Selecting IQ mode stops BS/SC mode (and vice versa) -- see Operating Modes.

The IQ stream server is configured via POST /cgi-bin/config with an iq block.

Parameter Reference

Field Type Default Description
port integer 9000 TCP port the IQ stream server listens on.

port merges like every other field: omitting it (posting {}) keeps whatever port is already stored, rather than resetting to 9000. To explicitly reset the port back to 9000, send it as "".

Read Current Configuration

curl -s http://172.30.1.2/cgi-bin/config | jq .
{
  "iq": {
    "port": 9000
  }
}

Configure Custom Port

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

Resetting Configuration

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

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. progress and step are empty strings ("") whenever no percentage is available yet -- not a placeholder "0%" -- which includes every state other than installing, and briefly during installing itself before the first percentage is reported.

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 .
{}