Skip to content
This repository was archived by the owner on Jan 14, 2026. It is now read-only.

Commit 0baed87

Browse files
committed
feat: Add basic support for outlets
1 parent 7007ad3 commit 0baed87

6 files changed

Lines changed: 158 additions & 11 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ jspm_packages/
4747

4848
# Test configuration
4949
/test/hbConfig
50+
/src/discoverDevices.ts

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ This plugin currently focuses on **stability and simplicity** rather than featur
2222

2323
Currently, this plugin supports the following devices and actions within the **FRITZ!** ecosystem:
2424

25-
| Device | Supported actions |
26-
|-------------------------------------------|-------------------------------------------------------------------------------------------------|
27-
| **Thermostats**<br>(e.g. FRITZ!DECT 301) | - Power toggle<br>- Change target temperature<br>- Temperature reporting<br>- Recall templates |
25+
| Device | Supported actions |
26+
|--------------------------------------------|-------------------------------------------------------------------------------------------------|
27+
| **Thermostats**<br>(e.g. FRITZ!DECT 301) | - Temperature reporting<br>- Change target temperature<br>- Recall templates |
28+
| **Outlets**<br>(e.g. FRITZ!DECT 200 / 210) | - Power toggle<br>- Temperature reporting |
2829

29-
> Other FRITZ! SmartHome devices (e.g., plugs or lights) are not yet supported.
30+
> Other FRITZ! SmartHome devices (e.g. window sensors or lights) are not yet supported.
3031
> Contributions are welcome if you wish to extend functionality.
3132
> See below for further information.
3233
@@ -104,7 +105,6 @@ Some examples of possible devices and features are:
104105
| **FRITZ!Box**, FRITZ!Repeater | - Toggle guest WLAN |
105106
| **Buttons**<br>(e.g. FRITZ!DECT 400, 440) | - Temperature and humidity reporting<br>- *Note*: Button presses cannot be detected reliably |
106107
| **Lights**<br>(e.g. FRITZ!DECT 500) | - Power toggle<br>- Change brightness / color<br>- Support for adaptive lighting |
107-
| **Outlets**<br>(e.g. FRITZ!DECT 200) | - Power toggle<br>- Temperature reporting |
108108
| **Sensors**<br>(e.g. FRITZ!DECT 350) | - Window state reporting |
109109

110110
---

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "homebridge-fritz-light",
33
"displayName": "Homebridge FRITZ! Light",
44
"type": "module",
5-
"version": "1.0.2",
5+
"version": "1.0.3",
66
"description": "Homebridge integration for the FRITZ! ecosystem",
77
"author": "fox34",
88
"license": "Apache-2.0",

src/accessories/switch.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { CharacteristicValue, PlatformAccessory } from 'homebridge';
2+
import type { FritzLight } from '../platform.js';
3+
import { Switch as FritzSwitch } from '../fritzbox/accessories/switch.js';
4+
import { HomebridgeAccessory } from 'fritz-light';
5+
6+
export class Switch implements HomebridgeAccessory {
7+
constructor(platform: FritzLight, accessory: PlatformAccessory, device: FritzSwitch) {
8+
9+
// Set basic accessory information
10+
accessory.getService(platform.Service.AccessoryInformation)!
11+
.setCharacteristic(platform.Characteristic.Manufacturer, device.manufacturer)
12+
.setCharacteristic(platform.Characteristic.Model, device.productName)
13+
.setCharacteristic(platform.Characteristic.SerialNumber, device.ain)
14+
.setCharacteristic(platform.Characteristic.FirmwareRevision, device.firmwareVersion)
15+
;
16+
17+
// Create switch service
18+
const switchService = accessory.getService(platform.Service.Switch) || accessory.addService(platform.Service.Switch);
19+
20+
// Set the service name, this is what is displayed as the default name on the Home app
21+
switchService.setCharacteristic(platform.Characteristic.Name, device.name);
22+
23+
// Implement required characteristics
24+
switchService.getCharacteristic(platform.Characteristic.On)
25+
.onGet(() => device.on)
26+
.onSet(async (value: CharacteristicValue) => {
27+
platform.log.info(`Setting target state for ${device.name} to ${value}`);
28+
await device.setState(<boolean>value);
29+
})
30+
.updateValue(device.on)
31+
;
32+
33+
// Optional: temperature sensor
34+
if (device.currentTemperature !== undefined) {
35+
const temperatureSensorService = accessory.getService(platform.Service.TemperatureSensor) ||
36+
accessory.addService(platform.Service.TemperatureSensor);
37+
38+
temperatureSensorService.setCharacteristic(platform.Characteristic.Name, `${device.name} Temperature`);
39+
temperatureSensorService.getCharacteristic(platform.Characteristic.CurrentTemperature)
40+
.onGet(() => <number>device.currentTemperature)
41+
.updateValue(<number>device.currentTemperature)
42+
;
43+
}
44+
}
45+
}

