Skip to content

Commit

Permalink
Add Battery Level lightbulb service
Browse files Browse the repository at this point in the history
Closes #111
Closes #107
  • Loading branch information
nfarina committed Jul 14, 2023
1 parent f0e446a commit 90609b6
Show file tree
Hide file tree
Showing 21 changed files with 103 additions and 82 deletions.
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,11 @@ You can find the full list of configuration settings in [`config.schema.json`](c

The plugin exposes a single HomeKit "Accessory" representing the car, which contains multiple services for all the different switches and locks. _This is the way._

Unfortunately, there is a very annoying [known bug](https://github.com/homebridge/homebridge/issues/3210) with iOS 16 where, when adding accessories with multiple services, the services are all given the same name as the main accessory.

This means that if your accessory (car) name is "Model Y", then (for instance) the trunk service will be renamed to "Model Y". And you'll say "open the trunk" and Siri will say "I don't know what you mean."

You'll need to manually tap into each service tile and change its name back to what you want.
**NOTE** Tapping "X" on the accessory name will display the true name.

Additionally, you'll find that when you tap into the car in the Home app to, say, open the trunk, you'll see a big scrolling page of switches and locks with _no labels_. This is just what the Home app does.
After adding the accessory, you'll find that when you tap into the car in the Home app to, say, open the trunk, you'll see a big scrolling page of switches and locks with _no labels_. This is just what the Home app does.

To improve this, you can create a new "Room" in HomeKit for each car. So you might have a "Model Y" room, and you can place your Model Y accessory inside there. Then you can configure it to "Show as separate tiles" and you get this lovely presentation of all your widgets in the "room" (pictured at top).

Here's a [video demonstrating the complete setup process as of iOS 16](https://youtu.be/sgDJmwwSOYA).
Here's a [video demonstrating the complete setup process as of iOS 16](https://youtu.be/sgDJmwwSOYA). **NOTE**: ignore the part where I'm renaming the services for seemingly no reason - that was to work around a [bug](https://github.com/homebridge/homebridge/issues/3210) that is now fixed.

## Waking the Car Up

Expand Down
6 changes: 6 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
"default": false,
"description": "Example: 'Set the Charge Limit to 80%'. NOTE: Will appear in HomeKit as a Light bulb! There's no other way to do it."
},
"batteryLevel": {
"title": "Battery Level",
"type": "boolean",
"default": false,
"description": "Creates a read-only light bulb representing the current battery level. Useful for automations. NOTE: Will appear in HomeKit as a Light bulb! There's no other way to do it."
},
"climate": {
"title": "Climate",
"type": "boolean",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "homebridge-tesla",
"version": "4.2.1",
"version": "4.3.0",
"description": "Tesla support for Homebridge: https://github.com/nfarina/homebridge",
"license": "ISC",
"keywords": [
Expand Down
15 changes: 14 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require("@babel/polyfill");
import { AccessoryConfig, API, HAP, Logging } from "homebridge";
import { BatteryLevelService } from "./services/BatteryLevelService";
import { BatteryService } from "./services/BatteryService";
import { ChargeLimitService } from "./services/ChargeLimitService";
import { ChargePortService } from "./services/ChargePortServices";
Expand All @@ -15,7 +16,7 @@ import { StarterService } from "./services/StarterService";
import { SteeringWheelHeaterService } from "./services/SteeringWheelHeaterService";
import {
TeslaPluginService,
TeslaPluginServiceContext
TeslaPluginServiceContext,
} from "./services/TeslaPluginService";
import { FrontTrunk, RearTrunk, TrunkService } from "./services/TrunkService";
import { VehicleLockService } from "./services/VehicleLockService";
Expand Down Expand Up @@ -80,6 +81,10 @@ class TeslaAccessory {
this.services.push(new ChargeLimitService(context));
}

if (getConfigValue(config, "batteryLevel")) {
this.services.push(new BatteryLevelService(context));
}

if (getConfigValue(config, "chargePort")) {
this.services.push(new ChargePortService(context));
}
Expand Down Expand Up @@ -111,6 +116,14 @@ class TeslaAccessory {
) {
this.services.push(new HomeLinkService(context));
}

// Add the "ConfiguredName" characteristic to every service so that you get
// these as default names when adding the accessory.
for (const teslaService of this.services) {
const { service, serviceName } = teslaService;
service.addOptionalCharacteristic(hap.Characteristic.ConfiguredName);
service.setCharacteristic(hap.Characteristic.ConfiguredName, serviceName);
}
}

getServices() {
Expand Down
43 changes: 43 additions & 0 deletions src/services/BatteryLevelService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Service } from "homebridge";
import { VehicleData } from "../util/types";
import {
TeslaPluginService,
TeslaPluginServiceContext,
} from "./TeslaPluginService";

export class BatteryLevelService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context, "Battery Level");
const { hap, tesla } = context;

const service = new hap.Service.Lightbulb(this.serviceName, "batteryLevel");

const on = service
.getCharacteristic(hap.Characteristic.On)
.on("get", this.createGetter(this.getOn));

const brightness = service
.addCharacteristic(hap.Characteristic.Brightness)
.on("get", this.createGetter(this.getLevel));

this.service = service;

tesla.on("vehicleDataUpdated", (data) => {
on.updateValue(this.getOn(data));
brightness.updateValue(this.getLevel(data));
});
}

getOn(data: VehicleData | null) {
// Show off when not connected and no last-known state. Otherwise always
// "on".
return data ? true : false;
}

getLevel(data: VehicleData | null) {
// Assume 50% when not connected and no last-known state.
return data ? data.charge_state.battery_level : 50;
}
}
7 changes: 2 additions & 5 deletions src/services/BatteryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ export class BatteryService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Battery");
const { hap, tesla } = context;

