homebridge-verano 2.1.3 → 3.0.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.
@@ -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,31 @@ 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 startPolling;
22
+ private stopPolling;
23
+ private clampAndStep;
24
+ private ensureSession;
21
25
  private requestAuthorization;
26
+ private fetchDataTiles;
22
27
  private requestTemperatureChange;
28
+ private requestThermostatState;
29
+ private refreshStateSafe;
30
+ private pushStateToHomeKit;
31
+ private setTargetTemperatureC;
23
32
  }
24
33
  //# 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;IAgG7B,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;IAqI3B,QAAQ,CAAC,IAAI,IAAI;IAIjB,WAAW,IAAI,OAAO,EAAE;IAIxB,cAAc,CAAC,IAAI,UAAU,EAAE;IAM/B,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,YAAY;YAKN,aAAa;YAKb,oBAAoB;YA+BpB,cAAc;YAoBd,wBAAwB;YA4BxB,sBAAsB;YAiBtB,gBAAgB;IAS9B,OAAO,CAAC,kBAAkB;YAaZ,qBAAqB;CAkBtC"}
package/dist/index.js CHANGED
@@ -10,207 +10,280 @@ 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('Verano accessory plugin initializing');
25
- this.config = config;
26
- this.name = config.name;
27
- this.api = api;
28
- this.isAuthorized = false;
29
- this.sessionCookie = '';
30
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));
31
61
  this.informationService = new this.api.hap.Service.AccessoryInformation()
32
- .setCharacteristic(this.api.hap.Characteristic.Manufacturer, "Verano")
33
- .setCharacteristic(this.api.hap.Characteristic.Model, "VER-24 WiFi");
34
- this.service = new this.api.hap.Service.Thermostat(this.name);
35
- // 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
36
66
  this.service.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState)
