Skip to content

Commit

Permalink
More progress
Browse files Browse the repository at this point in the history
  • Loading branch information
Bre77 committed Jun 13, 2024
1 parent 914aad2 commit 9b9e6b0
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 57 deletions.
9 changes: 4 additions & 5 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "homebridge";

import { PLATFORM_NAME, PLUGIN_NAME } from "./settings.js";
import { VehicleAccessory } from "./vehicle.js";
import { VehicleAccessory, VehicleContext } from "./vehicle.js";

import { Teslemetry } from "tesla-fleet-api";

Expand All @@ -24,7 +24,7 @@ export class TeslaFleetApiPlatform implements DynamicPlatformPlugin {
public readonly TeslaFleetApi: Teslemetry;

// this is used to track restored cached accessories
public readonly accessories: PlatformAccessory[] = [];
public readonly accessories: PlatformAccessory<VehicleContext>[] = [];

constructor(
public readonly log: Logging,
Expand Down Expand Up @@ -59,7 +59,7 @@ export class TeslaFleetApiPlatform implements DynamicPlatformPlugin {
* 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) {
configureAccessory(accessory: PlatformAccessory<VehicleContext>) {
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
Expand Down Expand Up @@ -90,11 +90,10 @@ export class TeslaFleetApiPlatform implements DynamicPlatformPlugin {
}

this.log.info("Adding new accessory:", product.display_name);
const newAccessory = new this.api.platformAccessory(
const newAccessory = new this.api.platformAccessory<VehicleContext>(
product.display_name,
uuid
);

newAccessory.context.vin = product.vin;
newAccessory.context.state = product.state;
newAccessory.displayName = product.display_name;
Expand Down
31 changes: 14 additions & 17 deletions src/services/battery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Characteristic, Service } from "homebridge";
import { VehicleDataResponse } from "tesla-fleet-api/dist/types/vehicle_data.js";
import { Service } from "homebridge";

Check warning on line 1 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 1 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote
import { VehicleAccessory } from "../vehicle.js";

Check warning on line 2 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 2 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote

export class BatteryService {
Expand All @@ -12,29 +11,29 @@ export class BatteryService {

const batteryLevel = this.service
.getCharacteristic(this.parent.platform.Characteristic.BatteryLevel)
.onGet(() => this.getLevel(this.parent.accessory.context.data));
.onGet(this.getLevel);

const chargingState = this.service
.getCharacteristic(this.parent.platform.Characteristic.ChargingState)
.onGet(() => this.getChargingState(this.parent.accessory.context.data));
.onGet(this.getChargingState);

const lowBattery = this.service
.getCharacteristic(this.parent.platform.Characteristic.StatusLowBattery)
.onGet(() => this.getLowBattery(this.parent.accessory.context.data));
.onGet(this.getLowBattery);

this.parent.emitter.on("vehicle_data", (data) => {
batteryLevel.updateValue(this.getLevel(data));
chargingState.updateValue(this.getChargingState(data));
lowBattery.updateValue(this.getLowBattery(data));
this.parent.emitter.on("vehicle_data", () => {

Check warning on line 24 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 24 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote
batteryLevel.updateValue(this.getLevel());
chargingState.updateValue(this.getChargingState());
lowBattery.updateValue(this.getLowBattery());
});
}

getLevel(data: VehicleDataResponse): number {
return data?.charge_state?.battery_level ?? 50;
getLevel(): number {
return this.parent.accessory.context?.charge_state?.battery_level ?? 50;
}

getChargingState(data: VehicleDataResponse): number {
switch (data?.charge_state?.charging_state) {
getChargingState(): number {
switch (this.parent.accessory.context?.charge_state?.charging_state) {
case "Starting":

Check warning on line 37 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 37 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote
return this.parent.platform.Characteristic.ChargingState.CHARGING;
case "Charging":

Check warning on line 39 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 39 in src/services/battery.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote
Expand All @@ -48,9 +47,7 @@ export class BatteryService {
}
}

getLowBattery(data: VehicleDataResponse): boolean {
return data?.charge_state?.battery_level
? data.charge_state.battery_level <= 20
: false;
getLowBattery(): boolean {
return this.getLevel() <= 20;
}
}
52 changes: 52 additions & 0 deletions src/services/door.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { CharacteristicValue, Service } from "homebridge";

Check warning on line 1 in src/services/door.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 1 in src/services/door.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote
import { VehicleAccessory } from "../vehicle.js";

Check warning on line 2 in src/services/door.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 2 in src/services/door.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote

export class DoorService {
service: Service;
key: "ft" | "rt";

Check warning on line 6 in src/services/door.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Strings must use singlequote

Check warning on line 6 in src/services/door.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Strings must use singlequote

constructor(
private parent: VehicleAccessory,
private trunk: "front" | "rear"
) {
this.service =
this.parent.accessory.getService(this.parent.platform.Service.Door) ||
this.parent.accessory.addService(this.parent.platform.Service.Door);

this.key = this.trunk === "front" ? "ft" : "rt";

const currentPosition = this.service
.getCharacteristic(this.parent.platform.Characteristic.CurrentPosition)
.onGet(this.getPosition);

/*const positionState = this.service
.getCharacteristic(this.parent.platform.Characteristic.PositionState)
.onGet(() => this.getChargingState());*/

const targetPosition = this.service
.getCharacteristic(this.parent.platform.Characteristic.TargetPosition)
.onGet(this.getPosition)
.onSet(this.setPosition);

this.parent.emitter.on("vehicle_data", () => {
currentPosition.updateValue(this.getPosition());
targetPosition.updateValue(this.getPosition());
});
}

getPosition(): number {
return this.parent.accessory.context?.vehicle_state?.[this.key] ? 100 : 0;
}

async setPosition(value: CharacteristicValue) {
console.log(value);
const position = this.getPosition();
if (
(position === 0 && value === 100) ||
(position === 100 && value === 0 && this.trunk === "rear")
) {
return this.parent.vehicle.actuate_truck(this.trunk).then(() => value);
}
return position;
}
}
59 changes: 59 additions & 0 deletions src/services/information.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// https://developers.homebridge.io/#/service/AccessoryInformation

import { CharacteristicValue, Service } from "homebridge";
import { VehicleAccessory } from "../vehicle.js";

export class AccessoryInformationService {
service: Service;

constructor(private parent: VehicleAccessory) {
this.service =
this.parent.accessory.getService(
this.parent.platform.Service.AccessoryInformation
) ||
this.parent.accessory.addService(
this.parent.platform.Service.AccessoryInformation
);

this.service // Move this to a separate service
.setCharacteristic(
this.parent.platform.Characteristic.Manufacturer,
"Tesla"
)
.setCharacteristic(
this.parent.platform.Characteristic.Model,
this.parent.vehicle.model
)
.setCharacteristic(
this.parent.platform.Characteristic.SerialNumber,
this.parent.vehicle.vin
);

const version = this.service
.getCharacteristic(this.parent.platform.Characteristic.FirmwareRevision)
.onGet(this.getVersion);

this.service
.getCharacteristic(this.parent.platform.Characteristic.Identify)
.onSet(this.setIdentify);

this.parent.emitter.on("vehicle_data", () => {
version.updateValue(this.getVersion());
});
}

getVersion(): string {
return (
this.parent.accessory.context?.vehicle_state?.software_update?.version ??
"unknown"
);
}

async setIdentify(value: CharacteristicValue) {
console.log(value);
return this.parent
.wake_up()
.then(() => this.parent.vehicle.flash_lights())
.then(() => false);
}
}
File renamed without changes.
9 changes: 9 additions & 0 deletions src/utils/wake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Returns a Promise that waits for the given number of milliseconds
* (via setTimeout), then resolves.
*/
export async function wait(ms: number = 0) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
88 changes: 53 additions & 35 deletions src/vehicle.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
import { CharacteristicValue, PlatformAccessory, Service } from "homebridge";

import { VehicleSpecific } from "tesla-fleet-api";
import {
ChargeState,
ClimateState,
DriveState,
GUISettings,
VehicleConfig,
VehicleState,
} from "tesla-fleet-api/dist/types/vehicle_data";
import { VehicleDataResponse } from "tesla-fleet-api/dist/types/vehicle_data.js";
import { EventEmitter } from "./event.js";
import { TeslaFleetApiPlatform } from "./platform.js";
import { BatteryService } from "./services/battery.js";
import { AccessoryInformationService } from "./services/information.js";
import { REFRESH_INTERVAL } from "./settings.js";
import { EventEmitter } from "./utils/event.js";

export interface VehicleData {
vehicle_data(data: VehicleDataResponse): void;
export type VehicleContext = {
vin: string;
state: string;
charge_state: ChargeState;
climate_state: ClimateState;
drive_state: DriveState;
gui_settings: GUISettings;
vehicle_config: VehicleConfig;
vehicle_state: VehicleState;
};

export interface VehicleDataEvent {
vehicle_data(data: VehicleContext): void;
}

export class VehicleAccessory {
private vehicle: VehicleSpecific;
public emitter: EventEmitter<VehicleData>;
private information: Service;
public vehicle: VehicleSpecific;
public emitter: EventEmitter<VehicleDataEvent>;

constructor(
public readonly platform: TeslaFleetApiPlatform,
public readonly accessory: PlatformAccessory
public readonly accessory: PlatformAccessory<VehicleContext>
) {
if (!this.platform.TeslaFleetApi?.vehicle) {
throw new Error("TeslaFleetApi not initialized");
Expand All @@ -30,24 +49,13 @@ export class VehicleAccessory {

this.emitter = new EventEmitter();

this.information = this.accessory.getService(
this.platform.Service.AccessoryInformation
)!;

this.information
.setCharacteristic(this.platform.Characteristic.Manufacturer, "Tesla")
.setCharacteristic(this.platform.Characteristic.Model, this.vehicle.model)
.setCharacteristic(
this.platform.Characteristic.SerialNumber,
this.vehicle.vin
);

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.

new AccessoryInformationService(this);
new BatteryService(this);
}

Expand All @@ -61,25 +69,35 @@ export class VehicleAccessory {
"vehicle_state",
])
.then(({ charge_state, climate_state, drive_state, vehicle_state }) => {
this.accessory.context.data = {
charge_state,
climate_state,
drive_state,
vehicle_state,
};
this.emitter.emit("vehicle_data", this.accessory.context.data);

this.information.updateCharacteristic(
this.platform.Characteristic.Active,
true
);
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);
})
.catch((error: string) => {
this.platform.log.warn(error);
this.information.updateCharacteristic(
this.platform.Characteristic.Active,
false
);
this.accessory.context.state = "offline";
});
}

async wake_up() {
if (this.accessory.context.state === "online") {
return Promise.resolve();
}
await this.vehicle.wake_up();

let interval = 2000;
for (let x = 0; x < 5; x++) {
await new Promise((resolve) => setTimeout(resolve, interval));
const { state } = await this.vehicle.vehicle();
this.accessory.context.state = state;
if (state === "online") {
return Promise.resolve();
}
interval = interval + 2000;
}
return Promise.reject("Vehicle didn't wake up");
}
}

0 comments on commit 9b9e6b0

Please sign in to comment.