1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cisco Spacesに頼らずCatalyst 9800でBLEセンサーを読む: Sensor ConnectとMQTT/protobuf decode入門

1
Last updated at Posted at 2026-06-03

Cisco Spacesに頼らずCatalyst 9800でBLEセンサーを読む: Sensor ConnectとMQTT/protobuf decode入門

はじめに

Cisco Catalyst 9800 Wireless Controller と Cisco AP の BLE radio を使い、Cisco Spaces の cloud firehose に依存せずに BLE センサーの値を取得することを試みたので、記事として残します。

Cisco のドキュメントでは、このソリューション全体は Cisco Sensor Connect for IoT Services、9800 上に deploy する IOx application / UI / API endpoint は IoT Orchestrator と説明されています。そのため、この記事では運用上の表示名を Cisco Sensor Connect (IoT Orchestrator) として扱います。

拙作 home-metrics の内部名は実装経緯と endpoint の役割が分かるように cisco_iot_orchestrator / CISCO_IOT_ORCH_* のまま維持しています。

Cisco Sensor Connect for IoT Services とは何か

Cisco Sensor Connect for IoT Services は、Cisco AP の BLE radio を BLE gateway として使い、BLE device の onboarding、control、telemetry 取得を行うための仕組みです。key component は IoT Orchestrator で、これは Cisco Catalyst 9800 Wireless Controller 上に deploy する Cisco IOx application として動作します。

ざっくり言うと、以下のような役割を持ちます。

役割 内容
BLE onboarding SCIM API で BLE device を IoT Orchestrator に登録する
BLE control connect / disconnect / GATT read / write / subscribe などを control API で扱う
BLE telemetry AP が scan した advertisement や GATT notification を MQTT で data app に渡す
policy / inventory IoT Orchestrator UI で AP inventory、BLE inventory、scan policy、allow-list などを見る

重要なのは、Sensor Connect が「センサー値をすべて標準 JSON に変換してくれるサービス」ではないことです。少なくとも今回の Minew S1 のような advertisement 型 sensor では、MQTT payload の中に BLE advertisement の raw bytes が protobuf binary として届き、その中身をアプリケーション側で decode する必要がありました。

公式資料では、cloud-based IoT solution が使いにくい環境、たとえば air-gapped requirement、internet connectivity が不安定な環境、data sovereignty の要件がある環境での利用シナリオが説明されています。つまり、Cisco Spaces の cloud 経由 firehose ではなく、9800 と AP の既存 wireless infrastructure を使って on-prem に近い形で BLE data path を作れるのが特徴です。

Cisco Spaces との違い

Cisco Spaces の IoT Service / Firehose API と、Cisco Sensor Connect (IoT Orchestrator) は混同しやすいです。どちらも Cisco AP を BLE gateway として活用しますが、data path と運用責任がかなり違います。

観点 Cisco Spaces / IoT Service Cisco Sensor Connect (IoT Orchestrator)
主な data path AP/WLC から Cisco Spaces cloud / Connector へ telemetry を送る AP から WLC 上の IoT Orchestrator へ送り、MQTT で外部 app が受ける
依存先 Cisco Spaces cloud、Connector、Firehose API Catalyst 9800 上の IoT Orchestrator、MQTT、SCIM/control API
受けるデータ Firehose API の event / telemetry JSON protobuf binary に入った BLE advertisement / event
device onboarding Cisco Spaces 側の workflow に寄る SCIM API で app 側から登録
data app 管理 Cisco Spaces 側の app / firehose subscription IoT Orchestrator の data app / topic / subscribe
向いている用途 cloud 連携、Cisco Spaces の location/IoT service と合わせた運用 cloud に依存しにくい構成、独自 app で BLE raw data を扱いたい構成

Cisco Spaces より単純になるというより、cloud 側のブラックボックスを減らす代わりに、アプリケーション側で API call と decode を持つ と考えるのがよいです。

また、Cisco Spaces では IoT Marketplace のセンサーのみがオンボード可能ですが、Cisco Sensor Connect (IoT Orchestrator)ではそのような制限がないというのも1つの特長だと思います。

Catalyst 9800 Wireless Controller へ設定が必要なこと

