Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add smart lock support. #120

Merged
merged 6 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- Add IR Remote Control support (`infrared_tv`, `infrared_stb`, `infrared_box`, `infrared_ac`, `infrared_fan`, `infrared_light`, `infrared_amplifier`, `infrared_projector`, `infrared_waterheater`, `infrared_airpurifier`).
- Add IR AC Controller support (`hwktwkq`).
- Add Fingerbot support (`szjqr`).
- Add Smart Lock support (`ms`, `jtmspro`). Thanks @pfgimutao for the contribution


### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Fork version of the official Tuya Homebridge plugin, with a focus on fixing bugs
- Lower development costs for new accessory categories.
- Supports Tuya Scenes (Tap-to-Run).
- Includes the ability to override device configurations, which enables support for "non-standard" DPs.
- Supports over 60+ device categories, including most lights, switches, sensors, cameras, IR remote control, etc.
- Supports over 60+ device categories, including most light, switch, sensor, camera, lock, IR remote control, etc.


## Supported Tuya Devices
Expand Down
2 changes: 1 addition & 1 deletion SUPPORTED_DEVICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Most category code is pinyin abbreviation of Chinese name.
| Methane Detector | 甲烷报警传感器 | jwbj | Leak Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryjwbj?id=Kaiuz40u98lkm) |
| Human Motion Sensor | 人体运动传感器 | pir | Motion Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categorypir?id=Kaiuz3ss11b80) |
| Human Presence Sensor | 人体存在传感器 | hps | Occupancy Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/categoryhps?id=Kaiuz42yhn1hs) |
| Smart Lock | 智能门锁 | ms | | | [Documentation](https://developer.tuya.com/en/docs/iot/ms?id=Kb0o2s20fn9sy) |
| Smart Lock | 智能门锁 | ms<br> jtmspro | LockMechanism | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/ms?id=Kb0o2s20fn9sy) |
| Environmental Detector | 环境检测仪 | hjjcy | Air Quality Sensor | ✅ | [Documentation](https://developer.tuya.com/en/docs/iot/hjjcy?id=Kbeoad8y1nnlv) |


Expand Down
5 changes: 5 additions & 0 deletions src/accessory/AccessoryFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import FanAccessory from './FanAccessory';
import GarageDoorAccessory from './GarageDoorAccessory';
import WindowAccessory from './WindowAccessory';
import WindowCoveringAccessory from './WindowCoveringAccessory';
import LockAccessory from './LockAccessory';
import ThermostatAccessory from './ThermostatAccessory';
import HeaterAccessory from './HeaterAccessory';
import ValveAccessory from './ValveAccessory';
Expand Down Expand Up @@ -171,6 +172,10 @@ export default class AccessoryFactory {
case 'hps':
handler = new HumanPresenceSensorAccessory(platform, accessory);
break;
case 'ms':
case 'jtmspro':
handler = new LockAccessory(platform, accessory);
break;

// Other
case 'scene':
Expand Down
59 changes: 59 additions & 0 deletions src/accessory/LockAccessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import BaseAccessory from './BaseAccessory';

const SCHEMA_CODE = {
LOCK_CURRENT_STATE: ['lock_motor_state'],
LOCK_TARGET_STATE: ['lock_motor_state'],
};

export default class LockAccessory extends BaseAccessory {

requiredSchema() {
return [SCHEMA_CODE.LOCK_TARGET_STATE];
}

configureServices() {
this.configureLockCurrentState();
this.configureLockTargetState();
}

mainService() {
return this.accessory.getService(this.Service.LockMechanism)
|| this.accessory.addService(this.Service.LockMechanism);
}

configureLockCurrentState() {
const schema = this.getSchema(...SCHEMA_CODE.LOCK_CURRENT_STATE);
if (!schema) {
return;
}

const { UNSECURED, SECURED } = this.Characteristic.LockCurrentState;
this.mainService().getCharacteristic(this.Characteristic.LockCurrentState)
.onGet(() => {
const status = this.getStatus(schema.code)!;
return (status.value as boolean) ? UNSECURED : SECURED;
});
}

configureLockTargetState() {
const schema = this.getSchema(...SCHEMA_CODE.LOCK_TARGET_STATE);
if (!schema) {
return;
}

const { UNSECURED, SECURED } = this.Characteristic.LockTargetState;
this.mainService().getCharacteristic(this.Characteristic.LockTargetState)
.onGet(() => {
const status = this.getStatus(schema.code)!;
return (status.value as boolean) ? UNSECURED : SECURED;
})
.onSet(async value => {
const res = await this.deviceManager.getLockTemporaryKey(this.device.id);
if (!res.success) {
return;
}
await this.deviceManager.sendLockCommands(this.device.id, res.result.ticket_id, (value === UNSECURED));
});
}

}
20 changes: 20 additions & 0 deletions src/device/TuyaDeviceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,26 @@ export default class TuyaDeviceManager extends EventEmitter {
return res;
}


async getLockTemporaryKey(deviceID: string) {
// const res = await this.api.post(`/v1.0/smart-lock/devices/${deviceID}/door-lock/password-ticket`);
const res = await this.api.post(`/v1.0/smart-lock/devices/${deviceID}/password-ticket`);
if (res.success === false) {
this.log.warn('Get Temporary Pass failed. devID = %s, code = %s, msg = %s', deviceID, res.code, res.msg);
}
return res;
}

async sendLockCommands(deviceID: string, ticketID: string, open: boolean) {
const res = await this.api.post(`/v1.0/smart-lock/devices/${deviceID}/password-free/door-operate`, {
device_id: deviceID,
ticket_id: ticketID,
open,
});
return res;
}


async sendCommands(deviceID: string, commands: TuyaDeviceStatus[]) {
const res = await this.api.post(`/v1.0/devices/${deviceID}/commands`, { commands });
return res.result;
Expand Down