homebridge-verano 2.1.4 → 3.0.1

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.
@@ -0,0 +1,36 @@
1
+ name: Build and Publish
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ tags:
9
+ - 'v*'
10
+ pull_request:
11
+ branches:
12
+ - main
13
+ - master
14
+
15
+ jobs:
16
+ publish:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+
21
+ - name: Use Node.js
22
+ uses: actions/setup-node@v6
23
+ with:
24
+ node-version: '25.x'
25
+ registry-url: 'https://registry.npmjs.org'
26
+
27
+ - name: Install dependencies
28
+ run: npm ci
29
+
30
+ - name: Build
31
+ run: npm run build
32
+
33
+ - name: Publish to npm
34
+ run: npm publish
35
+ env:
36
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md CHANGED
@@ -1,6 +1,64 @@
1
+ # Homebridge Verano WiFi
1
2
 
2
- https://github.com/senscho/homebridge-tutorial/blob/master/README.md
3
+ Homebridge accessory plugin to control Verano VER-24 WiFi heaters via emodul.pl.
3
4
 
4
- https://developers.homebridge.io/#/
5
+ Features:
6
+ - Thermostat service (heat/off)
7
+ - Current and target temperature
8
+ - Periodic polling to keep HomeKit in sync
9
+ - Robust login with cookie reuse and auto-reauth
5
10
 