Catalyst 9800 側で行うことは、大きく分けると次の 2 段階になります。

  1. Day-0: IoT Orchestrator application を Catalyst 9800 上に deploy して起動します。
  2. Day-1: IoT Orchestrator から WLC/AP を管理し、AP の BLE radio と外部 application をつなぎます。

Cisco Software Download の Spaces Orchestrator Software セクションから IoT Orchestrator の image をダウンロードし、WLC Web UI の Configuration > Services > IoT Services から deploy します。
内部的には IOx container を Catalyst 9800 上で動かす構成ですが、通常の app-hosting CLI や IOx Web UI で手作業するものではありません。Cisco の Quick Start Guide でも、IoT Services UI から Day-0 / Day-1 operation を行う前提になっています。

Day-0 で決める一番重要な値は、IoT Orchestrator container の IP address と、その default gateway になる WLC 側 VirtualPortGroup の IP address です。今回の検証では、NAT ではなく上位スイッチで routing する構成にしました。

Catalyst 9800 / WLC側 VirtualPortGroup: 192.168.67.5/30
IoT Orchestrator container:            192.168.67.6/30

IoT Orchestrator の IP address は、Catalyst 9800 の既存 interface や他の network と重複しないものを選ぶ必要があります。/30 にすると、WLC 側 gateway と container 側 IP の関係が明確で、上位 network へ route を出す範囲も小さくできます。

この /30 は、次の 3 方向から到達できる必要があります。

通信元 通信先 必要な理由
管理端末 / application server IoT Orchestrator :8081 UI、SCIM API、control API を呼び出す
AP IoT Orchestrator :43626, :50221 AP から BLE advertisement / inventory / control path を IoT Orchestrator に送る
data receiver app IoT Orchestrator :41883 MQTT broker に接続して topic を subscribe する

今回の例では、上位 switch で 192.168.67.4/30 を routing し、管理端末、AP subnet、拙作application から 192.168.67.6 に到達できるようにしました。WLC と AP が同じ管理 network に見えていても、AP から IoT Orchestrator container IP へ到達できなければ BLE advertisement は MQTT まで流れません。

NAT でも構成可能とされていますが、今回は routing 構成で検証しました。NAT 構成では AP から IoT Orchestrator へ向かう port と、外部 app から REST/MQTT に向かう port の見え方が複雑になります。最初の検証では、IoT Orchestrator の IP address をそのまま routing した方が切り分けしやすいです。

Catalyst 9800 WLC で deploy が完了すると、以下のように表示されます。

Screenshot 2026-06-04 at 18.16.49.png

Day-0 で確認することは以下です。

項目 確認内容
IoT Services deployment IoT Orchestrator image を upload し、application が Running になっているか
IP address VirtualPortGroup 側 IP と IoT Orchestrator IP が重複していないか
UI access 管理端末から https://<orchestrator-ip>:8081 にアクセスできるか
initial login IoT Orchestrator UI に login し、初期 wizard を完了できるか
route 上位 switch から Orchestrator の /30 に到達できるか

Day-1 で確認することは以下です。

項目 確認内容
WLC registration IoT Orchestrator UI から Catalyst 9800 WLC を登録・接続できているか
AP Inventory 対象 AP が Connected として見えるか
AP version / platform 対象 AP が Sensor Connect 対応 platform / software version か
IoT Radio Mode 対象 AP が Scan になっているか
BLE allow-list / scan policy 対象 MAC prefix/full MAC が filter で落ちていないか
BLE live log 対象 BLE MAC の advertisement が見えるか
Data App Topics registerDataApp / registerTopic 後に topic が見えるか
MQTT data app credential で :41883 に接続できるか

今回の検証では、AP Inventory で AP が connected、IoT Radio Mode が Scan、BLE live log に Minew S1 の MAC 00:FA:B6:07:DE:4B が見えるところまで確認してから、SCIM onboarding と data app / topic 登録に進みました。

切り分けの順番としては、以下が分かりやすかったです。

  1. 管理端末から IoT Orchestrator UI/API :8081 に到達できることを確認します。
  2. AP から IoT Orchestrator へ到達でき、AP Inventory が connected になることを確認します。
  3. BLE live log で対象 sensor の advertisement が見えることを確認します。
  4. SCIM onboarding で device ID を取得します。
  5. registerDataAppregisterTopicsubscribe を実行します。
  6. data app credential で MQTT :41883 に接続し、payload が届くことを確認します。

