homebridge-tasmota-control 1.4.0-beta.9 → 1.4.1-beta.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.
- package/CHANGELOG.md +1 -0
- package/index.js +108 -59
- package/package.json +1 -1
- package/src/constants.js +1 -2
- package/src/deviceinfo.js +84 -0
- package/src/fans.js +541 -0
- package/src/lights.js +352 -0
- package/src/mielhvac.js +1395 -0
- package/src/sensors.js +549 -0
- package/src/switches.js +231 -0
- package/src/tasmotadevice.js +0 -2534
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
## Changes
|
|
11
11
|
|
|
12
12
|
- added support for iFan, solved [#24](https://github.com/grzegorz914/homebridge-tasmota-control/issues/24)
|
|
13
|
+
- full code refactor
|
|
13
14
|
- config schema updated
|
|
14
15
|
- redme updated
|
|
15
16
|
- cleanup
|
package/index.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { mkdirSync, existsSync, writeFileSync } from 'fs';
|
|
3
|
-
import
|
|
3
|
+
import DeviceInfo from './src/deviceinfo.js';
|
|
4
|
+
import MiElHvac from './src/mielhvac.js';
|
|
5
|
+
import Switches from './src/switches.js';
|
|
6
|
+
import Lights from './src/lights.js';
|
|
7
|
+
import Fans from './src/fans.js';
|
|
8
|
+
import Sensors from './src/sensors.js';
|
|
4
9
|
import ImpulseGenerator from './src/impulsegenerator.js';
|
|
5
10
|
import { PluginName, PlatformName } from './src/constants.js';
|
|
6
11
|
|
|
@@ -39,6 +44,12 @@ class tasmotaPlatform {
|
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
//log config
|
|
47
|
+
const url = `http://${host}/cm?cmnd=`;
|
|
48
|
+
const auth = device.auth || false;
|
|
49
|
+
const user = device.user || '';
|
|
50
|
+
const passwd = device.passwd || '';
|
|
51
|
+
const loadNameFromDevice = device.loadNameFromDevice || false;
|
|
52
|
+
const refreshInterval = device.refreshInterval * 1000 || 5000;
|
|
42
53
|
const enableDebugMode = device.enableDebugMode || false;
|
|
43
54
|
const disableLogDeviceInfo = device.disableLogDeviceInfo || false;
|
|
44
55
|
const disableLogInfo = device.disableLogInfo || false;
|
|
@@ -46,55 +57,19 @@ class tasmotaPlatform {
|
|
|
46
57
|
const disableLogWarn = device.disableLogWarn || false;
|
|
47
58
|
const disableLogError = device.disableLogError || false;
|
|
48
59
|
const debug = enableDebugMode ? log.info(`Device: ${host} ${deviceName}, debug: Did finish launching.`) : false;
|
|
49
|
-
const
|
|
60
|
+
const newConfig = {
|
|
50
61
|
...device,
|
|
51
62
|
user: 'removed',
|
|
52
63
|
passwd: 'removed'
|
|
53
64
|
};
|
|
54
|
-
const debug1 = !enableDebugMode ? false : log.info(`Device: ${host} ${deviceName}, Config: ${JSON.stringify(
|
|
65
|
+
const debug1 = !enableDebugMode ? false : log.info(`Device: ${host} ${deviceName}, Config: ${JSON.stringify(newConfig, null, 2)}.`);
|
|
55
66
|
|
|
56
|
-
//check files exists, if not then create it
|
|
57
|
-
const postFix = device.host.split('.').join('');
|
|
58
|
-
const defaultHeatingSetTemperatureFile = `${prefDir}/defaultHeatingSetTemperature_${postFix}`;
|
|
59
|
-
const defaultCoolingSetTemperatureFile = `${prefDir}/defaultCoolingSetTemperature_${postFix}`;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const files = [
|
|
63
|
-
defaultHeatingSetTemperatureFile,
|
|
64
|
-
defaultCoolingSetTemperatureFile
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
files.forEach((file, index) => {
|
|
68
|
-
if (!existsSync(file)) {
|
|
69
|
-
const data = ['20', '23'][index]
|
|
70
|
-
writeFileSync(file, data);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
} catch (error) {
|
|
74
|
-
log.error(`Device: ${host} ${deviceName}, Prepare files error: ${error}`);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
//tasmota device
|
|
79
67
|
try {
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const emitLog = disableLogSuccess ? false : log.success(`Device: ${host} ${deviceName}, Published as external accessory.`);
|
|
68
|
+
//get device info
|
|
69
|
+
const deviceInfo = new DeviceInfo(url, auth, user, passwd, deviceName, loadNameFromDevice, enableDebugMode, refreshInterval);
|
|
70
|
+
deviceInfo.on('debug', (debug) => {
|
|
71
|
+
const emitLog = !enableDebugMode ? false : log.info(`Device: ${host} ${deviceName}, debug: ${debug}.`);
|
|
85
72
|
})
|
|
86
|
-
.on('devInfo', (devInfo) => {
|
|
87
|
-
const emitLog = disableLogDeviceInfo ? false : log.info(devInfo);
|
|
88
|
-
})
|
|
89
|
-
.on('success', (success) => {
|
|
90
|
-
const emitLog = disableLogSuccess ? false : log.success(`Device: ${host} ${deviceName}, ${success}.`);
|
|
91
|
-
})
|
|
92
|
-
.on('info', (info) => {
|
|
93
|
-
const emitLog = disableLogInfo ? false : log.info(`Device: ${host} ${deviceName}, ${info}.`);
|
|
94
|
-
})
|
|
95
|
-
.on('debug', (debug) => {
|
|
96
|
-
const emitLog = !enableDebugMode ? false : log.info(`Device: ${host} ${deviceName}, debug: ${debug}.`);
|
|
97
|
-
})
|
|
98
73
|
.on('warn', (warn) => {
|
|
99
74
|
const emitLog = disableLogWarn ? false : log.warn(`Device: ${host} ${deviceName}, ${warn}.`);
|
|
100
75
|
})
|
|
@@ -102,24 +77,98 @@ class tasmotaPlatform {
|
|
|
102
77
|
const emitLog = disableLogError ? false : log.error(`Device: ${host} ${deviceName}, ${error}.`);
|
|
103
78
|
});
|
|
104
79
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
80
|
+
const info = await deviceInfo.getInfo();
|
|
81
|
+
if (!info.serialNumber) {
|
|
82
|
+
log.warn(`Device: ${host} ${deviceName}, serial not found.`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const type of info.deviceTypes) {
|
|
87
|
+
let deviceType;
|
|
88
|
+
switch (type) {
|
|
89
|
+
case 0://mielhvac
|
|
90
|
+
//check files exists, if not then create it
|
|
91
|
+
try {
|
|
92
|
+
const postFix = device.host.split('.').join('');
|
|
93
|
+
info.defaultHeatingSetTemperatureFile = `${prefDir}/defaultHeatingSetTemperature_${postFix}`;
|
|
94
|
+
info.defaultCoolingSetTemperatureFile = `${prefDir}/defaultCoolingSetTemperature_${postFix}`;
|
|
95
|
+
const files = [
|
|
96
|
+
info.defaultHeatingSetTemperatureFile,
|
|
97
|
+
info.defaultCoolingSetTemperatureFile
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
files.forEach((file, index) => {
|
|
101
|
+
if (!existsSync(file)) {
|
|
102
|
+
const data = ['20', '23'][index]
|
|
103
|
+
writeFileSync(file, data);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
log.error(`Device: ${host} ${deviceName}, Prepare files error: ${error}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
deviceType = new MiElHvac(api, device, info, refreshInterval);
|
|
112
|
+
break;
|
|
113
|
+
case 1: //switches
|
|
114
|
+
deviceType = new Switches(api, device, info, refreshInterval);
|
|
115
|
+
break;
|
|
116
|
+
case 2: //lights
|
|
117
|
+
deviceType = new Lights(api, device, info, refreshInterval);
|
|
118
|
+
break;
|
|
119
|
+
case 3: //fans
|
|
120
|
+
deviceType = new Fans(api, device, info, refreshInterval);
|
|
121
|
+
break;
|
|
122
|
+
case 4: //sensors
|
|
123
|
+
deviceType = new Sensors(api, device, info, refreshInterval);
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
const emitLog = disableLogWarn ? false : log.warn(`Device: ${host} ${deviceName}, unknown device: ${info.deviceType}.`);
|
|
127
|
+
return;
|
|
116
128
|
}
|
|
117
|
-
}).on('state', (state) => {
|
|
118
|
-
const emitLog = !enableDebugMode ? false : state ? log.info(`Device: ${host} ${deviceName}, Start impulse generator started.`) : log.info(`Device: ${host} ${deviceName}, Start impulse generator stopped.`);
|
|
119
|
-
});
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
deviceType.on('publishAccessory', (accessory) => {
|
|
131
|
+
api.publishExternalAccessories(PluginName, [accessory]);
|
|
132
|
+
const emitLog = disableLogSuccess ? false : log.success(`Device: ${host} ${deviceName}, Published as external accessory.`);
|
|
133
|
+
})
|
|
134
|
+
.on('devInfo', (devInfo) => {
|
|
135
|
+
const emitLog = disableLogDeviceInfo ? false : log.info(devInfo);
|
|
136
|
+
})
|
|
137
|
+
.on('success', (success) => {
|
|
138
|
+
const emitLog = disableLogSuccess ? false : log.success(`Device: ${host} ${deviceName}, ${success}.`);
|
|
139
|
+
})
|
|
140
|
+
.on('info', (info) => {
|
|
141
|
+
const emitLog = disableLogInfo ? false : log.info(`Device: ${host} ${deviceName}, ${info}.`);
|
|
142
|
+
})
|
|
143
|
+
.on('debug', (debug) => {
|
|
144
|
+
const emitLog = !enableDebugMode ? false : log.info(`Device: ${host} ${deviceName}, debug: ${debug}.`);
|
|
145
|
+
})
|
|
146
|
+
.on('warn', (warn) => {
|
|
147
|
+
const emitLog = disableLogWarn ? false : log.warn(`Device: ${host} ${deviceName}, ${warn}.`);
|
|
148
|
+
})
|
|
149
|
+
.on('error', (error) => {
|
|
150
|
+
const emitLog = disableLogError ? false : log.error(`Device: ${host} ${deviceName}, ${error}.`);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
//create impulse generator
|
|
154
|
+
const impulseGenerator = new ImpulseGenerator();
|
|
155
|
+
impulseGenerator.on('start', async () => {
|
|
156
|
+
try {
|
|
157
|
+
const startDone = await deviceType.start();
|
|
158
|
+
const stopImpulseGenerator = startDone ? await impulseGenerator.stop() : false;
|
|
159
|
+
|
|
160
|
+
//start impulse generator
|
|
161
|
+
const startImpulseGenerator = stopImpulseGenerator ? await deviceType.startImpulseGenerator() : false
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const emitLog = disableLogError ? false : log.error(`Device: ${host} ${deviceName}, ${error}, trying again.`);
|
|
164
|
+
}
|
|
165
|
+
}).on('state', (state) => {
|
|
166
|
+
const emitLog = !enableDebugMode ? false : state ? log.info(`Device: ${host} ${deviceName}, Start impulse generator started.`) : log.info(`Device: ${host} ${deviceName}, Start impulse generator stopped.`);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
//start impulse generator
|
|
170
|
+
await impulseGenerator.start([{ name: 'start', sampling: 45000 }]);
|
|
171
|
+
}
|
|
123
172
|
} catch (error) {
|
|
124
173
|
const emitLog = disableLogError ? false : log.error(`Device: ${host} ${deviceName}, Did finish launching error: ${error}.`);
|
|
125
174
|
}
|
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import EventEmitter from 'events';
|
|
3
|
+
import { ApiCommands, LightKeys, SensorKeys } from './constants.js';
|
|
4
|
+
|
|
5
|
+
class DeviceInfo extends EventEmitter {
|
|
6
|
+
constructor(url, auth, user, passwd, deviceName, loadNameFromDevice, enableDebugMode, refreshInterval) {
|
|
7
|
+
super();
|
|
8
|
+
this.name = deviceName
|
|
9
|
+
this.loadNameFromDevice = loadNameFromDevice;
|
|
10
|
+
this.enableDebugMode = enableDebugMode;
|
|
11
|
+
|
|
12
|
+
//axios instance
|
|
13
|
+
this.axiosInstance = axios.create({
|
|
14
|
+
method: 'GET',
|
|
15
|
+
baseURL: url,
|
|
16
|
+
timeout: refreshInterval > 10000 ? 10000 : refreshInterval,
|
|
17
|
+
withCredentials: auth,
|
|
18
|
+
auth: {
|
|
19
|
+
username: user,
|
|
20
|
+
password: passwd
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getInfo() {
|
|
27
|
+
const debug = this.enableDebugMode ? this.emit('debug', `Requesting info`) : false;
|
|
28
|
+
try {
|
|
29
|
+
const deviceInfoData = await this.axiosInstance(ApiCommands.Status);
|
|
30
|
+
const deviceInfo = deviceInfoData.data ?? {};
|
|
31
|
+
const debug = this.enableDebugMode ? this.emit('debug', `Info: ${JSON.stringify(deviceInfo, null, 2)}`) : false;
|
|
32
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
33
|
+
|
|
34
|
+
//status
|
|
35
|
+
const friendlyNames = [];
|
|
36
|
+
const status = deviceInfo.Status ?? {};
|
|
37
|
+
const deviceName = this.loadNameFromDevice ? status.DeviceName ?? 'Unknown' : this.name;
|
|
38
|
+
const friendlyName = status.FriendlyName ?? [];
|
|
39
|
+
const relaysName = Array.isArray(friendlyName) ? friendlyName : [friendlyName];
|
|
40
|
+
for (const relayName of relaysName) {
|
|
41
|
+
const name = relayName ?? 'Unknown'
|
|
42
|
+
friendlyNames.push(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//status FWR
|
|
46
|
+
const statusFwr = deviceInfo.StatusFWR ?? {};
|
|
47
|
+
const firmwareRevision = statusFwr.Version ?? 'Unknown';
|
|
48
|
+
const modelName = statusFwr.Hardware ?? 'Unknown';
|
|
49
|
+
|
|
50
|
+
//status NET
|
|
51
|
+
const statusNet = deviceInfo.StatusNET ?? {};
|
|
52
|
+
const addressMac = statusNet.Mac ?? false;
|
|
53
|
+
|
|
54
|
+
//status SNS
|
|
55
|
+
const statusSns = deviceInfo.StatusSNS ?? {};
|
|
56
|
+
const statusSnsKeys = Object.keys(statusSns);
|
|
57
|
+
|
|
58
|
+
//status STS
|
|
59
|
+
const statusSts = deviceInfo.StatusSTS ?? {};
|
|
60
|
+
const statusStsKeys = Object.keys(statusSts);
|
|
61
|
+
|
|
62
|
+
//device types
|
|
63
|
+
const types = [];
|
|
64
|
+
const mielhvac = statusSnsKeys.includes('MiElHVAC') ? types.push(0) : false;
|
|
65
|
+
const lights = statusStsKeys.some(key => LightKeys.includes(key)) ? types.push(2) : false;
|
|
66
|
+
const fans = statusStsKeys.includes('FanSpeed') ? types.push(3) : false;
|
|
67
|
+
const sensors = statusSnsKeys.some(key => SensorKeys.includes(key)) ? types.push(4) : false;
|
|
68
|
+
const switches = !mielhvac && !lights && !fans && !sensors ? types.push(1) : false
|
|
69
|
+
const obj = {
|
|
70
|
+
deviceTypes: types,
|
|
71
|
+
deviceName: deviceName,
|
|
72
|
+
friendlyNames: friendlyNames,
|
|
73
|
+
modelName: modelName,
|
|
74
|
+
serialNumber: addressMac,
|
|
75
|
+
firmwareRevision: firmwareRevision,
|
|
76
|
+
relaysCount: friendlyNames.length
|
|
77
|
+
};
|
|
78
|
+
return obj;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error(`Check info error: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export default DeviceInfo;
|