@boozilla/homebridge-shome 1.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.
- package/.idea/aws.xml +11 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/homebridge-shome.iml +9 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +66 -0
- package/config.schema.json +32 -0
- package/dist/accessories/doorlockAccessory.d.ts +10 -0
- package/dist/accessories/doorlockAccessory.js +55 -0
- package/dist/accessories/doorlockAccessory.js.map +1 -0
- package/dist/accessories/heaterAccessory.d.ts +16 -0
- package/dist/accessories/heaterAccessory.js +75 -0
- package/dist/accessories/heaterAccessory.js.map +1 -0
- package/dist/accessories/lightAccessory.d.ts +10 -0
- package/dist/accessories/lightAccessory.js +32 -0
- package/dist/accessories/lightAccessory.js.map +1 -0
- package/dist/accessories/ventilatorAccessory.d.ts +12 -0
- package/dist/accessories/ventilatorAccessory.js +65 -0
- package/dist/accessories/ventilatorAccessory.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +98 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.d.ts +8 -0
- package/dist/settings.js +9 -0
- package/dist/settings.js.map +1 -0
- package/dist/shomeClient.d.ts +30 -0
- package/dist/shomeClient.js +163 -0
- package/dist/shomeClient.js.map +1 -0
- package/eslint.config.js +35 -0
- package/nodemon.json +12 -0
- package/package.json +50 -0
- package/src/accessories/doorlockAccessory.ts +62 -0
- package/src/accessories/heaterAccessory.ts +94 -0
- package/src/accessories/lightAccessory.ts +41 -0
- package/src/accessories/ventilatorAccessory.ts +78 -0
- package/src/index.ts +7 -0
- package/src/platform.ts +113 -0
- package/src/settings.ts +9 -0
- package/src/shomeClient.ts +196 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Logger } from 'homebridge';
|
|
2
|
+
export interface MainDevice {
|
|
3
|
+
thngId: string;
|
|
4
|
+
thngModelTypeName: string;
|
|
5
|
+
nickname: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface SubDevice {
|
|
9
|
+
deviceId: string;
|
|
10
|
+
nickname: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export declare class ShomeClient {
|
|
14
|
+
private readonly log;
|
|
15
|
+
private readonly username;
|
|
16
|
+
private readonly password;
|
|
17
|
+
private readonly deviceId;
|
|
18
|
+
private cachedAccessToken;
|
|
19
|
+
private ihdId;
|
|
20
|
+
private tokenExpiry;
|
|
21
|
+
constructor(log: Logger, username: string, password: string, deviceId: string);
|
|
22
|
+
login(): Promise<string | null>;
|
|
23
|
+
getDeviceList(): Promise<MainDevice[]>;
|
|
24
|
+
getDeviceInfo(thingId: string, type: string): Promise<SubDevice[] | null>;
|
|
25
|
+
setDevice(thingId: string, deviceId: string, type: string, controlType: string, state: string): Promise<boolean>;
|
|
26
|
+
unlockDoorlock(thingId: string): Promise<boolean>;
|
|
27
|
+
private sha512;
|
|
28
|
+
private isTokenExpired;
|
|
29
|
+
private getDateTime;
|
|
30
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import CryptoJS from 'crypto-js';
|
|
3
|
+
const BASE_URL = 'https://shome-api.samsung-ihp.com';
|
|
4
|
+
const APP_REGST_ID = '6110736314d9eef6baf393f3e43a5342f9ccde6ef300d878385acd9264cf14d5';
|
|
5
|
+
const CHINA_APP_REGST_ID = 'SHOME==6110736314d9eef6baf393f3e43a5342f9ccde6ef300d878385acd9264cf14d5';
|
|
6
|
+
const LANGUAGE = 'KOR';
|
|
7
|
+
export class ShomeClient {
|
|
8
|
+
log;
|
|
9
|
+
username;
|
|
10
|
+
password;
|
|
11
|
+
deviceId;
|
|
12
|
+
cachedAccessToken = null;
|
|
13
|
+
ihdId = null;
|
|
14
|
+
tokenExpiry = 0;
|
|
15
|
+
constructor(log, username, password, deviceId) {
|
|
16
|
+
this.log = log;
|
|
17
|
+
this.username = username;
|
|
18
|
+
this.password = password;
|
|
19
|
+
this.deviceId = deviceId;
|
|
20
|
+
}
|
|
21
|
+
async login() {
|
|
22
|
+
if (!this.isTokenExpired()) {
|
|
23
|
+
return this.cachedAccessToken;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const createDate = this.getDateTime();
|
|
27
|
+
const hashedPassword = this.sha512(this.password);
|
|
28
|
+
const hashData = this.sha512(`IHRESTAPI${this.username}${hashedPassword}${this.deviceId}` +
|
|
29
|
+
`${APP_REGST_ID}${CHINA_APP_REGST_ID}${LANGUAGE}${createDate}`);
|
|
30
|
+
const response = await axios.put(`${BASE_URL}/v18/users/login`, null, {
|
|
31
|
+
params: {
|
|
32
|
+
appRegstId: APP_REGST_ID,
|
|
33
|
+
chinaAppRegstId: CHINA_APP_REGST_ID,
|
|
34
|
+
createDate: createDate,
|
|
35
|
+
hashData: hashData,
|
|
36
|
+
language: LANGUAGE,
|
|
37
|
+
mobileDeviceIdno: this.deviceId,
|
|
38
|
+
password: hashedPassword,
|
|
39
|
+
userId: this.username,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
if (response.data && response.data.accessToken) {
|
|
43
|
+
this.cachedAccessToken = response.data.accessToken;
|
|
44
|
+
this.ihdId = response.data.ihdId;
|
|
45
|
+
// Decode token to find expiry
|
|
46
|
+
const payload = JSON.parse(Buffer.from(this.cachedAccessToken.split('.')[1], 'base64').toString());
|
|
47
|
+
this.tokenExpiry = payload.exp * 1000;
|
|
48
|
+
this.log.info('Successfully logged in to sHome API.');
|
|
49
|
+
return this.cachedAccessToken;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
this.log.error('Login failed: Invalid credentials or API error.');
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.log.error(`Login error: ${error}`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async getDeviceList() {
|
|
62
|
+
const token = await this.login();
|
|
63
|
+
if (!token || !this.ihdId) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const createDate = this.getDateTime();
|
|
68
|
+
const hashData = this.sha512(`IHRESTAPI${this.ihdId}${createDate}`);
|
|
69
|
+
const response = await axios.get(`${BASE_URL}/v16/settings/${this.ihdId}/devices/`, {
|
|
70
|
+
params: { createDate, hashData },
|
|
71
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
72
|
+
});
|
|
73
|
+
return response.data.deviceList || [];
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.log.error(`Error getting device list: ${error}`);
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async getDeviceInfo(thingId, type) {
|
|
81
|
+
const token = await this.login();
|
|
82
|
+
if (!token) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const createDate = this.getDateTime();
|
|
87
|
+
const hashData = this.sha512(`IHRESTAPI${thingId}${createDate}`);
|
|
88
|
+
const typePath = type.toLowerCase().replace(/_/g, '');
|
|
89
|
+
const response = await axios.get(`${BASE_URL}/v18/settings/${typePath}/${thingId}`, {
|
|
90
|
+
params: { createDate, hashData },
|
|
91
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
92
|
+
});
|
|
93
|
+
return response.data.deviceInfoList || null;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.log.error(`Error getting device info for ${thingId}: ${error}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async setDevice(thingId, deviceId, type, controlType, state) {
|
|
101
|
+
const token = await this.login();
|
|
102
|
+
if (!token) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const createDate = this.getDateTime();
|
|
107
|
+
const hashData = this.sha512(`IHRESTAPI${thingId}${deviceId}${state}${createDate}`);
|
|
108
|
+
const typePath = type.toLowerCase().replace(/_/g, '');
|
|
109
|
+
const controlPath = controlType.toLowerCase().replace(/_/g, '-');
|
|
110
|
+
await axios.put(`${BASE_URL}/v18/settings/${typePath}/${thingId}/${deviceId}/${controlPath}`, null, {
|
|
111
|
+
params: {
|
|
112
|
+
createDate,
|
|
113
|
+
[controlType === 'WINDSPEED' ? 'mode' : 'state']: state,
|
|
114
|
+
hashData,
|
|
115
|
+
},
|
|
116
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
117
|
+
});
|
|
118
|
+
this.log.info(`Set ${type} [${thingId}/${deviceId}] to ${state}`);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
this.log.error(`Error setting device ${thingId}: ${error}`);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async unlockDoorlock(thingId) {
|
|
127
|
+
const token = await this.login();
|
|
128
|
+
if (!token) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const createDate = this.getDateTime();
|
|
133
|
+
const hashData = this.sha512(`IHRESTAPI${thingId}${createDate}`);
|
|
134
|
+
await axios.put(`${BASE_URL}/v16/settings/doorlocks/${thingId}/open-mode`, null, {
|
|
135
|
+
params: {
|
|
136
|
+
createDate,
|
|
137
|
+
pin: '',
|
|
138
|
+
hashData,
|
|
139
|
+
},
|
|
140
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
141
|
+
});
|
|
142
|
+
this.log.info(`Unlocked doorlock [${thingId}]`);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.log.error(`Error unlocking doorlock ${thingId}: ${error}`);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
sha512(input) {
|
|
151
|
+
return CryptoJS.SHA512(input).toString();
|
|
152
|
+
}
|
|
153
|
+
isTokenExpired() {
|
|
154
|
+
return !this.cachedAccessToken || Date.now() >= this.tokenExpiry;
|
|
155
|
+
}
|
|
156
|
+
getDateTime() {
|
|
157
|
+
const now = new Date();
|
|
158
|
+
const pad = (num) => num.toString().padStart(2, '0');
|
|
159
|
+
return `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}` +
|
|
160
|
+
`${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=shomeClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shomeClient.js","sourceRoot":"","sources":["../src/shomeClient.ts"],"names":[],"mappings":"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"}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
|
|
4
|
+
export default tseslint.config(
|
|
5
|
+
{
|
|
6
|
+
ignores: ['dist/**'],
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
rules: {
|
|
10
|
+
'quotes': ['error', 'single'],
|
|
11
|
+
'indent': ['error', 2, { 'SwitchCase': 0 }],
|
|
12
|
+
'linebreak-style': ['error', 'unix'],
|
|
13
|
+
'semi': ['error', 'always'],
|
|
14
|
+
'comma-dangle': ['error', 'always-multiline'],
|
|
15
|
+
'dot-notation': 'error',
|
|
16
|
+
'eqeqeq': ['error', 'smart'],
|
|
17
|
+
'curly': ['error', 'all'],
|
|
18
|
+
'brace-style': ['error'],
|
|
19
|
+
'prefer-arrow-callback': 'warn',
|
|
20
|
+
'max-len': ['warn', 160],
|
|
21
|
+
'object-curly-spacing': ['error', 'always'],
|
|
22
|
+
'no-use-before-define': 'off',
|
|
23
|
+
'@typescript-eslint/no-use-before-define': ['error', { 'classes': false, 'enums': false }],
|
|
24
|
+
'@typescript-eslint/no-unused-vars': ['error', { 'caughtErrors': 'none' }],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
languageOptions: {
|
|
29
|
+
ecmaVersion: 2022,
|
|
30
|
+
sourceType: 'module',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
eslint.configs.recommended,
|
|
34
|
+
...tseslint.configs.recommended,
|
|
35
|
+
);
|
package/nodemon.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@boozilla/homebridge-shome",
|
|
3
|
+
"displayName": "sHome Plugin",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"private": false,
|
|
6
|
+
"description": "A Homebridge plugin for Samsung Smart Home",
|
|
7
|
+
"author": "boozilla",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/boozilla/homebridge-shome.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/boozilla/homebridge-shome/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/boozilla/homebridge-shome#readme",
|
|
19
|
+
"scripts": {
|
|
20
|
+
"lint": "eslint . --max-warnings=0",
|
|
21
|
+
"watch": "npm run build && npm link && nodemon",
|
|
22
|
+
"build": "rimraf ./dist && tsc",
|
|
23
|
+
"prepublishOnly": "npm run lint && npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"homebridge-plugin",
|
|
27
|
+
"shome",
|
|
28
|
+
"samsung"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": "^18.15.0 || ^20.7.0 || ^22",
|
|
32
|
+
"homebridge": "^1.8.0 || ^2.0.0-beta.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"axios": "^1.7.2",
|
|
36
|
+
"crypto-js": "^4.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^9.36.0",
|
|
40
|
+
"@types/crypto-js": "^4.2.2",
|
|
41
|
+
"@types/node": "^20.12.12",
|
|
42
|
+
"eslint": "^9.36.0",
|
|
43
|
+
"homebridge": "^2.0.0-beta.30",
|
|
44
|
+
"nodemon": "^3.1.10",
|
|
45
|
+
"rimraf": "^5.0.7",
|
|
46
|
+
"ts-node": "^10.9.2",
|
|
47
|
+
"typescript": "^5.4.5",
|
|
48
|
+
"typescript-eslint": "^8.44.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
|
|
2
|
+
import { ShomePlatform } from '../platform.js';
|
|
3
|
+
|
|
4
|
+
export class DoorlockAccessory {
|
|
5
|
+
private service: Service;
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly platform: ShomePlatform,
|
|
9
|
+
private readonly accessory: PlatformAccessory,
|
|
10
|
+
) {
|
|
11
|
+
const device = this.accessory.context.device;
|
|
12
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)!
|
|
13
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Samsung')
|
|
14
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'Doorlock')
|
|
15
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, device.thngId);
|
|
16
|
+
|
|
17
|
+
this.service = this.accessory.getService(this.platform.Service.LockMechanism) ||
|
|
18
|
+
this.accessory.addService(this.platform.Service.LockMechanism);
|
|
19
|
+
|
|
20
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, device.nickname);
|
|
21
|
+
|
|
22
|
+
this.service.getCharacteristic(this.platform.Characteristic.LockCurrentState)
|
|
23
|
+
.onGet(this.getCurrentState.bind(this));
|
|
24
|
+
|
|
25
|
+
this.service.getCharacteristic(this.platform.Characteristic.LockTargetState)
|
|
26
|
+
.onGet(this.getCurrentState.bind(this))
|
|
27
|
+
.onSet(this.setTargetState.bind(this));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getCurrentState(): Promise<CharacteristicValue> {
|
|
31
|
+
const device = this.accessory.context.device;
|
|
32
|
+
if (device.status === 0 || device.status === false) {
|
|
33
|
+
return this.platform.Characteristic.LockCurrentState.UNSECURED;
|
|
34
|
+
} else {
|
|
35
|
+
return this.platform.Characteristic.LockCurrentState.SECURED;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async setTargetState(value: CharacteristicValue) {
|
|
40
|
+
const device = this.accessory.context.device;
|
|
41
|
+
|
|
42
|
+
if (value === this.platform.Characteristic.LockTargetState.UNSECURED) {
|
|
43
|
+
this.platform.log.info(`Unlocking ${device.nickname}...`);
|
|
44
|
+
const success = await this.platform.shomeClient.unlockDoorlock(device.thngId);
|
|
45
|
+
|
|
46
|
+
if (success) {
|
|
47
|
+
this.platform.log.info(`${device.nickname} unlocked successfully.`);
|
|
48
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LockCurrentState, this.platform.Characteristic.LockCurrentState.UNSECURED);
|
|
49
|
+
} else {
|
|
50
|
+
this.platform.log.error(`Failed to unlock ${device.nickname}.`);
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LockTargetState, this.platform.Characteristic.LockTargetState.SECURED);
|
|
53
|
+
}, 1000);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
this.platform.log.info(`Locking ${device.nickname} via the app is not supported.`);
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
this.service.updateCharacteristic(this.platform.Characteristic.LockTargetState, this.platform.Characteristic.LockTargetState.SECURED);
|
|
59
|
+
}, 1000);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
|
|
2
|
+
import { ShomePlatform } from '../platform.js';
|
|
3
|
+
|
|
4
|
+
export class HeaterAccessory {
|
|
5
|
+
private service: Service;
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly platform: ShomePlatform,
|
|
9
|
+
private readonly accessory: PlatformAccessory,
|
|
10
|
+
) {
|
|
11
|
+
const device = this.accessory.context.device;
|
|
12
|
+
const subDevice = this.accessory.context.subDevice;
|
|
13
|
+
|
|
14
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)!
|
|
15
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Samsung')
|
|
16
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'Heater')
|
|
17
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, `${device.thngId}-${subDevice.deviceId}`);
|
|
18
|
+
|
|
19
|
+
this.service = this.accessory.getService(this.platform.Service.HeaterCooler) ||
|
|
20
|
+
this.accessory.addService(this.platform.Service.HeaterCooler);
|
|
21
|
+
|
|
22
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, subDevice.nickname);
|
|
23
|
+
|
|
24
|
+
this.service.getCharacteristic(this.platform.Characteristic.Active)
|
|
25
|
+
.onGet(this.getActive.bind(this))
|
|
26
|
+
.onSet(this.setActive.bind(this));
|
|
27
|
+
|
|
28
|
+
this.service.getCharacteristic(this.platform.Characteristic.CurrentHeaterCoolerState)
|
|
29
|
+
.onGet(this.getCurrentState.bind(this));
|
|
30
|
+
|
|
31
|
+
this.service.getCharacteristic(this.platform.Characteristic.TargetHeaterCoolerState)
|
|
32
|
+
// 버그 수정: validValues를 HEAT로만 설정하여 다른 모드(냉방, 자동)를 UI에서 제거
|
|
33
|
+
.setProps({
|
|
34
|
+
validValues: [this.platform.Characteristic.TargetHeaterCoolerState.HEAT],
|
|
35
|
+
})
|
|
36
|
+
.onSet(this.setTargetState.bind(this))
|
|
37
|
+
.onGet(this.getTargetState.bind(this));
|
|
38
|
+
|
|
39
|
+
this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
|
|
40
|
+
.onGet(this.getCurrentTemperature.bind(this));
|
|
41
|
+
|
|
42
|
+
this.service.getCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature)
|
|
43
|
+
.setProps({ minValue: 5, maxValue: 40, minStep: 1 })
|
|
44
|
+
.onGet(this.getTargetTemperature.bind(this))
|
|
45
|
+
.onSet(this.setTargetTemperature.bind(this));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ... (이하 다른 함수들은 이전과 동일)
|
|
49
|
+
|
|
50
|
+
async getActive(): Promise<CharacteristicValue> {
|
|
51
|
+
const subDevice = this.accessory.context.subDevice;
|
|
52
|
+
return subDevice.deviceStatus === 1
|
|
53
|
+
? this.platform.Characteristic.Active.ACTIVE
|
|
54
|
+
: this.platform.Characteristic.Active.INACTIVE;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getCurrentState(): Promise<CharacteristicValue> {
|
|
58
|
+
const subDevice = this.accessory.context.subDevice;
|
|
59
|
+
return subDevice.deviceStatus === 1
|
|
60
|
+
? this.platform.Characteristic.CurrentHeaterCoolerState.HEATING
|
|
61
|
+
: this.platform.Characteristic.CurrentHeaterCoolerState.INACTIVE;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getTargetState(): Promise<CharacteristicValue> {
|
|
65
|
+
return this.platform.Characteristic.TargetHeaterCoolerState.HEAT;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async getCurrentTemperature(): Promise<CharacteristicValue> {
|
|
69
|
+
const subDevice = this.accessory.context.subDevice;
|
|
70
|
+
return subDevice.currentTemp;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getTargetTemperature(): Promise<CharacteristicValue> {
|
|
74
|
+
const subDevice = this.accessory.context.subDevice;
|
|
75
|
+
return subDevice.setTemp;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async setActive(value: CharacteristicValue) {
|
|
79
|
+
const device = this.accessory.context.device;
|
|
80
|
+
const subDevice = this.accessory.context.subDevice;
|
|
81
|
+
const state = value === this.platform.Characteristic.Active.ACTIVE ? 'ON' : 'OFF';
|
|
82
|
+
await this.platform.shomeClient.setDevice(device.thngId, subDevice.deviceId.toString(), 'HEATER', 'ON_OFF', state);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async setTargetState() {
|
|
86
|
+
// 이 함수는 setProps로 인해 HEAT 값만 받게 되므로 별도 처리가 불필요합니다.
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async setTargetTemperature(value: CharacteristicValue) {
|
|
90
|
+
const device = this.accessory.context.device;
|
|
91
|
+
const subDevice = this.accessory.context.subDevice;
|
|
92
|
+
await this.platform.shomeClient.setDevice(device.thngId, subDevice.deviceId.toString(), 'HEATER', 'TEMPERATURE', value.toString());
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
|
|
2
|
+
import { ShomePlatform } from '../platform.js';
|
|
3
|
+
|
|
4
|
+
export class LightAccessory {
|
|
5
|
+
private service: Service;
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly platform: ShomePlatform,
|
|
9
|
+
private readonly accessory: PlatformAccessory,
|
|
10
|
+
) {
|
|
11
|
+
const device = this.accessory.context.device;
|
|
12
|
+
const subDevice = this.accessory.context.subDevice;
|
|
13
|
+
|
|
14
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)!
|
|
15
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Samsung')
|
|
16
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'Smart Light')
|
|
17
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, `${device.thngId}-${subDevice.deviceId}`);
|
|
18
|
+
|
|
19
|
+
this.service = this.accessory.getService(this.platform.Service.Lightbulb) ||
|
|
20
|
+
this.accessory.addService(this.platform.Service.Lightbulb);
|
|
21
|
+
|
|
22
|
+
this.service.setCharacteristic(this.platform.Characteristic.Name, subDevice.nickname);
|
|
23
|
+
|
|
24
|
+
this.service.getCharacteristic(this.platform.Characteristic.On)
|
|
25
|
+
.onGet(this.getOn.bind(this))
|
|
26
|
+
.onSet(this.setOn.bind(this));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getOn(): Promise<CharacteristicValue> {
|
|
30
|
+
const subDevice = this.accessory.context.subDevice;
|
|
31
|
+
return subDevice.deviceStatus === 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async setOn(value: CharacteristicValue) {
|
|
35
|
+
const device = this.accessory.context.device;
|
|
36
|
+
const subDevice = this.accessory.context.subDevice;
|
|
37
|
+
|
|
38
|
+
const state = value ? 'ON' : 'OFF';
|
|
39
|
+
await this.platform.shomeClient.setDevice(device.thngId, subDevice.deviceId.toString(), 'LIGHT', 'ON_OFF', state);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { CharacteristicValue, PlatformAccessory, Service } from 'homebridge';
|
|
2
|
+
import { ShomePlatform } from '../platform.js';
|
|
3
|
+
|
|
4
|
+
export class VentilatorAccessory {
|
|
5
|
+
private fanService: Service;
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly platform: ShomePlatform,
|
|
9
|
+
private readonly accessory: PlatformAccessory,
|
|
10
|
+
) {
|
|
11
|
+
const device = this.accessory.context.device;
|
|
12
|
+
const subDevice = this.accessory.context.subDevice;
|
|
13
|
+
|
|
14
|
+
this.accessory.getService(this.platform.Service.AccessoryInformation)!
|
|
15
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Samsung')
|
|
16
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'Ventilator')
|
|
17
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, `${device.thngId}-${subDevice.deviceId}`);
|
|
18
|
+
|
|
19
|
+
this.fanService = this.accessory.getService(this.platform.Service.Fanv2) ||
|
|
20
|
+
this.accessory.addService(this.platform.Service.Fanv2);
|
|
21
|
+
|
|
22
|
+
this.fanService.setCharacteristic(this.platform.Characteristic.Name, subDevice.nickname);
|
|
23
|
+
|
|
24
|
+
this.fanService.getCharacteristic(this.platform.Characteristic.Active)
|
|
25
|
+
.onGet(this.getActive.bind(this))
|
|
26
|
+
.onSet(this.setActive.bind(this));
|
|
27
|
+
|
|
28
|
+
this.fanService.getCharacteristic(this.platform.Characteristic.RotationSpeed)
|
|
29
|
+
.onGet(this.getRotationSpeed.bind(this))
|
|
30
|
+
.onSet(this.setRotationSpeed.bind(this));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getActive(): Promise<CharacteristicValue> {
|
|
34
|
+
const subDevice = this.accessory.context.subDevice;
|
|
35
|
+
return subDevice.deviceStatus === 1
|
|
36
|
+
? this.platform.Characteristic.Active.ACTIVE
|
|
37
|
+
: this.platform.Characteristic.Active.INACTIVE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getRotationSpeed(): Promise<CharacteristicValue> {
|
|
41
|
+
const subDevice = this.accessory.context.subDevice;
|
|
42
|
+
const apiSpeed = subDevice.windSpeedMode; // 1, 2, 3
|
|
43
|
+
|
|
44
|
+
// sHome API (1:강, 2:중, 3:약) -> HomeKit % (100:강, 66:중, 33:약)
|
|
45
|
+
if (apiSpeed === 1) {
|
|
46
|
+
return 100;
|
|
47
|
+
} else if (apiSpeed === 2) {
|
|
48
|
+
return 66;
|
|
49
|
+
} else {
|
|
50
|
+
return 33;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async setActive(value: CharacteristicValue) {
|
|
55
|
+
const device = this.accessory.context.device;
|
|
56
|
+
const subDevice = this.accessory.context.subDevice;
|
|
57
|
+
|
|
58
|
+
const state = value === this.platform.Characteristic.Active.ACTIVE ? 'ON' : 'OFF';
|
|
59
|
+
await this.platform.shomeClient.setDevice(device.thngId, subDevice.deviceId.toString(), 'VENTILATOR', 'ON_OFF', state);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async setRotationSpeed(value: CharacteristicValue) {
|
|
63
|
+
const device = this.accessory.context.device;
|
|
64
|
+
const subDevice = this.accessory.context.subDevice;
|
|
65
|
+
const numericValue = Number(value);
|
|
66
|
+
|
|
67
|
+
// HomeKit % -> sHome API (1:강, 2:중, 3:약)
|
|
68
|
+
let apiSpeed = 3;
|
|
69
|
+
if (numericValue > 33) {
|
|
70
|
+
apiSpeed = 2;
|
|
71
|
+
}
|
|
72
|
+
if (numericValue > 66) {
|
|
73
|
+
apiSpeed = 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await this.platform.shomeClient.setDevice(device.thngId, subDevice.deviceId.toString(), 'VENTILATOR', 'WINDSPEED', apiSpeed.toString());
|
|
77
|
+
}
|
|
78
|
+
}
|