ここで 1 から 3 が通っていなければ WLC/AP/route/policy 側の問題、4 以降で詰まるなら application ID、API key、topic、subscribe の問題として切り分けやすいです。

WLCの登録画面
Screenshot 2026-06-04 at 18.14.23.png

WLCから対応するAPの一覧と状態が表示される
Screenshot 2026-06-04 at 18.14.34.png

参考:

先に今回のポイント

今回の構成で重要だった点は、細かい decode の前に次の 3 つでした。

  1. app ID は onboardcontroldata を別々に作る必要がありました。
  2. API call は SCIM onboarding、control API による data app / topic 登録、data app による MQTT subscribe の順で揃える必要がありました。
  3. BLE には scan と connect がありますが、Minew S1 は scan で見える advertisement だけで temperature / humidity / battery を取得できました。

最初に詰まったのは 1 つ目で、IoT Orchestrator の UI では同じ app ID で 3 つの役割を持てるように見えますが、実際にはそれぞれで別の app ID を作成しないと API Key が作成されません。

2 つ目は、SCIM onboarding が「device を IoT Orchestrator の管理対象に入れる」処理であり、「その device の telemetry を data app に流す」処理ではないことです。onboard 後に、control API で data app と topic を登録し、その topic に BLE device を subscribe させる必要があります。

3 つ目は BLE の動作モードです。BLE sensor data の取得方法には大きく scan と connect があります。scan は advertisement を受信するだけで、connect は GATT read / notify のために AP から BLE device へ接続します。今回の Minew S1 は scan-only の扱いでよく、GATT connect は不要でした。

参考:

今回作ったもの

Cisco Sensor Connect for IoT Services (IoT Orchestrator) からデータ取得を行い、すでに作成してあった拙作 home-metrics の collector に追加しました。
今回は主に Minew S1 のセンサー実機データをもとに、必要になった項目をまとめていきます。

構成は次の通りです。

BLE sensor
  -> Cisco AP BLE radio
  -> Catalyst 9800 上の IoT Orchestrator
  -> MQTT broker :41883
  -> hm-cisco-iot-orchestrator-collector
  -> PostgreSQL / TimescaleDB sensor_minute
  -> home-metrics Web UI

IoT Orchestrator の REST API は :8081、MQTT は :41883 を使います。AP から IoT Orchestrator へは 43626/tcp50221/tcp が必要になります。

scan と connect の違い

BLE の値取得には、主に次の 2 種類があります。

種類 使うもの 特徴 今回
scan BLE advertisement device に接続せず、AP が聞こえた advertising packet を拾う Minew S1 はこれ
connect GATT read / notify AP が BLE device に接続して characteristic を読む 今回は使わない

Minew S1 のように advertisement に温度、湿度、battery が入っている sensor は scan だけでよいです。connect を使わないため、AP の BLE active connection 数、GATT service discovery、notify subscription の安定性に依存しません。

IoT Orchestrator の UI では AP の IoT Radio Mode が Scan になっていることを確認しました。BLE Inventory に device が出ない場合でも、Data App / Topic / Subscribe が未設定だと MQTT へ出てこないため、scan policy と data path は分けて切り分けた方がよいです。

Minew S1 実機では、IoT Orchestrator の Orchestrator debug live log に次のような response が出ました。

2026-06-02T15:14:16.820 ... [00:FA:B6:07:DE:4B] Onboarded:
  AP = 84:5a:3e:d6:b7:80
  BLE = 00:FA:B6:07:DE:4B
  RSSI = -68
  TIME = 2026-06-02 15:14:16.82
  advData = 0201061B166AFE03050610FF2098041103FFFF041600FFFF03133A1A02123B

2026-06-02T15:14:16.821 ... [00:FA:B6:07:DE:4B]
  Sending the advertisement of deviceId: d96231e7-13b4-4ccd-bafa-ec5c60b95c88
  to topics: [ioslab/home-metrics/ble/advertisements/v1]

