diff --git a/.changeset/add-charging-v1-module.md b/.changeset/add-charging-v1-module.md new file mode 100644 index 0000000..afdd386 --- /dev/null +++ b/.changeset/add-charging-v1-module.md @@ -0,0 +1,5 @@ +--- +"@teslemetry/tesla-protocol": minor +--- + +Add a new `charging` source-of-record module modelling `com.tesla.proto.charging.v1`, the EV charge-session energy metering and per-session billing schema (`Energy`, `StemInfo`, `StemEventInfo`, `ChargeSessionTimeSeries`, `StemUi`, `StemBilling`) - based on our own observations and contributions from the community. diff --git a/AGENTS.md b/AGENTS.md index b9f1d11..f06a138 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,7 @@ This file is the project's committed home for project-intrinsic agent knowledge: - `CarServer.Response.response_msg` (`proto/command/car_server.proto`) models the full server reply surface except fields 17 and 19, which are `reserved` with a reason comment (`centerdisplay.server`/`webrtc_comms` replies, app-only namespaces, intentionally excluded). Field 12 (`GetRateTariffResponse`) is modelled, mirroring the tariff document already declared for `SetRateTariffRequest` (tag 55). `scripts/check_reply_coverage.py` (run in CI) fails if any field in its known-field set is neither modelled in the oneof nor `reserved` with a comment - before adding a new reply oneof field, check whether its payload type pulls in an app-only namespace first, and either model it or add it to `KNOWN_REPLY_FIELDS` as a reserved entry. - Un-reserving a field to model it (as field 12 above did) trips buf's `RESERVED_MESSAGE_NO_DELETE` breaking check even though it's a wire-compatible addition; `buf.yaml` excepts that rule repo-wide for this reason, so the same move for fields 17/19 later won't need a fresh exception. - The committed `proto/command/` baseline already reflects one specific Tesla app build reconstructed via cross-version protobuf diffing (`app-4.58.6`, see `TESLEMETRY-EXT` markers). A later app-version-diff pass will find most candidate fields already modelled here - check the current `.proto` files for the exact field/tag before assuming it needs adding, and only model the genuine delta. +- `proto/charging/` is a documented exception to the "app-only namespaces are not published" policy (`PROVENANCE.md`): it has no BLE or Fleet API wire path today, but was published because it describes the vehicle-charging product domain. Any future app-only module wanting the same exception should be argued explicitly, not assumed from this precedent. ## Maintaining this file diff --git a/PROVENANCE.md b/PROVENANCE.md index ba4be9c..7a70002 100644 --- a/PROVENANCE.md +++ b/PROVENANCE.md @@ -14,6 +14,7 @@ directories no public upstream exists and this repo is the source of record. | `proto/command/session.proto` | Teslemetry source-of-record | no upstream (Teslemetry session persistence) | | `proto/energy_device/`, `proto/energy_command/` | Teslemetry source-of-record | no upstream (TEG gateway local API, from our own observations and contributions from the community) | | `proto/teslapower/` | Teslemetry source-of-record | no upstream (Powerwall local API, from our own observations and contributions from the community, credit @brianhealey) | +| `proto/charging/` | Teslemetry source-of-record | no upstream (EV charge-session energy metering / billing schema, from our own observations and contributions from the community) | ## How the boundary is kept visible @@ -52,4 +53,9 @@ policed for marker presence. - Wire-compatible additions = minor; field/enum renames = major; tag renumbering = forbidden (`buf breaking` gate). - Tesla's internal app-only schema namespaces are deliberately NOT published - here; only consumer-needed command/telemetry/energy surfaces are. + here; only consumer-needed command/telemetry/energy surfaces are. `proto/charging/` + is a deliberate, narrow exception: it models the vehicle-charging product + domain even though no BLE or Fleet API surface exposes it as protobuf today + (the public Fleet API charging endpoints return JSON), because the schema + itself is a genuine, self-contained description of Tesla's charge-session + metering/billing data. diff --git a/README.md b/README.md index 05f9998..b2d4814 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ proto/ source of truth (the only hand-edited artifacts) ├── telemetry/ fleet-telemetry protos (upstream verbatim) ├── energy_device/ TEG gateway local-API protos ├── energy_command/ energy command identifiers -└── teslapower/ Powerwall local-API proto +├── teslapower/ Powerwall local-API proto +└── charging/ EV charge-session metering/billing ("STEM") protos packages/typescript/ @teslemetry/tesla-protocol (generated, committed) packages/python/ tesla-protocol (generated, committed) ``` diff --git a/buf.yaml b/buf.yaml index 8cc3dfb..c6240e4 100644 --- a/buf.yaml +++ b/buf.yaml @@ -5,6 +5,7 @@ modules: - path: proto/energy_device - path: proto/energy_command - path: proto/teslapower + - path: proto/charging lint: # Tesla's upstream style (package CarServer, enum value SpeedLimit, file-level # layout) violates buf's naming rules; we inherit their style, we don't fix it. diff --git a/packages/python/tesla_protocol/charging/__init__.py b/packages/python/tesla_protocol/charging/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/python/tesla_protocol/charging/__init__.pyi b/packages/python/tesla_protocol/charging/__init__.pyi new file mode 100644 index 0000000..db6c3b5 --- /dev/null +++ b/packages/python/tesla_protocol/charging/__init__.pyi @@ -0,0 +1 @@ +from . import charging_pb2 diff --git a/packages/python/tesla_protocol/charging/charging_pb2.py b/packages/python/tesla_protocol/charging/charging_pb2.py new file mode 100644 index 0000000..c561211 --- /dev/null +++ b/packages/python/tesla_protocol/charging/charging_pb2.py @@ -0,0 +1,26 @@ +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion(_runtime_version.Domain.PUBLIC, 6, 31, 1, '', 'charging.proto') +_sym_db = _symbol_database.Default() +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0echarging.proto\x12\x1bcom.tesla.proto.charging.v1"6\n\x06Energy\x12\x12\n\nwatt_hours\x18\x01 \x01(\r\x12\x18\n\x10milli_watt_hours\x18\x02 \x01(\r"\\\n\x08StemInfo\x12\x12\n\nfw_githash\x18\x01 \x01(\x0c\x12\x15\n\rserial_number\x18\x02 \x01(\t\x12\x13\n\x0bpart_number\x18\x03 \x01(\t\x12\x10\n\x08meter_id\x18\x04 \x01(\t"W\n\rStemEventInfo\x12\x16\n\x0eevent_occurred\x18\x01 \x01(\x08\x12\x13\n\x0binvalidated\x18\x02 \x01(\x08\x12\x19\n\x11time_synchronized\x18\x03 \x01(\x08"\xd1\x02\n\x17ChargeSessionTimeSeries\x12\x1a\n\x12start_time_epoch_s\x18\x01 \x01(\x04\x12\x18\n\x10end_time_epoch_s\x18\x02 \x01(\x04\x12\x1e\n\x16total_energy_vended_wh\x18\x03 \x01(\r\x12\x19\n\x11bucket_duration_s\x18\x04 \x01(\r\x12\x1f\n\x17first_bucket_duration_s\x18\x05 \x01(\r\x12\x1f\n\x17bucket_energy_vended_wh\x18\x06 \x03(\r\x12@\n\x13total_energy_vended\x18\x07 \x01(\x0b2#.com.tesla.proto.charging.v1.Energy\x12A\n\x14bucket_energy_vended\x18\x08 \x03(\x0b2#.com.tesla.proto.charging.v1.Energy"\x85\x02\n\x06StemUi\x128\n\tstem_info\x18\x01 \x01(\x0b2%.com.tesla.proto.charging.v1.StemInfo\x12\x12\n\nsession_id\x18\x02 \x01(\r\x12\x1b\n\x13system_time_epoch_s\x18\x03 \x01(\x04\x12\x13\n\x0bidle_time_s\x18\x04 \x01(\r\x12\x1a\n\x12start_time_epoch_s\x18\x05 \x01(\x04\x12C\n\x0fstem_event_info\x18\x06 \x01(\x0b2*.com.tesla.proto.charging.v1.StemEventInfo\x12\x1a\n\x12session_energy_kwh\x18\x07 \x01(\x02"\xd3\x02\n\x0bStemBilling\x128\n\tstem_info\x18\x01 \x01(\x0b2%.com.tesla.proto.charging.v1.StemInfo\x12\x12\n\nsession_id\x18\x02 \x01(\r\x12C\n\x0fstem_event_info\x18\x03 \x01(\x0b2*.com.tesla.proto.charging.v1.StemEventInfo\x12!\n\x19lifetime_energy_start_kwh\x18\x04 \x01(\x02\x12\x1f\n\x17lifetime_energy_end_kwh\x18\x05 \x01(\x02\x12\x13\n\x0bidle_time_s\x18\x06 \x01(\r\x12X\n\x1acharge_session_time_series\x18\x07 \x01(\x0b24.com.tesla.proto.charging.v1.ChargeSessionTimeSeriesb\x06proto3') +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'charging_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_ENERGY']._serialized_start = 47 + _globals['_ENERGY']._serialized_end = 101 + _globals['_STEMINFO']._serialized_start = 103 + _globals['_STEMINFO']._serialized_end = 195 + _globals['_STEMEVENTINFO']._serialized_start = 197 + _globals['_STEMEVENTINFO']._serialized_end = 284 + _globals['_CHARGESESSIONTIMESERIES']._serialized_start = 287 + _globals['_CHARGESESSIONTIMESERIES']._serialized_end = 624 + _globals['_STEMUI']._serialized_start = 627 + _globals['_STEMUI']._serialized_end = 888 + _globals['_STEMBILLING']._serialized_start = 891 + _globals['_STEMBILLING']._serialized_end = 1230 \ No newline at end of file diff --git a/packages/python/tesla_protocol/charging/charging_pb2.pyi b/packages/python/tesla_protocol/charging/charging_pb2.pyi new file mode 100644 index 0000000..ee7a969 --- /dev/null +++ b/packages/python/tesla_protocol/charging/charging_pb2.pyi @@ -0,0 +1,104 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union +DESCRIPTOR: _descriptor.FileDescriptor + +class Energy(_message.Message): + __slots__ = ('watt_hours', 'milli_watt_hours') + WATT_HOURS_FIELD_NUMBER: _ClassVar[int] + MILLI_WATT_HOURS_FIELD_NUMBER: _ClassVar[int] + watt_hours: int + milli_watt_hours: int + + def __init__(self, watt_hours: _Optional[int]=..., milli_watt_hours: _Optional[int]=...) -> None: + ... + +class StemInfo(_message.Message): + __slots__ = ('fw_githash', 'serial_number', 'part_number', 'meter_id') + FW_GITHASH_FIELD_NUMBER: _ClassVar[int] + SERIAL_NUMBER_FIELD_NUMBER: _ClassVar[int] + PART_NUMBER_FIELD_NUMBER: _ClassVar[int] + METER_ID_FIELD_NUMBER: _ClassVar[int] + fw_githash: bytes + serial_number: str + part_number: str + meter_id: str + + def __init__(self, fw_githash: _Optional[bytes]=..., serial_number: _Optional[str]=..., part_number: _Optional[str]=..., meter_id: _Optional[str]=...) -> None: + ... + +class StemEventInfo(_message.Message): + __slots__ = ('event_occurred', 'invalidated', 'time_synchronized') + EVENT_OCCURRED_FIELD_NUMBER: _ClassVar[int] + INVALIDATED_FIELD_NUMBER: _ClassVar[int] + TIME_SYNCHRONIZED_FIELD_NUMBER: _ClassVar[int] + event_occurred: bool + invalidated: bool + time_synchronized: bool + + def __init__(self, event_occurred: bool=..., invalidated: bool=..., time_synchronized: bool=...) -> None: + ... + +class ChargeSessionTimeSeries(_message.Message): + __slots__ = ('start_time_epoch_s', 'end_time_epoch_s', 'total_energy_vended_wh', 'bucket_duration_s', 'first_bucket_duration_s', 'bucket_energy_vended_wh', 'total_energy_vended', 'bucket_energy_vended') + START_TIME_EPOCH_S_FIELD_NUMBER: _ClassVar[int] + END_TIME_EPOCH_S_FIELD_NUMBER: _ClassVar[int] + TOTAL_ENERGY_VENDED_WH_FIELD_NUMBER: _ClassVar[int] + BUCKET_DURATION_S_FIELD_NUMBER: _ClassVar[int] + FIRST_BUCKET_DURATION_S_FIELD_NUMBER: _ClassVar[int] + BUCKET_ENERGY_VENDED_WH_FIELD_NUMBER: _ClassVar[int] + TOTAL_ENERGY_VENDED_FIELD_NUMBER: _ClassVar[int] + BUCKET_ENERGY_VENDED_FIELD_NUMBER: _ClassVar[int] + start_time_epoch_s: int + end_time_epoch_s: int + total_energy_vended_wh: int + bucket_duration_s: int + first_bucket_duration_s: int + bucket_energy_vended_wh: _containers.RepeatedScalarFieldContainer[int] + total_energy_vended: Energy + bucket_energy_vended: _containers.RepeatedCompositeFieldContainer[Energy] + + def __init__(self, start_time_epoch_s: _Optional[int]=..., end_time_epoch_s: _Optional[int]=..., total_energy_vended_wh: _Optional[int]=..., bucket_duration_s: _Optional[int]=..., first_bucket_duration_s: _Optional[int]=..., bucket_energy_vended_wh: _Optional[_Iterable[int]]=..., total_energy_vended: _Optional[_Union[Energy, _Mapping]]=..., bucket_energy_vended: _Optional[_Iterable[_Union[Energy, _Mapping]]]=...) -> None: + ... + +class StemUi(_message.Message): + __slots__ = ('stem_info', 'session_id', 'system_time_epoch_s', 'idle_time_s', 'start_time_epoch_s', 'stem_event_info', 'session_energy_kwh') + STEM_INFO_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + SYSTEM_TIME_EPOCH_S_FIELD_NUMBER: _ClassVar[int] + IDLE_TIME_S_FIELD_NUMBER: _ClassVar[int] + START_TIME_EPOCH_S_FIELD_NUMBER: _ClassVar[int] + STEM_EVENT_INFO_FIELD_NUMBER: _ClassVar[int] + SESSION_ENERGY_KWH_FIELD_NUMBER: _ClassVar[int] + stem_info: StemInfo + session_id: int + system_time_epoch_s: int + idle_time_s: int + start_time_epoch_s: int + stem_event_info: StemEventInfo + session_energy_kwh: float + + def __init__(self, stem_info: _Optional[_Union[StemInfo, _Mapping]]=..., session_id: _Optional[int]=..., system_time_epoch_s: _Optional[int]=..., idle_time_s: _Optional[int]=..., start_time_epoch_s: _Optional[int]=..., stem_event_info: _Optional[_Union[StemEventInfo, _Mapping]]=..., session_energy_kwh: _Optional[float]=...) -> None: + ... + +class StemBilling(_message.Message): + __slots__ = ('stem_info', 'session_id', 'stem_event_info', 'lifetime_energy_start_kwh', 'lifetime_energy_end_kwh', 'idle_time_s', 'charge_session_time_series') + STEM_INFO_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + STEM_EVENT_INFO_FIELD_NUMBER: _ClassVar[int] + LIFETIME_ENERGY_START_KWH_FIELD_NUMBER: _ClassVar[int] + LIFETIME_ENERGY_END_KWH_FIELD_NUMBER: _ClassVar[int] + IDLE_TIME_S_FIELD_NUMBER: _ClassVar[int] + CHARGE_SESSION_TIME_SERIES_FIELD_NUMBER: _ClassVar[int] + stem_info: StemInfo + session_id: int + stem_event_info: StemEventInfo + lifetime_energy_start_kwh: float + lifetime_energy_end_kwh: float + idle_time_s: int + charge_session_time_series: ChargeSessionTimeSeries + + def __init__(self, stem_info: _Optional[_Union[StemInfo, _Mapping]]=..., session_id: _Optional[int]=..., stem_event_info: _Optional[_Union[StemEventInfo, _Mapping]]=..., lifetime_energy_start_kwh: _Optional[float]=..., lifetime_energy_end_kwh: _Optional[float]=..., idle_time_s: _Optional[int]=..., charge_session_time_series: _Optional[_Union[ChargeSessionTimeSeries, _Mapping]]=...) -> None: + ... \ No newline at end of file diff --git a/packages/typescript/src/charging/charging.ts b/packages/typescript/src/charging/charging.ts new file mode 100644 index 0000000..5185623 --- /dev/null +++ b/packages/typescript/src/charging/charging.ts @@ -0,0 +1,953 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.7.7 +// protoc v6.31.1 +// source: charging.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "com.tesla.proto.charging.v1"; + +/** + * TESLEMETRY SOURCE-OF-RECORD - no upstream. + * Provenance: Tesla EV charge-session energy metering / billing ("STEM") + * schema, from our own observations and contributions from the community. + */ + +/** + * Split integer energy representation: whole watt-hours plus a + * sub-watt-hour remainder in milli-watt-hours. + */ +export interface Energy { + wattHours: number; + milliWattHours: number; +} + +/** Identity and firmware of the metering device that recorded a charge session. */ +export interface StemInfo { + fwGithash: Uint8Array; + serialNumber: string; + partNumber: string; + meterId: string; +} + +/** Validity flags for a charge-session metering record. */ +export interface StemEventInfo { + eventOccurred: boolean; + invalidated: boolean; + timeSynchronized: boolean; +} + +/** Bucketed energy vended (delivered) over a charge session. */ +export interface ChargeSessionTimeSeries { + startTimeEpochS: number; + endTimeEpochS: number; + totalEnergyVendedWh: number; + bucketDurationS: number; + firstBucketDurationS: number; + bucketEnergyVendedWh: number[]; + totalEnergyVended: Energy | undefined; + bucketEnergyVended: Energy[]; +} + +/** UI-facing summary of a live or recently completed charge session. */ +export interface StemUi { + stemInfo: StemInfo | undefined; + sessionId: number; + systemTimeEpochS: number; + idleTimeS: number; + startTimeEpochS: number; + stemEventInfo: StemEventInfo | undefined; + sessionEnergyKwh: number; +} + +/** Billing record for a completed charge session. */ +export interface StemBilling { + stemInfo: StemInfo | undefined; + sessionId: number; + stemEventInfo: StemEventInfo | undefined; + lifetimeEnergyStartKwh: number; + lifetimeEnergyEndKwh: number; + idleTimeS: number; + chargeSessionTimeSeries: ChargeSessionTimeSeries | undefined; +} + +function createBaseEnergy(): Energy { + return { wattHours: 0, milliWattHours: 0 }; +} + +export const Energy: MessageFns = { + encode(message: Energy, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.wattHours !== 0) { + writer.uint32(8).uint32(message.wattHours); + } + if (message.milliWattHours !== 0) { + writer.uint32(16).uint32(message.milliWattHours); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Energy { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEnergy(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.wattHours = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.milliWattHours = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Energy { + return { + wattHours: isSet(object.wattHours) ? globalThis.Number(object.wattHours) : 0, + milliWattHours: isSet(object.milliWattHours) ? globalThis.Number(object.milliWattHours) : 0, + }; + }, + + toJSON(message: Energy): unknown { + const obj: any = {}; + if (message.wattHours !== undefined) { + obj.wattHours = Math.round(message.wattHours); + } + if (message.milliWattHours !== undefined) { + obj.milliWattHours = Math.round(message.milliWattHours); + } + return obj; + }, + + create, I>>(base?: I): Energy { + return Energy.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Energy { + const message = createBaseEnergy(); + message.wattHours = object.wattHours ?? 0; + message.milliWattHours = object.milliWattHours ?? 0; + return message; + }, +}; + +function createBaseStemInfo(): StemInfo { + return { fwGithash: new Uint8Array(0), serialNumber: "", partNumber: "", meterId: "" }; +} + +export const StemInfo: MessageFns = { + encode(message: StemInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.fwGithash.length !== 0) { + writer.uint32(10).bytes(message.fwGithash); + } + if (message.serialNumber !== "") { + writer.uint32(18).string(message.serialNumber); + } + if (message.partNumber !== "") { + writer.uint32(26).string(message.partNumber); + } + if (message.meterId !== "") { + writer.uint32(34).string(message.meterId); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): StemInfo { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStemInfo(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.fwGithash = reader.bytes(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.serialNumber = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.partNumber = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.meterId = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): StemInfo { + return { + fwGithash: isSet(object.fwGithash) ? bytesFromBase64(object.fwGithash) : new Uint8Array(0), + serialNumber: isSet(object.serialNumber) ? globalThis.String(object.serialNumber) : "", + partNumber: isSet(object.partNumber) ? globalThis.String(object.partNumber) : "", + meterId: isSet(object.meterId) ? globalThis.String(object.meterId) : "", + }; + }, + + toJSON(message: StemInfo): unknown { + const obj: any = {}; + if (message.fwGithash !== undefined) { + obj.fwGithash = base64FromBytes(message.fwGithash); + } + if (message.serialNumber !== undefined) { + obj.serialNumber = message.serialNumber; + } + if (message.partNumber !== undefined) { + obj.partNumber = message.partNumber; + } + if (message.meterId !== undefined) { + obj.meterId = message.meterId; + } + return obj; + }, + + create, I>>(base?: I): StemInfo { + return StemInfo.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): StemInfo { + const message = createBaseStemInfo(); + message.fwGithash = object.fwGithash ?? new Uint8Array(0); + message.serialNumber = object.serialNumber ?? ""; + message.partNumber = object.partNumber ?? ""; + message.meterId = object.meterId ?? ""; + return message; + }, +}; + +function createBaseStemEventInfo(): StemEventInfo { + return { eventOccurred: false, invalidated: false, timeSynchronized: false }; +} + +export const StemEventInfo: MessageFns = { + encode(message: StemEventInfo, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.eventOccurred !== false) { + writer.uint32(8).bool(message.eventOccurred); + } + if (message.invalidated !== false) { + writer.uint32(16).bool(message.invalidated); + } + if (message.timeSynchronized !== false) { + writer.uint32(24).bool(message.timeSynchronized); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): StemEventInfo { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStemEventInfo(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.eventOccurred = reader.bool(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.invalidated = reader.bool(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.timeSynchronized = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): StemEventInfo { + return { + eventOccurred: isSet(object.eventOccurred) ? globalThis.Boolean(object.eventOccurred) : false, + invalidated: isSet(object.invalidated) ? globalThis.Boolean(object.invalidated) : false, + timeSynchronized: isSet(object.timeSynchronized) ? globalThis.Boolean(object.timeSynchronized) : false, + }; + }, + + toJSON(message: StemEventInfo): unknown { + const obj: any = {}; + if (message.eventOccurred !== undefined) { + obj.eventOccurred = message.eventOccurred; + } + if (message.invalidated !== undefined) { + obj.invalidated = message.invalidated; + } + if (message.timeSynchronized !== undefined) { + obj.timeSynchronized = message.timeSynchronized; + } + return obj; + }, + + create, I>>(base?: I): StemEventInfo { + return StemEventInfo.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): StemEventInfo { + const message = createBaseStemEventInfo(); + message.eventOccurred = object.eventOccurred ?? false; + message.invalidated = object.invalidated ?? false; + message.timeSynchronized = object.timeSynchronized ?? false; + return message; + }, +}; + +function createBaseChargeSessionTimeSeries(): ChargeSessionTimeSeries { + return { + startTimeEpochS: 0, + endTimeEpochS: 0, + totalEnergyVendedWh: 0, + bucketDurationS: 0, + firstBucketDurationS: 0, + bucketEnergyVendedWh: [], + totalEnergyVended: undefined, + bucketEnergyVended: [], + }; +} + +export const ChargeSessionTimeSeries: MessageFns = { + encode(message: ChargeSessionTimeSeries, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.startTimeEpochS !== 0) { + writer.uint32(8).uint64(message.startTimeEpochS); + } + if (message.endTimeEpochS !== 0) { + writer.uint32(16).uint64(message.endTimeEpochS); + } + if (message.totalEnergyVendedWh !== 0) { + writer.uint32(24).uint32(message.totalEnergyVendedWh); + } + if (message.bucketDurationS !== 0) { + writer.uint32(32).uint32(message.bucketDurationS); + } + if (message.firstBucketDurationS !== 0) { + writer.uint32(40).uint32(message.firstBucketDurationS); + } + writer.uint32(50).fork(); + for (const v of message.bucketEnergyVendedWh) { + writer.uint32(v); + } + writer.join(); + if (message.totalEnergyVended !== undefined) { + Energy.encode(message.totalEnergyVended, writer.uint32(58).fork()).join(); + } + for (const v of message.bucketEnergyVended) { + Energy.encode(v!, writer.uint32(66).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ChargeSessionTimeSeries { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseChargeSessionTimeSeries(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.startTimeEpochS = longToNumber(reader.uint64()); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.endTimeEpochS = longToNumber(reader.uint64()); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.totalEnergyVendedWh = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.bucketDurationS = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.firstBucketDurationS = reader.uint32(); + continue; + } + case 6: { + if (tag === 48) { + message.bucketEnergyVendedWh.push(reader.uint32()); + + continue; + } + + if (tag === 50) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.bucketEnergyVendedWh.push(reader.uint32()); + } + + continue; + } + + break; + } + case 7: { + if (tag !== 58) { + break; + } + + message.totalEnergyVended = Energy.decode(reader, reader.uint32()); + continue; + } + case 8: { + if (tag !== 66) { + break; + } + + message.bucketEnergyVended.push(Energy.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ChargeSessionTimeSeries { + return { + startTimeEpochS: isSet(object.startTimeEpochS) ? globalThis.Number(object.startTimeEpochS) : 0, + endTimeEpochS: isSet(object.endTimeEpochS) ? globalThis.Number(object.endTimeEpochS) : 0, + totalEnergyVendedWh: isSet(object.totalEnergyVendedWh) ? globalThis.Number(object.totalEnergyVendedWh) : 0, + bucketDurationS: isSet(object.bucketDurationS) ? globalThis.Number(object.bucketDurationS) : 0, + firstBucketDurationS: isSet(object.firstBucketDurationS) ? globalThis.Number(object.firstBucketDurationS) : 0, + bucketEnergyVendedWh: globalThis.Array.isArray(object?.bucketEnergyVendedWh) + ? object.bucketEnergyVendedWh.map((e: any) => globalThis.Number(e)) + : [], + totalEnergyVended: isSet(object.totalEnergyVended) ? Energy.fromJSON(object.totalEnergyVended) : undefined, + bucketEnergyVended: globalThis.Array.isArray(object?.bucketEnergyVended) + ? object.bucketEnergyVended.map((e: any) => Energy.fromJSON(e)) + : [], + }; + }, + + toJSON(message: ChargeSessionTimeSeries): unknown { + const obj: any = {}; + if (message.startTimeEpochS !== undefined) { + obj.startTimeEpochS = Math.round(message.startTimeEpochS); + } + if (message.endTimeEpochS !== undefined) { + obj.endTimeEpochS = Math.round(message.endTimeEpochS); + } + if (message.totalEnergyVendedWh !== undefined) { + obj.totalEnergyVendedWh = Math.round(message.totalEnergyVendedWh); + } + if (message.bucketDurationS !== undefined) { + obj.bucketDurationS = Math.round(message.bucketDurationS); + } + if (message.firstBucketDurationS !== undefined) { + obj.firstBucketDurationS = Math.round(message.firstBucketDurationS); + } + if (message.bucketEnergyVendedWh?.length) { + obj.bucketEnergyVendedWh = message.bucketEnergyVendedWh.map((e) => Math.round(e)); + } + if (message.totalEnergyVended !== undefined) { + obj.totalEnergyVended = Energy.toJSON(message.totalEnergyVended); + } + if (message.bucketEnergyVended?.length) { + obj.bucketEnergyVended = message.bucketEnergyVended.map((e) => Energy.toJSON(e)); + } + return obj; + }, + + create, I>>(base?: I): ChargeSessionTimeSeries { + return ChargeSessionTimeSeries.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ChargeSessionTimeSeries { + const message = createBaseChargeSessionTimeSeries(); + message.startTimeEpochS = object.startTimeEpochS ?? 0; + message.endTimeEpochS = object.endTimeEpochS ?? 0; + message.totalEnergyVendedWh = object.totalEnergyVendedWh ?? 0; + message.bucketDurationS = object.bucketDurationS ?? 0; + message.firstBucketDurationS = object.firstBucketDurationS ?? 0; + message.bucketEnergyVendedWh = object.bucketEnergyVendedWh?.map((e) => e) || []; + message.totalEnergyVended = (object.totalEnergyVended !== undefined && object.totalEnergyVended !== null) + ? Energy.fromPartial(object.totalEnergyVended) + : undefined; + message.bucketEnergyVended = object.bucketEnergyVended?.map((e) => Energy.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseStemUi(): StemUi { + return { + stemInfo: undefined, + sessionId: 0, + systemTimeEpochS: 0, + idleTimeS: 0, + startTimeEpochS: 0, + stemEventInfo: undefined, + sessionEnergyKwh: 0, + }; +} + +export const StemUi: MessageFns = { + encode(message: StemUi, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.stemInfo !== undefined) { + StemInfo.encode(message.stemInfo, writer.uint32(10).fork()).join(); + } + if (message.sessionId !== 0) { + writer.uint32(16).uint32(message.sessionId); + } + if (message.systemTimeEpochS !== 0) { + writer.uint32(24).uint64(message.systemTimeEpochS); + } + if (message.idleTimeS !== 0) { + writer.uint32(32).uint32(message.idleTimeS); + } + if (message.startTimeEpochS !== 0) { + writer.uint32(40).uint64(message.startTimeEpochS); + } + if (message.stemEventInfo !== undefined) { + StemEventInfo.encode(message.stemEventInfo, writer.uint32(50).fork()).join(); + } + if (message.sessionEnergyKwh !== 0) { + writer.uint32(61).float(message.sessionEnergyKwh); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): StemUi { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStemUi(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.stemInfo = StemInfo.decode(reader, reader.uint32()); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.sessionId = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.systemTimeEpochS = longToNumber(reader.uint64()); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.idleTimeS = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.startTimeEpochS = longToNumber(reader.uint64()); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.stemEventInfo = StemEventInfo.decode(reader, reader.uint32()); + continue; + } + case 7: { + if (tag !== 61) { + break; + } + + message.sessionEnergyKwh = reader.float(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): StemUi { + return { + stemInfo: isSet(object.stemInfo) ? StemInfo.fromJSON(object.stemInfo) : undefined, + sessionId: isSet(object.sessionId) ? globalThis.Number(object.sessionId) : 0, + systemTimeEpochS: isSet(object.systemTimeEpochS) ? globalThis.Number(object.systemTimeEpochS) : 0, + idleTimeS: isSet(object.idleTimeS) ? globalThis.Number(object.idleTimeS) : 0, + startTimeEpochS: isSet(object.startTimeEpochS) ? globalThis.Number(object.startTimeEpochS) : 0, + stemEventInfo: isSet(object.stemEventInfo) ? StemEventInfo.fromJSON(object.stemEventInfo) : undefined, + sessionEnergyKwh: isSet(object.sessionEnergyKwh) ? globalThis.Number(object.sessionEnergyKwh) : 0, + }; + }, + + toJSON(message: StemUi): unknown { + const obj: any = {}; + if (message.stemInfo !== undefined) { + obj.stemInfo = StemInfo.toJSON(message.stemInfo); + } + if (message.sessionId !== undefined) { + obj.sessionId = Math.round(message.sessionId); + } + if (message.systemTimeEpochS !== undefined) { + obj.systemTimeEpochS = Math.round(message.systemTimeEpochS); + } + if (message.idleTimeS !== undefined) { + obj.idleTimeS = Math.round(message.idleTimeS); + } + if (message.startTimeEpochS !== undefined) { + obj.startTimeEpochS = Math.round(message.startTimeEpochS); + } + if (message.stemEventInfo !== undefined) { + obj.stemEventInfo = StemEventInfo.toJSON(message.stemEventInfo); + } + if (message.sessionEnergyKwh !== undefined) { + obj.sessionEnergyKwh = message.sessionEnergyKwh; + } + return obj; + }, + + create, I>>(base?: I): StemUi { + return StemUi.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): StemUi { + const message = createBaseStemUi(); + message.stemInfo = (object.stemInfo !== undefined && object.stemInfo !== null) + ? StemInfo.fromPartial(object.stemInfo) + : undefined; + message.sessionId = object.sessionId ?? 0; + message.systemTimeEpochS = object.systemTimeEpochS ?? 0; + message.idleTimeS = object.idleTimeS ?? 0; + message.startTimeEpochS = object.startTimeEpochS ?? 0; + message.stemEventInfo = (object.stemEventInfo !== undefined && object.stemEventInfo !== null) + ? StemEventInfo.fromPartial(object.stemEventInfo) + : undefined; + message.sessionEnergyKwh = object.sessionEnergyKwh ?? 0; + return message; + }, +}; + +function createBaseStemBilling(): StemBilling { + return { + stemInfo: undefined, + sessionId: 0, + stemEventInfo: undefined, + lifetimeEnergyStartKwh: 0, + lifetimeEnergyEndKwh: 0, + idleTimeS: 0, + chargeSessionTimeSeries: undefined, + }; +} + +export const StemBilling: MessageFns = { + encode(message: StemBilling, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.stemInfo !== undefined) { + StemInfo.encode(message.stemInfo, writer.uint32(10).fork()).join(); + } + if (message.sessionId !== 0) { + writer.uint32(16).uint32(message.sessionId); + } + if (message.stemEventInfo !== undefined) { + StemEventInfo.encode(message.stemEventInfo, writer.uint32(26).fork()).join(); + } + if (message.lifetimeEnergyStartKwh !== 0) { + writer.uint32(37).float(message.lifetimeEnergyStartKwh); + } + if (message.lifetimeEnergyEndKwh !== 0) { + writer.uint32(45).float(message.lifetimeEnergyEndKwh); + } + if (message.idleTimeS !== 0) { + writer.uint32(48).uint32(message.idleTimeS); + } + if (message.chargeSessionTimeSeries !== undefined) { + ChargeSessionTimeSeries.encode(message.chargeSessionTimeSeries, writer.uint32(58).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): StemBilling { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseStemBilling(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.stemInfo = StemInfo.decode(reader, reader.uint32()); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.sessionId = reader.uint32(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.stemEventInfo = StemEventInfo.decode(reader, reader.uint32()); + continue; + } + case 4: { + if (tag !== 37) { + break; + } + + message.lifetimeEnergyStartKwh = reader.float(); + continue; + } + case 5: { + if (tag !== 45) { + break; + } + + message.lifetimeEnergyEndKwh = reader.float(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.idleTimeS = reader.uint32(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.chargeSessionTimeSeries = ChargeSessionTimeSeries.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): StemBilling { + return { + stemInfo: isSet(object.stemInfo) ? StemInfo.fromJSON(object.stemInfo) : undefined, + sessionId: isSet(object.sessionId) ? globalThis.Number(object.sessionId) : 0, + stemEventInfo: isSet(object.stemEventInfo) ? StemEventInfo.fromJSON(object.stemEventInfo) : undefined, + lifetimeEnergyStartKwh: isSet(object.lifetimeEnergyStartKwh) + ? globalThis.Number(object.lifetimeEnergyStartKwh) + : 0, + lifetimeEnergyEndKwh: isSet(object.lifetimeEnergyEndKwh) ? globalThis.Number(object.lifetimeEnergyEndKwh) : 0, + idleTimeS: isSet(object.idleTimeS) ? globalThis.Number(object.idleTimeS) : 0, + chargeSessionTimeSeries: isSet(object.chargeSessionTimeSeries) + ? ChargeSessionTimeSeries.fromJSON(object.chargeSessionTimeSeries) + : undefined, + }; + }, + + toJSON(message: StemBilling): unknown { + const obj: any = {}; + if (message.stemInfo !== undefined) { + obj.stemInfo = StemInfo.toJSON(message.stemInfo); + } + if (message.sessionId !== undefined) { + obj.sessionId = Math.round(message.sessionId); + } + if (message.stemEventInfo !== undefined) { + obj.stemEventInfo = StemEventInfo.toJSON(message.stemEventInfo); + } + if (message.lifetimeEnergyStartKwh !== undefined) { + obj.lifetimeEnergyStartKwh = message.lifetimeEnergyStartKwh; + } + if (message.lifetimeEnergyEndKwh !== undefined) { + obj.lifetimeEnergyEndKwh = message.lifetimeEnergyEndKwh; + } + if (message.idleTimeS !== undefined) { + obj.idleTimeS = Math.round(message.idleTimeS); + } + if (message.chargeSessionTimeSeries !== undefined) { + obj.chargeSessionTimeSeries = ChargeSessionTimeSeries.toJSON(message.chargeSessionTimeSeries); + } + return obj; + }, + + create, I>>(base?: I): StemBilling { + return StemBilling.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): StemBilling { + const message = createBaseStemBilling(); + message.stemInfo = (object.stemInfo !== undefined && object.stemInfo !== null) + ? StemInfo.fromPartial(object.stemInfo) + : undefined; + message.sessionId = object.sessionId ?? 0; + message.stemEventInfo = (object.stemEventInfo !== undefined && object.stemEventInfo !== null) + ? StemEventInfo.fromPartial(object.stemEventInfo) + : undefined; + message.lifetimeEnergyStartKwh = object.lifetimeEnergyStartKwh ?? 0; + message.lifetimeEnergyEndKwh = object.lifetimeEnergyEndKwh ?? 0; + message.idleTimeS = object.idleTimeS ?? 0; + message.chargeSessionTimeSeries = + (object.chargeSessionTimeSeries !== undefined && object.chargeSessionTimeSeries !== null) + ? ChargeSessionTimeSeries.fromPartial(object.chargeSessionTimeSeries) + : undefined; + return message; + }, +}; + +function bytesFromBase64(b64: string): Uint8Array { + if ((globalThis as any).Buffer) { + return Uint8Array.from(globalThis.Buffer.from(b64, "base64")); + } else { + const bin = globalThis.atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; + } +} + +function base64FromBytes(arr: Uint8Array): string { + if ((globalThis as any).Buffer) { + return globalThis.Buffer.from(arr).toString("base64"); + } else { + const bin: string[] = []; + arr.forEach((byte) => { + bin.push(globalThis.String.fromCharCode(byte)); + }); + return globalThis.btoa(bin.join("")); + } +} + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function longToNumber(int64: { toString(): string }): number { + const num = globalThis.Number(int64.toString()); + if (num > globalThis.Number.MAX_SAFE_INTEGER) { + throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER"); + } + if (num < globalThis.Number.MIN_SAFE_INTEGER) { + throw new globalThis.Error("Value is smaller than Number.MIN_SAFE_INTEGER"); + } + return num; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/proto/charging/charging.proto b/proto/charging/charging.proto new file mode 100644 index 0000000..22d27af --- /dev/null +++ b/proto/charging/charging.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package com.tesla.proto.charging.v1; + +// Split integer energy representation: whole watt-hours plus a +// sub-watt-hour remainder in milli-watt-hours. +message Energy { + uint32 watt_hours = 1; + uint32 milli_watt_hours = 2; +} + +// Identity and firmware of the metering device that recorded a charge session. +message StemInfo { + bytes fw_githash = 1; + string serial_number = 2; + string part_number = 3; + string meter_id = 4; +} + +// Validity flags for a charge-session metering record. +message StemEventInfo { + bool event_occurred = 1; + bool invalidated = 2; + bool time_synchronized = 3; +} + +// Bucketed energy vended (delivered) over a charge session. +message ChargeSessionTimeSeries { + uint64 start_time_epoch_s = 1; + uint64 end_time_epoch_s = 2; + uint32 total_energy_vended_wh = 3; + uint32 bucket_duration_s = 4; + uint32 first_bucket_duration_s = 5; + repeated uint32 bucket_energy_vended_wh = 6; + Energy total_energy_vended = 7; + repeated Energy bucket_energy_vended = 8; +} + +// UI-facing summary of a live or recently completed charge session. +message StemUi { + StemInfo stem_info = 1; + uint32 session_id = 2; + uint64 system_time_epoch_s = 3; + uint32 idle_time_s = 4; + uint64 start_time_epoch_s = 5; + StemEventInfo stem_event_info = 6; + float session_energy_kwh = 7; +} + +// Billing record for a completed charge session. +message StemBilling { + StemInfo stem_info = 1; + uint32 session_id = 2; + StemEventInfo stem_event_info = 3; + float lifetime_energy_start_kwh = 4; + float lifetime_energy_end_kwh = 5; + uint32 idle_time_s = 6; + ChargeSessionTimeSeries charge_session_time_series = 7; +} diff --git a/scripts/generate.sh b/scripts/generate.sh index 8e68db3..55f627e 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -23,7 +23,7 @@ TS_OPTS="emitDefaultValues=json-methods,importSuffix=.js" TS_OUT=packages/typescript/src PY_PKG=packages/python/tesla_protocol -GROUPS_LIST="command telemetry energy_device energy_command teslapower" +GROUPS_LIST="command telemetry energy_device energy_command teslapower charging" rm -rf "$TS_OUT" for group in $GROUPS_LIST; do