Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wall-connector-totals-stream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@teslemetry/api": minor
---

Add the `wall_connector_totals` stream event: per-Wall-Connector daily Wh totals keyed by DIN (`totals`), with the installation-local `date`, on both `TeslemetryStream` and `TeslemetryEnergySiteStream`. It is now a valid `topics` name, and `startLocalCache()` replays the last value to new listeners. The generated client was regenerated from the live spec.
2 changes: 2 additions & 0 deletions packages/api/src/TeslemetryEnergySiteStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
SseSiteInfo,
SseTariffContentV2,
SseEnergyTotals,
SseWallConnectorTotals,
} from "./const.js";
import { Logger } from "./logger.js";
import { composeEnergySiteInfo, EnergySiteCache } from "./TeslemetryStream.js";
Expand All @@ -14,6 +15,7 @@ type TeslemetryEnergySiteStreamEventMap = {
site_info: SseSiteInfo;
tariff_content_v2: SseTariffContentV2;
energy_totals: SseEnergyTotals;
wall_connector_totals: SseWallConnectorTotals;
};

export declare interface TeslemetryEnergySiteStream {
Expand Down
45 changes: 43 additions & 2 deletions packages/api/src/TeslemetryStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
SseLiveStatus,
SseSiteInfo,
SseEnergyTotals,
SseWallConnectorTotals,
isWallConnectorTotals,
SseTariffContentV2,
Signals,
} from "./const.js";
Expand Down Expand Up @@ -59,6 +61,7 @@ type TeslemetryStreamEventMap = {
site_info: SseSiteInfo;
tariff_content_v2: SseTariffContentV2;
energy_totals: SseEnergyTotals;
wall_connector_totals: SseWallConnectorTotals;
connect: void;
disconnect: void;
stream_error: TeslemetryStreamErrorEvent;
Expand Down Expand Up @@ -110,6 +113,13 @@ export interface EnergySiteCache {
totals: SseEnergyTotals["totals"];
date: SseEnergyTotals["date"];
};
wall_connector_totals?: {
totals: SseWallConnectorTotals["totals"];
date: SseWallConnectorTotals["date"];
/** The source event's `createdAt`, replayed as-is so consumers can reject
* an older replay by comparing it with a live event. */
createdAt: SseWallConnectorTotals["createdAt"];
};
}