この時点で、AP は Minew S1 の advertisement を scan できており、IoT Orchestrator は deviceId と topic の対応を使って MQTT 側へ送ろうとしていることが分かります。

アプリケーション ID は分けた

最終的には、用途ごとに app ID と API key を分けました。

Screenshot 2026-06-04 at 18.14.58.png

CISCO_IOT_ORCH_ONBOARD_APP_ID=onboard
CISCO_IOT_ORCH_ONBOARD_API_KEY=...

CISCO_IOT_ORCH_CONTROL_APP_ID=control
CISCO_IOT_ORCH_CONTROL_API_KEY=...

CISCO_IOT_ORCH_DATA_APP_ID=data
CISCO_IOT_ORCH_DATA_API_KEY=...

CISCO_IOT_ORCH_TOPIC=ioslab/home-metrics/ble/advertisements/v1

最初は同じ app ID を使い回せると思っていましたが、Data App Topics に期待した topic が出ませんでした。少なくとも今回の環境では、onboarding / control / data receiver を分ける必要がある挙動でした。

役割は次のように整理しました。

app ID 用途 使う API / protocol
onboard BLE device を IoT Orchestrator に登録する SCIM
control data app / topic / subscribe を登録する Control API
data MQTT broker に接続して telemetry を受ける MQTT

API key 認証では IoTOrchestrator自体は証明書認証もサポートしていますが今回は API Keyをつかうので、 header は X-API-Key を使います。

SCIM onboarding の挙動

BLE device の登録は SCIM API で行います。

参考:

SCIM の endpoint は次です。

POST /scim/v2/Devices

BLE device extension では MAC address と random address かどうかを渡します。今回対象にしたセンサーは public MAC として扱うため isRandom: false にしました。

概念的にはこのような payload になります。

{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:Device",
    "urn:ietf:params:scim:schemas:extension:ble:2.0:Device",
    "urn:ietf:params:scim:schemas:extension:endpointapps:2.0:Device"
  ],
  "deviceDisplayName": "Living",
  "adminState": true,
  "urn:ietf:params:scim:schemas:extension:ble:2.0:Device": {
    "deviceMacAddress": "00:FA:B6:07:DE:4B",
    "isRandom": false,
    "mobility": true,
    "pairingMethods": [
      "urn:ietf:params:scim:schemas:extension:pairingNull:2.0:Device"
    ]
  },
  "urn:ietf:params:scim:schemas:extension:endpointapps:2.0:Device": {
    "onboardingApp": "onboard",
    "controlApp": "control"
  }
}

SCIM の response で IoT Orchestrator 側の device ID が返ります。この ID が後続の topic 登録や subscribe で必要になります。

SCIM onboarding が成功したときの response は、SCIM device resource として返ります。実際には version や設定によって field の有無が変わりますが、後続処理で最も重要なのは id です。

{
  "schemas": [
    "urn:ietf:params:scim:schemas:core:2.0:Device",
    "urn:ietf:params:scim:schemas:extension:ble:2.0:Device",
    "urn:ietf:params:scim:schemas:extension:endpointapps:2.0:Device"
  ],
  "id": "5ae0c228-24c9-433c-9001-5a23f063ffd0",
  "deviceDisplayName": "Living",
  "adminState": true,
  "meta": {
    "resourceType": "Device",
    "location": "/scim/v2/Devices/5ae0c228-24c9-433c-9001-5a23f063ffd0"
  },
  "urn:ietf:params:scim:schemas:extension:ble:2.0:Device": {
    "deviceMacAddress": "00:FA:B6:07:DE:4B",
    "isRandom": false
  },
  "urn:ietf:params:scim:schemas:extension:endpointapps:2.0:Device": {
    "onboardingApp": "onboard",
    "controlApp": "control"
  }
}

この例では 5ae0c228-24c9-433c-9001-5a23f063ffd0 が IoT Orchestrator 側の device ID です。以降の registerTopic や subscribe では、BLE MAC ではなくこの id を渡す場面があります。

重要なのは、SCIM onboarding だけでは MQTT にデータは流れない ことです。BLE Inventory に device が見えても、Data App / Topic / Subscribe が揃っていないと collector 側には何も来ません。