const service = new hap.Service.BatteryService(
this.serviceName("Battery"),
"battery",
);
const service = new hap.Service.BatteryService(this.serviceName, "battery");

const batteryLevel = service
.getCharacteristic(hap.Characteristic.BatteryLevel)
Expand Down
7 changes: 2 additions & 5 deletions src/services/ChargeLimitService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@ export class ChargeLimitService extends TeslaPluginService {
setLimitTimeoutId: NodeJS.Timeout | null = null;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Charge Limit");
const { hap, tesla } = context;

const service = new hap.Service.Lightbulb(
this.serviceName("Charge Limit"),
"chargeLimit",
);
const service = new hap.Service.Lightbulb(this.serviceName, "chargeLimit");

const on = service
.getCharacteristic(hap.Characteristic.On)
Expand Down
4 changes: 2 additions & 2 deletions src/services/ChargePortServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ export class ChargePortService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Charge Port");
const { hap, tesla } = context;

const service = new hap.Service.LockMechanism(
this.serviceName("Charge Port"),
this.serviceName,
"chargePort",
);

Expand Down
7 changes: 2 additions & 5 deletions src/services/ChargerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ export class ChargerService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Charger");
const { hap, tesla } = context;

const service = new hap.Service.Switch(
this.serviceName("Charger"),
"charger",
);
const service = new hap.Service.Switch(this.serviceName, "charger");

const on = service
.getCharacteristic(hap.Characteristic.On)
Expand Down
9 changes: 3 additions & 6 deletions src/services/ChargingAmpsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Service } from "homebridge";
import { VehicleData } from "../util/types";
import {
TeslaPluginService,
TeslaPluginServiceContext
TeslaPluginServiceContext,
} from "./TeslaPluginService";