src/fritzbox/accessories/switch.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { HomebridgeAccessory } from 'fritz-light';
2+
import type { FritzBox } from '../fritzbox.js';
3+
import { Switch as HomebridgeSwitch } from '../../accessories/switch.js';
4+
import type { PlatformAccessory } from 'homebridge';
5+
import type { FritzLight } from '../../platform.js';
6+
import { objectsEqualShallow } from '../../utils/utils.js';
7+
import { FritzAccessory } from '../smarthome.js';
8+
9+
type SwitchState = {
10+
name: string;
11+
firmwareVersion: string;
12+
on: boolean;
13+
currentTemperature: number | undefined;
14+
}
15+
16+
export class Switch implements FritzAccessory {
17+
constructor(
18+
private fritzbox: FritzBox,
19+
public ain: string,
20+
public manufacturer: string,
21+
public productName: string,
22+
private _state: SwitchState,
23+
) {
24+
}
25+
26+
createHomebridgeAccessoryHandler(platform: FritzLight, accessory: PlatformAccessory): HomebridgeAccessory {
27+
return new HomebridgeSwitch(platform, accessory, this);
28+
}
29+
30+
set state(state: SwitchState) {
31+
// Nothing changed
32+
if (objectsEqualShallow(state, this._state)) {
33+
return;
34+
}
35+
36+
this._state = state;
37+
}
38+
39+
get name() {
40+
return this._state.name;
41+
}
42+
43+
get firmwareVersion() {
44+
return this._state.firmwareVersion;
45+
}
46+
47+
get on() {
48+
return this._state.on;
49+
}
50+
51+
async setState(on: boolean) {
52+
const url = `webservices/homeautoswitch.lua?switchcmd=setswitch${on ? 'on' : 'off'}&ain=${this.ain}`;
53+
await this.fritzbox.getAHA(url);
54+
}
55+
56+
get currentTemperature(): number | undefined {
57+
return this._state.currentTemperature;
58+
}
59+
}

src/fritzbox/smarthome.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { HomebridgeAccessory } from 'fritz-light';
33
import type { FritzBox } from './fritzbox.js';
44
import { XMLClient } from './XMLClient.js';
55
import { Template } from './accessories/template.js';
6+
import { Switch } from './accessories/switch.js';
67
import { Thermostat } from './accessories/thermostat.js';
78
import { PlatformAccessory, PlatformConfig } from 'homebridge';
89
import { FritzLight } from '../platform.js';
910

1011
// Supported accessory types
1112
enum AccessoryType {
13+
Switch = 'switch',
1214
Thermostat = 'thermostat', // "hkr" = "Heizkörperregler"
1315
}
1416

@@ -44,7 +46,7 @@ export interface FritzAccessory {
4446
}
4547

4648
export class SmartHome {
47-
private devices: Map<string, FritzAccessory & (Template | Thermostat)> = new Map();
49+
private devices: Map<string, FritzAccessory & (Switch | Template | Thermostat)> = new Map();
4850

4951
/**
5052
* @link https://fritz.support/resources/AHA-HTTP-Interface.pdf
@@ -54,7 +56,7 @@ export class SmartHome {
5456
/**
5557
* Get list of currently registered devices, update existing instances
5658
*/
57-
public async getDevices(config: PlatformConfig): Promise<(FritzAccessory & (Template | Thermostat))[]> {
59+
public async getDevices(config: PlatformConfig): Promise<(FritzAccessory & (Switch | Template | Thermostat))[]> {
5860
await this.fritzbox.init();
5961
const sid = await this.fritzbox.getSid();
6062

@@ -92,10 +94,16 @@ export class SmartHome {
9294
];
9395
}
9496

95-
private getDeviceFromApi(device: AHADevice): FritzAccessory & Thermostat {
97+
private getDeviceFromApi(device: AHADevice): FritzAccessory & (Switch | Thermostat) {
9698
// Nothing else implemented yet
97-
//switch (this.getDeviceType(device)) { ... }
98-
return this.getThermostat(device);
99+
switch (this.getDeviceType(device)) {
100+
case AccessoryType.Switch:
101+
return this.getSwitch(device);
102+
case AccessoryType.Thermostat:
103+
return this.getThermostat(device);
104+
}
105+
106+
throw new Error(`Unknown device type: ${this.getDeviceType(device)}`);
99107
}
100108

101109
private getTemplate(apiTemplate: AHATemplate): Template {
@@ -112,6 +120,33 @@ export class SmartHome {
112120
}
113121
return template;
114122
}
123+
124+
private getSwitch(device: AHADevice): Switch {
125+
const state = {
126+
name: device.name,
127+
firmwareVersion: device['@_fwversion'],
128+
on: <boolean>(device.switch?.state || device.simpleonoff?.state || false),
129+
currentTemperature: device.temperature ? device.temperature.celsius / 10 : undefined,
130+
};
131+
132+
let switchDevice: Switch;
133+
if (this.devices.has(device['@_identifier'])) {
134+
// Update device state
135+
switchDevice = <Switch>this.devices.get(device['@_identifier']);
136+
switchDevice.state = state;
137+
138+
} else {
139+
switchDevice = new Switch(
140+
this.fritzbox,
141+
device['@_identifier'],
142+
device['@_manufacturer'],
143+
device['@_productname'],
144+
state,
145+
);
146+
this.devices.set(switchDevice.ain, switchDevice);
147+
}
148+
return switchDevice;
149+
}
115150

116151
private getThermostat(device: AHADevice): Thermostat {
117152
if (!device.hkr) {
@@ -152,6 +187,13 @@ export class SmartHome {
152187
return AccessoryType.Thermostat;
153188
}
154189

190+
if (
191+
((device['@_functionbitmask'] & AHAFunctionBitmask.Outlet) > 0 && device.switch) ||
192+
((device['@_functionbitmask'] & AHAFunctionBitmask.OnOff) > 0 && device.simpleonoff)
193+
) {
194+
return AccessoryType.Switch;
195+
}
196+
155197
// Not implemented
156198
return undefined;
157199
}

0 commit comments

Comments
 (0)