SCIM onboarding が成功すると、IoT Orchestrator にも BLEデバイスの一覧が表示されます
Screenshot 2026-06-04 at 18.14.42.png

Data App と topic の登録

データを受けるには、少なくとも次の順番で揃える必要がありました。

  1. BLE device を SCIM で onboard します。
  2. Data receiver application を登録します。
  3. advertisement topic を登録します。
  4. BLE device を topic に subscribe します。
  5. Data app ID / API key で MQTT broker に接続して subscribe します。

参考:

Data app 登録は次の endpoint です。

POST /control/registration/registerDataApp

Cisco の guide にある形に合わせて、home-metrics では次の payload を送ります。

{
  "dataApps": [
    {
      "dataAppID": "data"
    }
  ],
  "topic": "ioslab/home-metrics/ble/advertisements/v1",
  "dataFormat": "default",
  "controlApp": "control"
}

成功時の response は、DevNet の schema では statustopic が返る形になっています。

{
  "status": "SUCCESS",
  "topic": "ioslab/home-metrics/ble/advertisements/v1"
}

ここで statusSUCCESS であることと、topic が意図した topic 名で返っていることを確認します。HTTP status code だけでなく response body を見るのが重要でした。

Topic 登録は次の endpoint で行います。

POST /control/registration/registerTopic

DevNet の Register a Topic では、SCIM onboarding response で得た device ID、control app、topic、data format、BLE event type を渡します。advertisement を受ける場合は ble.typeadvertisements にします。

payload の例は次のようになります。

{
  "technology": "ble",
  "id": "5ae0c228-24c9-433c-9001-5a23f063ffd0",
  "controlApp": "control",
  "ids": [
    "5ae0c228-24c9-433c-9001-5a23f063ffd0"
  ],
  "topic": "ioslab/home-metrics/ble/advertisements/v1",
  "dataFormat": "default",
  "ble": {
    "type": "advertisements",
    "serviceID": "fe6a",
    "characteristicID": "fe6a"
  }
}

serviceID / characteristicID は GATT の read / notify では実際の service / characteristic UUID を指します。今回の Minew S1 は scan-only の advertisement を使うため、アプリケーション側で見るべき UUID は BLE advertisement 内の Service Data UUID 0xfe6a です。環境によって advertisements でも schema 上これらの field を要求されるため、API の実挙動と DevNet schema に合わせて指定します。

成功時の response は registerDataApp と同じく、topic 登録の結果を示します。

{
  "status": "SUCCESS",
  "topic": "ioslab/home-metrics/ble/advertisements/v1"
}

この response が返っても、まだ MQTT で data が流れるとは限りません。次に対象 BLE device を topic に subscribe させる必要があります。

Subscribe は次の endpoint で行います。

POST /control/data/subscribe

成功時の response は、対象 device ID と request ID を含みます。

{
  "status": "SUCCESS",
  "id": "5ae0c228-24c9-433c-9001-5a23f063ffd0",
  "requestID": "ebfd7e14-d4b7-4aa4-97f9-2dfaffed7c08"
}

Device Topics
Screenshot 2026-06-04 at 18.19.41.png

DataApp Topics
Screenshot 2026-06-04 at 18.20.08.png

ここまで揃ってから、data app ID / data app API key で MQTT broker に接続して topic を subscribe します。SCIM onboarding、Data App 登録、Topic 登録、Device subscribe のどこかが欠けると、AP が BLE advertisement を scan できていても MQTT payload は届きません。

実装箇所:

この処理は CISCO_IOT_ORCH_REGISTER_DATA_APP=true のとき collector 起動時に実行するようにしました。通常運用では毎回登録する必要はないため、false で起動できます。

BLE UUID の指定方法

Cisco Sensor Connect 側の register topic では、BLE の topic type として advertisements / gatt / connection_events があります。今回は connectionless advertisement を MQTT へ流したいので、advertisements に寄せました。

Minew S1 は scan だけで sensor data が出るため、topic は advertisement 用にします。GATT read / notify の UUID を登録する方向ではなく、advertisement payload 内の Service Data UUID をアプリケーション側で decode する方向にしました。