export class ChargingAmpsService extends TeslaPluginService {
Expand All @@ -16,13 +16,10 @@ export class ChargingAmpsService extends TeslaPluginService {
setAmpsTimeoutId: NodeJS.Timeout | null = null;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Charging Amps");
const { hap, tesla } = context;

const service = new hap.Service.Lightbulb(
this.serviceName("Charging Amps"),
"chargingAmps",
);
const service = new hap.Service.Lightbulb(this.serviceName, "chargingAmps");

const on = service
.getCharacteristic(hap.Characteristic.On)
Expand Down
9 changes: 3 additions & 6 deletions src/services/ClimateService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CharacteristicValue, Service } from "homebridge";
import { getConfigValue, VehicleData } from "../util/types";
import { VehicleData, getConfigValue } from "../util/types";
import {
TeslaPluginService,
TeslaPluginServiceContext,
Expand All @@ -9,13 +9,10 @@ export class ClimateService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Climate");
const { config, hap, tesla } = context;

const service = new hap.Service.Thermostat(
this.serviceName("Climate"),
"climate",
);
const service = new hap.Service.Thermostat(this.serviceName, "climate");

// Apply desired temp units from config.
service
Expand Down
7 changes: 2 additions & 5 deletions src/services/ClimateSwitchService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ export class ClimateSwitchService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Climate");
const { config, hap, tesla } = context;

const service = new hap.Service.Switch(
this.serviceName("Climate"),
"climate",
);
const service = new hap.Service.Switch(this.serviceName, "climate");

const on = service
.getCharacteristic(hap.Characteristic.On)
Expand Down
7 changes: 2 additions & 5 deletions src/services/ConnectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ export class ConnectionService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Connection");
const { hap } = context;

const service = new hap.Service.Switch(
this.serviceName("Connection"),
"connection",
);
const service = new hap.Service.Switch(this.serviceName, "connection");

service
.getCharacteristic(hap.Characteristic.On)
Expand Down
7 changes: 2 additions & 5 deletions src/services/DefrostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ export class DefrostService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Defrost");
const { hap, tesla } = context;

const service = new hap.Service.Switch(
this.serviceName("Defrost"),
"defrost",
);
const service = new hap.Service.Switch(this.serviceName, "defrost");

const on = service
.getCharacteristic(hap.Characteristic.On)
Expand Down
4 changes: 2 additions & 2 deletions src/services/HomeLinkService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ export class HomeLinkService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "HomeLink");
const { hap, tesla } = context;

const service = new hap.Service.GarageDoorOpener(
this.serviceName("HomeLink"),
this.serviceName,
"homeLink",
);

Expand Down
7 changes: 2 additions & 5 deletions src/services/SentryModeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ export class SentryModeService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Sentry Mode");
const { hap, tesla } = context;

const service = new hap.Service.Switch(
this.serviceName("Sentry Mode"),
"sentryMode",
);
const service = new hap.Service.Switch(this.serviceName, "sentryMode");

const on = service
.getCharacteristic(hap.Characteristic.On)
Expand Down
7 changes: 2 additions & 5 deletions src/services/StarterService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ export class StarterService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Starter");
const { hap, tesla } = context;

const service = new hap.Service.Switch(
this.serviceName("Starter"),
"starter",
);
const service = new hap.Service.Switch(this.serviceName, "starter");

const on = service
.getCharacteristic(hap.Characteristic.On)
Expand Down
4 changes: 2 additions & 2 deletions src/services/SteeringWheelHeaterService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ export class SteeringWheelHeaterService extends TeslaPluginService {
service: Service;

constructor(context: TeslaPluginServiceContext) {
super(context);
super(context, "Steering Wheel Heater");
const { hap, tesla } = context;

const service = new hap.Service.Switch(
this.serviceName("Steering Wheel Heater"),
this.serviceName,
"steeringWheelHeater",
);

Expand Down
11 changes: 5 additions & 6 deletions src/services/TeslaPluginService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,20 @@ export type TeslaPluginServiceContext = {
export abstract class TeslaPluginService {
protected context: TeslaPluginServiceContext;
public abstract service: Service;
public serviceName: string;

constructor(context: TeslaPluginServiceContext) {
constructor(context: TeslaPluginServiceContext, baseName: string) {
this.context = context;
}

protected serviceName(name: string): string {
const { config } = this.context;
const { config } = context;

// Optional prefix to prepend to all accessory names.
const prefix = (config.prefix ?? "").trim();

if (prefix.length > 0) {
return `${prefix} ${name}`;
this.serviceName = `${prefix} ${baseName}`;
} else {
return name;
this.serviceName = baseName;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/services/TrunkService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ export class TrunkService extends TeslaPluginService {
service: Service;

constructor(trunk: Trunk, context: TeslaPluginServiceContext) {
super(context);
super(context, trunk.name);
this.trunk = trunk;

const { hap, tesla } = context;

const service = new hap.Service.LockMechanism(
this.serviceName(trunk.name),
this.serviceName,
trunk.subtype,
);

Expand Down

0 comments on commit 90609b6

Please sign in to comment.