@peripherals/smart-home
v0.1.0
Published
React Native smart home integration — Home Assistant WebSocket/REST, MQTT, and HomeKit native module
Maintainers
Readme
@peripherals/smart-home
React Native smart home integration — Home Assistant, MQTT, and HomeKit.
Architecture
| Layer | Transport | Coverage | Use case |
|---|---|---|---|
| Home Assistant WebSocket | ws:// | 2,000+ integrations | Primary hub — real-time state, service calls |
| Home Assistant REST | http:// | Same | History, templates, batch queries |
| MQTT | wss:// | Direct devices, Zigbee2MQTT | Low-latency device control, HA discovery |
| HomeKit | Native (iOS only) | Apple ecosystem | Siri, Matter commissioning, local control |
Installation
npm install @peripherals/smart-home
# Optional: MQTT support
npm install mqtthome-assistant-js-websocket is bundled as a package dependency, so no separate install is required.
Quick start — Home Assistant
import {
useHomeAssistant,
useEntity,
useService,
Light,
lightTurnOn,
lightTurnOff,
} from '@peripherals/smart-home';
function App() {
const { client, isConnected, connectionState } = useHomeAssistant({
url: 'http://192.168.1.100:8123',
auth: { type: 'longLivedToken', token: 'eyJ...' },
});
if (!isConnected) return <Text>Status: {connectionState}</Text>;
return <LightControl client={client} entityId="light.living_room" />;
}
function LightControl({ client, entityId }) {
const { device, isAvailable } = useEntity<Light>(client, entityId);
const { execute, isLoading } = useService(client);
if (!isAvailable) return <Text>Unavailable</Text>;
return (
<View>
<Text>{device.friendlyName}: {device.isOn ? 'On' : 'Off'}</Text>
<Text>Brightness: {device.brightnessPercent}%</Text>
<Button
title={device.isOn ? 'Turn Off' : 'Turn On'}
disabled={isLoading}
onPress={() =>
execute(device.isOn ? lightTurnOff(entityId) : lightTurnOn(entityId, { brightness: 200 }))
}
/>
</View>
);
}Hooks reference
useHomeAssistant(config) — Connection management
const { client, isConnected, connectionState, haVersion, error, reconnect, disconnect } =
useHomeAssistant({
url: 'http://192.168.1.100:8123',
auth: { type: 'longLivedToken', token: 'eyJ...' },
});Auto-connects on mount, disconnects on unmount. Wraps home-assistant-js-websocket with typed entity tracking.
useEntity<T>(client, entityId) — Single entity tracking
const { entity, device, state, isAvailable, callService, executeServiceCall } =
useEntity<Light>(client, 'light.kitchen');
// device is a typed Light with .isOn, .brightness, .colorTemp, etc.useEntities(client, options) — Multi-entity tracking
// All lights
const { devices, anyActive, activeCount } = useEntities(client, { domain: 'light' });
// Specific entities
const { entities } = useEntities(client, {
entityIds: ['sensor.temperature', 'sensor.humidity'],
});
// By area
const { devices } = useEntities(client, { areaId: 'living_room' });
// Custom filter
const { entities } = useEntities(client, {
filter: (e) => e.attributes.device_class === 'temperature',
});useAreas(client) — Areas with entity counts
const { areas, isLoading } = useAreas(client);
// Each area has: name, areaId, entities[], entityCount, activeCountuseService(client) — Service call execution
const { callService, execute, isLoading, error } = useService(client);
// Direct call
await callService('light', 'turn_on', {
target: { entityId: 'light.kitchen' },
serviceData: { brightness: 200 },
});
// Using service call builders
await execute(lightTurnOn('light.kitchen', { brightness: 200 }));
await execute(climateSetTemperature('climate.thermostat', 22));
await execute(lockUnlock('lock.front_door', '1234'));useAutomation(client, entityId) — Automation control
const { automation, isEnabled, lastTriggered, trigger, enable, disable, toggleEnabled } =
useAutomation(client, 'automation.morning_routine');useHistory(restConfig, options) — State history
const { history, isLoading, refresh } = useHistory(
{ url: 'http://ha:8123', token: 'eyJ...' },
{ entityId: 'sensor.temperature', start: '2025-01-01T00:00:00Z' },
);useMQTT(config) — MQTT connection
const { client, isConnected, publish } = useMQTT({
brokerUrl: 'wss://192.168.1.100:8884/mqtt',
username: 'user',
password: 'pass',
});useMQTTSubscription<T>(client, topic) — Topic subscription
const { data, messageCount } = useMQTTSubscription<{ temperature: number }>(
client,
'zigbee2mqtt/temp_sensor',
);
// data?.temperature automatically parsed from JSONDevice abstractions
Each domain has a typed wrapper with domain-specific properties:
| Class | Domain | Key properties |
|---|---|---|
| Light | light | isOn, brightness, brightnessPercent, colorTemp, rgbColor, supportsColor |
| Climate | climate | hvacMode, currentTemperature, targetTemperature, isHeating, isCooling, presetModes |
| Lock | lock | isLocked, isUnlocked, isJammed, requiresCode |
| Cover | cover | isOpen, isClosed, currentPosition, deviceClass |
| Fan | fan | isOn, percentage, isOscillating, direction |
| MediaPlayer | media_player | isPlaying, mediaTitle, volume, volumePercent, source, sourceList |
| Vacuum | vacuum | isCleaning, isDocked, batteryLevel, fanSpeed |
| AlarmPanel | alarm_control_panel | isArmedHome, isArmedAway, isDisarmed, isTriggered |
| Sensor | sensor | numericValue, unitOfMeasurement, formattedState, deviceClass |
| BinarySensor | binary_sensor | isOn, displayState (context-aware: "Open"/"Closed", "Detected"/"Clear") |
import { createDevice, Light, Climate } from '@peripherals/smart-home';
const entity = client.getEntity('light.kitchen');
const light = createDevice(entity) as Light;
console.log(light.supportsColor, light.brightnessPercent);Service call builders
Pre-built, typed service call objects for every common operation:
import {
lightTurnOn, // brightness, color, transition
lightTurnOff,
climateSetTemperature,
climateSetHvacMode,
lockLock,
lockUnlock,
coverOpen,
coverSetPosition,
fanSetPercentage,
mediaPlayerPlay,
mediaPlayerSetVolume,
vacuumStart,
vacuumReturnToBase,
alarmArmHome,
alarmDisarm,
activateScene,
triggerAutomation,
runScript,
// ... and more
} from '@peripherals/smart-home';
await client.executeServiceCall(
lightTurnOn('light.kitchen', {
brightness: 200,
colorTempKelvin: 3000,
transition: 2,
})
);MQTT with Zigbee2MQTT
function Z2MExample() {
const { client, isConnected } = useMQTT({ brokerUrl: 'wss://ha:8884/mqtt' });
useEffect(() => {
if (!isConnected) return;
// Subscribe to all Z2M devices + bridge info
const unsub = client.subscribeZ2M();
client.on('z2mDeviceState', ({ friendlyName, state }) => {
console.log(`${friendlyName}:`, state.temperature, state.humidity);
});
return unsub;
}, [client, isConnected]);
const toggleLight = () => {
client.z2mSetState('bedroom_light', { state: 'TOGGLE' });
};
return <Button title="Toggle" onPress={toggleLight} />;
}MQTT HA Discovery
const unsub = client.subscribeDiscovery();
client.on('discoveryUpdate', ({ component, nodeId, objectId, config }) => {
console.log(`Discovered ${component}: ${config?.name}`);
// Automatically registers devices in HA via MQTT
});HomeKit (iOS only)
HomeKit requires a custom Expo native module. The package provides the JS interface and types.
Setup
- Create the native module:
npx create-expo-module@latest --local expo-homekit - Implement
HomeKitNativeModulein Swift (wrappingHMHomeManager) - Add entitlement via config plugin and
NSHomeKitUsageDescription
Usage
import { HomeKitClient } from '@peripherals/smart-home';
const homekit = new HomeKitClient();
if (homekit.isAvailable) {
const homes = await homekit.getHomes();
const primary = homes.find(h => h.isPrimary);
const accessories = await homekit.getAccessories(primary.uniqueIdentifier);
// Execute a scene
const scenes = await homekit.getActionSets(primary.uniqueIdentifier);
await homekit.executeActionSet(primary.uniqueIdentifier, scenes[0].uniqueIdentifier);
}Entity utilities
import {
extractDomain, // "light.kitchen" → "light"
isStateActive, // "on" | "open" | "unlocked" | "playing" → true
isStateUnavailable, // "unavailable" | "unknown" → true
numericState, // "22.5" → 22.5
filterByDomain, // filter entity array by domain
filterByArea, // filter entity array by area
groupByDomain, // group entities into Record<domain, entity[]>
groupByArea, // group entities into Record<areaId, entity[]>
} from '@peripherals/smart-home';Imperative client usage
The clients can be used without React hooks for non-React contexts:
import { HomeAssistantClient, MQTTClient } from '@peripherals/smart-home';
const ha = new HomeAssistantClient({
url: 'http://192.168.1.100:8123',
auth: { type: 'longLivedToken', token: 'eyJ...' },
});
await ha.connect();
// Subscribe to state changes
ha.onStateChanged((event) => {
console.log(`${event.entityId}: ${event.newState?.state}`);
});
// Subscribe to specific entities
ha.onEntityStateChanged(['light.kitchen', 'sensor.temp'], (event) => {
console.log(event);
});
// Registry queries
const areas = await ha.getAreas();
const devices = await ha.getDevices();
const config = await ha.getConfig();
// Raw WebSocket commands
await ha.sendCommand({ type: 'config/area_registry/list' });
// Custom event subscriptions
const unsub = await ha.subscribeEvents('state_changed', (data) => {
console.log('State changed:', data);
});
// Fire custom events
await ha.fireEvent('my_custom_event', { key: 'value' });Requirements
- React Native ≥ 0.73
- Home Assistant WebSocket support is bundled (
home-assistant-js-websocket) mqtt≥ 5.0.0 (optional, for MQTT features; bundled as an optional dependency)- Home Assistant ≥ 2024.1 (WebSocket API)
- iOS only: custom Expo Module for HomeKit
Recommended architecture
For most apps, Home Assistant as the universal bridge gives the broadest device coverage:
┌─────────────────────────────────────────────┐
│ React Native App │
├──────────┬──────────┬──────────┬────────────┤
│ useEntity│ useMQTT │ useAreas │ HomeKit* │
├──────────┴──────────┴──────────┴────────────┤
│ @peripherals/smart-home │
├─────────────────┬──────────┬────────────────┤
│ HA WebSocket │ MQTT │ HomeKit Native │
├─────────────────┴──────────┴────────────────┤
│ Home Assistant │
├──────┬──────┬──────┬──────┬─────┬───────────┤
│Zigbee│Z-Wave│Matter│ Hue │Wi-Fi│ 2000+ ... │
└──────┴──────┴──────┴──────┴─────┴───────────┘*HomeKit provides iOS-native Siri integration and direct Matter commissioning without HA.
License
MIT