一方で、センサー値そのものは BLE advertisement の AD structure から読みます。今回の collector は AD type 0x16、つまり Service Data - 16-bit UUID を見ています。

実装では次の 16-bit UUID を許可しています。

uuid == 0xfe6a || uuid == 0xffe1 || uuid == 0xfeaa

BLE advertisement 上の 16-bit UUID は little endian で入るため、payload 先頭の 2 byte を binary.LittleEndian.Uint16 で読みます。service UUID の 2 byte を取り除いた残りが、センサー固有の service data になります。

Minew S1 実機の温度・湿度 frame では、advertisement 全体は次のような byte 列でした。

02 01 06 1b 16 6a fe 03 05 06 10 ff 20 98 04 11 03 ff ff 04 16 00 ff ff 03 13 3a 1a 02 12 3b

AD structure として分解すると次の通りです。

bytes 意味
02 01 06 Flags
1b 次の AD structure の length
16 AD type: Service Data - 16-bit UUID
6a fe 16-bit UUID 0xfe6a little endian
03 05 06 10 ff 20 98 04 11 03 ff ff 04 16 00 ff ff 03 13 3a 1a 02 12 3b Minew S1 service data

0xfe6a は little endian なので packet 上は 6a fe として出ます。decode ではこの 2 byte を外し、残りを sensor payload として扱います。

同じ Minew S1 でも battery は別 frame として出ていました。

02 01 06 11 16 6a fe 02 80 02 06 53 04 32 35 5a 69 30 30 30 32

この frame では service data の 02 80 02 ... を battery payload として扱い、0x5383% として decode しました。

実装箇所:

別の sensor family を追加する場合は、まず serviceDataFromAdvertisement の UUID allow list を増やし、次に decodeServiceData に payload decode を追加します。

MQTT に push される内容

IoT Orchestrator の MQTT payload は JSON ではなく protobuf 形式の binary data でした。

Cisco の Data Telemetry guide では、telemetry message は DataBatch に encapsulate され、その中に複数の DataSubscription が入ると説明されています。DataSubscription には device ID、BLE payload、subscription 情報、BLE advertisement の MAC / RSSI などが入ります。

home-metrics では外部の protobuf 生成コードを持たず、必要な field だけを読む小さな decoder にしました。

読んでいる主な field は次です。

field 内容 home-metrics での扱い
DataBatch field 1 repeated DataSubscription message list として展開
DataSubscription field 1 device_id BLE MAC が無い場合の fallback
DataSubscription field 2 data bytes BLE advertisement payload
DataSubscription field 3 timestamp sensor_minute.ts の元時刻
DataSubscription field 4 AP MAC 現状は保存しない
DataSubscription field 12 BLEAdvertisement BLE MAC と RSSI
DataSubscription field 16 ApplicationEvent restart/stop event の decode 用

Minew S1 00:fa:b6:07:de:4b の実機ログをもとに home-metrics の test data を作ると、MQTT で受け取る protobuf binary data は次のような hex になります。

0a7d0a2464393632333165372d313362342d346363642d626166612d656335633630623935633838121f0201061b166afe03050610ff2098041103ffff041600ffff03133a1a02123b1a0608c8e6fbd006221138343a35613a33653a64363a62373a383062190a1130303a46413a42363a30373a44453a344210bcffffff0f

これは 127 bytes の DataBatch として扱っています。field を分解すると次のようになります。

protobuf path hex decode
DataBatch[1] 0a7d ... length 125 の DataSubscription
DataSubscription.device_id 0a24 643936... d96231e7-13b4-4ccd-bafa-ec5c60b95c88
DataSubscription.data 121f 0201061b166afe... BLE advertisement raw bytes
DataSubscription.timestamp.seconds 1a06 08c8e6fbd006 1780413256 = 2026-06-02T15:14:16Z
DataSubscription.ap_mac 2211 38343a3561... 84:5a:3e:d6:b7:80
DataSubscription.ble_advertisement.mac 6219 0a11 30303a46413a42363a30373a44453a3442 ... 00:FA:B6:07:DE:4B
DataSubscription.ble_advertisement.rssi 10bcffffff0f -68 dBm

RSSI は protobuf の varint として入ってきますが、実装側では int32 として扱うことで 0xffffffbc-68 として解釈しています。

