获取设备状态

借助 Cloud IoT Core,您可以监控每个已连接设备的状态。设备将状态报告为二进制数据。设备状态更新通常由设备更改触发(来自 Cloud IoT Core 的配置更新或其他外部来源的类似更改,例如固件更新)。

设备状态与设备配置不同。配置数据从 Cloud IoT Core 发送到设备。设备会将状态数据发送到 Cloud IoT Core。您可以将配置视为外部指令,并将状态视为内部表示形式。

Cloud IoT Core 可以帮您解答有关配置和状态的基本问题:设备目前应具备什么功能?这与设备的最新配置相比如何?

限制

状态更新限制为每台设备每秒 1 次更新。但是,为达到最佳效果,设备状态更新频率应低得多(最多每 10 秒一次)。

更新速率按最近服务器确认和下一次更新请求之间的时间计算。

报告设备状态

MQTT 网桥

如需通过 MQTT 网桥向 Cloud IoT Core 报告状态,请向 /devices/DEVICE_ID/state MQTT 主题发布消息。您可以在创建或更新注册表时选择 Cloud Pub/Sub 主题来存储状态事件。

如需了解详情,请参阅通过 MQTT 网桥发布

HTTP 网桥

如需通过 HTTP 网桥向 Cloud IoT Core 报告状态,设备应使用 setState 请求。二进制状态数据作为 base64 编码的字符串在请求的正文中传递。

如需了解详情,请参阅通过 HTTP 网桥发布

获取设备状态数据

本部分介绍如何获取设备向 Cloud IoT Core 报告的状态数据。(设备本身无法从云端读取状态数据。)

以二进制格式返回状态数据。状态数据的结构可能与触发状态更改的配置数据不同。

例如,假设您的设备有多个风扇。您的配置数据可能是 JSON 对象,其中包含用于启用或停用冷却的简单布尔值:

配置数据示例

{
  'cooling': true
}

但是,设备的状态数据可能包括诊断信息,以及您希望在响应 'cooling' 更改时看到的风扇数据:

状态数据示例

{
  'fan1_target_rpm': 1000,
  'fan2_target_rpm': 1200,
  'firmware_version': '1.2.3b'
}

设备的 firmware_version 与配置数据无关,但设备会返回其状态的完整内部表示形式。此示例说明了设备状态在一般情况下可用于调试以及确认设备已确认特定配置有何作用。

您可以使用 Cloud Pub/Sub 主题、Cloud Platform Console、Cloud IoT Core API 或 gcloud 获取设备状态数据。

Cloud Pub/Sub 主题

Cloud IoT Core 会将状态数据保留在存储空间中。您还可以通过 Cloud ConsolegcloudAPI 在每个设备注册表中配置可选的状态通知 Cloud Pub/Sub 主题。此主题可与遥测事件主题相同或不同。

状态数据会尽最大努力发布到 Cloud Pub/Sub:如果发布到主题失败,将不会重试。如果未定义主题,则 Cloud IoT Core 仍会在内部保留设备状态更新,但仅保留最近 10 个状态。

控制台

  1. 转到 Cloud Console 中的注册表页面。

    转到注册表页面

  2. 点击设备的注册表的 ID。

  3. 在左侧的注册表菜单中,点击设备

  4. 点击设备 ID 以转到设备详细信息页面。

  5. 点击配置记录和状态记录。使用复选框显示配置历史记录和/或状态历史记录。默认情况下,两者都会显示。

    • 绿色对勾标记表示设备已确认配置(仅限 MQTT)。
    • 黄色警告符号表示设备尚未确认配置(仅限 MQTT)。
    • 点击一行以获取 JSON 中的完整配置或状态数据,以及时间戳和版本。
  6. 点击比较,将配置数据与状态数据进行比较。此视图可帮助您调试配置;如果您使用的是 MQTT,请确保设备已确认特定配置版本。(HTTP 网桥不支持配置的确认。)

