Skip to content

Owlet

One Owlet sock, from the custom owlet integration: its sleep state with its heart rate, oxygen, charging and alert sensors, and the base station’s switch.

import { useHaOwlet } from "@dashhaus/core";
/**
* A sock's readings and alerts. The heart rate, oxygen, charging and alert sensors are found beside the sleep state
* sensor; `live` turns false once no reading has changed for a while, so old figures aren't read as current.
*/
export function Vitals() {
const { entity } = useHaOwlet("sensor.owlet_sock_ac000w0123_sleep_state", { name: "Nova" });
if (entity.charging) return <p>{entity.name}'s sock is charging.</p>;
return (
<div>
<p>
{entity.name}: {entity.state.replace("_", " ")}
{entity.heartRate != null && `, ${entity.heartRate} bpm`}
{entity.oxygen != null && `, ${entity.oxygen}% O₂`}
{entity.live === false && " (readings stopped)"}
</p>
{entity.alerts?.length ? <p role="alert">{entity.alerts.join(", ").replaceAll("_", " ")}</p> : null}
</div>
);
}

The entity id is the sleep state sensor. The heart rate, oxygen, charging and ten alert sensors and the base station switch are found beside it by the integration’s names; give their ids when yours are named otherwise.

<HaOwlet> is useHaOwlet as a component that draws nothing of its own. It takes every option of the hook as a prop, and hands the hook’s result to its child function. Use it where hooks can’t go, such as one per item of a list.

entityIdstring
The Home Assistant entity id.
children(live: HaOwletResult) => ReactNode
Draws the hook's result. The component itself draws nothing.
useHaOwlet(entityId: string, options?: HaOwletOptions): HaOwletResult

One live Owlet sock for any UI: composes the sleep state, readings, charging and alert sensors. Its one action is the base station's switch; the readings are read-only.

It reads the sleep state, every companion, its area and today’s history of sleep, heart rate and oxygen. staleAfterMinutes (default 3) is how long without a changed reading before live turns false.

heartRateEntityId?string
Default: the integration's `…_heart_rate` beside the sleep state sensor.
oxygenEntityId?string
Default: the integration's `…_o2_saturation` beside the sleep state sensor.
chargingEntityId?string
Default: the integration's `binary_sensor.…_charging`.
baseEntityId?string
Default: the integration's `switch.…_base_station_on`, switched from the sheet.
alertEntityIds?Partial<Record<OwletAlert, string>>
Per alert, the binary sensor to read. Default: the integration's (`binary_sensor.…_low_oxygen_alert` …).
name?string
Usually the baby's name. Default: the sock's device name.
area?string
Overrides the area Home Assistant's registries resolve for the entity.
lastSeen?string
Overrides when the entity was last reachable, shown while it is unavailable.
history?OwletHistory
Host-owned history override; when omitted, today's history is read from Home Assistant.
loadHistory?boolean
Set false to skip the Home Assistant history read.
staleAfterMinutes?number
Minutes without a changed reading before the card warns that readings stopped. Default 3.
onActionError?(error: HaActionError) => void
Called with an HaActionError when an action is refused or fails.
entityOwletEntity
historyOwletHistory
actionsOwletHandlers
entityIdstring
The sleep state sensor, `sensor.*_sleep_state`.
namestring
Usually the baby's name.
area?string
Its area: the `area` option, else the one Home Assistant's registries resolve; none when neither has one.
state"unavailable" | SleepState
"unavailable" while the sock charges, is off, or can't tell yet.
heartRate?number
Beats per minute; omitted while there is no reading.
oxygen?number
Oxygen saturation, 0-100 %; omitted while there is no reading.
live?boolean
Whether readings are still arriving: true while one has changed recently, false once none has for a while (the figures shown are then old). Omitted when the host can't tell.
alerts?OwletAlert[]
The alerts sounding now, in any order.
vitalsSince?number
When the vitals alerts sounding now started, in ms since the epoch: the first of them to go on. Omitted while none sounds, or when the host can't tell.
charging?boolean
On its charger: the reason it reports nothing.
baseOn?boolean
The base station's switch: false while it is off and sounds no alarm (the sock may still send readings). Omitted when the host has no such switch or can't tell.
lastUpdated?string
When the newest reading arrived ("11:54").
lastSeen?string
While it is unavailable, when it was last reachable: the `lastSeen` option, else what Home Assistant reports.

