From f5b99f9c904be77740965b8c3bcb2cad8ee51c5c Mon Sep 17 00:00:00 2001 From: Brett Date: Sun, 16 Jun 2024 21:08:48 +1000 Subject: [PATCH] Rework services --- package-lock.json | 8 +- package.json | 2 +- src/vehicle-services/base.ts | 24 +++--- src/vehicle-services/battery.ts | 34 ++++---- src/vehicle-services/chargecurrent.ts | 42 +++++----- src/vehicle-services/chargelimit.ts | 43 +++++----- src/vehicle-services/chargeport.ts | 49 ++++++----- src/vehicle-services/chargeswitch.ts | 23 ++--- src/vehicle-services/climate.ts | 116 ++++++++++++-------------- src/vehicle-services/door.ts | 40 ++++----- src/vehicle-services/information.ts | 14 +--- src/vehicle-services/lock.ts | 36 ++++++++ src/vehicle-services/update.ts | 39 +++------ src/vehicle-services/windows.ts | 51 +++++------ src/vehicle.ts | 44 ++++++---- 15 files changed, 288 insertions(+), 277 deletions(-) create mode 100644 src/vehicle-services/lock.ts diff --git a/package-lock.json b/package-lock.json index 7a4097a..a74c8d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "tesla-fleet-api": "^0.0.17" + "tesla-fleet-api": "^0.0.18" }, "devDependencies": { "@types/node": "^20.12.13", @@ -3139,9 +3139,9 @@ } }, "node_modules/tesla-fleet-api": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/tesla-fleet-api/-/tesla-fleet-api-0.0.17.tgz", - "integrity": "sha512-QSCT4QeJCUquwH4+EWOPQcc+WTqoL7hj4JrMxpWBuAn6Fs9+a4HrnjC5Q4Jde0FKD68NfVjjkhZnzqaKAz1uYg==" + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/tesla-fleet-api/-/tesla-fleet-api-0.0.18.tgz", + "integrity": "sha512-0ibleIbNTzc5uzU7KUCJyPpk3jxUj7NrIBBcOychkerz07e6gMHQMlLzjARO+y3aNegEM7j9wIMlUq6FOZAAUA==" }, "node_modules/text-table": { "version": "0.2.0", diff --git a/package.json b/package.json index 8f39ded..b6e74e9 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,6 @@ "typescript": "^5.4.5" }, "dependencies": { - "tesla-fleet-api": "^0.0.17" + "tesla-fleet-api": "^0.0.18" } } diff --git a/src/vehicle-services/base.ts b/src/vehicle-services/base.ts index 589f555..16e9129 100644 --- a/src/vehicle-services/base.ts +++ b/src/vehicle-services/base.ts @@ -1,41 +1,45 @@ import { Logging, PlatformAccessory, Service, WithUUID } from "homebridge"; +import { VehicleSpecific } from "tesla-fleet-api"; import { TeslaFleetApiPlatform } from "../platform.js"; -import { VehicleAccessory, VehicleContext } from "../vehicle.js"; +import { EventEmitter } from "../utils/event.js"; +import { VehicleAccessory, VehicleContext, VehicleDataEvent } from "../vehicle.js"; export abstract class BaseService { protected service: Service; protected log: Logging; protected platform: TeslaFleetApiPlatform; protected accessory: PlatformAccessory; - + protected emitter: EventEmitter; + protected vehicle: VehicleSpecific; constructor( protected parent: VehicleAccessory, definition: WithUUID, - description: string, + name: string, subtype: string, ) { this.log = parent.platform.log; this.platform = parent.platform; this.accessory = parent.accessory; + this.emitter = parent.emitter; + this.vehicle = parent.vehicle; - - const name = `${this.parent.accessory.displayName} ${description}`; - //const uuid = this.parent.platform.api.hap.uuid.generate(this.parent.accessory.context.vin); + name = `${this.parent.accessory.displayName} ${name}`; if (this.parent.accessory.getServiceById(definition, subtype)) { - this.log.info(`Restoring service ${name}`); + this.log.info(`Restoring ${this.accessory.displayName} service ${name}`); } else { - this.log.info(`Creating service ${name}`); + this.log.info(`Creating ${this.accessory.displayName} service ${name}`); } this.service = this.parent.accessory.getServiceById(definition, subtype) || this.parent.accessory.addService(definition, name, subtype); - this.service.setCharacteristic( + console.log(this.service.getCharacteristic(this.parent.platform.Characteristic.Name).value); + /*this.service.updateCharacteristic( this.parent.platform.Characteristic.Name, name - ); + );*/ } } diff --git a/src/vehicle-services/battery.ts b/src/vehicle-services/battery.ts index ec93fcc..29631c5 100644 --- a/src/vehicle-services/battery.ts +++ b/src/vehicle-services/battery.ts @@ -3,33 +3,33 @@ import { BaseService } from "./base.js"; export class BatteryService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Battery, "SOC", "soc"); + super(parent, parent.platform.Service.Battery, "Battery", "soc"); const batteryLevel = this.service - .getCharacteristic(this.parent.platform.Characteristic.BatteryLevel) - .onGet(this.getLevel.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.BatteryLevel); + //.onGet(this.getLevel.bind(this)); const chargingState = this.service - .getCharacteristic(this.parent.platform.Characteristic.ChargingState) - .onGet(this.getChargingState.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.ChargingState); + //.onGet(this.getChargingState.bind(this)); const lowBattery = this.service - .getCharacteristic(this.parent.platform.Characteristic.StatusLowBattery) - .onGet(this.getLowBattery.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.StatusLowBattery); + //.onGet(this.getLowBattery.bind(this)); - this.parent.emitter.on("vehicle_data", () => { - batteryLevel.updateValue(this.getLevel()); - chargingState.updateValue(this.getChargingState()); - lowBattery.updateValue(this.getLowBattery()); + this.parent.emitter.on("vehicle_data", (data) => { + batteryLevel.updateValue(this.getLevel(data)); + chargingState.updateValue(this.getChargingState(data)); + lowBattery.updateValue(this.getLowBattery(data)); }); } - getLevel(): number { - return this.parent.accessory.context?.charge_state?.battery_level ?? 50; + getLevel(data): number { + return data.charge_state?.battery_level ?? 50; } - getChargingState(): number { - switch (this.parent.accessory.context?.charge_state?.charging_state) { + getChargingState(data): number { + switch (data.charge_state?.charging_state) { case "Starting": return this.parent.platform.Characteristic.ChargingState.CHARGING; case "Charging": @@ -43,7 +43,7 @@ export class BatteryService extends BaseService { } } - getLowBattery(): boolean { - return this.getLevel() <= 20; + getLowBattery(data): boolean { + return this.getLevel(data) <= 20; } } diff --git a/src/vehicle-services/chargecurrent.ts b/src/vehicle-services/chargecurrent.ts index 691b5c7..15719cb 100644 --- a/src/vehicle-services/chargecurrent.ts +++ b/src/vehicle-services/chargecurrent.ts @@ -1,39 +1,39 @@ -import { CharacteristicValue } from "homebridge"; +import { Characteristic, CharacteristicValue } from "homebridge"; +import { VehicleDataResponse } from "tesla-fleet-api/dist/types/vehicle_data.js"; import { debounce } from "../utils/debounce.js"; import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; export class ChargeCurrentService extends BaseService { + + min: number = 2; + max: number = 16; + constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Lightbulb, "charge current", "charge_current"); + super(parent, parent.platform.Service.Lightbulb, "Charge Current", "charge_current"); const on = this.service - .getCharacteristic(this.parent.platform.Characteristic.On) - .onGet(this.getOn.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.On); + //.onGet(this.getOn.bind(this)); const level = this.service .getCharacteristic(this.parent.platform.Characteristic.Brightness) - .onGet(this.getLevel.bind(this)) - .onSet(debounce(this.setLevel.bind(this), 3000)); + //.onGet(this.getLevel.bind(this)) + .onSet(debounce((value) => this.setLevel(value, level), 3000)); - this.parent.emitter.on("vehicle_data", () => { - on.updateValue(this.getOn()); - level.updateValue(this.getLevel()); + this.parent.emitter.on("vehicle_data", (data) => { + on.updateValue(true); + this.max = data.charge_state.charge_current_request_max; + level.updateValue(data.charge_state.charge_current_request); }); } - getOn(): boolean { - return !!this.parent.accessory.context?.charge_state; - } - - getLevel(): number { - return this.parent.accessory.context?.charge_state?.charge_current_request ?? 16; - } + async setLevel(value: CharacteristicValue, characteristic: Characteristic): Promise { + value = Math.max(this.min, Math.min(this.max, value as number)); - setLevel(value: CharacteristicValue): Promise { - const min = 2; - const max = this.parent.accessory.context.charge_state.charge_current_request_max ?? 32; - value = Math.max(min, Math.min(max, value as number)); - return this.parent.vehicle.set_charge_limit(value).then(() => value); + await this.parent.vehicle.wake_up() + .then(() => this.parent.vehicle.set_charging_amps(value)) + .then(() => characteristic.updateValue(value)); + //.then(() => value); //to confirm this is okay } } diff --git a/src/vehicle-services/chargelimit.ts b/src/vehicle-services/chargelimit.ts index a2ef37e..f47247d 100644 --- a/src/vehicle-services/chargelimit.ts +++ b/src/vehicle-services/chargelimit.ts @@ -1,39 +1,38 @@ -import { CharacteristicValue } from "homebridge"; +import { Characteristic, CharacteristicValue } from "homebridge"; +import { VehicleDataResponse } from "tesla-fleet-api/dist/types/vehicle_data.js"; import { debounce } from "../utils/debounce.js"; import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; export class ChargeLimitService extends BaseService { + + min: number = 50; + max: number = 100; + constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Lightbulb, "charge limit", "charge_limit"); + super(parent, parent.platform.Service.Lightbulb, "Charge Limit", "charge_limit"); const on = this.service - .getCharacteristic(this.parent.platform.Characteristic.On) - .onGet(this.getOn.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.On); + //.onGet(this.getOn.bind(this)); const level = this.service .getCharacteristic(this.parent.platform.Characteristic.Brightness) - .onGet(this.getLevel.bind(this)) - .onSet(debounce(this.setLevel.bind(this), 3000)); + //.onGet(this.getLevel.bind(this)) + .onSet(debounce((value) => this.setLevel(value, level), 3000)); - this.parent.emitter.on("vehicle_data", () => { - on.updateValue(this.getOn()); - level.updateValue(this.getLevel()); + this.parent.emitter.on("vehicle_data", (data) => { + this.min = data.charge_state.charge_limit_soc_min ?? this.min; + this.max = data.charge_state.charge_limit_soc_max ?? this.max; + on.updateValue(true); + level.updateValue(data.charge_state.charge_limit_soc); }); } - getOn(): boolean { - return !!this.parent.accessory.context?.charge_state; - } - - getLevel(): number { - return this.parent.accessory.context?.charge_state?.charge_limit_soc ?? 50; - } - - setLevel(value: CharacteristicValue): Promise { - const min = this.parent.accessory.context.charge_state.charge_limit_soc_min ?? 50; - const max = this.parent.accessory.context.charge_state.charge_limit_soc_max ?? 100; - value = Math.max(min, Math.min(max, value as number)); - return this.parent.vehicle.set_charge_limit(value).then(() => value); + async setLevel(value: CharacteristicValue, characteristic: Characteristic): Promise { + value = Math.max(this.min, Math.min(this.max, value as number)); + await this.vehicle.wake_up() + .then(() => this.vehicle.set_charge_limit(value)) + .then(() => characteristic.updateValue(value)); } } diff --git a/src/vehicle-services/chargeport.ts b/src/vehicle-services/chargeport.ts index 6251992..f89e3d2 100644 --- a/src/vehicle-services/chargeport.ts +++ b/src/vehicle-services/chargeport.ts @@ -1,41 +1,40 @@ -import { CharacteristicValue } from "homebridge"; +import { Characteristic, CharacteristicValue } from "homebridge"; import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; export class ChargePortService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.LockMechanism, "charge port", "charge_port"); + super(parent, parent.platform.Service.LockMechanism, "Charge Port", "charge_port"); const currentState = this.service - .getCharacteristic(this.parent.platform.Characteristic.LockCurrentState) - .onGet(this.getState.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.LockCurrentState); + //.onGet(this.getState.bind(this)); - this.service + const targetState = this.service .getCharacteristic(this.parent.platform.Characteristic.LockTargetState) - .onGet(this.getState.bind(this)) - .onSet(this.setState.bind(this)); + //.onGet(this.getState.bind(this)) + .onSet((value) => this.setState(value, targetState)); - this.parent.emitter.on("vehicle_data", () => { - currentState.updateValue(this.getState()); + this.parent.emitter.on("vehicle_data", (data) => { + const state = (data.charge_state.charge_port_latch === "Engaged") ? + this.platform.Characteristic.LockTargetState.SECURED : + this.platform.Characteristic.LockTargetState.UNSECURED; + currentState.updateValue(state); + targetState.updateValue(state); }); } - getState(): number { - return this.parent.accessory.context?.charge_state?.charge_port_latch ? - this.parent.platform.Characteristic.LockTargetState.SECURED : - this.parent.platform.Characteristic.LockTargetState.UNSECURED; - } - - setState(value: CharacteristicValue): Promise { - const open = value === this.parent.platform.Characteristic.LockTargetState.UNSECURED; - - if (open) { - return this.parent.vehicle.charge_port_door_open().then(() => - this.parent.platform.Characteristic.LockTargetState.SECURED - ); - } - return this.parent.vehicle.charge_port_door_close().then(() => - this.parent.platform.Characteristic.LockTargetState.UNSECURED + async setState(value: CharacteristicValue, characteristic: Characteristic): Promise { + await this.vehicle.wake_up().then(() => + value === this.parent.platform.Characteristic.LockTargetState.SECURED ? + this.vehicle.charge_port_door_close() + .then(() => + characteristic.updateValue(this.parent.platform.Characteristic.LockTargetState.SECURED) + ) : + this.vehicle.charge_port_door_open() + .then(() => + characteristic.updateValue(this.parent.platform.Characteristic.LockTargetState.UNSECURED) + ) ); } } diff --git a/src/vehicle-services/chargeswitch.ts b/src/vehicle-services/chargeswitch.ts index b38b47b..cc9f12c 100644 --- a/src/vehicle-services/chargeswitch.ts +++ b/src/vehicle-services/chargeswitch.ts @@ -1,27 +1,30 @@ -import { CharacteristicValue } from "homebridge"; +import { Characteristic, CharacteristicValue } from "homebridge"; import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; export class ChargeSwitchService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Switch, "charge", "charge"); + super(parent, parent.platform.Service.Switch, "Charge", "charge"); const on = this.service .getCharacteristic(this.parent.platform.Characteristic.On) - .onGet(this.getOn.bind(this)) - .onSet(this.setOn.bind(this)); + //.onGet(this.getOn.bind(this)) + .onSet((value) => this.setOn(value, on)); - this.parent.emitter.on("vehicle_data", () => { - on.updateValue(this.getOn()); + this.parent.emitter.on("vehicle_data", (data) => { + on.updateValue(data.charge_state?.user_charge_enable_request ?? data.charge_state?.charge_enable_request); }); } getOn(): boolean { - return this.parent.accessory.context?.charge_state?.user_charge_enable_request - ?? this.parent.accessory.context?.charge_state?.charge_enable_request; + return !!(this.parent.accessory.context?.charge_state?.user_charge_enable_request + ?? this.parent.accessory.context?.charge_state?.charge_enable_request); } - setOn(value: CharacteristicValue): Promise { - return value ? this.parent.vehicle.charge_start().then(() => true) : this.parent.vehicle.charge_stop().then(() => false); + async setOn(value: CharacteristicValue, characteristic: Characteristic): Promise { + await this.parent.wake_up().then(() => + value ? this.parent.vehicle.charge_start().then(() => characteristic.updateValue(value)) + : this.parent.vehicle.charge_stop().then(() => characteristic.updateValue(value)) + ); } } diff --git a/src/vehicle-services/climate.ts b/src/vehicle-services/climate.ts index 4596e94..47c2f35 100644 --- a/src/vehicle-services/climate.ts +++ b/src/vehicle-services/climate.ts @@ -1,95 +1,87 @@ // https://developers.homebridge.io/#/service/Thermostat -import { CharacteristicValue } from "homebridge"; +import { Characteristic, CharacteristicValue } from "homebridge"; import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; export class ClimateService extends BaseService { + displayUnits: number = 0; // Celsius by default constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Thermostat, "climate", "climate"); + super(parent, parent.platform.Service.Thermostat, "Climate", "climate"); const currentState = this.service .getCharacteristic( this.parent.platform.Characteristic.CurrentHeatingCoolingState - ) - .onGet(this.getCurrentState.bind(this)); + ); + //.onGet(this.getCurrentState.bind(this)); const targetState = this.service .getCharacteristic( this.parent.platform.Characteristic.TargetHeatingCoolingState ) - .onGet(this.getTargetState.bind(this)) - .onSet(this.setTargetState.bind(this)); + //.onGet(this.getTargetState.bind(this)) + .onSet((value) => this.setTargetState(value, targetState)); const currentTemp = this.service - .getCharacteristic(this.parent.platform.Characteristic.CurrentTemperature) - .onGet(this.getCurrentTemp.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.CurrentTemperature); + //.onGet(this.getCurrentTemp.bind(this)); const targetTemp = this.service .getCharacteristic(this.parent.platform.Characteristic.TargetTemperature) - .onGet(this.getTargetTemp.bind(this)) - .onSet(this.setTargetTemp.bind(this)); + //.onGet(this.getTargetTemp.bind(this)) + .onSet((value) => this.setTargetTemp(value, targetTemp)); + + this.service + .getCharacteristic(this.parent.platform.Characteristic.TemperatureDisplayUnits) + //.onGet(() => this.displayUnits) + .onSet((value: CharacteristicValue) => { + this.displayUnits = value as number; + }); - this.service.setCharacteristic( + /*this.service.setCharacteristic( this.parent.platform.Characteristic.TemperatureDisplayUnits, this.parent.platform.Characteristic.TemperatureDisplayUnits.CELSIUS - ); - - this.parent.emitter.on("vehicle_data", () => { - currentState.updateValue(this.getCurrentState()); - targetState.updateValue(this.getTargetState()); - currentTemp.updateValue(this.getCurrentTemp()); - targetTemp.updateValue(this.getTargetTemp()); - }); - } + );*/ - getCurrentState(): number { - if (!this.parent.accessory.context?.climate_state.is_climate_on) { - return this.parent.platform.Characteristic.CurrentHeatingCoolingState.OFF; - } - if (this.getCurrentTemp() < this.getTargetTemp()) { - return this.parent.platform.Characteristic.CurrentHeatingCoolingState - .HEAT; - } - return this.parent.platform.Characteristic.CurrentHeatingCoolingState.COOL; - } - - getTargetState(): number { - if (!this.parent.accessory.context?.climate_state?.is_climate_on) { - return this.parent.platform.Characteristic.TargetHeatingCoolingState.OFF; - } - return this.parent.platform.Characteristic.TargetHeatingCoolingState.AUTO; - } - - async setTargetState(value: CharacteristicValue) { - return value - ? this.parent.vehicle - .auto_conditioning_start() - .then( - () => - this.parent.platform.Characteristic.TargetHeatingCoolingState.AUTO - ) - : this.parent.vehicle - .auto_conditioning_stop() - .then( - () => - this.parent.platform.Characteristic.TargetHeatingCoolingState.OFF + this.parent.emitter.on("vehicle_data", (data) => { + if (data.climate_state.is_climate_on === false) { + // Off + currentState.updateValue(this.platform.Characteristic.CurrentHeatingCoolingState.OFF); + targetState.updateValue(this.platform.Characteristic.TargetHeatingCoolingState.OFF); + } else { + // On + currentState.updateValue(data.climate_state.inside_temp < data.climate_state.driver_temp_setting + ? this.platform.Characteristic.CurrentHeatingCoolingState.HEAT + : this.platform.Characteristic.CurrentHeatingCoolingState.COOL ); - } + targetState.updateValue(this.platform.Characteristic.TargetHeatingCoolingState.AUTO); + } - getCurrentTemp(): number { - return this.parent.accessory.context?.climate_state?.inside_temp ?? 0; + currentTemp.updateValue(data.climate_state.inside_temp); + targetTemp.updateValue(data.climate_state.driver_temp_setting); + }); } - getTargetTemp(): number { - return ( - this.parent.accessory.context?.climate_state?.driver_temp_setting ?? 10 - ); + async setTargetState(value: CharacteristicValue, characteristic: Characteristic): Promise { + await this.vehicle.wake_up() + .then(() => value + ? this.vehicle + .auto_conditioning_start() + .then( + () => + characteristic.updateValue(this.parent.platform.Characteristic.TargetHeatingCoolingState.AUTO) + ) + : this.vehicle + .auto_conditioning_stop() + .then( + () => + characteristic.updateValue(this.parent.platform.Characteristic.TargetHeatingCoolingState.OFF) + )); } - async setTargetTemp(value: CharacteristicValue) { - return this.parent.vehicle - .set_temps(value as number, value as number) - .then(() => value); + async setTargetTemp(value: CharacteristicValue, characteristic: Characteristic): Promise { + await this.vehicle.wake_up().then(() => + this.vehicle.set_temps(value as number, value as number) + .then(() => characteristic.updateValue(value))); } } diff --git a/src/vehicle-services/door.ts b/src/vehicle-services/door.ts index c988f8b..5f54ed2 100644 --- a/src/vehicle-services/door.ts +++ b/src/vehicle-services/door.ts @@ -1,25 +1,26 @@ //https://developers.homebridge.io/#/service/Door -import { CharacteristicValue } from "homebridge"; +import { Characteristic, CharacteristicValue } from "homebridge"; import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; export class DoorService extends BaseService { key: "ft" | "rt"; + open: boolean = false; constructor(parent: VehicleAccessory, private trunk: "front" | "rear") { super( parent, parent.platform.Service.Door, - trunk === "front" ? "frunk" : "trunk", + trunk === "front" ? "Frunk" : "Trunk", trunk ); this.key = this.trunk === "front" ? "ft" : "rt"; const currentPosition = this.service - .getCharacteristic(this.parent.platform.Characteristic.CurrentPosition) - .onGet(this.getPosition.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.CurrentPosition); + //.onGet(this.getPosition.bind(this)); /*const positionState = this.service .getCharacteristic(this.parent.platform.Characteristic.PositionState) @@ -27,27 +28,26 @@ export class DoorService extends BaseService { const targetPosition = this.service .getCharacteristic(this.parent.platform.Characteristic.TargetPosition) - .onGet(this.getPosition.bind(this)) - .onSet(this.setPosition.bind(this)); - - this.parent.emitter.on("vehicle_data", () => { - currentPosition.updateValue(this.getPosition()); - targetPosition.updateValue(this.getPosition()); + //.onGet(this.getPosition.bind(this)) + .onSet((value) => this.setPosition(value, targetPosition)); + + this.parent.emitter.on("vehicle_data", (data) => { + this.open = data.vehicle_state[this.key] === 1; + const position = this.open ? 100 : 0; + currentPosition.updateValue(position); + targetPosition.updateValue(position); }); } - getPosition(): number { - return this.parent.accessory.context?.vehicle_state?.[this.key] ? 100 : 0; - } - - async setPosition(value: CharacteristicValue) { - const position = this.getPosition(); + async setPosition(value: CharacteristicValue, characteristic: Characteristic): Promise { + value = value as number; if ( - (position === 0 && value === 100) || - (position === 100 && value === 0 && this.trunk === "rear") + (!this.open && value > 50) || + (this.open && value < 50 && this.trunk === "rear") ) { - return this.parent.vehicle.actuate_truck(this.trunk).then(() => value); + this.vehicle.wake_up() + .then(() => this.parent.vehicle.actuate_truck(this.trunk)) + .then(() => characteristic.updateValue(value)); } - return position; } } diff --git a/src/vehicle-services/information.ts b/src/vehicle-services/information.ts index 3133363..bb47a83 100644 --- a/src/vehicle-services/information.ts +++ b/src/vehicle-services/information.ts @@ -29,20 +29,14 @@ export class AccessoryInformationService { .onSet(this.setIdentify.bind(this)); const version = this.service - .getCharacteristic(this.parent.platform.Characteristic.FirmwareRevision) - .onGet(this.getVersion.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.FirmwareRevision); + //.onGet(this.getVersion.bind(this)); - this.parent.emitter.on("vehicle_data", () => { - version.updateValue(this.getVersion()); + this.parent.emitter.on("vehicle_data", (data) => { + version.updateValue(data.vehicle_state.car_version); }); } - getVersion(): string { - return ( - this.parent.accessory.context?.vehicle_state?.car_version ?? "unknown" - ); - } - async setIdentify(): Promise { await this.parent.wake_up().then(() => this.parent.vehicle.flash_lights()); } diff --git a/src/vehicle-services/lock.ts b/src/vehicle-services/lock.ts new file mode 100644 index 0000000..727677b --- /dev/null +++ b/src/vehicle-services/lock.ts @@ -0,0 +1,36 @@ +import { Characteristic, CharacteristicValue } from "homebridge"; +import { VehicleAccessory } from "../vehicle.js"; +import { BaseService } from "./base.js"; + +export class LockService extends BaseService { + constructor(parent: VehicleAccessory) { + super(parent, parent.platform.Service.LockMechanism, "Lock", "lock"); + + const currentState = this.service + .getCharacteristic(this.parent.platform.Characteristic.LockCurrentState); + //.onGet(this.getState.bind(this)); + + const targetState = this.service + .getCharacteristic(this.parent.platform.Characteristic.LockTargetState) + //.onGet(this.getState.bind(this)) + .onSet((value) => this.setState(value, targetState)); + + this.parent.emitter.on("vehicle_data", (data) => { + currentState.updateValue(data.vehicle_state.locked ? + this.platform.Characteristic.LockTargetState.SECURED : + this.platform.Characteristic.LockTargetState.UNSECURED); + }); + } + + async setState(value: CharacteristicValue, characteristic: Characteristic): Promise { + const open = value === this.parent.platform.Characteristic.LockTargetState.UNSECURED; + + await this.parent.wake_up().then(() => + open ? + this.parent.vehicle.door_lock() + .then(() => characteristic.updateValue(this.parent.platform.Characteristic.LockTargetState.SECURED)) : + this.parent.vehicle.door_unlock() + .then(() => characteristic.updateValue(this.parent.platform.Characteristic.LockTargetState.UNSECURED)) + ); + } +} diff --git a/src/vehicle-services/update.ts b/src/vehicle-services/update.ts index 23c6376..5ec6b4c 100644 --- a/src/vehicle-services/update.ts +++ b/src/vehicle-services/update.ts @@ -10,44 +10,25 @@ export class UpdateService extends BaseService { const readiness = this.service .getCharacteristic( this.parent.platform.Characteristic.FirmwareUpdateReadiness - ) - .onGet(this.getReadiness.bind(this)); + ); + //.onGet(this.getReadiness.bind(this)); const status = this.service .getCharacteristic( this.parent.platform.Characteristic.FirmwareUpdateStatus - ) - .onGet(this.getStatus.bind(this)); + ); + //.onGet(this.getStatus.bind(this)); const staged = this.service .getCharacteristic( this.parent.platform.Characteristic.StagedFirmwareVersion - ) - .onGet(this.getStaged.bind(this)); + ); + //.onGet(this.getStaged.bind(this)); - this.parent.emitter.on("vehicle_data", () => { - readiness.updateValue(this.getReadiness()); - status.updateValue(this.getStatus()); - staged.updateValue(this.getStaged()); + this.parent.emitter.on("vehicle_data", (data) => { + readiness.updateValue(data.vehicle_state.software_update?.status === "downloaded"); + status.updateValue(data.vehicle_state.software_update?.status || "No update"); + staged.updateValue(data.vehicle_state.software_update?.version); }); } - - getReadiness(): boolean { - return ( - this.parent.accessory?.context?.vehicle_state?.software_update?.status === - "downloaded" - ); - } - - getStatus(): string { - return ( - this.parent.accessory?.context?.vehicle_state?.software_update?.status || - "No update" - ); - } - - getStaged(): string { - return this.parent.accessory?.context?.vehicle_state?.software_update - ?.version; - } } diff --git a/src/vehicle-services/windows.ts b/src/vehicle-services/windows.ts index bf87588..caad2dd 100644 --- a/src/vehicle-services/windows.ts +++ b/src/vehicle-services/windows.ts @@ -1,16 +1,16 @@ // https://developers.homebridge.io/#/service/Window -import { CharacteristicValue } from "homebridge"; +import { Characteristic, CharacteristicValue } from "homebridge"; import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; export class WindowService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Window, "vent windows", "vent_windows"); + super(parent, parent.platform.Service.Window, "Vent Windows", "vent_windows"); const currentPosition = this.service - .getCharacteristic(this.parent.platform.Characteristic.CurrentPosition) - .onGet(this.getPosition.bind(this)); + .getCharacteristic(this.parent.platform.Characteristic.CurrentPosition); + //.onGet(this.getPosition.bind(this)); /*const positionState = this.service .getCharacteristic(this.parent.platform.Characteristic.PositionState) @@ -18,34 +18,27 @@ export class WindowService extends BaseService { const targetPosition = this.service .getCharacteristic(this.parent.platform.Characteristic.TargetPosition) - .onGet(this.getPosition.bind(this)) - .onSet(this.setPosition.bind(this)); - - this.parent.emitter.on("vehicle_data", () => { - currentPosition.updateValue(this.getPosition()); - targetPosition.updateValue(this.getPosition()); + //.onGet(this.getPosition.bind(this)) + .onSet((value) => this.setPosition(value, targetPosition)); + + this.parent.emitter.on("vehicle_data", (data) => { + const position = data.vehicle_state.fd_window * 25 + + data.vehicle_state.fp_window * 25 + + data.vehicle_state.rd_window * 25 + + data.vehicle_state.rp_window * 25; + currentPosition.updateValue(position); + targetPosition.updateValue(position); }); } - getPosition(): number { - if (!this.parent.accessory.context?.vehicle_state) { - return 0; - } - return this.parent.accessory.context.vehicle_state.fd_window * 25 + - this.parent.accessory.context.vehicle_state.fp_window * 25 + - this.parent.accessory.context.vehicle_state.rd_window * 25 + - this.parent.accessory.context.vehicle_state.rp_window * 25; - } - - async setPosition(value: CharacteristicValue): Promise { - if (value === 100 || value === 0) { - - this.log.info("Setting windows to", value); - const { latitude, longitude } = - this.parent.accessory.context?.drive_state ?? {}; + async setPosition(value: CharacteristicValue, characteristic: Characteristic): Promise { + value = Math.round(value as number / 100) * 100; + this.log.debug("Setting windows to", value); + const { latitude, longitude } = + this.parent.accessory.context?.drive_state ?? {}; - await this.parent.vehicle - .window_control(value === 100 ? "vent" : "close", latitude, longitude); - } + await this.parent.vehicle.wake_up() + .then(() => this.parent.vehicle.window_control(value === 100 ? "vent" : "close", latitude, longitude)) + .then(() => characteristic.updateValue(value)); } } diff --git a/src/vehicle.ts b/src/vehicle.ts index 5aec7e5..7598f8f 100644 --- a/src/vehicle.ts +++ b/src/vehicle.ts @@ -7,9 +7,12 @@ import { DriveState, GUISettings, VehicleConfig, + VehicleDataResponse, VehicleState, } from "tesla-fleet-api/dist/types/vehicle_data"; import { TeslaFleetApiPlatform } from "./platform.js"; +import { REFRESH_INTERVAL } from "./settings.js"; +import { EventEmitter } from "./utils/event.js"; import { BatteryService } from "./vehicle-services/battery.js"; import { ChargeCurrentService } from "./vehicle-services/chargecurrent.js"; import { ChargeLimitService } from "./vehicle-services/chargelimit.js"; @@ -18,9 +21,8 @@ import { ChargeSwitchService } from "./vehicle-services/chargeswitch.js"; import { ClimateService } from "./vehicle-services/climate.js"; import { DoorService } from "./vehicle-services/door.js"; import { AccessoryInformationService } from "./vehicle-services/information.js"; +import { LockService } from "./vehicle-services/lock.js"; import { WindowService } from "./vehicle-services/windows.js"; -import { REFRESH_INTERVAL } from "./settings.js"; -import { EventEmitter } from "./utils/event.js"; export type VehicleContext = { vin: string; @@ -34,7 +36,7 @@ export type VehicleContext = { }; export interface VehicleDataEvent { - vehicle_data(data: VehicleContext): void; + vehicle_data(data: VehicleDataResponse): void; } export class VehicleAccessory { @@ -55,11 +57,7 @@ export class VehicleAccessory { this.emitter = new EventEmitter(); - this.refresh(); - setInterval(() => this.refresh(), REFRESH_INTERVAL); - - // each service must implement at-minimum the "required characteristics" for the given service type - // see https://developers.homebridge.io/#/service/Lightbulb. + // Create services new AccessoryInformationService(this); new BatteryService(this); @@ -70,7 +68,13 @@ export class VehicleAccessory { new ChargeSwitchService(this); new DoorService(this, "front"); new DoorService(this, "rear"); + new LockService(this); new WindowService(this); + + // Get data and schedule refresh + + this.refresh(); + setInterval(() => this.refresh(), REFRESH_INTERVAL); } async refresh(): Promise { @@ -82,17 +86,23 @@ export class VehicleAccessory { "location_data", "vehicle_state", ]) - .then(({ charge_state, climate_state, drive_state, vehicle_state }) => { + .then((data) => { this.accessory.context.state = "online"; - this.accessory.context.charge_state = charge_state; - this.accessory.context.climate_state = climate_state; - this.accessory.context.drive_state = drive_state; - this.accessory.context.vehicle_state = vehicle_state; - this.emitter.emit("vehicle_data", this.accessory.context); + this.accessory.context.charge_state = data.charge_state; + this.accessory.context.climate_state = data.climate_state; + this.accessory.context.drive_state = data.drive_state; + this.accessory.context.vehicle_state = data.vehicle_state; + this.emitter.emit("vehicle_data", data); }) - .catch((error: string) => { - this.platform.log.warn(error); - this.accessory.context.state = "offline"; + .catch((data) => { + if (data?.status === 408) { + this.accessory.context.state = "offline"; + return; + } + if (data?.error) { + this.platform.log.warn(data.error); + } + this.platform.log.error(data); }); }