gcloud

如需获取设备(最多 10 台)的最新状态消息,请运行 gcloud iot devices states list 命令。

gcloud iot devices states list \
    --registry=REGISTRY_ID \
    --device=DEVICE_ID \
    --region=REGION

gcloud iot registries createupdate 命令可以使用状态通知 Cloud Pub/Sub 标志 (--state-pubsub-topic):

gcloud iot registries create REGISTRY_ID \
    --project=PROJECT_ID \
    --region=REGION \
    [--event-notification-config=topic=TOPIC,[subfolder=SUBFOLDER] [--event-notification-config=...]]
    [--state-pubsub-topic=STATE_PUBSUB_TOPIC]
gcloud iot registries update REGISTRY_ID \
    --project=PROJECT_ID \
    --region=REGION \
    [--event-notification-config=topic=TOPIC,[subfolder=SUBFOLDER] [--event-notification-config=...]]
    [--state-pubsub-topic=STATE_PUBSUB_TOPIC]

API

使用设备 states.list 方法可获取最新的设备状态(最多 10 个)。每个 Device 资源都有一个 DeviceState 字段,其中包含最近从设备接收的状态。DeviceRegistry 资源有一个 StateNotificationConfig 字段,可用于创建或更新注册表时指定状态通知 Cloud Pub/Sub 主题。

以下示例展示了如何从设备注册表检索设备状态:

C#

public static object GetDeviceStates(string projectId, string cloudRegion, string registryId, string deviceId)
{
    var cloudIot = CreateAuthorizedClient();

    // The resource name of the location associated with the key rings.
    var name = $"projects/{projectId}/locations/{cloudRegion}/registries/{registryId}/devices/{deviceId}";

    try
    {
        Console.WriteLine("States: ");
        var res = cloudIot.Projects.Locations.Registries.Devices.States.List(name).Execute();
        res.DeviceStates.ToList().ForEach(state =>
        {
            Console.WriteLine($"\t{state.UpdateTime}: {state.BinaryData}");
        });
    }
    catch (Google.GoogleApiException e)
    {
        Console.WriteLine(e.Message);
        if (e.Error != null) return e.Error.Code;
        return -1;
    }
    return 0;
}

Go


// getDeviceStates retrieves and lists device states.
func getDeviceStates(w io.Writer, projectID string, region string, registryID string, device string) ([]*cloudiot.DeviceState, error) {
	// Authorize the client using Application Default Credentials.
	// See https://g.co/dv/identity/protocols/application-default-credentials
	ctx := context.Background()
	httpClient, err := google.DefaultClient(ctx, cloudiot.CloudPlatformScope)
	if err != nil {
		return nil, err
	}
	client, err := cloudiot.New(httpClient)
	if err != nil {
		return nil, err
	}

	path := fmt.Sprintf("projects/%s/locations/%s/registries/%s/devices/%s", projectID, region, registryID, device)
	response, err := client.Projects.Locations.Registries.Devices.States.List(path).Do()
	if err != nil {
		return nil, err
	}

	fmt.Fprintln(w, "Successfully retrieved device states!")

	for _, state := range response.DeviceStates {
		fmt.Fprintf(w, "%s : %s\n", state.UpdateTime, state.BinaryData)
	}

	return response.DeviceStates, nil
}

Java

/** Retrieves device metadata from a registry. * */
protected static List<DeviceState> getDeviceStates(
    String deviceId, String projectId, String cloudRegion, String registryName)
    throws GeneralSecurityException, IOException {
  GoogleCredentials credential =
      GoogleCredentials.getApplicationDefault().createScoped(CloudIotScopes.all());
  JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
  HttpRequestInitializer init = new HttpCredentialsAdapter(credential);
  final CloudIot service =
      new CloudIot.Builder(GoogleNetHttpTransport.newTrustedTransport(), jsonFactory, init)
          .setApplicationName(APP_NAME)
          .build();

  final String devicePath =
      String.format(
          "projects/%s/locations/%s/registries/%s/devices/%s",
          projectId, cloudRegion, registryName, deviceId);

  System.out.println("Retrieving device states " + devicePath);

  ListDeviceStatesResponse resp =
      service.projects().locations().registries().devices().states().list(devicePath).execute();

  return resp.getDeviceStates();
}

