跳转至

IoT 协议(MQTT)

Inklet 设备通过 AWS IoT Core 使用 MQTT 协议与后端通信。所有连接均使用 X.509 证书双向 TLS(mTLS)进行认证。本页面介绍主题结构、消息格式和安全策略。

连接信息

参数
协议 MQTT over TLS(端口 8883)
认证方式 X.509 客户端证书(mTLS)
代理 AWS IoT Core(xxxx-ats.iot.us-east-1.amazonaws.com
QoS 1(至少一次送达)

主题结构

所有主题遵循 inklet/dev/{thingName}/direction/type 的模式,其中 {thingName} 是在配网过程中分配的 AWS IoT Core Thing 名称。

inklet/dev/{thingName}/
├── up/                  # 设备 → 后端
│   ├── heartbeat        # 定时健康上报
│   ├── state            # 任意设备状态
│   └── request_claim    # 请求配对码
└── down/                # 后端 → 设备
    └── cmd              # 后端下发的命令

上行主题(设备到后端)

inklet/dev/{thingName}/up/heartbeat

设备按可配置的间隔(默认 30 秒)发送的定时健康上报。

载荷:

{
  "hwId": "a1b2c3d4-5678-9012-abcd-ef0123456789",
  "ts": 1705395000,
  "firmware": "1.2.0",
  "battery": 85
}
字段 类型 描述
hwId string 硬件 UUID
ts integer Unix 时间戳(秒)
firmware string 固件版本字符串
battery integer 电池百分比(0--100)

后端行为:

  1. 如果数据库中不存在该设备记录,则根据 hwIdthingName 创建
  2. 更新 firmwarebatterylastSeenAtonline 状态
  3. 如果设备未绑定且没有配对码,后端会生成一个并发送 claim_code 命令

inklet/dev/{thingName}/up/state

设备以 JSON 对象形式上报任意状态。后端将其作为 JSON 字符串存储在 state 列中。

载荷:

{
  "screen": "text",
  "lastCmd": "01912345-9999-7abc-def0-aaaaaaaaaaaa",
  "brightness": 50,
  "temperature": 22.5
}

载荷可以包含任意有效的 JSON。后端不解析其内容,直接存储。

后端行为:

  1. 将完整的 JSON 载荷存储为设备的 state
  2. 更新 stateUpdatedAt

inklet/dev/{thingName}/up/request_claim

设备请求配对码以进行用户配对。在设备未绑定且需要显示配对码时发送。

载荷:

{}

后端行为:

  1. 如果设备已绑定到用户,发送 already_bound 命令
  2. 如果设备未绑定,生成一个 6 位字母数字配对码
  3. 将配对码存储到数据库
  4. 向设备发送 claim_code 命令

下行主题(后端到设备)

inklet/dev/{thingName}/down/cmd

后端向设备发送的命令。所有命令共享一个通用结构,通过 kind 字段区分命令类型。


命令:text

向设备发送要在电子墨水屏上渲染的文本内容。

{
  "kind": "text",
  "id": "01912345-9999-7abc-def0-aaaaaaaaaaaa",
  "text": "Hello from Inklet!"
}
字段 类型 描述
kind string "text"
id UUID 用于跟踪的唯一命令 ID
text string 要渲染的文本内容

命令:claim_code

向设备发送配对码供其显示。用户输入此配对码即可将设备绑定到自己的账户。

{
  "kind": "claim_code",
  "code": "A3X9K2"
}
字段 类型 描述
kind string "claim_code"
code string 6 位字母数字配对码

命令:bound

通知设备已被绑定到某个用户。

{
  "kind": "bound",
  "userId": "01912345-0000-7abc-def0-000000000001"
}
字段 类型 描述
kind string "bound"
userId UUID 绑定该设备的用户 ID

命令:unbound

通知设备已与所有者解绑。设备应清除屏幕内容并重新请求配对码。

{
  "kind": "unbound"
}
字段 类型 描述
kind string "unbound"

命令:already_bound

当设备请求配对码但已绑定到用户时发送。设备不应显示配对界面。

{
  "kind": "already_bound"
}
字段 类型 描述
kind string "already_bound"

AWS IoT Core 策略

三个 IAM 策略管控 MQTT 访问权限,每个策略都遵循最小权限原则。

inklet-device-policy

附加到每个设备证书的逐设备隔离策略。使用 IoT Core 策略变量 ${iot:Connection.Thing.ThingName},确保设备只能访问自己的主题。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:us-east-1:*:client/${iot:Connection.Thing.ThingName}"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topic/inklet/dev/${iot:Connection.Thing.ThingName}/up/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topicfilter/inklet/dev/${iot:Connection.Thing.ThingName}/down/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topic/inklet/dev/${iot:Connection.Thing.ThingName}/down/*"
      ]
    }
  ]
}

逐设备隔离

${iot:Connection.Thing.ThingName} 变量由 AWS IoT Core 在连接时解析。关联到 Thing inklet-a1b2c3d4 的设备证书只能发布到 inklet/dev/inklet-a1b2c3d4/up/* 并订阅 inklet/dev/inklet-a1b2c3d4/down/*,无法访问其他设备的主题。

inklet-backend-policy

后端 MQTT 客户端(inklet-backend)的特权策略。后端订阅所有设备的上行主题,并可向任意设备的下行主题发布命令。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:us-east-1:*:client/inklet-backend"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topicfilter/inklet/dev/+/up/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topic/inklet/dev/+/up/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topic/inklet/dev/+/down/*"
      ]
    }
  ]
}

inklet-claim-policy

用于 Fleet Provisioning 的 claim 证书的受限策略。claim 证书只能执行 Fleet Provisioning 的 MQTT 事务 --- 不能发布或订阅应用主题。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topic/$aws/certificates/create/*",
        "arn:aws:iot:us-east-1:*:topic/$aws/provisioning-templates/*/provision/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topicfilter/$aws/certificates/create/*",
        "arn:aws:iot:us-east-1:*:topicfilter/$aws/provisioning-templates/*/provision/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": [
        "arn:aws:iot:us-east-1:*:topic/$aws/certificates/create/*",
        "arn:aws:iot:us-east-1:*:topic/$aws/provisioning-templates/*/provision/*"
      ]
    }
  ]
}

Claim 证书安全性

claim 证书在所有设备间共享,且只授予 Fleet Provisioning 主题的访问权限。它们应被安全存储,但敏感程度不及逐设备证书 --- 泄露的 claim 证书只能创建新的 Thing,不能冒充已有设备。


Fleet Provisioning

新设备使用 Fleet Provisioning by Claim 在首次启动时获取设备专属证书。

流程:

设备(首次启动)
    ├── 使用 claim 证书连接到 IoT Core
    ├── 发布到 $aws/certificates/create/json
    │   └── 接收新证书 + 私钥
    ├── 发布到 $aws/provisioning-templates/{template}/provision/json
    │   └── 发送: { "SerialNumber": "{hwId}" }
    │   └── 接收: { "thingName": "inklet-{prefix}" }
    ├── 存储设备证书、私钥和 thingName
    └── 使用设备证书重新连接
        └── 开始正常运行(心跳、命令)

配网完成后,设备在本地存储证书和 Thing 名称,之后不再使用 claim 证书。