この protobuf を 拙作home-metrics の decoder に通すと、まず次の中間表現になります。

DeviceID: d96231e7-13b4-4ccd-bafa-ec5c60b95c88
BLEMAC: 00:FA:B6:07:DE:4B
APMAC: 84:5a:3e:d6:b7:80
RSSI: -68
Timestamp: 2026-06-02T15:14:16Z
Data: 02 01 06 1b 16 6a fe 03 05 06 10 ff 20 98 04 11 03 ff ff 04 16 00 ff ff 03 13 3a 1a 02 12 3b

その後、Data を BLE advertisement としてさらに decode します。ここで初めて temperature / humidity / battery が出てきます。

mac: 00:fa:b6:07:de:4b
rssi_dbm: -68
temperature_c: 26.23
humidity_percent: 59

別の battery frame を受けると、同じ minute window に battery_percent: 83 が入ります。

つまり MQTT payload の段階では「Cisco が温度や湿度を JSON でくれる」のではなく、protobuf の中に BLE advertisement raw bytes が入っており、その中の service data をアプリケーション側で読む必要があります。

MQTT server 側で最低限必要な設定は、IoT Orchestrator に data app と topic が登録され、対象 device がその topic に subscribe されていることです。外部に Mosquitto などを別途立てるのではなく、IoT Orchestrator の MQTT broker :41883 に data app credential で接続します。

今回の最小構成は次です。

host: 192.168.67.6
port: 41883
username: data
password: <data app api key>
topic: ioslab/home-metrics/ble/advertisements/v1
payload: default format / protobuf binary

home-metrics collector では、最低限次の env が必要になります。

CISCO_IOT_ORCH_MQTT_ADDR=192.168.67.6:41883
CISCO_IOT_ORCH_DATA_APP_ID=data
CISCO_IOT_ORCH_DATA_API_KEY=<data app api key>
CISCO_IOT_ORCH_TOPIC=ioslab/home-metrics/ble/advertisements/v1

mosquitto で確認するなら、概念的には次の形になります。

mosquitto_sub -h 192.168.67.6 -p 41883 \
  -u data --pw '<data app api key>' \
  -t 'ioslab/home-metrics/ble/advertisements/v1' -v

実装では MQTT 3.1.1 の CONNECT / SUBSCRIBE / PINGREQ / PUBLISH だけを処理する軽量 client にしました。

decode の方法

今回の BLE センサーは、Cisco Sensor Connect が値を解釈して JSON 化してくれるわけではありません。MQTT で受けた data bytes から、アプリケーション側で sensor vendor の advertisement format を decode する必要があります。

処理の流れは次です。

MQTT payload
  -> protobuf-like DataBatch を読む
  -> DataSubscription.data を取り出す
  -> BLE advertisement の AD structure を走査
  -> AD type 0x16 の service data を取り出す
  -> service UUID 2 byte を外す
  -> sensor family ごとに値を decode
  -> 1分 window に集約
  -> median を sensor_minute に upsert

Minew S1 実機では、温度・湿度と battery が別々の advertisement frame として届きました。

実際のサンプルとして、00:fa:b6:07:de:4b の Minew S1 は次のように decode できます。

BLE MAC: 00:fa:b6:07:de:4b
advertisement: 02 01 06 1b 16 6a fe 03 05 06 10 ff 20 98 04 11 03 ff ff 04 16 00 ff ff 03 13 3a 1a 02 12 3b
service UUID: 0xfe6a
service data: 03 05 06 10 ff 20 98 04 11 03 ff ff 04 16 00 ff ff 03 13 3a 1a 02 12 3b

温度・湿度 frame の重要な marker は次です。

marker bytes decode
temperature 03 13 3a 1a 0x1a3a / 256 = 26.23 C
humidity 02 12 3b 0x3b = 59%

battery frame は別の advertisement として届きました。

advertisement: 02 01 06 11 16 6a fe 02 80 02 06 53 04 32 35 5a 69 30 30 30 32
service UUID: 0xfe6a
service data: 02 80 02 06 53 04 32 35 5a 69 30 30 30 32
battery: 0x53 = 83%

このサンプルは、home-metrics では次の値として sensor_minute に入ります。