6
- `npm publish` # Publish to npm
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install -g homebridge-verano
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ Add an accessory block in your Homebridge config UI or JSON. Required fields: `username`, `password`.
20
+
21
+ Optional tuning parameters are shown with defaults.
22
+
23
+ ```jsonc
24
+ {
25
+ "accessories": [
26
+ {
27
+ "accessory": "VeranoAccessoryPlugin",
28
+ "name": "Verano",
29
+ "username": "you@example.com",
30
+ "password": "••••••••",
31
+ // optional
32
+ "tileId": 58,
33
+ "setpointIdo": 139,
34
+ "temperatureDivider": 10,
35
+ "offThresholdC": 10,
36
+ "minC": 10,
37
+ "maxC": 30,
38
+ "stepC": 0.5,
39
+ "pollIntervalSec": 30
40
+ }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ Notes:
46
+ - Only HEAT/OFF modes are supported.
47
+ - Temperatures are in Celsius.
48
+
49
+ ## Development
50
+
51
+ ```sh
52
+ npm ci
53
+ npm run build
54
+ ```
55
+
56
+ Link locally into a dev Homebridge if desired:
57
+
58
+ ```sh
59
+ npm link
60
+ ```
61
+
62
+ ## License
63
+
64
+ Apache-2.0
@@ -22,6 +22,54 @@
22
22
  "type": "string",
23
23
  "required": true,
24
24
  "default": "Verano"
25
+ },
26
+ "tileId": {
27
+ "title": "Tile ID (target/current tile)",
28
+ "type": "number",
29
+ "required": false,
30
+ "default": 58
31
+ },
32
+ "setpointIdo": {
33
+ "title": "Setpoint IDO (control)",
34
+ "type": "number",
35
+ "required": false,
36
+ "default": 139
37
+ },
38
+ "temperatureDivider": {
39
+ "title": "Temperature Divider",
40
+ "type": "number",
41
+ "required": false,
42
+ "default": 10
43
+ },
44
+ "offThresholdC": {
45
+ "title": "Off Threshold (°C)",
46
+ "type": "number",
47
+ "required": false,
48
+ "default": 10
49
+ },
50
+ "minC": {
51
+ "title": "Minimum Temperature (°C)",
52
+ "type": "number",
53
+ "required": false,
54
+ "default": 10
55
+ },
56
+ "maxC": {
57
+ "title": "Maximum Temperature (°C)",
58
+ "type": "number",
59
+ "required": false,
60
+ "default": 30
61
+ },
62
+ "stepC": {
63
+ "title": "Temperature Step (°C)",
64
+ "type": "number",
65
+ "required": false,
66
+ "default": 0.5
67
+ },
68
+ "pollIntervalSec": {
69
+ "title": "Polling Interval (seconds)",
70
+ "type": "number",
71
+ "required": false,
72
+ "default": 30
25
73
  }
26
74
  }
27
75
  }
package/dist/index.d.ts CHANGED
@@ -3,22 +3,32 @@ export declare class VeranoAccessoryPlugin implements AccessoryPlugin {
3
3
  private readonly log;
4
4
  private readonly config;
5
5
  private readonly api;
6
- private readonly TEMPERATURE_DIVIDER;
7
- private readonly TURN_ON_OFF_TEMPERATURE;
8
- private readonly informationService;
9
- private readonly name;
10
6
  private readonly service;
11
- private thermostatState;
12
- private isAuthorized;
7
+ private readonly informationService;
8
+ private readonly Characteristic;
9
+ private readonly cfg;
10
+ private axios;
13
11
  private sessionCookie;
14
- private Characteristic;
12
+ private isAuthorized;
13
+ private pollTimer?;
14
+ private writeInFlight;
15
+ private thermostatState;
16
+ private readonly credentials;
15
17
  constructor(log: Logging, config: AccessoryConfig, api: API);
16
18
  identify?(): void;
17
19
  getServices(): Service[];
18
20
  getControllers?(): Controller[];
19
- private requestThermostatState;
20
- private fetchDataTiles;
21
+ private executeWithReauth;
22
+ private startPolling;
23
+ private stopPolling;
24
+ private clampAndStep;
25
+ private ensureSession;
21
26
  private requestAuthorization;
27
+ private fetchDataTiles;
22
28
  private requestTemperatureChange;
29
+ private requestThermostatState;
30
+ private refreshStateSafe;
31
+ private pushStateToHomeKit;
32
+ private setTargetTemperatureC;
23
33
  }
24
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAC,MAAM,YAAY,CAAC;AAa/F,qBAAa,qBAAsB,YAAW,eAAe;IAmBrD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAnBxB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAc;IAClD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAc;IAEtD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAM;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,eAAe,CAIrB;IAEF,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAAM;gBAGP,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,GAAG;IAiG7B,QAAQ,CAAC,IAAI,IAAI;IAIjB,WAAW,IAAI,OAAO,EAAE;IAOxB,cAAc,CAAC,IAAI,UAAU,EAAE;YAIjB,sBAAsB;YActB,cAAc;YAiCd,oBAAoB;YAwBpB,wBAAwB;CA2BzC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAkB,MAAM,YAAY,CAAC;AA4CjH,qBAAa,qBAAsB,YAAW,eAAe;IA+BvD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAhCtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;IAEvD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAUjB;IAEH,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAIrB;IAEF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyC;gBAGlD,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,GAAG;IA+H3B,QAAQ,CAAC,IAAI,IAAI;IAIjB,WAAW,IAAI,OAAO,EAAE;IAIxB,cAAc,CAAC,IAAI,UAAU,EAAE;YAMjB,iBAAiB;IAc/B,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,YAAY;YAKN,aAAa;YAKb,oBAAoB;YA+BpB,cAAc;YAUd,wBAAwB;YAkBxB,sBAAsB;YAiBtB,gBAAgB;IAS9B,OAAO,CAAC,kBAAkB;YAaZ,qBAAqB;CAkBtC"}
package/dist/index.js CHANGED
@@ -10,208 +10,276 @@ module.exports = (api) => {
10
10
  };
11
11
  class VeranoAccessoryPlugin {
12
12
  constructor(log, config, api) {
13
+ var _a, _b, _c, _d, _e, _f, _g, _h;
13
14
  this.log = log;
14
15
  this.config = config;
15
16
  this.api = api;
16
- this.TEMPERATURE_DIVIDER = 10;
17
- this.TURN_ON_OFF_TEMPERATURE = 10;
17
+ this.sessionCookie = '';
18
+ this.isAuthorized = false;
19
+ this.writeInFlight = false;
18
20
  this.thermostatState = {
19
21
  currentTemperature: 0,
20
22
  targetTemperature: 0,
21
- heating: false
23
+ heating: false,
22
24
  };
23
- this.log = log;
24
- this.log.info('Initializing VeranoAccessoryPlugin');
25
- this.config = config;
26
- this.name = config.name;
27
- this.api = api;
28
- this.isAuthorized = false;
29
- this.sessionCookie = '';
30
- this.requestAuthorization();
31
25
  this.Characteristic = this.api.hap.Characteristic;
26
+ // Extract credentials and validate presence
27
+ const raw = this.config;
28
+ if (!raw.username || !raw.password) {
29
+ throw new Error('Missing required config: username and/or password');
30
+ }
31
+ this.credentials = { username: raw.username, password: raw.password };
32
+ // Normalize config
33
+ this.cfg = {
34
+ tileId: (_a = raw.tileId) !== null && _a !== void 0 ? _a : 58,
35
+ setpointIdo: (_b = raw.setpointIdo) !== null && _b !== void 0 ? _b : 139,
36
+ temperatureDivider: (_c = raw.temperatureDivider) !== null && _c !== void 0 ? _c : 10,
37
+ offThresholdC: (_d = raw.offThresholdC) !== null && _d !== void 0 ? _d : 10,
38
+ minC: (_e = raw.minC) !== null && _e !== void 0 ? _e : 10,
39
+ maxC: (_f = raw.maxC) !== null && _f !== void 0 ? _f : 30,
40
+ stepC: (_g = raw.stepC) !== null && _g !== void 0 ? _g : 0.5,
41
+ pollIntervalSec: (_h = raw.pollIntervalSec) !== null && _h !== void 0 ? _h : 30,
42
+ };
43
+ // Axios instance with base URL and timeout
44
+ this.axios = axios_1.default.create({
45
+ baseURL: 'https://emodul.pl',
46
+ timeout: 8000,
47
+ withCredentials: true,
48
+ validateStatus: (s) => s >= 200 && s < 500, // handle 401/403 gracefully
49
+ });
50
+ // Attach cookie if available
51
+ this.axios.interceptors.request.use((req) => {
52
+ var _a;
53
+ if (this.sessionCookie) {
54
+ req.headers = (_a = req.headers) !== null && _a !== void 0 ? _a : {};
55
+ req.headers['Cookie'] = this.sessionCookie;
56
+ }
57
+ return req;
58
+ });
59
+ // Basic 401 reauth and retry once
60
+ this.axios.interceptors.response.use((res) => res, (err) => Promise.reject(err));
32
61
  this.informationService = new this.api.hap.Service.AccessoryInformation()
33
- .setCharacteristic(this.api.hap.Characteristic.Manufacturer, "Verano")
34
- .setCharacteristic(this.api.hap.Characteristic.Model, "VER-24 WiFi");
35
- this.service = new this.api.hap.Service.Thermostat(this.name);
36
- // GET CURRENT STATE
62
+ .setCharacteristic(this.Characteristic.Manufacturer, 'Verano')
63
+ .setCharacteristic(this.Characteristic.Model, 'VER-24 WiFi');
64
+ this.service = new this.api.hap.Service.Thermostat(this.config.name);
65
+ // Current Heating/Cooling State
37
66
  this.service.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState)
38
- .on('get', (callback) => {
39
- this.log.debug('Get CurrentHeatingCoolingState');
40
- const heatingState = this.thermostatState.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF;
41
- callback(null, heatingState);
67
+ .onGet(() => {
68
+ return this.thermostatState.heating
69
+ ? this.Characteristic.CurrentHeatingCoolingState.HEAT
70
+ : this.Characteristic.CurrentHeatingCoolingState.OFF;
42
71
  });
43
- // GET TARGET STATE
72
+ // Target Heating/Cooling State (HEAT/OFF only)
44
73
  this.service.getCharacteristic(this.Characteristic.TargetHeatingCoolingState)
45
- .setProps({
46
- validValues: [
74
+ .setProps({ validValues: [
47
75
  this.Characteristic.TargetHeatingCoolingState.OFF,
48
- this.Characteristic.TargetHeatingCoolingState.HEAT
49
- ]
76
+ this.Characteristic.TargetHeatingCoolingState.HEAT,
77
+ ] })
78
+ .onGet(() => {
79
+ return this.thermostatState.heating
80
+ ? this.Characteristic.TargetHeatingCoolingState.HEAT
81
+ : this.Characteristic.TargetHeatingCoolingState.OFF;
50
82
  })
51
- .on('get', (callback) => {
52
- this.log.debug('Get TargetHeatingCoolingState');
53
- this.requestThermostatState()
54
- .then(thermostatState => {
55
- const heatingState = thermostatState.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF;
56
- callback(null, heatingState);
57
- })
58
- .catch(error => callback(error));
59
- })
60
- .on('set', (value, callback) => {
61
- this.thermostatState.heating = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
62
- if (this.thermostatState.heating) {
63
- callback(null);
64
- return;
83
+ .onSet(async (value) => {
84
+ const heat = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
85
+ // Turning off maps to sending the off threshold setpoint if device requires it
86
+ if (!heat) {
87
+ await this.setTargetTemperatureC(this.cfg.offThresholdC);
65
88
  }
66
- this.log.info('Turning off heating...');
67
- this.requestTemperatureChange(this.TURN_ON_OFF_TEMPERATURE)
68
- .then(() => {
69
- this.log.info('Heating turned off');
70
- callback(null);
71
- })
72
- .catch(error => callback(error));
89
+ this.thermostatState.heating = heat;
90
+ this.pushStateToHomeKit();
73
91
  });
74
- // GET TEMPERATURE
92
+ // Current Temperature
75
93
  this.service.getCharacteristic(this.Characteristic.CurrentTemperature)
76
- .on('get', (callback) => {
77
- this.log.debug('Get CurrentTemperature');
78
- this.requestThermostatState()
79
- .then(thermostatState => callback(null, thermostatState.currentTemperature))
80
- .catch(error => callback(error));
94
+ .setProps({ minStep: 0.1 })
95
+ .onGet(() => {
96
+ return this.thermostatState.currentTemperature;
81
97
  });
82
- // GET TARGET TEMPERATURE
98
+ // Target Temperature
83
99
  this.service.getCharacteristic(this.Characteristic.TargetTemperature)
84
- .on('get', (callback) => {
85
- this.log.debug('Get TargetTemperature');
86
- this.requestThermostatState()
87
- .then(thermostatState => callback(null, thermostatState.targetTemperature))
88
- .catch(error => callback(error));
100
+ .setProps({
101
+ minValue: this.cfg.minC,
102
+ maxValue: this.cfg.maxC,
103
+ minStep: this.cfg.stepC,
89
104
  })
90
- .on('set', (value, callback) => {
91
- this.log.debug('Set TargetTemperature:', value);
92
- this.requestTemperatureChange(value)
93
- .then(() => callback(null))
94
- .catch(error => callback(error));
105
+ .onGet(() => {
106
+ return this.thermostatState.targetTemperature;
95
107
  })
96
- .setProps({
97
- minValue: 10,
98
- maxValue: 30,
99
- minStep: 0.5
108
+ .onSet(async (value) => {
109
+ const v = this.clampAndStep(Number(value), this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
110
+ await this.setTargetTemperatureC(v);
111
+ this.thermostatState.heating = v > this.cfg.offThresholdC;
112
+ this.pushStateToHomeKit();
100
113
  });
114
+ // Celsius only
101
115
  this.service.getCharacteristic(this.Characteristic.TemperatureDisplayUnits)
102
- .on('get', (callback) => {
103
- callback(null, this.Characteristic.TemperatureDisplayUnits.CELSIUS);
104
- })
105
- .on('set', (value, callback) => {
116
+ .onGet(() => this.Characteristic.TemperatureDisplayUnits.CELSIUS)
117
+ .onSet(() => { });
118
+ // Defer network until platform is ready
119
+ this.api.on('didFinishLaunching', async () => {
120
+ var _a;
121
+ try {
122
+ await this.ensureSession();
123
+ await this.refreshStateSafe();
124
+ this.startPolling();
125
+ }
126
+ catch (e) {
127
+ this.log.error('Startup failed:', (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e);
128
+ }
129
+ });
130
+ // Cleanup on shutdown
131
+ this.api.on('shutdown', () => {
132
+ this.stopPolling();
106
133
  });
107
134
  }
108
135
  identify() {
109
136
  this.log('Identify!');
110
137
  }
111
138
  getServices() {
112
- return [
113
- this.informationService,
114
- this.service,
115
- ];
139
+ return [this.informationService, this.service];
116
140
  }
117
141
  getControllers() {
118
142
  return [];
119
143
  }
120
- async requestThermostatState() {
121
- const tiles = await this.fetchDataTiles();
122
- const foundTile = tiles.filter(tile => tile.id === 58)[0];
123
- const targetTemperature = foundTile.params.widget1.value / this.TEMPERATURE_DIVIDER;
124
- const currentTemperature = foundTile.params.widget2.value / this.TEMPERATURE_DIVIDER;
125
- this.thermostatState = {
126
- currentTemperature: currentTemperature,
127
- targetTemperature: targetTemperature,
128
- heating: targetTemperature > this.TURN_ON_OFF_TEMPERATURE
129
- };
130
- this.log.info('Thermostat state:', this.thermostatState);
131
- return this.thermostatState;
132
- }
133
- async fetchDataTiles() {
134
- this.log.debug('Fetching data');
135
- if (!this.isAuthorized) {
136
- this.log.error('Not authorized, cannot get tiles, trying to authorize');
137
- await this.requestAuthorization();
144
+ // --- Helpers ---
145
+ async executeWithReauth(operation) {
146
+ var _a, _b;
147
+ try {
148
+ return await operation();
138
149
  }
139
- const config = {
140
- headers: {
141
- 'Cookie': this.sessionCookie
142
- },
143
- withCredentials: true,
144
- };
145
- return axios_1.default
146
- .get('https://emodul.pl/frontend/module_data', config)
147
- .then(response => {
148
- this.log.debug('Successfully fetched data');
149
- return response.data.tiles;
150
- })
151
- .catch(error => {
152
- if (error.response) {
153
- this.log.error("Error during tiles fetch");
154
- this.log.error("Status:", error.response.status);
155
- this.log.error("Response:");
156
- this.log.error(error.response.data);
157
- }
158
- else {
159
- this.log.error("Error during tiles fetch", error);
150
+ catch (err) {
151
+ const error = err;
152
+ if (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 401 || ((_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.status) === 403) {
153
+ this.isAuthorized = false;
154
+ await this.requestAuthorization();
155
+ return operation();
160
156
  }
161
- this.isAuthorized = false;
162
- throw error;
163
- });
157
+ throw err;
158
+ }
159
+ }
160
+ startPolling() {
161
+ if (this.pollTimer)
162
+ clearInterval(this.pollTimer);
163
+ this.pollTimer = setInterval(async () => {
164
+ await this.refreshStateSafe();
165
+ }, this.cfg.pollIntervalSec * 1000);
166
+ }
167
+ stopPolling() {
168
+ if (this.pollTimer) {
169
+ clearInterval(this.pollTimer);
170
+ this.pollTimer = undefined;
171
+ }
172
+ }
173
+ clampAndStep(v, min, max, step) {
174
+ const clamped = Math.min(Math.max(v, min), max);
175
+ return Math.round(clamped / step) * step;
176
+ }
177
+ async ensureSession() {
178
+ if (this.isAuthorized && this.sessionCookie)
179
+ return;
180
+ await this.requestAuthorization();
164
181
  }
165
- ;
166
182
  async requestAuthorization() {
167
- const requestBody = {
168
- username: this.config.username,
169
- password: this.config.password,
183
+ const body = {
184
+ username: this.credentials.username,
185
+ password: this.credentials.password,
170
186
  rememberMe: true,
171
- languageId: 'en'
187
+ languageId: 'en',
172
188
  };
173
- this.log.info('Trying to authorize with user', requestBody.username);
174
- return axios_1.default
175
- .post('https://emodul.pl/login', requestBody)
176
- .then(loginResponse => {
177
- this.log.info('Successfully authorized', requestBody.username);
178
- const cookies = loginResponse.headers['set-cookie'];
179
- this.sessionCookie = cookies.filter(cookie => cookie.includes('session'))[0];
180
- this.isAuthorized = true;
181
- return loginResponse;
182
- })
183
- .catch(error => {
184
- this.log.error("Error during authorization", requestBody.username, error);
189
+ this.log.info('Authorizing user', this.credentials.username);
190
+ const res = await this.axios.post('/login', body).catch((e) => {
191
+ this.isAuthorized = false;
192
+ throw e;
193
+ });
194
+ if (res.status !== 200) {
185
195
  this.isAuthorized = false;
186
- throw error;
196
+ throw new Error(`Authorization failed: HTTP ${res.status}`);
197
+ }
198
+ const setCookie = res.headers['set-cookie'];
199
+ const session = setCookie === null || setCookie === void 0 ? void 0 : setCookie.find((c) => /session/i.test(c));
200
+ if (!session) {
201
+ this.isAuthorized = false;
202
+ throw new Error('Authorization failed: missing session cookie');
203
+ }
204
+ // Keep only the cookie key=value part
205
+ this.sessionCookie = session.split(';')[0];
206
+ this.isAuthorized = true;
207
+ this.log.info('Authorized as', this.credentials.username);
208
+ }
209
+ async fetchDataTiles() {
210
+ return this.executeWithReauth(async () => {
211
+ const res = await this.axios.get('/frontend/module_data');
212
+ if (res.status !== 200) {
213
+ throw new Error(`Tiles fetch failed: HTTP ${res.status}`);
214
+ }
215
+ return res.data.tiles;
187
216
  });
188
217
  }
189
- async requestTemperatureChange(targetTemperature) {
190
- this.log.info('Changing temperature to', targetTemperature + '°C');
191
- const requestBody = [
218
+ async requestTemperatureChange(targetC) {
219
+ const body = [
192
220
  {
193
- ido: 139,
194
- params: targetTemperature * this.TEMPERATURE_DIVIDER,
195
- module_index: 0
196
- }
197
- ];
198
- const config = {
199
- headers: {
200
- 'Cookie': this.sessionCookie
221
+ ido: this.cfg.setpointIdo,
222
+ params: Math.round(targetC * this.cfg.temperatureDivider),
223
+ module_index: 0,
201
224
  },
202
- withCredentials: true
203
- };
204
- return axios_1.default
205
- .post('https://emodul.pl/send_control_data', requestBody, config)
206
- .then(response => {
207
- this.log.info('Successfully changed temperature to', targetTemperature + '°C');
208
- return response === null || response === void 0 ? void 0 : response.data;
209
- })
210
- .catch(error => {
211
- this.log.error("Error during temperature change", error);
212
- throw error;
225
+ ];
226
+ return this.executeWithReauth(async () => {
227
+ const res = await this.axios.post('/send_control_data', body);
228
+ if (res.status < 200 || res.status >= 300) {
229
+ throw new Error(`Temperature change failed: HTTP ${res.status}`);
230
+ }
231
+ return res.data;
213
232
  });
214
233
  }
234
+ async requestThermostatState() {
235
+ const tiles = await this.fetchDataTiles();
236
+ const tile = tiles.find((t) => t.id === this.cfg.tileId);
237
+ if (!tile)
238
+ throw new Error(`Tile ${this.cfg.tileId} not found`);
239
+ const targetC = tile.params.widget1.value / this.cfg.temperatureDivider;
240
+ const currentC = tile.params.widget2.value / this.cfg.temperatureDivider;
241
+ this.thermostatState = {
242
+ currentTemperature: currentC,
243
+ targetTemperature: targetC,
244
+ heating: targetC > this.cfg.offThresholdC,
245
+ };
246
+ return this.thermostatState;
247
+ }
248
+ async refreshStateSafe() {
249
+ var _a;
250
+ try {
251
+ const state = await this.requestThermostatState();
252
+ this.pushStateToHomeKit(state);
253
+ }
254
+ catch (e) {
255
+ this.log.debug('Refresh failed:', (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e);
256
+ }
257
+ }
258
+ pushStateToHomeKit(state = this.thermostatState) {
259
+ this.service.updateCharacteristic(this.Characteristic.CurrentTemperature, state.currentTemperature);
260
+ this.service.updateCharacteristic(this.Characteristic.CurrentHeatingCoolingState, state.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF);
261
+ this.service.updateCharacteristic(this.Characteristic.TargetTemperature, state.targetTemperature);
262
+ this.service.updateCharacteristic(this.Characteristic.TargetHeatingCoolingState, state.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF);
263
+ }
264
+ async setTargetTemperatureC(targetC) {
265
+ if (this.writeInFlight) {
266
+ // Simple debounce: skip if a write is in progress
267
+ this.log.debug('Skipping set, write in flight');
268
+ return;
269
+ }
270
+ this.writeInFlight = true;
271
+ const bounded = this.clampAndStep(targetC, this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
272
+ this.log.info('Setting target temperature to', `${bounded}°C`);
273
+ try {
274
+ await this.ensureSession();
275
+ await this.requestTemperatureChange(bounded);
276
+ // Refresh from source of truth
277
+ await this.refreshStateSafe();
278
+ }
279
+ finally {
280
+ this.writeInFlight = false;
281
+ }
282
+ }
215
283
  }
216
284
  exports.VeranoAccessoryPlugin = VeranoAccessoryPlugin;
217
285
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-verano",
3
- "version": "2.1.4",
3
+ "version": "3.0.1",
4
4
  "description": "Plugin for Verano Heating System",
5
5
  "displayName": "Homebridge Verano WiFi",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -1,8 +1,39 @@
1
- import {AccessoryConfig, AccessoryPlugin, API, Controller, Logging, Service} from 'homebridge';
2
- import axios from "axios";
1
+ import { AccessoryConfig, AccessoryPlugin, API, Controller, Logging, Service, Characteristic } from 'homebridge';
2
+ import axios, { AxiosInstance } from 'axios';
3
3
 
4
4
  module.exports = (api: API) => {
5
5
  api.registerAccessory('VeranoAccessoryPlugin', VeranoAccessoryPlugin);
6
+ };
7
+
8
+ interface VeranoAccessoryConfig extends AccessoryConfig {
9
+ username: string;
10
+ password: string;
11
+ tileId?: number; // default: 58
12
+ setpointIdo?: number; // default: 139
13
+ temperatureDivider?: number; // default: 10
14
+ offThresholdC?: number; // default: 10
15
+ minC?: number; // default: 10
16
+ maxC?: number; // default: 30
17
+ stepC?: number; // default: 0.5
18
+ pollIntervalSec?: number; // default: 30
19
+ }
20
+
21
+ interface TileWidget {
22
+ value: number;
23
+ }
24
+
25
+ interface TileParams {
26
+ widget1: TileWidget; // target
27
+ widget2: TileWidget; // current
28
+ }
29
+
30
+ interface Tile {
31
+ id: number;
32
+ params: TileParams;
33
+ }
34
+
35
+ interface ModuleDataResponse {
36
+ tiles: Tile[];
6
37
  }
7
38
 
8
39
  interface ThermostatState {
@@ -12,121 +43,163 @@ interface ThermostatState {
12
43
  }
13
44
 
14
45
  export class VeranoAccessoryPlugin implements AccessoryPlugin {
46
+ private readonly service: Service;
47
+ private readonly informationService: Service;
48
+ private readonly Characteristic: typeof Characteristic;
15
49
 
16
- private readonly TEMPERATURE_DIVIDER: number = 10;
17
- private readonly TURN_ON_OFF_TEMPERATURE: number = 10;
50
+ private readonly cfg: Required<Pick<
51
+ VeranoAccessoryConfig,
52
+ | 'tileId'
53
+ | 'setpointIdo'
54
+ | 'temperatureDivider'
55
+ | 'offThresholdC'
56
+ | 'minC'
57
+ | 'maxC'
58
+ | 'stepC'
59
+ | 'pollIntervalSec'
60
+ >>;
18
61
 
19
- private readonly informationService: any;
20
- private readonly name: string;
21
- private readonly service: Service;
62
+ private axios: AxiosInstance;
63
+ private sessionCookie: string = '';
64
+ private isAuthorized = false;
65
+ private pollTimer?: NodeJS.Timeout;
66
+ private writeInFlight = false;
22
67
  private thermostatState: ThermostatState = {
23
68
  currentTemperature: 0,
24
69
  targetTemperature: 0,
25
- heating: false
70
+ heating: false,
26
71
  };
27
72
 
28
- private isAuthorized: boolean;
29
- private sessionCookie: string;
30
- private Characteristic: any;
73
+ private readonly credentials: { username: string; password: string };
31
74
 
32
75
  constructor(
33
- private readonly log: Logging,
34
- private readonly config: AccessoryConfig,
35
- private readonly api: API,
76
+ private readonly log: Logging,
77
+ private readonly config: AccessoryConfig,
78
+ private readonly api: API,
36
79
  ) {
37
- this.log = log;
38
- this.log.info('Initializing VeranoAccessoryPlugin');
39
- this.config = config;
40
- this.name = config.name;
41
- this.api = api;
42
- this.isAuthorized = false;
43
- this.sessionCookie = '';
80
+ this.Characteristic = this.api.hap.Characteristic;
44
81
 
45
- this.requestAuthorization();
82
+ // Extract credentials and validate presence
83
+ const raw = this.config as Partial<VeranoAccessoryConfig>;
84
+ if (!raw.username || !raw.password) {
85
+ throw new Error('Missing required config: username and/or password');
86
+ }
87
+ this.credentials = { username: raw.username, password: raw.password };
46
88
 
47
- this.Characteristic = this.api.hap.Characteristic;
89
+ // Normalize config
90
+ this.cfg = {
91
+ tileId: raw.tileId ?? 58,
92
+ setpointIdo: raw.setpointIdo ?? 139,
93
+ temperatureDivider: raw.temperatureDivider ?? 10,
94
+ offThresholdC: raw.offThresholdC ?? 10,
95
+ minC: raw.minC ?? 10,
96
+ maxC: raw.maxC ?? 30,
97
+ stepC: raw.stepC ?? 0.5,
98
+ pollIntervalSec: raw.pollIntervalSec ?? 30,
99
+ };
100
+
101
+ // Axios instance with base URL and timeout
102
+ this.axios = axios.create({
103
+ baseURL: 'https://emodul.pl',
104
+ timeout: 8000,
105
+ withCredentials: true,
106
+ validateStatus: (s) => s >= 200 && s < 500, // handle 401/403 gracefully
107
+ });
108
+
109
+ // Attach cookie if available
110
+ this.axios.interceptors.request.use((req) => {
111
+ if (this.sessionCookie) {
112
+ req.headers = req.headers ?? {};
113
+ (req.headers as any)['Cookie'] = this.sessionCookie;
114
+ }
115
+ return req;
116
+ });
117
+
118
+ // Basic 401 reauth and retry once
119
+ this.axios.interceptors.response.use(
120
+ (res) => res,
121
+ (err) => Promise.reject(err),
122
+ );
48
123
 
49
124
  this.informationService = new this.api.hap.Service.AccessoryInformation()
50
- .setCharacteristic(this.api.hap.Characteristic.Manufacturer, "Verano")
51
- .setCharacteristic(this.api.hap.Characteristic.Model, "VER-24 WiFi");
125
+ .setCharacteristic(this.Characteristic.Manufacturer, 'Verano')
126
+ .setCharacteristic(this.Characteristic.Model, 'VER-24 WiFi');
52
127
 
53
- this.service = new this.api.hap.Service.Thermostat(this.name);
128
+ this.service = new this.api.hap.Service.Thermostat(this.config.name);
54
129
 
55
- // GET CURRENT STATE
130
+ // Current Heating/Cooling State
56
131
  this.service.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState)
57
- .on('get', (callback) => {
58
- this.log.debug('Get CurrentHeatingCoolingState');
59
- const heatingState = this.thermostatState.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF;
60
- callback(null, heatingState);
61
- });
132
+ .onGet(() => {
133
+ return this.thermostatState.heating
134
+ ? this.Characteristic.CurrentHeatingCoolingState.HEAT
135
+ : this.Characteristic.CurrentHeatingCoolingState.OFF;
136
+ });
62
137
 
63
- // GET TARGET STATE
138
+ // Target Heating/Cooling State (HEAT/OFF only)
64
139
  this.service.getCharacteristic(this.Characteristic.TargetHeatingCoolingState)
65
- .setProps({
66
- validValues: [
67
- this.Characteristic.TargetHeatingCoolingState.OFF,
68
- this.Characteristic.TargetHeatingCoolingState.HEAT
69
- ]
70
- })
71
- .on('get', (callback) => {
72
- this.log.debug('Get TargetHeatingCoolingState');
73
- this.requestThermostatState()
74
- .then(thermostatState => {
75
- const heatingState = thermostatState.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF;
76
- callback(null, heatingState);
77
- })
78
- .catch(error => callback(error));
79
- })
80
- .on('set', (value, callback) => {
81
- this.thermostatState.heating = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
82
- if (this.thermostatState.heating) {
83
- callback(null);
84
- return;
85
- }
86
- this.log.info('Turning off heating...');
87
- this.requestTemperatureChange(this.TURN_ON_OFF_TEMPERATURE)
88
- .then(() => {
89
- this.log.info('Heating turned off');
90
- callback(null);
91
- })
92
- .catch(error => callback(error));
93
- });
94
-
95
- // GET TEMPERATURE
140
+ .setProps({ validValues: [
141
+ this.Characteristic.TargetHeatingCoolingState.OFF,
142
+ this.Characteristic.TargetHeatingCoolingState.HEAT,
143
+ ]})
144
+ .onGet(() => {
145
+ return this.thermostatState.heating
146
+ ? this.Characteristic.TargetHeatingCoolingState.HEAT
147
+ : this.Characteristic.TargetHeatingCoolingState.OFF;
148
+ })
149
+ .onSet(async (value) => {
150
+ const heat = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
151
+ // Turning off maps to sending the off threshold setpoint if device requires it
152
+ if (!heat) {
153
+ await this.setTargetTemperatureC(this.cfg.offThresholdC);
154
+ }
155
+ this.thermostatState.heating = heat;
156
+ this.pushStateToHomeKit();
157
+ });
158
+
159
+ // Current Temperature
96
160
  this.service.getCharacteristic(this.Characteristic.CurrentTemperature)
97
- .on('get', (callback) => {
98
- this.log.debug('Get CurrentTemperature');
99
- this.requestThermostatState()
100
- .then(thermostatState => callback(null, thermostatState.currentTemperature))
101
- .catch(error => callback(error));
102
- });
103
-
104
- // GET TARGET TEMPERATURE
161
+ .setProps({ minStep: 0.1 })
162
+ .onGet(() => {
163
+ return this.thermostatState.currentTemperature;
164
+ });
165
+
166
+ // Target Temperature
105
167
  this.service.getCharacteristic(this.Characteristic.TargetTemperature)
106
- .on('get', (callback) => {
107
- this.log.debug('Get TargetTemperature');
108
- this.requestThermostatState()
109
- .then(thermostatState => callback(null, thermostatState.targetTemperature))
110
- .catch(error => callback(error));
111
- })
112
- .on('set', (value, callback) => {
113
- this.log.debug('Set TargetTemperature:', value);
114
- this.requestTemperatureChange(value as number)
115
- .then(() => callback(null))
116
- .catch(error => callback(error));
117
- })
118
- .setProps({
119
- minValue: 10,
120
- maxValue: 30,
121
- minStep: 0.5
122
- });
168
+ .setProps({
169
+ minValue: this.cfg.minC,
170
+ maxValue: this.cfg.maxC,
171
+ minStep: this.cfg.stepC,
172
+ })
173
+ .onGet(() => {
174
+ return this.thermostatState.targetTemperature;
175
+ })
176
+ .onSet(async (value) => {
177
+ const v = this.clampAndStep(Number(value), this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
178
+ await this.setTargetTemperatureC(v);
179
+ this.thermostatState.heating = v > this.cfg.offThresholdC;
180
+ this.pushStateToHomeKit();
181
+ });
123
182
 
183
+ // Celsius only
124
184
  this.service.getCharacteristic(this.Characteristic.TemperatureDisplayUnits)
125
- .on('get', (callback) => {
126
- callback(null, this.Characteristic.TemperatureDisplayUnits.CELSIUS);
127
- })
128
- .on('set', (value, callback) => {
129
- });
185
+ .onGet(() => this.Characteristic.TemperatureDisplayUnits.CELSIUS)
186
+ .onSet(() => { /* no-op, force Celsius */ });
187
+
188
+ // Defer network until platform is ready
189
+ this.api.on('didFinishLaunching', async () => {
190
+ try {
191
+ await this.ensureSession();
192
+ await this.refreshStateSafe();
193
+ this.startPolling();
194
+ } catch (e) {
195
+ this.log.error('Startup failed:', (e as Error)?.message ?? e);
196
+ }
197
+ });
198
+
199
+ // Cleanup on shutdown
200
+ this.api.on('shutdown', () => {
201
+ this.stopPolling();
202
+ });
130
203
  }
131
204
 
132
205
  identify?(): void {
@@ -134,112 +207,167 @@ export class VeranoAccessoryPlugin implements AccessoryPlugin {
134
207
  }
135
208
 
136
209
  getServices(): Service[] {
137
- return [
138
- this.informationService,
139
- this.service,
140
- ];
210
+ return [this.informationService, this.service];
141
211
  }
142
212
 
143
213
  getControllers?(): Controller[] {
144
214
  return [];
145
215
  }
146
216
 
147
- private async requestThermostatState() {
148
- const tiles = await this.fetchDataTiles();
149
- const foundTile = tiles.filter(tile => tile.id === 58)[0];
150
- const targetTemperature = foundTile.params.widget1.value / this.TEMPERATURE_DIVIDER;
151
- const currentTemperature = foundTile.params.widget2.value / this.TEMPERATURE_DIVIDER;
152
- this.thermostatState = {
153
- currentTemperature: currentTemperature,
154
- targetTemperature: targetTemperature,
155
- heating: targetTemperature > this.TURN_ON_OFF_TEMPERATURE
156
- };
157
- this.log.info('Thermostat state:', this.thermostatState);
158
- return this.thermostatState;
217
+ // --- Helpers ---
218
+
219
+ private async executeWithReauth<T>(operation: () => Promise<T>): Promise<T> {
220
+ try {
221
+ return await operation();
222
+ } catch (err) {
223
+ const error = err as any;
224
+ if (error?.response?.status === 401 || error?.response?.status === 403) {
225
+ this.isAuthorized = false;
226
+ await this.requestAuthorization();
227
+ return operation();
228
+ }
229
+ throw err;
230
+ }
231
+ }
232
+
233
+ private startPolling() {
234
+ if (this.pollTimer) clearInterval(this.pollTimer);
235
+ this.pollTimer = setInterval(async () => {
236
+ await this.refreshStateSafe();
237
+ }, this.cfg.pollIntervalSec * 1000);
159
238
  }
160
239
 
161
- private async fetchDataTiles() {
162
- this.log.debug('Fetching data');
163
- if (!this.isAuthorized) {
164
- this.log.error('Not authorized, cannot get tiles, trying to authorize');
165
- await this.requestAuthorization();
240
+ private stopPolling() {
241
+ if (this.pollTimer) {
242
+ clearInterval(this.pollTimer);
243
+ this.pollTimer = undefined;
166
244
  }
245
+ }
167
246
 
168
- const config = {
169
- headers: {
170
- 'Cookie': this.sessionCookie
171
- },
172
- withCredentials: true,
173
- };
174
- return axios
175
- .get('https://emodul.pl/frontend/module_data', config)
176
- .then(response => {
177
- this.log.debug('Successfully fetched data');
178
- return response.data.tiles;
179
- })
180
- .catch(error => {
181
- if (error.response) {
182
- this.log.error("Error during tiles fetch");
183
- this.log.error("Status:", error.response.status);
184
- this.log.error("Response:");
185
- this.log.error(error.response.data);
186
- } else {
187
- this.log.error("Error during tiles fetch", error);
188
- }
189
- this.isAuthorized = false;
190
- throw error;
191
- });
192
- };
247
+ private clampAndStep(v: number, min: number, max: number, step: number) {
248
+ const clamped = Math.min(Math.max(v, min), max);
249
+ return Math.round(clamped / step) * step;
250
+ }
251
+
252
+ private async ensureSession() {
253
+ if (this.isAuthorized && this.sessionCookie) return;
254
+ await this.requestAuthorization();
255
+ }
193
256
 
194
257
  private async requestAuthorization() {
195
- const requestBody = {
196
- username: this.config.username,
197
- password: this.config.password,
258
+ const body = {
259
+ username: this.credentials.username,
260
+ password: this.credentials.password,
198
261
  rememberMe: true,
199
- languageId: 'en'
262
+ languageId: 'en',
200
263
  };
201
- this.log.info('Trying to authorize with user', requestBody.username);
202
- return axios
203
- .post('https://emodul.pl/login', requestBody)
204
- .then(loginResponse => {
205
- this.log.info('Successfully authorized', requestBody.username);
206
- const cookies = loginResponse.headers['set-cookie'];
207
- this.sessionCookie = cookies.filter(cookie => cookie.includes('session'))[0];
208
- this.isAuthorized = true;
209
- return loginResponse;
210
- })
211
- .catch(error => {
212
- this.log.error("Error during authorization", requestBody.username, error);
213
- this.isAuthorized = false;
214
- throw error;
215
- });
264
+ this.log.info('Authorizing user', this.credentials.username);
265
+ const res = await this.axios.post('/login', body).catch((e) => {
266
+ this.isAuthorized = false;
267
+ throw e;
268
+ });
269
+
270
+ if (res.status !== 200) {
271
+ this.isAuthorized = false;
272
+ throw new Error(`Authorization failed: HTTP ${res.status}`);
273
+ }
274
+
275
+ const setCookie = res.headers['set-cookie'] as string[] | undefined;
276
+ const session = setCookie?.find((c) => /session/i.test(c));
277
+ if (!session) {
278
+ this.isAuthorized = false;
279
+ throw new Error('Authorization failed: missing session cookie');
280
+ }
281
+
282
+ // Keep only the cookie key=value part
283
+ this.sessionCookie = session.split(';')[0];
284
+ this.isAuthorized = true;
285
+ this.log.info('Authorized as', this.credentials.username);
216
286
  }
217
287
 
218
- private async requestTemperatureChange(targetTemperature: number) {
219
- this.log.info('Changing temperature to', targetTemperature + '°C');
220
- const requestBody = [
221
- {
222
- ido: 139,
223
- params: targetTemperature * this.TEMPERATURE_DIVIDER,
224
- module_index: 0
288
+ private async fetchDataTiles(): Promise<Tile[]> {
289
+ return this.executeWithReauth(async () => {
290
+ const res = await this.axios.get<ModuleDataResponse>('/frontend/module_data');
291
+ if (res.status !== 200) {
292
+ throw new Error(`Tiles fetch failed: HTTP ${res.status}`);
225
293
  }
226
- ]
227
- const config = {
228
- headers: {
229
- 'Cookie': this.sessionCookie
294
+ return res.data.tiles;
295
+ });
296
+ }
297
+
298
+ private async requestTemperatureChange(targetC: number) {
299
+ const body = [
300
+ {
301
+ ido: this.cfg.setpointIdo,
302
+ params: Math.round(targetC * this.cfg.temperatureDivider),
303
+ module_index: 0,
230
304
  },
231
- withCredentials: true
305
+ ];
306
+
307
+ return this.executeWithReauth(async () => {
308
+ const res = await this.axios.post('/send_control_data', body);
309
+ if (res.status < 200 || res.status >= 300) {
310
+ throw new Error(`Temperature change failed: HTTP ${res.status}`);
311
+ }
312
+ return res.data;
313
+ });
314
+ }
315
+
316
+ private async requestThermostatState(): Promise<ThermostatState> {
317
+ const tiles = await this.fetchDataTiles();
318
+ const tile = tiles.find((t) => t.id === this.cfg.tileId);
319
+ if (!tile) throw new Error(`Tile ${this.cfg.tileId} not found`);
320
+
321
+ const targetC = tile.params.widget1.value / this.cfg.temperatureDivider;
322
+ const currentC = tile.params.widget2.value / this.cfg.temperatureDivider;
323
+
324
+ this.thermostatState = {
325
+ currentTemperature: currentC,
326
+ targetTemperature: targetC,
327
+ heating: targetC > this.cfg.offThresholdC,
232
328
  };
233
- return axios
234
- .post('https://emodul.pl/send_control_data', requestBody, config)
235
- .then(response => {
236
- this.log.info('Successfully changed temperature to', targetTemperature + '°C');
237
- return response?.data;
238
- })
239
- .catch(error => {
240
- this.log.error("Error during temperature change", error);
241
- throw error;
242
- })
329
+
330
+ return this.thermostatState;
243
331
  }
244
332
 
333
+ private async refreshStateSafe() {
334
+ try {
335
+ const state = await this.requestThermostatState();
336
+ this.pushStateToHomeKit(state);
337
+ } catch (e) {
338
+ this.log.debug('Refresh failed:', (e as Error)?.message ?? e);
339
+ }
340
+ }
341
+
342
+ private pushStateToHomeKit(state: ThermostatState = this.thermostatState) {
343
+ this.service.updateCharacteristic(this.Characteristic.CurrentTemperature, state.currentTemperature);
344
+ this.service.updateCharacteristic(
345
+ this.Characteristic.CurrentHeatingCoolingState,
346
+ state.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF,
347
+ );
348
+ this.service.updateCharacteristic(this.Characteristic.TargetTemperature, state.targetTemperature);
349
+ this.service.updateCharacteristic(
350
+ this.Characteristic.TargetHeatingCoolingState,
351
+ state.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF,
352
+ );
353
+ }
354
+
355
+ private async setTargetTemperatureC(targetC: number) {
356
+ if (this.writeInFlight) {
357
+ // Simple debounce: skip if a write is in progress
358
+ this.log.debug('Skipping set, write in flight');
359
+ return;
360
+ }
361
+ this.writeInFlight = true;
362
+ const bounded = this.clampAndStep(targetC, this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
363
+ this.log.info('Setting target temperature to', `${bounded}°C`);
364
+ try {
365
+ await this.ensureSession();
366
+ await this.requestTemperatureChange(bounded);
367
+ // Refresh from source of truth
368
+ await this.refreshStateSafe();
369
+ } finally {
370
+ this.writeInFlight = false;
371
+ }
372
+ }
245
373
  }