From 64febc08d487f2dc0a075d4cbf3baf63d4f2b6f1 Mon Sep 17 00:00:00 2001 From: pandizajner Date: Sat, 6 Jul 2024 20:21:22 +0200 Subject: [PATCH] Initial update --- .vscode/settings.json | 2 +- package.json | 30 ++++++--- src/hon-api.ts | 23 +++++++ src/index.ts | 8 +-- src/platform.ts | 109 +++++------------------------- src/platformAccessory.ts | 141 +++++---------------------------------- src/settings.ts | 11 +-- 7 files changed, 79 insertions(+), 245 deletions(-) create mode 100644 src/hon-api.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 31b20d1..89d2e29 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "files.eol": "\n", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "editor.rulers": [ 140 ], "eslint.enable": true diff --git a/package.json b/package.json index 9f2b15f..edcac88 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,16 @@ { "private": true, - "displayName": "Plugin Name", - "name": "homebridge-plugin-name", + "displayName": "Homebridge Hon AC", + "name": "homebridge-hon-ac", "version": "1.0.0", - "description": "A short description about what your plugin does.", - "license": "Apache-2.0", + "description": "Homebridge plugin for Haier AC using HON API", + "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/USERNAME/GITHUB_PROJECT_NAME.git" + "url": "https://github.com/pandizajner/homebridge-hon-ac.git" }, "bugs": { - "url": "https://github.com/USERNAME/GITHUB_PROJECT_NAME/issues" + "url": "https://github.com/pandizajner/homebridge-hon-ac/issues" }, "engines": { "node": "^18.17.0 || ^20.9.0", @@ -18,13 +18,18 @@ }, "main": "dist/index.js", "scripts": { - "lint": "eslint src/**/*.ts --max-warnings=0", - "watch": "npm run build && npm link && nodemon", "build": "rimraf ./dist && tsc", + "lint": "eslint src --ext .ts", + "watch": "tsc -w", "prepublishOnly": "npm run lint && npm run build" }, "keywords": [ - "homebridge-plugin" + "homebridge-plugin", + "homebridge", + "homekit", + "HON", + "Haier", + "AC" ], "devDependencies": { "@types/node": "^20.12.7", @@ -36,5 +41,10 @@ "rimraf": "^5.0.5", "ts-node": "^10.9.2", "typescript": "^5.4.5" - } + }, + "dependencies": { + "axios": "^0.21.1" + }, + "author": "", + "homepage": "https://github.com/pandizajner/homebridge-hon-ac#readme" } diff --git a/src/hon-api.ts b/src/hon-api.ts new file mode 100644 index 0000000..d197ceb --- /dev/null +++ b/src/hon-api.ts @@ -0,0 +1,23 @@ +import axios from 'axios'; + +export class HonAPI { + private email: string; + private password: string; + + constructor(email: string, password: string) { + this.email = email; + this.password = password; + } + + async getDevices() { + const response = await axios.post('https://geofence.haiersmart.com/v2/auth/login', { + email: this.email, + password: this.password, + }); + const token = response.data.token; + const devicesResponse = await axios.get('https://geofence.haiersmart.com/v2/device', { + headers: { Authorization: `Bearer ${token}` }, + }); + return devicesResponse.data.devices; + } +} diff --git a/src/index.ts b/src/index.ts index 7ec4c7a..20da8f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,7 @@ import { API } from 'homebridge'; - import { PLATFORM_NAME } from './settings'; -import { ExampleHomebridgePlatform } from './platform'; +import { HonACPlatform } from './platform'; -/** - * This method registers the platform with Homebridge - */ export = (api: API) => { - api.registerPlatform(PLATFORM_NAME, ExampleHomebridgePlatform); + api.registerPlatform(PLATFORM_NAME, HonACPlatform); }; diff --git a/src/platform.ts b/src/platform.ts index e9f6cf0..fda8919 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -1,19 +1,13 @@ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge'; - import { PLATFORM_NAME, PLUGIN_NAME } from './settings'; -import { ExamplePlatformAccessory } from './platformAccessory'; +import { HonACPlatformAccessory } from './platformAccessory'; +import { HonAPI } from './hon-api'; -/** - * HomebridgePlatform - * This class is the main constructor for your plugin, this is where you should - * parse the user config and discover/register accessories with Homebridge. - */ -export class ExampleHomebridgePlatform implements DynamicPlatformPlugin { +export class HonACPlatform implements DynamicPlatformPlugin { public readonly Service: typeof Service = this.api.hap.Service; public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic; - // this is used to track restored cached accessories - public readonly accessories: PlatformAccessory[] = []; + private readonly accessories: PlatformAccessory[] = []; constructor( public readonly log: Logger, @@ -21,100 +15,29 @@ export class ExampleHomebridgePlatform implements DynamicPlatformPlugin { public readonly api: API, ) { this.log.debug('Finished initializing platform:', this.config.name); - - // Homebridge 1.8.0 introduced a `log.success` method that can be used to log success messages - // For users that are on a version prior to 1.8.0, we need a 'polyfill' for this method - if (!log.success) { - log.success = log.info; - } - - // When this event is fired it means Homebridge has restored all cached accessories from disk. - // Dynamic Platform plugins should only register new accessories after this event was fired, - // in order to ensure they weren't added to homebridge already. This event can also be used - // to start discovery of new accessories. - this.api.on('didFinishLaunching', () => { - log.debug('Executed didFinishLaunching callback'); - // run the method to discover / register your devices as accessories - this.discoverDevices(); - }); + this.api = new HonAPI(this.config.email, this.config.password); + this.api.on('didFinishLaunching', this.discoverDevices.bind(this)); } - /** - * This function is invoked when homebridge restores cached accessories from disk at startup. - * It should be used to set up event handlers for characteristics and update respective values. - */ configureAccessory(accessory: PlatformAccessory) { this.log.info('Loading accessory from cache:', accessory.displayName); - - // add the restored accessory to the accessories cache, so we can track if it has already been registered this.accessories.push(accessory); } - /** - * This is an example method showing how to register discovered accessories. - * Accessories must only be registered once, previously created accessories - * must not be registered again to prevent "duplicate UUID" errors. - */ - discoverDevices() { - - // EXAMPLE ONLY - // A real plugin you would discover accessories from the local network, cloud services - // or a user-defined array in the platform config. - const exampleDevices = [ - { - exampleUniqueId: 'ABCD', - exampleDisplayName: 'Bedroom', - }, - { - exampleUniqueId: 'EFGH', - exampleDisplayName: 'Kitchen', - }, - ]; - - // loop over the discovered devices and register each one if it has not already been registered - for (const device of exampleDevices) { - - // generate a unique id for the accessory this should be generated from - // something globally unique, but constant, for example, the device serial - // number or MAC address - const uuid = this.api.hap.uuid.generate(device.exampleUniqueId); - - // see if an accessory with the same uuid has already been registered and restored from - // the cached devices we stored in the `configureAccessory` method above + async discoverDevices() { + const devices = await this.api.getDevices(); + for (const device of devices) { + this.log.info('Found device:', device.name); + const uuid = this.api.hap.uuid.generate(device.id); const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); if (existingAccessory) { - // the accessory already exists - this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); - - // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. e.g.: - // existingAccessory.context.device = device; - // this.api.updatePlatformAccessories([existingAccessory]); - - // create the accessory handler for the restored accessory - // this is imported from `platformAccessory.ts` - new ExamplePlatformAccessory(this, existingAccessory); - - // it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, e.g.: - // remove platform accessories when no longer present - // this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]); - // this.log.info('Removing existing accessory from cache:', existingAccessory.displayName); + this.log.info('Restoring existing accessory:', existingAccessory.displayName); + new HonACPlatformAccessory(this, existingAccessory, device); } else { - // the accessory does not yet exist, so we need to create it - this.log.info('Adding new accessory:', device.exampleDisplayName); - - // create a new accessory - const accessory = new this.api.platformAccessory(device.exampleDisplayName, uuid); - - // store a copy of the device object in the `accessory.context` - // the `context` property can be used to store any data about the accessory you may need - accessory.context.device = device; - - // create the accessory handler for the newly create accessory - // this is imported from `platformAccessory.ts` - new ExamplePlatformAccessory(this, accessory); - - // link the accessory to your platform + this.log.info('Adding new accessory:', device.name); + const accessory = new this.api.platformAccessory(device.name, uuid); + new HonACPlatformAccessory(this, accessory, device); this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); } } diff --git a/src/platformAccessory.ts b/src/platformAccessory.ts index a51e4d3..a6ebc08 100644 --- a/src/platformAccessory.ts +++ b/src/platformAccessory.ts @@ -1,141 +1,30 @@ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; +import { HonACPlatform } from './platform'; -import { ExampleHomebridgePlatform } from './platform'; - -/** - * Platform Accessory - * An instance of this class is created for each accessory your platform registers - * Each accessory may expose multiple services of different service types. - */ -export class ExamplePlatformAccessory { +export class HonACPlatformAccessory { private service: Service; - /** - * These are just used to create a working example - * You should implement your own code to track the state of your accessory - */ - private exampleStates = { - On: false, - Brightness: 100, - }; - constructor( - private readonly platform: ExampleHomebridgePlatform, + private readonly platform: HonACPlatform, private readonly accessory: PlatformAccessory, + private readonly device: any, ) { + this.service = this.accessory.getService(this.platform.Service.Thermostat) || + this.accessory.addService(this.platform.Service.Thermostat); + this.service.setCharacteristic(this.platform.Characteristic.Name, device.name); - // set accessory information - this.accessory.getService(this.platform.Service.AccessoryInformation)! - .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Default-Manufacturer') - .setCharacteristic(this.platform.Characteristic.Model, 'Default-Model') - .setCharacteristic(this.platform.Characteristic.SerialNumber, 'Default-Serial'); - - // get the LightBulb service if it exists, otherwise create a new LightBulb service - // you can create multiple services for each accessory - this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb); - - // set the service name, this is what is displayed as the default name on the Home app - // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. - this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.exampleDisplayName); - - // each service must implement at-minimum the "required characteristics" for the given service type - // see https://developers.homebridge.io/#/service/Lightbulb - - // register handlers for the On/Off Characteristic - this.service.getCharacteristic(this.platform.Characteristic.On) - .onSet(this.setOn.bind(this)) // SET - bind to the `setOn` method below - .onGet(this.getOn.bind(this)); // GET - bind to the `getOn` method below - - // register handlers for the Brightness Characteristic - this.service.getCharacteristic(this.platform.Characteristic.Brightness) - .onSet(this.setBrightness.bind(this)); // SET - bind to the 'setBrightness` method below - - /** - * Creating multiple services of the same type. - * - * To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, - * when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: - * this.accessory.getService('NAME') || this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE_ID'); - * - * The USER_DEFINED_SUBTYPE must be unique to the platform accessory (if you platform exposes multiple accessories, each accessory - * can use the same subtype id.) - */ - - // Example: add two "motion sensor" services to the accessory - const motionSensorOneService = this.accessory.getService('Motion Sensor One Name') || - this.accessory.addService(this.platform.Service.MotionSensor, 'Motion Sensor One Name', 'YourUniqueIdentifier-1'); - - const motionSensorTwoService = this.accessory.getService('Motion Sensor Two Name') || - this.accessory.addService(this.platform.Service.MotionSensor, 'Motion Sensor Two Name', 'YourUniqueIdentifier-2'); - - /** - * Updating characteristics values asynchronously. - * - * Example showing how to update the state of a Characteristic asynchronously instead - * of using the `on('get')` handlers. - * Here we change update the motion sensor trigger states on and off every 10 seconds - * the `updateCharacteristic` method. - * - */ - let motionDetected = false; - setInterval(() => { - // EXAMPLE - inverse the trigger - motionDetected = !motionDetected; + this.service.getCharacteristic(this.platform.Characteristic.CurrentTemperature) + .onGet(this.getCurrentTemperature.bind(this)); - // push the new value to HomeKit - motionSensorOneService.updateCharacteristic(this.platform.Characteristic.MotionDetected, motionDetected); - motionSensorTwoService.updateCharacteristic(this.platform.Characteristic.MotionDetected, !motionDetected); - - this.platform.log.debug('Triggering motionSensorOneService:', motionDetected); - this.platform.log.debug('Triggering motionSensorTwoService:', !motionDetected); - }, 10000); + this.service.getCharacteristic(this.platform.Characteristic.TargetTemperature) + .onSet(this.setTargetTemperature.bind(this)); } - /** - * Handle "SET" requests from HomeKit - * These are sent when the user changes the state of an accessory, for example, turning on a Light bulb. - */ - async setOn(value: CharacteristicValue) { - // implement your own code to turn your device on/off - this.exampleStates.On = value as boolean; - - this.platform.log.debug('Set Characteristic On ->', value); - } - - /** - * Handle the "GET" requests from HomeKit - * These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on. - * - * GET requests should return as fast as possible. A long delay here will result in - * HomeKit being unresponsive and a bad user experience in general. - * - * If your device takes time to respond you should update the status of your device - * asynchronously instead using the `updateCharacteristic` method instead. - - * @example - * this.service.updateCharacteristic(this.platform.Characteristic.On, true) - */ - async getOn(): Promise { - // implement your own code to check if the device is on - const isOn = this.exampleStates.On; - - this.platform.log.debug('Get Characteristic On ->', isOn); - - // if you need to return an error to show the device as "Not Responding" in the Home app: - // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); - - return isOn; + async getCurrentTemperature(): Promise { + return this.device.currentTemperature; } - /** - * Handle "SET" requests from HomeKit - * These are sent when the user changes the state of an accessory, for example, changing the Brightness - */ - async setBrightness(value: CharacteristicValue) { - // implement your own code to set the brightness - this.exampleStates.Brightness = value as number; - - this.platform.log.debug('Set Characteristic Brightness -> ', value); + async setTargetTemperature(value: CharacteristicValue) { + this.device.setTargetTemperature(value); } - } diff --git a/src/settings.ts b/src/settings.ts index 8684475..8344933 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,9 +1,2 @@ -/** - * This is the name of the platform that users will use to register the plugin in the Homebridge config.json - */ -export const PLATFORM_NAME = 'ExampleHomebridgePlugin'; - -/** - * This must match the name of your plugin as defined the package.json - */ -export const PLUGIN_NAME = 'homebridge-plugin-name'; \ No newline at end of file +export const PLATFORM_NAME = 'HonACPlatform'; +export const PLUGIN_NAME = 'homebridge-hon-ac';