Node.js

// const cloudRegion = 'us-central1';
// const deviceId = 'my-device';
// const projectId = 'adjective-noun-123';
// const registryId = 'my-registry';
const iot = require('@google-cloud/iot');
const iotClient = new iot.v1.DeviceManagerClient({
  // optional auth parameters.
});

async function listDeviceStates() {
  const devicePath = iotClient.devicePath(
    projectId,
    cloudRegion,
    registryId,
    deviceId
  );

  const [response] = await iotClient.listDeviceStates({name: devicePath});
  const states = response.deviceStates;
  if (states.length === 0) {
    console.log(`No States for device: ${deviceId}`);
  } else {
    console.log(`States for device: ${deviceId}`);
  }

  for (let i = 0; i < states.length; i++) {
    const state = states[i];
    console.log(
      'State:',
      state,
      '\nData:\n',
      state.binaryData.toString('utf8')
    );
  }
}

listDeviceStates();

PHP

use Google\Cloud\Iot\V1\DeviceManagerClient;

/**
 * Retrieve a device's state blobs.
 *
 * @param string $registryId IOT Device Registry ID
 * @param string $deviceId IOT Device ID
 * @param string $projectId Google Cloud project ID
 * @param string $location (Optional) Google Cloud region
 */
function get_device_state(
    $registryId,
    $deviceId,
    $projectId,
    $location = 'us-central1'
) {
    print('Getting device state' . PHP_EOL);

    // Instantiate a client.
    $deviceManager = new DeviceManagerClient();
    $deviceName = $deviceManager->deviceName($projectId, $location, $registryId, $deviceId);

    $response = $deviceManager->listDeviceStates($deviceName);

    foreach ($response->getDeviceStates() as $state) {
        print('State:' . PHP_EOL);
        printf('    Data: %s' . PHP_EOL, $state->getBinaryData());
        printf('    Update Time: %s' . PHP_EOL,
            $state->getUpdateTime()->toDateTime()->format('Y-m-d H:i:s'));
    }
}

Python

此示例使用 Python 版 Google API 客户端库
# project_id = 'YOUR_PROJECT_ID'
# cloud_region = 'us-central1'
# registry_id = 'your-registry-id'
# device_id = 'your-device-id'
client = iot_v1.DeviceManagerClient()
device_path = client.device_path(project_id, cloud_region, registry_id, device_id)

device = client.get_device(request={"name": device_path})
print("Last state: {}".format(device.state))

print("State history")
states = client.list_device_states(request={"name": device_path}).device_states
for state in states:
    print("State: {}".format(state))

return states

Ruby

# project_id  = "Your Google Cloud project ID"
# location_id = "The Cloud region the registry is located in"
# registry_id = "The registry to get device states from"
# device_id   = "The identifier of the device to get states for"

require "google/apis/cloudiot_v1"

# Initialize the client and authenticate with the specified scope
Cloudiot   = Google::Apis::CloudiotV1
iot_client = Cloudiot::CloudIotService.new
iot_client.authorization = Google::Auth.get_application_default(
  "https://www.googleapis.com/auth/cloud-platform"
)

# The resource name of the location associated with the project
parent   = "projects/#{project_id}/locations/#{location_id}"
resource = "#{parent}/registries/#{registry_id}/devices/#{device_id}"

# List the configurations for the provided device
result = iot_client.list_project_location_registry_device_states(
  resource
)
if result.device_states
  result.device_states.each do |state|
    puts "#{state.update_time}: #{state.binary_data}"
  end
else
  puts "No state messages"
end