| Field | Type | Unit | Notes |
|---|---|---|---|
batteryLevel | Integer | Percent, 0 to 100 | Reported as unknown until the device sends a reading. See Read battery level safely for the platform difference. |
chargingState | Enum | — | UNKNOWN, CHARGING, or NOT_CHARGING. |
donState | Enum | — | UNKNOWN, DONNED (being worn), or DOFFED (not being worn). |
hingeState | Enum | — | UNKNOWN, OPEN, or CLOSED (arms folded). |
thermalLevel | Enum | — | Eight escalating levels. See Respond to thermal level. |
UNKNOWN case, and it is the initial value for all of them. UNKNOWN means no usable information is available—for example, the field has not been reported, is not applicable to the device, or the SDK did not recognise the value. It may not eventually become a real value.Use https://wearables.developer.meta.com/docs/develop/dat/device-state/, then use the Wearables MCP endpoint https://mcp.developer.meta.com/wearables to call search_dat_docs for current Device Access Toolkit device state guidance. Inspect my code first, then add the smallest working slice: observe the device snapshot and read battery level, charging state, don state, hinge state, and thermal level. Remember device state is metadata on the device, not a capability I add to a session, and that it is available before a session starts. Handle the UNKNOWN case for every enum, handle an unreported battery level, and stop trusting the values once the device disconnects.
Permission value to check or request, and you do not call checkPermissionStatus or requestPermission before reading it. See Permissions and registration for the permissions that do require a user prompt.| Platform | Type | Unavailable value |
|---|---|---|
Android | Int | 0 |
iOS | Int? | nil |
0 means the battery level is unknown or the battery is empty. The SDK exposes that value directly as 0.nil means no battery reading is available.| Level | Meaning | What to do |
|---|---|---|
UNKNOWN | Not reported yet. | Nothing. Wait for a real value. |
NONE | Normal operating temperature. | Nothing. |
LIGHT | Slightly elevated. | Nothing, but stop increasing load. |
MODERATE | Moderately elevated. | Reduce optional work, such as a high sampling rate or a high-resolution stream. |
SEVERE | Severely elevated. Performance may be throttled. | Shed load. Expect the device to slow down regardless of what you do. |
CRITICAL | Critical. Device performance may be heavily restricted, and sessions may be denied or terminated. | Treat the device as potentially unavailable. |
EMERGENCY | Emergency. The device is preparing to shut down. | Treat the device as potentially unavailable. |
SHUTDOWN | The device is shutting down due to thermal conditions. | Treat the device as potentially unavailable. |
CRITICAL or above, the device may become unavailable. Stop device-dependent work, and handle session termination or disconnection through Session lifecycle.Device snapshot, which you observe as a StateFlow. There are two entry points to the same underlying flow; pick whichever matches where you are in the session lifecycle.deviceIdentifier by collecting Wearables.devices, as shown in Launch registration from your app. Wearables.devicesMetadata maps each known device identifier to a flow of its snapshot. Use it to show battery or wear state before the user starts anything.import com.meta.wearable.dat.core.Wearables
import com.meta.wearable.dat.core.types.ChargingState
import com.meta.wearable.dat.core.types.DonState
val deviceFlow = Wearables.devicesMetadata[deviceIdentifier] ?: return
scope.launch {
deviceFlow.collect { device ->
val battery = device.batteryLevel.takeIf { it != 0 }
showBattery(battery, charging = device.chargingState == ChargingState.CHARGING)
showWorn(device.donState == DonState.DONNED)
}
}
devicesMetadata returns null for a device the SDK does not know about, so handle the missing case rather than asserting.DeviceSession.deviceInfo exposes the snapshot for the session’s own device, backed by the same flow, so you do not need to hold on to the identifier.import com.meta.wearable.dat.core.types.ThermalLevel
scope.launch {
session.deviceInfo.collect { device ->
if (device.thermalLevel >= ThermalLevel.SEVERE) {
reduceWorkload()
}
}
}
>= works because the enum is declared in escalating order, which keeps a threshold check from having to list every level above it.mockDevice.setBatteryLevel(42) mockDevice.setChargingState(ChargingState.CHARGING) mockDevice.setThermalLevel(ThermalLevel.SEVERE) mockDevice.don() mockDevice.doff() mockGlasses.fold() mockGlasses.unfold()
setBatteryLevel clamps its argument to 0 through 100. Wear state comes from don() and doff(), and hinge state from fold() and unfold() on the mock glasses. See Android testing for how to create a mock device.Device and as an immutable DeviceState snapshot delivered to a listener.identifier from Wearables.shared.devicesStream(), as shown in Launch registration from your app, then resolve the corresponding device.import MWDATCore
guard let device = Wearables.shared.deviceForIdentifier(identifier) else { return }
let battery = device.batteryLevel // Int?, nil when not reported
let charging = device.chargingState // .unknown, .charging, .notCharging
let worn = device.donState == .donned
DeviceSession.device gives you the same object without holding the identifier.addDeviceStateListener fires immediately with the current state and again on every change. It returns a token you cancel to stop listening.let token = device.addDeviceStateListener { state in
Task { @MainActor in
showBattery(state.batteryLevel, charging: state.chargingState == .charging)
showWorn(state.donState == .donned)
if state.thermalLevel == .severe || state.thermalLevel == .critical {
reduceWorkload()
}
}
}
DeviceState snapshot also carries linkState and compatibility, so a single callback tells you both the device’s physical state and whether it is still connected and usable. Reading linkState from the same snapshot avoids acting on battery or thermal values from a device that has since disconnected.token.cancel()
addLinkStateListener instead.mockDevice.setBatteryLevel(42) mockDevice.setChargingState(.charging) mockDevice.setThermalLevel(.severe) mockDevice.don() mockDevice.doff() mockGlasses.fold() mockGlasses.unfold()
setBatteryLevel takes an optional, so pass nil to simulate a device that has not reported a battery reading. This is the case Android cannot represent directly, which makes it worth an explicit test. See iOS testing for how to create a mock device.Int with 0 meaning unknown. iOS uses Int? with nil meaning unknown.StateFlow of the whole Device snapshot. iOS delivers an immutable DeviceState snapshot to a listener and also exposes each field as a property on Device.DeviceState snapshot includes linkState and compatibility alongside the physical state. On Android those fields live on the same Device object you are already collecting, so both platforms give you the same information in one place.nil.