37
- .on('get', (callback) => {
38
- this.log.debug('Get CurrentHeatingCoolingState');
39
- const heatingState = this.thermostatState.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF;
40
- callback(null, heatingState);
67
+ .onGet(async () => {
68
+ return this.thermostatState.heating
69
+ ? this.Characteristic.CurrentHeatingCoolingState.HEAT
70
+ : this.Characteristic.CurrentHeatingCoolingState.OFF;
41
71
  });
42
- // GET TARGET STATE
72
+ // Target Heating/Cooling State (HEAT/OFF only)
43
73
  this.service.getCharacteristic(this.Characteristic.TargetHeatingCoolingState)
44
- .setProps({
45
- validValues: [
74
+ .setProps({ validValues: [
46
75
  this.Characteristic.TargetHeatingCoolingState.OFF,
47
- this.Characteristic.TargetHeatingCoolingState.HEAT
48
- ]
49
- })
50
- .on('get', (callback) => {
51
- this.log.debug('Get TargetHeatingCoolingState');
52
- this.requestThermostatState()
53
- .then(thermostatState => {
54
- const heatingState = thermostatState.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF;
55
- callback(null, heatingState);
56
- })
57
- .catch(error => callback(error));
76
+ this.Characteristic.TargetHeatingCoolingState.HEAT,
77
+ ] })
78
+ .onGet(async () => {
79
+ await this.ensureSession();
80
+ await this.refreshStateSafe();
81
+ return this.thermostatState.heating
82
+ ? this.Characteristic.TargetHeatingCoolingState.HEAT
83
+ : this.Characteristic.TargetHeatingCoolingState.OFF;
58
84
  })
59
- .on('set', (value, callback) => {
60
- this.thermostatState.heating = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
61
- if (this.thermostatState.heating) {
62
- callback(null);
63
- return;
85
+ .onSet(async (value) => {
86
+ const heat = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
87
+ // Turning off maps to sending the off threshold setpoint if device requires it
88
+ if (!heat) {
89
+ await this.setTargetTemperatureC(this.cfg.offThresholdC);
64
90
  }
65
- this.log.info('Turning off heating...');
66
- this.requestTemperatureChange(this.TURN_ON_OFF_TEMPERATURE)
67
- .then(() => {
68
- this.log.info('Heating turned off');
69
- callback(null);
70
- })
71
- .catch(error => callback(error));
91
+ this.thermostatState.heating = heat;
92
+ this.pushStateToHomeKit();
72
93
  });
73
- // GET TEMPERATURE
94
+ // Current Temperature
74
95
  this.service.getCharacteristic(this.Characteristic.CurrentTemperature)
75
- .on('get', (callback) => {
76
- this.log.debug('Get CurrentTemperature');
77
- this.requestThermostatState()
78
- .then(thermostatState => callback(null, thermostatState.currentTemperature))
79
- .catch(error => callback(error));
96
+ .setProps({ minStep: 0.1 })
97
+ .onGet(async () => {
98
+ await this.ensureSession();
99
+ await this.refreshStateSafe();
100
+ return this.thermostatState.currentTemperature;
80
101
  });
81
- // GET TARGET TEMPERATURE
102
+ // Target Temperature
82
103
  this.service.getCharacteristic(this.Characteristic.TargetTemperature)
83
- .on('get', (callback) => {
84
- this.log.debug('Get TargetTemperature');
85
- this.requestThermostatState()
86
- .then(thermostatState => callback(null, thermostatState.targetTemperature))
87
- .catch(error => callback(error));
104
+ .setProps({
105
+ minValue: this.cfg.minC,
106
+ maxValue: this.cfg.maxC,
107
+ minStep: this.cfg.stepC,
88
108
  })
89
- .on('set', (value, callback) => {
90
- this.log.debug('Set TargetTemperature:', value);
91
- this.requestTemperatureChange(value)
92
- .then(() => callback(null))
93
- .catch(error => callback(error));
109
+ .onGet(async () => {
110
+ await this.ensureSession();
111
+ await this.refreshStateSafe();
112
+ return this.thermostatState.targetTemperature;
94
113
  })
95
- .setProps({
96
- minValue: 10,
97
- maxValue: 30,
98
- minStep: 0.5
114
+ .onSet(async (value) => {
115
+ const v = this.clampAndStep(Number(value), this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
116
+ await this.setTargetTemperatureC(v);
117
+ this.thermostatState.heating = v > this.cfg.offThresholdC;
118
+ this.pushStateToHomeKit();
99
119
  });
120
+ // Celsius only
100
121
  this.service.getCharacteristic(this.Characteristic.TemperatureDisplayUnits)
101
- .on('get', (callback) => {
102
- callback(null, this.Characteristic.TemperatureDisplayUnits.CELSIUS);
103
- })
104
- .on('set', (value, callback) => {
122
+ .onGet(async () => this.Characteristic.TemperatureDisplayUnits.CELSIUS)
123
+ .onSet(async () => { });
124
+ // Defer network until platform is ready
125
+ this.api.on('didFinishLaunching', async () => {
126
+ var _a;
127
+ try {
128
+ await this.ensureSession();
129
+ await this.refreshStateSafe();
130
+ this.startPolling();
131
+ }
132
+ catch (e) {
133
+ this.log.error('Startup failed:', (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e);
134
+ }
135
+ });
136
+ // Cleanup on shutdown
137
+ this.api.on('shutdown', () => {
138
+ this.stopPolling();
105
139
  });
106
- this.log.info('Verano accessory plugin initialized');
107
140
  }
108
141
  identify() {
109
142
  this.log('Identify!');
110
143
  }
111
144
  getServices() {
112
- return [
113
- this.informationService,
114
- this.service,
115
- ];
145
+ return [this.informationService, this.service];
116
146
  }
117
147
  getControllers() {
118
148
  return [];
119
149
  }
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;
150
+ // --- Helpers ---
151
+ startPolling() {
152
+ if (this.pollTimer)
153
+ clearInterval(this.pollTimer);
154
+ this.pollTimer = setInterval(async () => {
155
+ await this.refreshStateSafe();
156
+ }, this.cfg.pollIntervalSec * 1000);
132
157
  }
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();
158
+ stopPolling() {
159
+ if (this.pollTimer) {
160
+ clearInterval(this.pollTimer);
161
+ this.pollTimer = undefined;
138
162
  }
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);
160
- }
161
- this.isAuthorized = false;
162
- throw error;
163
- });
164
163
  }
165
- ;
164
+ clampAndStep(v, min, max, step) {
165
+ const clamped = Math.min(Math.max(v, min), max);
166
+ return Math.round(clamped / step) * step;
167
+ }
168
+ async ensureSession() {
169
+ if (this.isAuthorized && this.sessionCookie)
170
+ return;
171
+ await this.requestAuthorization();
172
+ }
166
173
  async requestAuthorization() {
167
- const requestBody = {
168
- username: this.config.username,
169
- password: this.config.password,
174
+ const body = {
175
+ username: this.credentials.username,
176
+ password: this.credentials.password,
170
177
  rememberMe: true,
171
- languageId: 'en'
178
+ languageId: 'en',
172
179
  };
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);
180
+ this.log.info('Authorizing user', this.credentials.username);
181
+ const res = await this.axios.post('/login', body).catch((e) => {
185
182
  this.isAuthorized = false;
186
- throw error;
183
+ throw e;
187
184
  });
185
+ if (res.status !== 200) {
186
+ this.isAuthorized = false;
187
+ throw new Error(`Authorization failed: HTTP ${res.status}`);
188
+ }
189
+ const setCookie = res.headers['set-cookie'];
190
+ const session = setCookie === null || setCookie === void 0 ? void 0 : setCookie.find((c) => /session/i.test(c));
191
+ if (!session) {
192
+ this.isAuthorized = false;
193
+ throw new Error('Authorization failed: missing session cookie');
194
+ }
195
+ // Keep only the cookie key=value part
196
+ this.sessionCookie = session.split(';')[0];
197
+ this.isAuthorized = true;
198
+ this.log.info('Authorized as', this.credentials.username);
188
199
  }
189
- async requestTemperatureChange(targetTemperature) {
190
- this.log.info('Changing temperature to', targetTemperature + '°C');
191
- const requestBody = [
192
- {
193
- ido: 139,
194
- params: targetTemperature * this.TEMPERATURE_DIVIDER,
195
- module_index: 0
200
+ async fetchDataTiles() {
201
+ const res = await this.axios.get('/frontend/module_data');
202
+ if (res.status === 401 || res.status === 403) {
203
+ this.isAuthorized = false;
204
+ await this.requestAuthorization();
205
+ const retry = await this.axios.get('/frontend/module_data');
206
+ if (retry.status !== 200) {
207
+ throw new Error(`Tiles fetch failed after reauth: HTTP ${retry.status}`);
196
208
  }
197
- ];
198
- const config = {
199
- headers: {
200
- 'Cookie': this.sessionCookie
209
+ return retry.data.tiles;
210
+ }
211
+ if (res.status !== 200) {
212
+ throw new Error(`Tiles fetch failed: HTTP ${res.status}`);
213
+ }
214
+ return res.data.tiles;
215
+ }
216
+ async requestTemperatureChange(targetC) {
217
+ const body = [
218
+ {
219
+ ido: this.cfg.setpointIdo,
220
+ params: Math.round(targetC * this.cfg.temperatureDivider),
221
+ module_index: 0,
201
222
  },
202
- withCredentials: true
223
+ ];
224
+ const res = await this.axios.post('/send_control_data', body);
225
+ if (res.status === 401 || res.status === 403) {
226
+ this.isAuthorized = false;
227
+ await this.requestAuthorization();
228
+ const retry = await this.axios.post('/send_control_data', body);
229
+ if (retry.status < 200 || retry.status >= 300) {
230
+ throw new Error(`Temperature change failed after reauth: HTTP ${retry.status}`);
231
+ }
232
+ return retry.data;
233
+ }
234
+ if (res.status < 200 || res.status >= 300) {
235
+ throw new Error(`Temperature change failed: HTTP ${res.status}`);
236
+ }
237
+ return res.data;
238
+ }
239
+ async requestThermostatState() {
240
+ const tiles = await this.fetchDataTiles();
241
+ const tile = tiles.find((t) => t.id === this.cfg.tileId);
242
+ if (!tile)
243
+ throw new Error(`Tile ${this.cfg.tileId} not found`);
244
+ const targetC = tile.params.widget1.value / this.cfg.temperatureDivider;
245
+ const currentC = tile.params.widget2.value / this.cfg.temperatureDivider;
246
+ this.thermostatState = {
247
+ currentTemperature: currentC,
248
+ targetTemperature: targetC,
249
+ heating: targetC > this.cfg.offThresholdC,
203
250
  };
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;
213
- });
251
+ return this.thermostatState;
252
+ }
253
+ async refreshStateSafe() {
254
+ var _a;
255
+ try {
256
+ const state = await this.requestThermostatState();
257
+ this.pushStateToHomeKit(state);
258
+ }
259
+ catch (e) {
260
+ this.log.debug('Refresh failed:', (_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e);
261
+ }
262
+ }
263
+ pushStateToHomeKit(state = this.thermostatState) {
264
+ this.service.updateCharacteristic(this.Characteristic.CurrentTemperature, state.currentTemperature);
265
+ this.service.updateCharacteristic(this.Characteristic.CurrentHeatingCoolingState, state.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF);
266
+ this.service.updateCharacteristic(this.Characteristic.TargetTemperature, state.targetTemperature);
267
+ this.service.updateCharacteristic(this.Characteristic.TargetHeatingCoolingState, state.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF);
268
+ }
269
+ async setTargetTemperatureC(targetC) {
270
+ if (this.writeInFlight) {
271
+ // Simple debounce: skip if a write is in progress
272
+ this.log.debug('Skipping set, write in flight');
273
+ return;
274
+ }
275
+ this.writeInFlight = true;
276
+ const bounded = this.clampAndStep(targetC, this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
277
+ this.log.info('Setting target temperature to', `${bounded}°C`);
278
+ try {
279
+ await this.ensureSession();
280
+ await this.requestTemperatureChange(bounded);
281
+ // Refresh from source of truth
282
+ await this.refreshStateSafe();
283
+ }
284
+ finally {
285
+ this.writeInFlight = false;
286
+ }
214
287
  }
215
288
  }
216
289
  exports.VeranoAccessoryPlugin = VeranoAccessoryPlugin;
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.3",
3
+ "version": "3.0.0",
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,120 +43,169 @@ 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('Verano accessory plugin initializing');
39
- this.config = config;
40
- this.name = config.name;
41
- this.api = api;
42
- this.isAuthorized = false;
43
- this.sessionCookie = '';
44
-
45
80
  this.Characteristic = this.api.hap.Characteristic;
46
81
 
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 };
88
+
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
+ );
123
+
47
124
  this.informationService = new this.api.hap.Service.AccessoryInformation()
48
- .setCharacteristic(this.api.hap.Characteristic.Manufacturer, "Verano")
49
- .setCharacteristic(this.api.hap.Characteristic.Model, "VER-24 WiFi");
125
+ .setCharacteristic(this.Characteristic.Manufacturer, 'Verano')
126
+ .setCharacteristic(this.Characteristic.Model, 'VER-24 WiFi');
50
127
 
51
- this.service = new this.api.hap.Service.Thermostat(this.name);
128
+ this.service = new this.api.hap.Service.Thermostat(this.config.name);
52
129
 
53
- // GET CURRENT STATE
130
+ // Current Heating/Cooling State
54
131
  this.service.getCharacteristic(this.Characteristic.CurrentHeatingCoolingState)
55
- .on('get', (callback) => {
56
- this.log.debug('Get CurrentHeatingCoolingState');
57
- const heatingState = this.thermostatState.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF;
58
- callback(null, heatingState);
59
- });
132
+ .onGet(async () => {
133
+ return this.thermostatState.heating
134
+ ? this.Characteristic.CurrentHeatingCoolingState.HEAT
135
+ : this.Characteristic.CurrentHeatingCoolingState.OFF;
136
+ });
60
137
 
61
- // GET TARGET STATE
138
+ // Target Heating/Cooling State (HEAT/OFF only)
62
139
  this.service.getCharacteristic(this.Characteristic.TargetHeatingCoolingState)
63
- .setProps({
64
- validValues: [
65
- this.Characteristic.TargetHeatingCoolingState.OFF,
66
- this.Characteristic.TargetHeatingCoolingState.HEAT
67
- ]
68
- })
69
- .on('get', (callback) => {
70
- this.log.debug('Get TargetHeatingCoolingState');
71
- this.requestThermostatState()
72
- .then(thermostatState => {
73
- const heatingState = thermostatState.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF;
74
- callback(null, heatingState);
75
- })
76
- .catch(error => callback(error));
77
- })
78
- .on('set', (value, callback) => {
79
- this.thermostatState.heating = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
80
- if (this.thermostatState.heating) {
81
- callback(null);
82
- return;
83
- }
84
- this.log.info('Turning off heating...');
85
- this.requestTemperatureChange(this.TURN_ON_OFF_TEMPERATURE)
86
- .then(() => {
87
- this.log.info('Heating turned off');
88
- callback(null);
89
- })
90
- .catch(error => callback(error));
91
- });
92
-
93
- // GET TEMPERATURE
140
+ .setProps({ validValues: [
141
+ this.Characteristic.TargetHeatingCoolingState.OFF,
142
+ this.Characteristic.TargetHeatingCoolingState.HEAT,
143
+ ]})
144
+ .onGet(async () => {
145
+ await this.ensureSession();
146
+ await this.refreshStateSafe();
147
+ return this.thermostatState.heating
148
+ ? this.Characteristic.TargetHeatingCoolingState.HEAT
149
+ : this.Characteristic.TargetHeatingCoolingState.OFF;
150
+ })
151
+ .onSet(async (value) => {
152
+ const heat = value === this.Characteristic.TargetHeatingCoolingState.HEAT;
153
+ // Turning off maps to sending the off threshold setpoint if device requires it
154
+ if (!heat) {
155
+ await this.setTargetTemperatureC(this.cfg.offThresholdC);
156
+ }
157
+ this.thermostatState.heating = heat;
158
+ this.pushStateToHomeKit();
159
+ });
160
+
161
+ // Current Temperature
94
162
  this.service.getCharacteristic(this.Characteristic.CurrentTemperature)
95
- .on('get', (callback) => {
96
- this.log.debug('Get CurrentTemperature');
97
- this.requestThermostatState()
98
- .then(thermostatState => callback(null, thermostatState.currentTemperature))
99
- .catch(error => callback(error));
100
- });
101
-
102
- // GET TARGET TEMPERATURE
163
+ .setProps({ minStep: 0.1 })
164
+ .onGet(async () => {
165
+ await this.ensureSession();
166
+ await this.refreshStateSafe();
167
+ return this.thermostatState.currentTemperature;
168
+ });
169
+
170
+ // Target Temperature
103
171
  this.service.getCharacteristic(this.Characteristic.TargetTemperature)
104
- .on('get', (callback) => {
105
- this.log.debug('Get TargetTemperature');
106
- this.requestThermostatState()
107
- .then(thermostatState => callback(null, thermostatState.targetTemperature))
108
- .catch(error => callback(error));
109
- })
110
- .on('set', (value, callback) => {
111
- this.log.debug('Set TargetTemperature:', value);
112
- this.requestTemperatureChange(value as number)
113
- .then(() => callback(null))
114
- .catch(error => callback(error));
115
- })
116
- .setProps({
117
- minValue: 10,
118
- maxValue: 30,
119
- minStep: 0.5
120
- });
172
+ .setProps({
173
+ minValue: this.cfg.minC,
174
+ maxValue: this.cfg.maxC,
175
+ minStep: this.cfg.stepC,
176
+ })
177
+ .onGet(async () => {
178
+ await this.ensureSession();
179
+ await this.refreshStateSafe();
180
+ return this.thermostatState.targetTemperature;
181
+ })
182
+ .onSet(async (value) => {
183
+ const v = this.clampAndStep(Number(value), this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
184
+ await this.setTargetTemperatureC(v);
185
+ this.thermostatState.heating = v > this.cfg.offThresholdC;
186
+ this.pushStateToHomeKit();
187
+ });
121
188
 
189
+ // Celsius only
122
190
  this.service.getCharacteristic(this.Characteristic.TemperatureDisplayUnits)
123
- .on('get', (callback) => {
124
- callback(null, this.Characteristic.TemperatureDisplayUnits.CELSIUS);
125
- })
126
- .on('set', (value, callback) => {
127
- });
128
- this.log.info('Verano accessory plugin initialized');
191
+ .onGet(async () => this.Characteristic.TemperatureDisplayUnits.CELSIUS)
192
+ .onSet(async () => { /* no-op, force Celsius */ });
193
+
194
+ // Defer network until platform is ready
195
+ this.api.on('didFinishLaunching', async () => {
196
+ try {
197
+ await this.ensureSession();
198
+ await this.refreshStateSafe();
199
+ this.startPolling();
200
+ } catch (e) {
201
+ this.log.error('Startup failed:', (e as Error)?.message ?? e);
202
+ }
203
+ });
204
+
205
+ // Cleanup on shutdown
206
+ this.api.on('shutdown', () => {
207
+ this.stopPolling();
208
+ });
129
209
  }
130
210
 
131
211
  identify?(): void {
@@ -133,112 +213,173 @@ export class VeranoAccessoryPlugin implements AccessoryPlugin {
133
213
  }
134
214
 
135
215
  getServices(): Service[] {
136
- return [
137
- this.informationService,
138
- this.service,
139
- ];
216
+ return [this.informationService, this.service];
140
217
  }
141
218
 
142
219
  getControllers?(): Controller[] {
143
220
  return [];
144
221
  }
145
222
 
146
- private async requestThermostatState() {
147
- const tiles = await this.fetchDataTiles();
148
- const foundTile = tiles.filter(tile => tile.id === 58)[0];
149
- const targetTemperature = foundTile.params.widget1.value / this.TEMPERATURE_DIVIDER;
150
- const currentTemperature = foundTile.params.widget2.value / this.TEMPERATURE_DIVIDER;
151
- this.thermostatState = {
152
- currentTemperature: currentTemperature,
153
- targetTemperature: targetTemperature,
154
- heating: targetTemperature > this.TURN_ON_OFF_TEMPERATURE
155
- };
156
- this.log.info('Thermostat state:', this.thermostatState);
157
- return this.thermostatState;
223
+ // --- Helpers ---
224
+
225
+ private startPolling() {
226
+ if (this.pollTimer) clearInterval(this.pollTimer);
227
+ this.pollTimer = setInterval(async () => {
228
+ await this.refreshStateSafe();
229
+ }, this.cfg.pollIntervalSec * 1000);
158
230
  }
159
231
 
160
- private async fetchDataTiles() {
161
- this.log.debug('Fetching data');
162
- if (!this.isAuthorized) {
163
- this.log.error('Not authorized, cannot get tiles, trying to authorize');
164
- await this.requestAuthorization();
232
+ private stopPolling() {
233
+ if (this.pollTimer) {
234
+ clearInterval(this.pollTimer);
235
+ this.pollTimer = undefined;
165
236
  }
237
+ }
166
238
 
167
- const config = {
168
- headers: {
169
- 'Cookie': this.sessionCookie
170
- },
171
- withCredentials: true,
172
- };
173
- return axios
174
- .get('https://emodul.pl/frontend/module_data', config)
175
- .then(response => {
176
- this.log.debug('Successfully fetched data');
177
- return response.data.tiles;
178
- })
179
- .catch(error => {
180
- if (error.response) {
181
- this.log.error("Error during tiles fetch");
182
- this.log.error("Status:", error.response.status);
183
- this.log.error("Response:");
184
- this.log.error(error.response.data);
185
- } else {
186
- this.log.error("Error during tiles fetch", error);
187
- }
188
- this.isAuthorized = false;
189
- throw error;
190
- });
191
- };
239
+ private clampAndStep(v: number, min: number, max: number, step: number) {
240
+ const clamped = Math.min(Math.max(v, min), max);
241
+ return Math.round(clamped / step) * step;
242
+ }
243
+
244
+ private async ensureSession() {
245
+ if (this.isAuthorized && this.sessionCookie) return;
246
+ await this.requestAuthorization();
247
+ }
192
248
 
193
249
  private async requestAuthorization() {
194
- const requestBody = {
195
- username: this.config.username,
196
- password: this.config.password,
250
+ const body = {
251
+ username: this.credentials.username,
252
+ password: this.credentials.password,
197
253
  rememberMe: true,
198
- languageId: 'en'
254
+ languageId: 'en',
199
255
  };
200
- this.log.info('Trying to authorize with user', requestBody.username);
201
- return axios
202
- .post('https://emodul.pl/login', requestBody)
203
- .then(loginResponse => {
204
- this.log.info('Successfully authorized', requestBody.username);
205
- const cookies = loginResponse.headers['set-cookie'];
206
- this.sessionCookie = cookies.filter(cookie => cookie.includes('session'))[0];
207
- this.isAuthorized = true;
208
- return loginResponse;
209
- })
210
- .catch(error => {
211
- this.log.error("Error during authorization", requestBody.username, error);
212
- this.isAuthorized = false;
213
- throw error;
214
- });
256
+ this.log.info('Authorizing user', this.credentials.username);
257
+ const res = await this.axios.post('/login', body).catch((e) => {
258
+ this.isAuthorized = false;
259
+ throw e;
260
+ });
261
+
262
+ if (res.status !== 200) {
263
+ this.isAuthorized = false;
264
+ throw new Error(`Authorization failed: HTTP ${res.status}`);
265
+ }
266
+
267
+ const setCookie = res.headers['set-cookie'] as string[] | undefined;
268
+ const session = setCookie?.find((c) => /session/i.test(c));
269
+ if (!session) {
270
+ this.isAuthorized = false;
271
+ throw new Error('Authorization failed: missing session cookie');
272
+ }
273
+
274
+ // Keep only the cookie key=value part
275
+ this.sessionCookie = session.split(';')[0];
276
+ this.isAuthorized = true;
277
+ this.log.info('Authorized as', this.credentials.username);
215
278
  }
216
279
 
217
- private async requestTemperatureChange(targetTemperature: number) {
218
- this.log.info('Changing temperature to', targetTemperature + '°C');
219
- const requestBody = [
220
- {
221
- ido: 139,
222
- params: targetTemperature * this.TEMPERATURE_DIVIDER,
223
- module_index: 0
280
+ private async fetchDataTiles(): Promise<Tile[]> {
281
+ const res = await this.axios.get<ModuleDataResponse>('/frontend/module_data');
282
+
283
+ if (res.status === 401 || res.status === 403) {
284
+ this.isAuthorized = false;
285
+ await this.requestAuthorization();
286
+ const retry = await this.axios.get<ModuleDataResponse>('/frontend/module_data');
287
+ if (retry.status !== 200) {
288
+ throw new Error(`Tiles fetch failed after reauth: HTTP ${retry.status}`);
224
289
  }
225
- ]
226
- const config = {
227
- headers: {
228
- 'Cookie': this.sessionCookie
290
+ return retry.data.tiles;
291
+ }
292
+
293
+ if (res.status !== 200) {
294
+ throw new Error(`Tiles fetch failed: HTTP ${res.status}`);
295
+ }
296
+
297
+ return res.data.tiles;
298
+ }
299
+
300
+ private async requestTemperatureChange(targetC: number) {
301
+ const body = [
302
+ {
303
+ ido: this.cfg.setpointIdo,
304
+ params: Math.round(targetC * this.cfg.temperatureDivider),
305
+ module_index: 0,
229
306
  },
230
- withCredentials: true
307
+ ];
308
+
309
+ const res = await this.axios.post('/send_control_data', body);
310
+
311
+ if (res.status === 401 || res.status === 403) {
312
+ this.isAuthorized = false;
313
+ await this.requestAuthorization();
314
+ const retry = await this.axios.post('/send_control_data', body);
315
+ if (retry.status < 200 || retry.status >= 300) {
316
+ throw new Error(`Temperature change failed after reauth: HTTP ${retry.status}`);
317
+ }
318
+ return retry.data;
319
+ }
320
+
321
+ if (res.status < 200 || res.status >= 300) {
322
+ throw new Error(`Temperature change failed: HTTP ${res.status}`);
323
+ }
324
+
325
+ return res.data;
326
+ }
327
+
328
+ private async requestThermostatState(): Promise<ThermostatState> {
329
+ const tiles = await this.fetchDataTiles();
330
+ const tile = tiles.find((t) => t.id === this.cfg.tileId);
331
+ if (!tile) throw new Error(`Tile ${this.cfg.tileId} not found`);
332
+
333
+ const targetC = tile.params.widget1.value / this.cfg.temperatureDivider;
334
+ const currentC = tile.params.widget2.value / this.cfg.temperatureDivider;
335
+
336
+ this.thermostatState = {
337
+ currentTemperature: currentC,
338
+ targetTemperature: targetC,
339
+ heating: targetC > this.cfg.offThresholdC,
231
340
  };
232
- return axios
233
- .post('https://emodul.pl/send_control_data', requestBody, config)
234
- .then(response => {
235
- this.log.info('Successfully changed temperature to', targetTemperature + '°C');
236
- return response?.data;
237
- })
238
- .catch(error => {
239
- this.log.error("Error during temperature change", error);
240
- throw error;
241
- })
341
+
342
+ return this.thermostatState;
343
+ }
344
+
345
+ private async refreshStateSafe() {
346
+ try {
347
+ const state = await this.requestThermostatState();
348
+ this.pushStateToHomeKit(state);
349
+ } catch (e) {
350
+ this.log.debug('Refresh failed:', (e as Error)?.message ?? e);
351
+ }
242
352
  }
243
353
 
354
+ private pushStateToHomeKit(state: ThermostatState = this.thermostatState) {
355
+ this.service.updateCharacteristic(this.Characteristic.CurrentTemperature, state.currentTemperature);
356
+ this.service.updateCharacteristic(
357
+ this.Characteristic.CurrentHeatingCoolingState,
358
+ state.heating ? this.Characteristic.CurrentHeatingCoolingState.HEAT : this.Characteristic.CurrentHeatingCoolingState.OFF,
359
+ );
360
+ this.service.updateCharacteristic(this.Characteristic.TargetTemperature, state.targetTemperature);
361
+ this.service.updateCharacteristic(
362
+ this.Characteristic.TargetHeatingCoolingState,
363
+ state.heating ? this.Characteristic.TargetHeatingCoolingState.HEAT : this.Characteristic.TargetHeatingCoolingState.OFF,
364
+ );
365
+ }
366
+
367
+ private async setTargetTemperatureC(targetC: number) {
368
+ if (this.writeInFlight) {
369
+ // Simple debounce: skip if a write is in progress
370
+ this.log.debug('Skipping set, write in flight');
371
+ return;
372
+ }
373
+ this.writeInFlight = true;
374
+ const bounded = this.clampAndStep(targetC, this.cfg.minC, this.cfg.maxC, this.cfg.stepC);
375
+ this.log.info('Setting target temperature to', `${bounded}°C`);
376
+ try {
377
+ await this.ensureSession();
378
+ await this.requestTemperatureChange(bounded);
379
+ // Refresh from source of truth
380
+ await this.refreshStateSafe();
381
+ } finally {
382
+ this.writeInFlight = false;
383
+ }
384
+ }
244
385
  }