/** Merges the cached slim `site_info` document with the last received
Expand Down Expand Up @@ -272,6 +282,17 @@ export class TeslemetryStream extends EventEmitter {
totals: siteCache.energy_totals.totals,
isCache: true,
} satisfies SseEnergyTotals);
} else if (
event === "wall_connector_totals" &&
siteCache.wall_connector_totals
) {
listener({
createdAt: siteCache.wall_connector_totals.createdAt,
id: siteId,
date: siteCache.wall_connector_totals.date,
totals: siteCache.wall_connector_totals.totals,
isCache: true,
} satisfies SseWallConnectorTotals);
}
}

Expand Down Expand Up @@ -467,7 +488,11 @@ export class TeslemetryStream extends EventEmitter {
} else if ("tariff_content_v2" in event) {
this.emit("tariff_content_v2", event);
} else if ("totals" in event) {
this.emit("energy_totals", event as SseEnergyTotals);
if (isWallConnectorTotals(event.totals)) {
this.emit("wall_connector_totals", event as SseWallConnectorTotals);
} else {
this.emit("energy_totals", event as SseEnergyTotals);
}
}
this.emit("all", event);

Expand All @@ -488,7 +513,11 @@ export class TeslemetryStream extends EventEmitter {
if ("totals" in event) {
const site = this.energySites.get(String(event.id));
if (site) {
site.emit("energy_totals", event as SseEnergyTotals);
if (isWallConnectorTotals(event.totals)) {
site.emit("wall_connector_totals", event as SseWallConnectorTotals);
} else {
site.emit("energy_totals", event as SseEnergyTotals);
}
}
return;
}
Expand Down Expand Up @@ -581,6 +610,16 @@ export class TeslemetryStream extends EventEmitter {
};
};

private cacheWallConnectorTotals = (event: SseWallConnectorTotals): void => {
const siteId = String(event.id);
this.energyCache[siteId] ??= {};
this.energyCache[siteId].wall_connector_totals = {
totals: event.totals,
date: event.date,
createdAt: event.createdAt,
};
};

public startLocalCache(): void {
this.localCache = true;
this.on("state", this.cacheState);
Expand All @@ -592,6 +631,7 @@ export class TeslemetryStream extends EventEmitter {
this.on("site_info", this.cacheSiteInfo);
this.on("tariff_content_v2", this.cacheTariffContentV2);
this.on("energy_totals", this.cacheEnergyTotals);
this.on("wall_connector_totals", this.cacheWallConnectorTotals);
this.logger.info(`Started local cache`);
}

Expand All @@ -606,6 +646,7 @@ export class TeslemetryStream extends EventEmitter {
this.off("site_info", this.cacheSiteInfo);
this.off("tariff_content_v2", this.cacheTariffContentV2);
this.off("energy_totals", this.cacheEnergyTotals);
this.off("wall_connector_totals", this.cacheWallConnectorTotals);
this.logger.info(`Stopped local cache`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/client/index.ts

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions packages/api/src/client/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ export const getApiImageByVin = <ThrowOnError extends boolean = true>(options: O
/**
* Vehicle Data
*
* > [!WARNING]
* > **Integrations should use [streaming](https://teslemetry.com/docs/getting-started/telemetry) instead.** Each vehicle_data refresh is billed in credits, while streaming delivers vehicle data in real time at no cost.
*
* Returns the cached vehicle data for this vehicle. location_data will only be present if you have provided the location_data scope.
*/
export const getApi1VehiclesByVinVehicleData = <ThrowOnError extends boolean = true>(options: Options<GetApi1VehiclesByVinVehicleDataData, ThrowOnError>): RequestResult<GetApi1VehiclesByVinVehicleDataResponses, GetApi1VehiclesByVinVehicleDataErrors, ThrowOnError> => (options.client ?? client).get<GetApi1VehiclesByVinVehicleDataResponses, GetApi1VehiclesByVinVehicleDataErrors, ThrowOnError>({
Expand Down Expand Up @@ -1351,7 +1354,7 @@ export const postApi1VehiclesByVinCommandSetChargingAmps = <ThrowOnError extends
/**
* Climate Keeper Mode
*
* Enables climate keeper mode. Accepted values are: 0,1,2,3. Mapping to respectively Off, Keep Mode, Dog Mode, Camp Mode.
* Sets climate keeper mode: Off, Keep Mode, Dog Mode, or Camp Mode.
*/
export const postApi1VehiclesByVinCommandSetClimateKeeperMode = <ThrowOnError extends boolean = true>(options: Options<PostApi1VehiclesByVinCommandSetClimateKeeperModeData, ThrowOnError>): RequestResult<PostApi1VehiclesByVinCommandSetClimateKeeperModeResponses, PostApi1VehiclesByVinCommandSetClimateKeeperModeErrors, ThrowOnError> => (options.client ?? client).post<PostApi1VehiclesByVinCommandSetClimateKeeperModeResponses, PostApi1VehiclesByVinCommandSetClimateKeeperModeErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }, {
Expand Down Expand Up @@ -2725,7 +2728,7 @@ export const getApi1EnergySitesByIdCalendarHistory = <ThrowOnError extends boole
/**
* Charging History
*
* Returns the charging history of a wall connector.
* Returns the charging history of a wall connector. While a Wall Connector at the site is charging, Teslemetry polls the current installation-local day (start_date at local 00:00:00, end_date at local 23:59:59 or the next local midnight, time_zone set to the installation timezone) and once more when charging stops; a query for exactly that day is served from this cache while Teslemetry still sees the site idle or the cached copy is under 30 seconds old. Every other query is fetched live from Tesla. Per-Wall-Connector daily totals are also pushed as the wall_connector_totals SSE event.
*/
export const getApi1EnergySitesByIdTelemetryHistory = <ThrowOnError extends boolean = true>(options: Options<GetApi1EnergySitesByIdTelemetryHistoryData, ThrowOnError>): RequestResult<GetApi1EnergySitesByIdTelemetryHistoryResponses, GetApi1EnergySitesByIdTelemetryHistoryErrors, ThrowOnError> => (options.client ?? client).get<GetApi1EnergySitesByIdTelemetryHistoryResponses, GetApi1EnergySitesByIdTelemetryHistoryErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }, {
Expand Down
Loading
Loading