mac: 00:fa:b6:07:de:4b
temperature_c: 26.23
humidity_percent: 59
battery_percent: 83

対応する test は TestDecodeMinewS1ScanOnlyAdvertisementSample として追加しています。

なお、拙作 home-metrics では、環境センサー系は過去の ble-scan で直接 BLE を読んだ実装と PostgreSQL の過去データを照合し、marker で値を拾う形にしました。

03 13 <temp little-endian int16 / 256>
02 12 <humidity>
03 05 17 <pressure little-endian float32>
04 1f 07 <co2 little-endian uint16>
04 1f 08 <etvoc little-endian uint16>
03 20 <lux little-endian uint16>

また、decode 後は異常値を捨てる実装にしています。

metric accepted range
temperature -40 .. 85 C
humidity 0 .. 100 %
battery 0 .. 100 %
RSSI -127 .. 20 dBm
pressure 300 .. 1100 hPa
CO2 0 .. 10000 ppm
lux 0 .. 65534
eTVOC 0 .. 60000

DB には raw telemetry は保存しません。sensor_minute に 1分単位の median として保存しています。

この読み取ったデータを拙作 home-metrics では内部で postgresql に保存し時系列データとして出す事が可能です。

Screenshot 2026-06-04 at 18.20.46.png

API call でひっかかった点

Data App Topics は UI では作れなかった

IoT Orchestrator UI の Data App Topics は確認には使えますが、今回の環境では作成操作は API 側で行う必要がありました。registerDataAppregisterTopicsubscribe を明示的に実行します。

registerDataApp の payload 形

DevNet のサンプルでは、dataApps の register にあたる API payload は以下の形になっています。

{
  "dataApps": [
    {
      "dataAppID": "telemetry-app-1"
    }
  ],
  "topic": "enterprise/hospital/pulse_oximeter",
  "dataFormat": "default",
  "controlApp": "controlApplication"
}

Devnet に記載がある実装 が参考になります。

今回の最終形は次のようにしました。

{
  "dataApps": [
    {
      "dataAppID": "data"
    }
  ],
  "topic": "ioslab/home-metrics/ble/advertisements/v1",
  "dataFormat": "default",
  "controlApp": "control"
}

この API は control 側の API key で呼び出します。data app の API key は MQTT 接続時に使う、という切り分けにすると整理しやすいです。

HTTP 200 でも body を見る

Cisco の release notes には、API の結果と response code の扱いに注意が必要な known issue があります。アプリケーション側では、HTTP status だけでなく JSON body の statusFAILURE ではないかも見るようにしました。

onboarding と data telemetry は別物

BLE Inventory に device が見えることと、MQTT に advertisement が push されることは別の状態でした。特に以下の切り分けが重要でした。

  • AP Inventory で AP が connected か。
  • BLE allow-list / scan policy が対象 MAC を通すか。
  • SCIM onboarding が成功し device ID があるか。
  • Data app が登録されているか。
  • Topic が advertisements として登録されているか。
  • Device が topic に subscribe されているか。
  • MQTT に data app ID / data API key で接続しているか。

connectionless advertisement に寄せる

GATT read / notify が必要な sensor もありますが、今回の対象は advertisement だけで温度、湿度、battery、CO2、lux、pressure、eTVOC を取得できました。したがって、GATT connect の安定性や AP ごとの同時 BLE connection 数に依存しない形に寄せました。

Release Notes でも AP ごとの BLE connection 数や connect operation に関する注意点があるため、advertisement だけで済む sensor は connectionless にする方が運用しやすいです。

まとめ

Cisco Sensor Connect (IoT Orchestrator) は、Cisco Spaces cloud firehose とは別経路で BLE telemetry を受けられます。ポイントは、AP が BLE を見ることだけではなく、SCIM onboarding、Data App 登録、Topic 登録、Device subscribe、MQTT decode がすべて必要なことでした。

拙作 home-metrics での実装では、利用しているセンサーの特性を考慮して、connectionless advertisement を MQTT で受け、protobuf-like payload と BLE service data を decode し、1分 median として TimescaleDB に保存する形にしました。実装の詳細は GitHub の collector と test を参照してください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?