diff --git a/publish.sh b/publish.sh new file mode 100644 index 0000000..cd48a75 --- /dev/null +++ b/publish.sh @@ -0,0 +1,9 @@ +git checkout teslemetry +git rebase dev +git push --force-with-lease +npm run publish + +git checkout tessie +git rebase dev +git push --force-with-lease +npm run publish diff --git a/src/platform.ts b/src/platform.ts index 70c633f..33143c2 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -72,10 +72,11 @@ export class TeslaFleetApiPlatform implements DynamicPlatformPlugin { * must not be registered again to prevent "duplicate UUID" errors. */ discoverDevices() { - const newAccessories: PlatformAccessory[] = []; + //const newAccessories: PlatformAccessory[] = []; this.TeslaFleetApi.products_by_type() - .then(({ vehicles, energy_sites }) => { - vehicles.forEach((product) => { + .then(async ({ vehicles, energy_sites }) => { + vehicles.forEach(async (product) => { + this.TeslaFleetApi.vehicle!; const uuid = this.api.hap.uuid.generate(product.vin); const cachedAccessory = this.accessories.find( (accessory) => accessory.UUID === uuid @@ -100,20 +101,16 @@ export class TeslaFleetApiPlatform implements DynamicPlatformPlugin { newAccessory.context.state = product.state; newAccessory.displayName = product.display_name; - new VehicleAccessory(this, newAccessory); - newAccessories.push(newAccessory); + new VehicleAccessory(this, newAccessory); + this.api.registerPlatformAccessories( + PLUGIN_NAME, + PLATFORM_NAME, + [newAccessory] + ); }); - energy_sites.forEach((product) => {}); - return newAccessories; - }) - .then((newAccessories) => - this.api.registerPlatformAccessories( - PLUGIN_NAME, - PLATFORM_NAME, - newAccessories - ) - ); + energy_sites.forEach((product) => { }); + }); } } diff --git a/src/services/base.ts b/src/services/base.ts index 26d1d1e..1f4ebae 100644 --- a/src/services/base.ts +++ b/src/services/base.ts @@ -6,13 +6,13 @@ export abstract class BaseService { constructor( protected parent: VehicleAccessory, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - definition: any, - name: string | undefined = undefined + definition: typeof Service, + name: string, + subtype: string, ) { this.service = - this.parent.accessory.getService(definition) || - this.parent.accessory.addService(definition); + this.parent.accessory.getService(`${this.parent.accessory.displayName} ${name}`) || + this.parent.accessory.addService(definition, `${this.parent.accessory.displayName} ${name}`, subtype); if (name) { this.service.setCharacteristic( diff --git a/src/services/battery.ts b/src/services/battery.ts index 8cd16f9..ec93fcc 100644 --- a/src/services/battery.ts +++ b/src/services/battery.ts @@ -3,7 +3,7 @@ import { BaseService } from "./base.js"; export class BatteryService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Battery, "SOC"); + super(parent, parent.platform.Service.Battery, "SOC", "soc"); const batteryLevel = this.service .getCharacteristic(this.parent.platform.Characteristic.BatteryLevel) diff --git a/src/services/charge.ts b/src/services/charge.ts new file mode 100644 index 0000000..3a97317 --- /dev/null +++ b/src/services/charge.ts @@ -0,0 +1,40 @@ +import { CharacteristicValue } from "homebridge"; +import { debounce } from "../utils/debounce.js"; +import { VehicleAccessory } from "../vehicle.js"; +import { BaseService } from "./base.js"; + +export class BatteryService extends BaseService { + constructor(parent: VehicleAccessory) { + super(parent, parent.platform.Service.Lightbulb, "charge", "charge"); + + const on = this.service + .getCharacteristic(this.parent.platform.Characteristic.On) + .onGet(this.getOn.bind(this)); + + const level = this.service + .getCharacteristic(this.parent.platform.Characteristic.ChargingState) + .onGet(this.getLevel.bind(this)) + .onSet(debounce(this.setLevel.bind(this), 3000)); + + this.parent.emitter.on("vehicle_data", () => { + on.updateValue(this.getOn()); + level.updateValue(this.getLevel()); + }); + } + + getOn(): boolean { + return this.parent.accessory.context?.charge_state?.user_charge_enable_request + ?? this.parent.accessory.context?.charge_state?.charge_enable_request; + } + + 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); + } +} diff --git a/src/services/chargeport.ts b/src/services/chargeport.ts new file mode 100644 index 0000000..e69c339 --- /dev/null +++ b/src/services/chargeport.ts @@ -0,0 +1,40 @@ +import { CharacteristicValue } from "homebridge"; +import { debounce } from "../utils/debounce.js"; +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"); + + const on = this.service + .getCharacteristic(this.parent.platform.Characteristic.On) + .onGet(this.getOn.bind(this)); + + const level = this.service + .getCharacteristic(this.parent.platform.Characteristic.ChargingState) + .onGet(this.getLevel.bind(this)) + .onSet(debounce(this.setLevel.bind(this), 3000)); + + this.parent.emitter.on("vehicle_data", () => { + on.updateValue(this.getOn()); + level.updateValue(this.getLevel()); + }); + } + + getOn(): boolean { + return this.parent.accessory.context?.charge_state?.user_charge_enable_request + ?? this.parent.accessory.context?.charge_state?.charge_enable_request; + } + + 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); + } +} diff --git a/src/services/thermostat.ts b/src/services/climate.ts similarity index 94% rename from src/services/thermostat.ts rename to src/services/climate.ts index 66d7fb9..4596e94 100644 --- a/src/services/thermostat.ts +++ b/src/services/climate.ts @@ -6,7 +6,7 @@ import { BaseService } from "./base.js"; export class ClimateService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Thermostat, "Climate"); + super(parent, parent.platform.Service.Thermostat, "climate", "climate"); const currentState = this.service .getCharacteristic( @@ -55,7 +55,7 @@ export class ClimateService extends BaseService { } getTargetState(): number { - if (!this.parent.accessory.context?.climate_state.is_climate_on) { + if (!this.parent.accessory.context?.climate_state?.is_climate_on) { return this.parent.platform.Characteristic.TargetHeatingCoolingState.OFF; } return this.parent.platform.Characteristic.TargetHeatingCoolingState.AUTO; @@ -83,7 +83,7 @@ export class ClimateService extends BaseService { getTargetTemp(): number { return ( - this.parent.accessory.context?.climate_state?.driver_temp_setting ?? 0 + this.parent.accessory.context?.climate_state?.driver_temp_setting ?? 10 ); } diff --git a/src/services/door.ts b/src/services/door.ts index 3851de5..f69e013 100644 --- a/src/services/door.ts +++ b/src/services/door.ts @@ -11,7 +11,8 @@ export class DoorService extends BaseService { super( parent, parent.platform.Service.Door, - trunk === "front" ? "Frunk" : "Trunk" + trunk === "front" ? "Frunk" : "Trunk", + trunk ); this.key = this.trunk === "front" ? "ft" : "rt"; diff --git a/src/services/information.ts b/src/services/information.ts index f7a3bd9..98aaeff 100644 --- a/src/services/information.ts +++ b/src/services/information.ts @@ -1,13 +1,13 @@ // https://developers.homebridge.io/#/service/AccessoryInformation +import { Service } from "homebridge"; import { VehicleAccessory } from "../vehicle.js"; -import { BaseService } from "./base.js"; -export class AccessoryInformationService extends BaseService { - constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.AccessoryInformation); +export class AccessoryInformationService { + service: Service; - this.service + constructor(private parent: VehicleAccessory) { + this.service = this.parent.accessory.getService(this.parent.platform.Service.AccessoryInformation)! .setCharacteristic( this.parent.platform.Characteristic.Manufacturer, "Tesla" @@ -21,14 +21,13 @@ export class AccessoryInformationService extends BaseService { this.parent.vehicle.vin ); + this.service.getCharacteristic(this.parent.platform.Characteristic.Identify) + .onSet(this.setIdentify.bind(this)); + const version = this.service .getCharacteristic(this.parent.platform.Characteristic.FirmwareRevision) .onGet(this.getVersion.bind(this)); - this.service - .getCharacteristic(this.parent.platform.Characteristic.Identify) - .onSet(this.setIdentify.bind(this)); - this.parent.emitter.on("vehicle_data", () => { version.updateValue(this.getVersion()); }); diff --git a/src/services/firmware.ts b/src/services/update.ts similarity index 95% rename from src/services/firmware.ts rename to src/services/update.ts index 379661b..23c6376 100644 --- a/src/services/firmware.ts +++ b/src/services/update.ts @@ -3,9 +3,9 @@ import { VehicleAccessory } from "../vehicle.js"; import { BaseService } from "./base.js"; -export class WindowService extends BaseService { +export class UpdateService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.FirmwareUpdate, "Update"); + super(parent, parent.platform.Service.FirmwareUpdate, "Update", "update"); const readiness = this.service .getCharacteristic( diff --git a/src/services/windows.ts b/src/services/windows.ts index b64c725..f3f4286 100644 --- a/src/services/windows.ts +++ b/src/services/windows.ts @@ -6,7 +6,7 @@ import { BaseService } from "./base.js"; export class WindowService extends BaseService { constructor(parent: VehicleAccessory) { - super(parent, parent.platform.Service.Window, "Windows"); + super(parent, parent.platform.Service.Window, "windows", "vent_windows"); const currentPosition = this.service .getCharacteristic(this.parent.platform.Characteristic.CurrentPosition) diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts new file mode 100644 index 0000000..ee413fe --- /dev/null +++ b/src/utils/debounce.ts @@ -0,0 +1,11 @@ +export function debounce any>(func: F, waitFor: number) { + let timeoutId: NodeJS.Timeout | null = null; + + return (...args: Parameters): void => { + if (timeoutId !== null) { + clearTimeout(timeoutId); + } + + timeoutId = setTimeout(() => func(...args), waitFor); + }; +} \ No newline at end of file diff --git a/src/vehicle.ts b/src/vehicle.ts index 308ba6e..1836fe3 100644 --- a/src/vehicle.ts +++ b/src/vehicle.ts @@ -13,7 +13,7 @@ import { TeslaFleetApiPlatform } from "./platform.js"; import { BatteryService } from "./services/battery.js"; import { DoorService } from "./services/door.js"; import { AccessoryInformationService } from "./services/information.js"; -import { ClimateService } from "./services/thermostat.js"; +import { ClimateService } from "./services/climate.js"; import { REFRESH_INTERVAL } from "./settings.js"; import { EventEmitter } from "./utils/event.js";