homebridge-neakasa 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Homebridge Neakasa Plugin Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # Homebridge Neakasa Plugin
2
+
3
+ [![npm version](https://badge.fury.io/js/homebridge-neakasa.svg)](https://badge.fury.io/js/homebridge-neakasa)
4
+ [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
5
+
6
+ Homebridge plugin for Neakasa M1 Cat Litter Box. This plugin allows you to monitor and control your Neakasa litter box through Apple HomeKit.
7
+
8
+ This is a TypeScript/Node.js conversion of the [Home Assistant Neakasa Integration](https://github.com/timniklas/hass-neakasa) by [@timniklas](https://github.com/timniklas).
9
+
10
+ ## Features
11
+
12
+ ### Sensors
13
+ - **Litter Level** - Shows sand/litter level percentage with low indicator
14
+ - **Bin Full** - Binary sensor indicating when the waste bin is full
15
+
16
+ ### Switches
17
+ - **Auto Clean** - Enable/disable automatic cleaning
18
+ - **Child Lock** - Enable/disable child safety lock
19
+ - **Auto Cover** - Enable/disable automatic litter coverage
20
+ - **Auto Leveling** - Enable/disable automatic sand leveling
21
+ - **Silent Mode** - Enable/disable quiet operation mode
22
+ - **Unstoppable Cycle** - Enable/disable uninterruptible cleaning cycles
23
+
24
+ ### Buttons (Stateless Switches)
25
+ - **Clean Now** - Trigger an immediate cleaning cycle
26
+ - **Level Now** - Trigger an immediate sand leveling cycle
27
+
28
+ ## Supported Devices
29
+
30
+ - Neakasa M1 Cat Litter Box
31
+
32
+ ## Installation
33
+
34
+ ### Option 1: Homebridge UI (Recommended)
35
+
36
+ 1. Search for "Neakasa" in the Homebridge Config UI X plugin search
37
+ 2. Click Install
38
+ 3. Configure your credentials in the plugin settings
39
+ 4. Restart Homebridge
40
+
41
+ ### Option 2: Manual Installation
42
+
43
+ ```bash
44
+ npm install -g homebridge-neakasa
45
+ ```
46
+
47
+ ## Configuration
48
+
49
+ Add the following to your Homebridge `config.json`:
50
+
51
+ ```json
52
+ {
53
+ "platforms": [
54
+ {
55
+ "platform": "Neakasa",
56
+ "name": "Neakasa",
57
+ "username": "your@email.com",
58
+ "password": "your_password",
59
+ "pollInterval": 60,
60
+ "debug": false
61
+ }
62
+ ]
63
+ }
64
+ ```
65
+
66
+ ### Configuration Options
67
+
68
+ | Option | Required | Default | Description |
69
+ |--------|----------|---------|-------------|
70
+ | `platform` | Yes | `"Neakasa"` | Must be "Neakasa" |
71
+ | `name` | Yes | `"Neakasa"` | Name for the platform |
72
+ | `username` | Yes | - | Your Neakasa account email |
73
+ | `password` | Yes | - | Your Neakasa account password |
74
+ | `pollInterval` | No | `60` | How often to check for updates (in seconds) |
75
+ | `debug` | No | `false` | Enable debug logging |
76
+
77
+ ## Usage
78
+
79
+ Once configured, your Neakasa litter box will appear in the Home app as an accessory with multiple controls:
80
+
81
+ ### Monitoring
82
+ - **Litter Level**: Check the current litter level percentage
83
+ - **Bin Status**: See if the waste bin needs to be emptied
84
+
85
+ ### Controls
86
+ - Turn on/off various automation features through switches
87
+ - Trigger manual cleaning or leveling cycles
88
+
89
+ ### Automation Ideas
90
+ - Get notified when the bin is full
91
+ - Get notified when litter is low
92
+ - Automatically turn on silent mode at night
93
+ - Create scenes that include litter box settings
94
+
95
+ ## Troubleshooting
96
+
97
+ ### "Failed to connect" errors
98
+
99
+ 1. Verify your username and password are correct
100
+ 2. Make sure you can log into the Neakasa mobile app with the same credentials
101
+ 3. Check your internet connection
102
+ 4. Enable debug logging to see detailed error messages
103
+
104
+ ### Devices not appearing
105
+
106
+ 1. Make sure your litter box is online in the Neakasa app
107
+ 2. Restart Homebridge after adding credentials
108
+ 3. Check the Homebridge logs for any errors
109
+ 4. Try removing and re-adding the platform configuration
110
+
111
+ ### State not updating
112
+
113
+ 1. Check the `pollInterval` setting - increase it if you're seeing rate limit errors
114
+ 2. Verify the device is online in the Neakasa mobile app
115
+ 3. Restart Homebridge
116
+
117
+ ## Development
118
+
119
+ ```bash
120
+ # Clone the repository
121
+ git clone https://github.com/YOUR_USERNAME/homebridge-neakasa.git
122
+ cd homebridge-neakasa
123
+
124
+ # Install dependencies
125
+ npm install
126
+
127
+ # Build the plugin
128
+ npm run build
129
+
130
+ # Link for local testing
131
+ npm link
132
+
133
+ # Watch for changes
134
+ npm run watch
135
+ ```
136
+
137
+ ## Credits
138
+
139
+ - Original Home Assistant integration by [@timniklas](https://github.com/timniklas) - [hass-neakasa](https://github.com/timniklas/hass-neakasa)
140
+ - Converted to Homebridge plugin by Claude AI
141
+
142
+ ## Disclaimer
143
+
144
+ This plugin is not officially endorsed or supported by Neakasa. Use at your own risk and ensure you comply with all relevant terms of service and privacy policies.
145
+
146
+ ## License
147
+
148
+ MIT
149
+
150
+ ## Support
151
+
152
+ If you find this plugin helpful, please consider:
153
+ - Starring the repository on GitHub
154
+ - Reporting issues and bugs
155
+ - Contributing improvements
156
+
157
+ For issues and feature requests, please use the [GitHub Issues](https://github.com/YOUR_USERNAME/homebridge-neakasa/issues) page.
@@ -0,0 +1,113 @@
1
+ {
2
+ "pluginAlias": "Neakasa",
3
+ "pluginType": "platform",
4
+ "singular": true,
5
+ "schema": {
6
+ "type": "object",
7
+ "properties": {
8
+ "name": {
9
+ "title": "Name",
10
+ "type": "string",
11
+ "required": true,
12
+ "default": "Neakasa"
13
+ },
14
+ "username": {
15
+ "title": "Email/Username",
16
+ "type": "string",
17
+ "required": true,
18
+ "placeholder": "your@email.com"
19
+ },
20
+ "password": {
21
+ "title": "Password",
22
+ "type": "string",
23
+ "required": true
24
+ },
25
+ "pollInterval": {
26
+ "title": "Poll Interval (seconds)",
27
+ "type": "integer",
28
+ "default": 60,
29
+ "minimum": 30,
30
+ "description": "How often to check for updates from the Neakasa cloud"
31
+ },
32
+ "deviceName": {
33
+ "title": "Device Name",
34
+ "type": "string",
35
+ "default": "Neakasa M1",
36
+ "description": "Display name for the device in HomeKit"
37
+ },
38
+ "debug": {
39
+ "title": "Enable Debug Logging",
40
+ "type": "boolean",
41
+ "default": false
42
+ },
43
+ "showStatusSensor": {
44
+ "title": "Show Status Sensor",
45
+ "type": "boolean",
46
+ "default": true,
47
+ "description": "Show a contact sensor indicating the current device status (Idle, Cleaning, etc.)"
48
+ },
49
+ "showBinStateSensor": {
50
+ "title": "Show Bin State Sensor",
51
+ "type": "boolean",
52
+ "default": true,
53
+ "description": "Show a leak sensor indicating waste bin state (Normal, Full, Missing)"
54
+ },
55
+ "showWifiSensor": {
56
+ "title": "Show WiFi Signal Sensor",
57
+ "type": "boolean",
58
+ "default": false,
59
+ "description": "Show a humidity sensor repurposed to display WiFi signal strength"
60
+ },
61
+ "showCatSensors": {
62
+ "title": "Show Cat Weight Sensors",
63
+ "type": "boolean",
64
+ "default": true,
65
+ "description": "Show per-cat weight sensors (one per registered cat)"
66
+ }
67
+ }
68
+ },
69
+ "layout": [
70
+ {
71
+ "type": "flex",
72
+ "flex-flow": "row wrap",
73
+ "items": [
74
+ {
75
+ "key": "username",
76
+ "flex": "1 1 50%"
77
+ },
78
+ {
79
+ "key": "password",
80
+ "flex": "1 1 50%"
81
+ }
82
+ ]
83
+ },
84
+ {
85
+ "type": "flex",
86
+ "flex-flow": "row wrap",
87
+ "items": [
88
+ {
89
+ "key": "deviceName",
90
+ "flex": "1 1 50%"
91
+ },
92
+ {
93
+ "key": "pollInterval",
94
+ "flex": "1 1 50%"
95
+ }
96
+ ]
97
+ },
98
+ {
99
+ "type": "fieldset",
100
+ "title": "Optional Sensors",
101
+ "expandable": true,
102
+ "items": [
103
+ "showStatusSensor",
104
+ "showBinStateSensor",
105
+ "showWifiSensor",
106
+ "showCatSensors"
107
+ ]
108
+ },
109
+ {
110
+ "key": "debug"
111
+ }
112
+ ]
113
+ }
@@ -0,0 +1,38 @@
1
+ import { PlatformAccessory, CharacteristicValue } from 'homebridge';
2
+ import { NeakasaPlatform } from './platform';
3
+ import { DeviceData, NeakasaPlatformConfig } from './types';
4
+ export declare class NeakasaAccessory {
5
+ private readonly platform;
6
+ private readonly accessory;
7
+ private readonly iotId;
8
+ private readonly deviceName;
9
+ private services;
10
+ private deviceData?;
11
+ private readonly config;
12
+ constructor(platform: NeakasaPlatform, accessory: PlatformAccessory, iotId: string, deviceName: string, config: NeakasaPlatformConfig);
13
+ private setupServices;
14
+ private addSwitch;
15
+ private removeServiceIfExists;
16
+ private rssiToPercent;
17
+ updateData(data: DeviceData): Promise<void>;
18
+ private updateCatSensors;
19
+ setAutoClean(value: CharacteristicValue): Promise<void>;
20
+ getAutoClean(): Promise<CharacteristicValue>;
21
+ setChildLock(value: CharacteristicValue): Promise<void>;
22
+ getChildLock(): Promise<CharacteristicValue>;
23
+ setAutoBury(value: CharacteristicValue): Promise<void>;
24
+ getAutoBury(): Promise<CharacteristicValue>;
25
+ setAutoLevel(value: CharacteristicValue): Promise<void>;
26
+ getAutoLevel(): Promise<CharacteristicValue>;
27
+ setSilentMode(value: CharacteristicValue): Promise<void>;
28
+ getSilentMode(): Promise<CharacteristicValue>;
29
+ setUnstoppableCycle(value: CharacteristicValue): Promise<void>;
30
+ getUnstoppableCycle(): Promise<CharacteristicValue>;
31
+ setAutoRecovery(value: CharacteristicValue): Promise<void>;
32
+ getAutoRecovery(): Promise<CharacteristicValue>;
33
+ setYoungCatMode(value: CharacteristicValue): Promise<void>;
34
+ getYoungCatMode(): Promise<CharacteristicValue>;
35
+ cleanNow(value: CharacteristicValue): Promise<void>;
36
+ levelNow(value: CharacteristicValue): Promise<void>;
37
+ }
38
+ //# sourceMappingURL=accessory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessory.d.ts","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAqC,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAE/F,qBAAa,gBAAgB;IAMzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAR7B,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAG5B,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,iBAAiB,EAC5B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EACnC,MAAM,EAAE,qBAAqB;IAa/B,OAAO,CAAC,aAAa;IAsFrB,OAAO,CAAC,SAAS;IAgBjB,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,aAAa;IAWf,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA8EjD,OAAO,CAAC,gBAAgB;IAqClB,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAavD,YAAY,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI5C,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,YAAY,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI5C,WAAW,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtD,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI3C,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,YAAY,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI5C,aAAa,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI7C,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9D,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAInD,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1D,eAAe,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI/C,eAAe,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1D,eAAe,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI/C,QAAQ,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAenD,QAAQ,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAc1D"}
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NeakasaAccessory = void 0;
4
+ const types_1 = require("./types");
5
+ class NeakasaAccessory {
6
+ constructor(platform, accessory, iotId, deviceName, config) {
7
+ this.platform = platform;
8
+ this.accessory = accessory;
9
+ this.iotId = iotId;
10
+ this.deviceName = deviceName;
11
+ this.services = new Map();
12
+ this.config = config;
13
+ this.accessory.getService(this.platform.Service.AccessoryInformation)
14
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Neakasa')
15
+ .setCharacteristic(this.platform.Characteristic.Model, 'M1')
16
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, iotId);
17
+ this.setupServices();
18
+ }
19
+ setupServices() {
20
+ const filterService = this.accessory.getService(this.platform.Service.FilterMaintenance) ||
21
+ this.accessory.addService(this.platform.Service.FilterMaintenance, 'Litter Level', 'sand-level');
22
+ filterService.setCharacteristic(this.platform.Characteristic.Name, 'Litter Level');
23
+ this.services.set('filter', filterService);
24
+ const binSensor = this.accessory.getService('bin-full') ||
25
+ this.accessory.addService(this.platform.Service.OccupancySensor, 'Waste Bin Full', 'bin-full');
26
+ binSensor.setCharacteristic(this.platform.Characteristic.Name, 'Waste Bin Full');
27
+ this.services.set('binFull', binSensor);
28
+ this.addSwitch('autoClean', 'Auto Clean', 'auto-clean', this.setAutoClean, this.getAutoClean);
29
+ this.addSwitch('childLock', 'Child Lock', 'child-lock', this.setChildLock, this.getChildLock);
30
+ this.addSwitch('autoBury', 'Auto Bury', 'auto-bury', this.setAutoBury, this.getAutoBury);
31
+ this.addSwitch('autoLevel', 'Auto Level', 'auto-level', this.setAutoLevel, this.getAutoLevel);
32
+ this.addSwitch('silentMode', 'Silent Mode', 'silent-mode', this.setSilentMode, this.getSilentMode);
33
+ this.addSwitch('unstoppable', 'Unstoppable Cycle', 'unstoppable-cycle', this.setUnstoppableCycle, this.getUnstoppableCycle);
34
+ this.addSwitch('autoRecovery', 'Auto Recovery', 'auto-recovery', this.setAutoRecovery, this.getAutoRecovery);
35
+ this.addSwitch('youngCatMode', 'Young Cat Mode', 'young-cat-mode', this.setYoungCatMode, this.getYoungCatMode);
36
+ const cleanSwitch = this.accessory.getService('clean-now') ||
37
+ this.accessory.addService(this.platform.Service.Switch, 'Clean Now', 'clean-now');
38
+ cleanSwitch.setCharacteristic(this.platform.Characteristic.Name, 'Clean Now');
39
+ cleanSwitch.getCharacteristic(this.platform.Characteristic.On)
40
+ .onSet(this.cleanNow.bind(this))
41
+ .onGet(() => false);
42
+ this.services.set('clean', cleanSwitch);
43
+ const levelSwitch = this.accessory.getService('level-now') ||
44
+ this.accessory.addService(this.platform.Service.Switch, 'Level Now', 'level-now');
45
+ levelSwitch.setCharacteristic(this.platform.Characteristic.Name, 'Level Now');
46
+ levelSwitch.getCharacteristic(this.platform.Characteristic.On)
47
+ .onSet(this.levelNow.bind(this))
48
+ .onGet(() => false);
49
+ this.services.set('level', levelSwitch);
50
+ if (this.config.showStatusSensor !== false) {
51
+ const statusSensor = this.accessory.getService('device-status') ||
52
+ this.accessory.addService(this.platform.Service.ContactSensor, 'Status', 'device-status');
53
+ statusSensor.setCharacteristic(this.platform.Characteristic.Name, 'Status');
54
+ this.services.set('status', statusSensor);
55
+ }
56
+ else {
57
+ this.removeServiceIfExists('device-status');
58
+ }
59
+ if (this.config.showBinStateSensor !== false) {
60
+ const binStateSensor = this.accessory.getService('bin-state') ||
61
+ this.accessory.addService(this.platform.Service.LeakSensor, 'Bin State', 'bin-state');
62
+ binStateSensor.setCharacteristic(this.platform.Characteristic.Name, 'Bin State');
63
+ this.services.set('binState', binStateSensor);
64
+ }
65
+ else {
66
+ this.removeServiceIfExists('bin-state');
67
+ }
68
+ if (this.config.showWifiSensor === true) {
69
+ const wifiSensor = this.accessory.getService('wifi-signal') ||
70
+ this.accessory.addService(this.platform.Service.HumiditySensor, 'WiFi Signal', 'wifi-signal');
71
+ wifiSensor.setCharacteristic(this.platform.Characteristic.Name, 'WiFi Signal');
72
+ this.services.set('wifi', wifiSensor);
73
+ }
74
+ else {
75
+ this.removeServiceIfExists('wifi-signal');
76
+ }
77
+ }
78
+ addSwitch(key, name, subType, setter, getter) {
79
+ const service = this.accessory.getService(subType) ||
80
+ this.accessory.addService(this.platform.Service.Switch, name, subType);
81
+ service.setCharacteristic(this.platform.Characteristic.Name, name);
82
+ service.getCharacteristic(this.platform.Characteristic.On)
83
+ .onSet(setter.bind(this))
84
+ .onGet(getter.bind(this));
85
+ this.services.set(key, service);
86
+ }
87
+ removeServiceIfExists(subType) {
88
+ const existing = this.accessory.getService(subType);
89
+ if (existing) {
90
+ this.accessory.removeService(existing);
91
+ }
92
+ }
93
+ rssiToPercent(rssi) {
94
+ if (rssi >= -50) {
95
+ return 100;
96
+ }
97
+ if (rssi <= -100) {
98
+ return 0;
99
+ }
100
+ return 2 * (rssi + 100);
101
+ }
102
+ async updateData(data) {
103
+ this.deviceData = data;
104
+ const filterService = this.services.get('filter');
105
+ const changeIndication = data.sandLevelState === types_1.SandLevel.INSUFFICIENT ?
106
+ this.platform.Characteristic.FilterChangeIndication.CHANGE_FILTER :
107
+ this.platform.Characteristic.FilterChangeIndication.FILTER_OK;
108
+ filterService.updateCharacteristic(this.platform.Characteristic.FilterChangeIndication, changeIndication);
109
+ filterService.updateCharacteristic(this.platform.Characteristic.FilterLifeLevel, data.sandLevelPercent);
110
+ const binSensor = this.services.get('binFull');
111
+ binSensor.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, data.binFullWaitReset ?
112
+ this.platform.Characteristic.OccupancyDetected.OCCUPANCY_DETECTED :
113
+ this.platform.Characteristic.OccupancyDetected.OCCUPANCY_NOT_DETECTED);
114
+ this.services.get('autoClean').updateCharacteristic(this.platform.Characteristic.On, data.cleanCfg?.active === 1);
115
+ this.services.get('childLock').updateCharacteristic(this.platform.Characteristic.On, data.childLockOnOff);
116
+ this.services.get('autoBury').updateCharacteristic(this.platform.Characteristic.On, data.autoBury);
117
+ this.services.get('autoLevel').updateCharacteristic(this.platform.Characteristic.On, data.autoLevel);
118
+ this.services.get('silentMode').updateCharacteristic(this.platform.Characteristic.On, data.silentMode);
119
+ this.services.get('unstoppable').updateCharacteristic(this.platform.Characteristic.On, data.bIntrptRangeDet);
120
+ this.services.get('autoRecovery').updateCharacteristic(this.platform.Characteristic.On, data.autoForceInit);
121
+ this.services.get('youngCatMode').updateCharacteristic(this.platform.Characteristic.On, data.youngCatMode);
122
+ const statusSensor = this.services.get('status');
123
+ if (statusSensor) {
124
+ const isActive = data.bucketStatus !== 0;
125
+ statusSensor.updateCharacteristic(this.platform.Characteristic.ContactSensorState, isActive ?
126
+ this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED :
127
+ this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED);
128
+ const statusName = types_1.BucketStatus[data.bucketStatus] || `Unknown (${data.bucketStatus})`;
129
+ statusSensor.updateCharacteristic(this.platform.Characteristic.Name, statusName);
130
+ }
131
+ const binStateSensor = this.services.get('binState');
132
+ if (binStateSensor) {
133
+ const leakDetected = data.room_of_bin !== 0;
134
+ binStateSensor.updateCharacteristic(this.platform.Characteristic.LeakDetected, leakDetected ?
135
+ this.platform.Characteristic.LeakDetected.LEAK_DETECTED :
136
+ this.platform.Characteristic.LeakDetected.LEAK_NOT_DETECTED);
137
+ const binStateName = types_1.BinState[data.room_of_bin] || `Unknown (${data.room_of_bin})`;
138
+ binStateSensor.updateCharacteristic(this.platform.Characteristic.Name, binStateName);
139
+ }
140
+ const wifiSensor = this.services.get('wifi');
141
+ if (wifiSensor) {
142
+ const signalPercent = this.rssiToPercent(data.wifiRssi);
143
+ wifiSensor.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, signalPercent);
144
+ }
145
+ if (this.config.showCatSensors !== false && data.cat_list && data.cat_list.length > 0) {
146
+ this.updateCatSensors(data);
147
+ }
148
+ this.platform.log.debug(`Updated ${this.deviceName}: Status=${types_1.BucketStatus[data.bucketStatus] || data.bucketStatus}, ` +
149
+ `Sand=${data.sandLevelPercent}%, Bin=${types_1.BinState[data.room_of_bin] || data.room_of_bin}`);
150
+ }
151
+ updateCatSensors(data) {
152
+ for (const cat of data.cat_list) {
153
+ const subType = `cat-${cat.id}`;
154
+ let catSensor = this.services.get(subType);
155
+ if (!catSensor) {
156
+ const existingService = this.accessory.getService(subType);
157
+ if (existingService) {
158
+ catSensor = existingService;
159
+ }
160
+ else {
161
+ catSensor = this.accessory.addService(this.platform.Service.HumiditySensor, cat.name, subType);
162
+ }
163
+ catSensor.setCharacteristic(this.platform.Characteristic.Name, cat.name);
164
+ this.services.set(subType, catSensor);
165
+ }
166
+ const catRecords = data.record_list
167
+ .filter(r => r.cat_id === cat.id)
168
+ .sort((a, b) => b.end_time - a.end_time);
169
+ if (catRecords.length > 0) {
170
+ const latestRecord = catRecords[0];
171
+ const weight = Math.min(100, Math.max(0, latestRecord.weight));
172
+ catSensor.updateCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity, weight);
173
+ }
174
+ }
175
+ }
176
+ async setAutoClean(value) {
177
+ const newValue = value;
178
+ try {
179
+ const cleanCfg = this.deviceData?.cleanCfg || { active: 0 };
180
+ cleanCfg.active = newValue ? 1 : 0;
181
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { cleanCfg });
182
+ this.platform.log.info(`Set Auto Clean to ${newValue}`);
183
+ }
184
+ catch (error) {
185
+ this.platform.log.error(`Failed to set Auto Clean: ${error}`);
186
+ throw new this.platform.api.hap.HapStatusError(-70402);
187
+ }
188
+ }
189
+ async getAutoClean() {
190
+ return this.deviceData?.cleanCfg?.active === 1;
191
+ }
192
+ async setChildLock(value) {
193
+ const newValue = value;
194
+ try {
195
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { childLockOnOff: newValue ? 1 : 0 });
196
+ this.platform.log.info(`Set Child Lock to ${newValue}`);
197
+ }
198
+ catch (error) {
199
+ this.platform.log.error(`Failed to set Child Lock: ${error}`);
200
+ throw new this.platform.api.hap.HapStatusError(-70402);
201
+ }
202
+ }
203
+ async getChildLock() {
204
+ return this.deviceData?.childLockOnOff || false;
205
+ }
206
+ async setAutoBury(value) {
207
+ const newValue = value;
208
+ try {
209
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { autoBury: newValue ? 1 : 0 });
210
+ this.platform.log.info(`Set Auto Bury to ${newValue}`);
211
+ }
212
+ catch (error) {
213
+ this.platform.log.error(`Failed to set Auto Bury: ${error}`);
214
+ throw new this.platform.api.hap.HapStatusError(-70402);
215
+ }
216
+ }
217
+ async getAutoBury() {
218
+ return this.deviceData?.autoBury || false;
219
+ }
220
+ async setAutoLevel(value) {
221
+ const newValue = value;
222
+ try {
223
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { autoLevel: newValue ? 1 : 0 });
224
+ this.platform.log.info(`Set Auto Level to ${newValue}`);
225
+ }
226
+ catch (error) {
227
+ this.platform.log.error(`Failed to set Auto Level: ${error}`);
228
+ throw new this.platform.api.hap.HapStatusError(-70402);
229
+ }
230
+ }
231
+ async getAutoLevel() {
232
+ return this.deviceData?.autoLevel || false;
233
+ }
234
+ async setSilentMode(value) {
235
+ const newValue = value;
236
+ try {
237
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { silentMode: newValue ? 1 : 0 });
238
+ this.platform.log.info(`Set Silent Mode to ${newValue}`);
239
+ }
240
+ catch (error) {
241
+ this.platform.log.error(`Failed to set Silent Mode: ${error}`);
242
+ throw new this.platform.api.hap.HapStatusError(-70402);
243
+ }
244
+ }
245
+ async getSilentMode() {
246
+ return this.deviceData?.silentMode || false;
247
+ }
248
+ async setUnstoppableCycle(value) {
249
+ const newValue = value;
250
+ try {
251
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { bIntrptRangeDet: newValue ? 1 : 0 });
252
+ this.platform.log.info(`Set Unstoppable Cycle to ${newValue}`);
253
+ }
254
+ catch (error) {
255
+ this.platform.log.error(`Failed to set Unstoppable Cycle: ${error}`);
256
+ throw new this.platform.api.hap.HapStatusError(-70402);
257
+ }
258
+ }
259
+ async getUnstoppableCycle() {
260
+ return this.deviceData?.bIntrptRangeDet || false;
261
+ }
262
+ async setAutoRecovery(value) {
263
+ const newValue = value;
264
+ try {
265
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { autoForceInit: newValue ? 1 : 0 });
266
+ this.platform.log.info(`Set Auto Recovery to ${newValue}`);
267
+ }
268
+ catch (error) {
269
+ this.platform.log.error(`Failed to set Auto Recovery: ${error}`);
270
+ throw new this.platform.api.hap.HapStatusError(-70402);
271
+ }
272
+ }
273
+ async getAutoRecovery() {
274
+ return this.deviceData?.autoForceInit || false;
275
+ }
276
+ async setYoungCatMode(value) {
277
+ const newValue = value;
278
+ try {
279
+ await this.platform.neakasaApi.setDeviceProperties(this.iotId, { youngCatMode: newValue ? 1 : 0 });
280
+ this.platform.log.info(`Set Young Cat Mode to ${newValue}`);
281
+ }
282
+ catch (error) {
283
+ this.platform.log.error(`Failed to set Young Cat Mode: ${error}`);
284
+ throw new this.platform.api.hap.HapStatusError(-70402);
285
+ }
286
+ }
287
+ async getYoungCatMode() {
288
+ return this.deviceData?.youngCatMode || false;
289
+ }
290
+ async cleanNow(value) {
291
+ if (value) {
292
+ try {
293
+ await this.platform.neakasaApi.cleanNow(this.iotId);
294
+ this.platform.log.info(`Triggered clean for ${this.deviceName}`);
295
+ setTimeout(() => {
296
+ this.services.get('clean').updateCharacteristic(this.platform.Characteristic.On, false);
297
+ }, 1000);
298
+ }
299
+ catch (error) {
300
+ this.platform.log.error(`Failed to trigger clean: ${error}`);
301
+ throw new this.platform.api.hap.HapStatusError(-70402);
302
+ }
303
+ }
304
+ }
305
+ async levelNow(value) {
306
+ if (value) {
307
+ try {
308
+ await this.platform.neakasaApi.sandLeveling(this.iotId);
309
+ this.platform.log.info(`Triggered leveling for ${this.deviceName}`);
310
+ setTimeout(() => {
311
+ this.services.get('level').updateCharacteristic(this.platform.Characteristic.On, false);
312
+ }, 1000);
313
+ }
314
+ catch (error) {
315
+ this.platform.log.error(`Failed to trigger leveling: ${error}`);
316
+ throw new this.platform.api.hap.HapStatusError(-70402);
317
+ }
318
+ }
319
+ }
320
+ }
321
+ exports.NeakasaAccessory = NeakasaAccessory;
322
+ //# sourceMappingURL=accessory.js.map