Today, from Home Assistant’s recorder, kept up to date by every change since. Pass history to supply your own, or loadHistory={false} to skip the read.

sleepHourSample[]
Per hour: 1 deep sleep, 0.5 light sleep, 0 awake, "unavailable", null for hours ahead.
heartRate?Reading[]
oxygen?Reading[]
currentHournumber
asleepMinutesnumber
Minutes asleep today (light or deep), including the current hour.
onBaseToggle?() => void
Only when a base station switch is found and reports on or off. Calls `switch.turn_on` or `switch.turn_off` for the state shown, never `switch.toggle`, so a repeated tap on a stale screen repeats the request instead of undoing it.

The readings are read-only; the base station is the one thing to switch.

Hooks can’t be called in a loop, so a list renders one headless component per entity. The selector returns a string: useHaSelector re-renders only when its value changes, and a string of ids changes only when an entity comes or goes. Lists skip each item’s history read with loadHistory={false}.

import { HaOwlet, useHaSelector } from "@dashhaus/core";
import type { HaSnapshot } from "@dashhaus/core";
/** Every sock, by its sleep state sensor, the way the Lovelace cards find them. */
const sockIds = (snapshot: HaSnapshot) =>
Object.keys(snapshot.entities).filter((id) => /^sensor\.\w+_sleep_state$/.test(id)).sort().join(" ");
export function AllSocks() {
const ids = useHaSelector(sockIds).split(" ").filter(Boolean);
return (
<ul>
{ids.map((id) => (
<li key={id}>
<HaOwlet entityId={id} loadHistory={false}>
{({ entity }) => (
<>
{entity.name}: {entity.alerts?.length ? "alert" : entity.state.replace("_", " ")}
</>
)}
</HaOwlet>
</li>
))}
</ul>
);
}
import { useHaOwlet } from "@dashhaus/core";
/** The one action: the base station's switch. It is absent when there is no base station switch to read. */
export function BaseStation({ entityId }: { entityId: string }) {
const { entity, actions } = useHaOwlet(entityId, { loadHistory: false });
if (!actions.onBaseToggle) return null;
return (
<button type="button" role="switch" aria-checked={entity.baseOn === true} onClick={actions.onBaseToggle}>
Base station {entity.baseOn ? "on" : "off: no alarm sounds"}
</button>
);
}

With the base station off, the sock may still send readings but no alarm sounds, so say that on the control.

Orb’s HaOwletTile and Field’s HaOwletModule are useHaOwlet above each skin’s card. They take the hook’s options as props, plus their own:

import { HaOwletTile } from "@dashhaus/orb";
import { HaOwletModule } from "@dashhaus/field";
export function OrbSock() {
return <HaOwletTile entityId="sensor.owlet_sock_ac000w0123_sleep_state" name="Nova" />;
}
/** A sock whose sensors aren't named after the integration's: name its companions. */
export function FieldSock() {
return (
<HaOwletModule
entityId="sensor.nova"
heartRateEntityId="sensor.nova_bpm"
oxygenEntityId="sensor.nova_spo2"
chargingEntityId="binary_sensor.nova_dock"
baseEntityId="switch.nova_base"
alertEntityIds={{ low_oxygen: "binary_sensor.nova_spo2_low", sock_off: "binary_sensor.nova_off" }}
/>
);
}
entityIdstring
The sock's sleep state sensor, `sensor.owlet_sock_<serial>_sleep_state` (custom `owlet` integration).
showControl?boolean
Compact only. false removes the inline control, shows the value in its place, and makes the row open the details. Default true.
footprint?CardFootprint
Card footprint. The skin's stylesheet sets the heights; the three names are the shared vocabulary.
defaultOpen?boolean
defaultAction?undefined

Every state of both is in Storybook: Orb and Field.