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/iobroker-tariff-read.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"iobroker.teslemetry": minor
---

Surface the site's time-of-use tariff (`tariff_id`, `tariff_content`, `tariff_content_v2`) as read-only states under `energy.{SITE_ID}.tariff`. Read-only, matching the node-red/Homey tariff precedent - no tariff write or VPP surfaces.
4 changes: 4 additions & 0 deletions packages/iobroker.teslemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ teslemetry.0
│ ├── mode (string, read-write)
│ ├── backup_reserve_percent (number, read-write)
│ └── off_grid_reserve_percent (number, read-write)
├── tariff
│ ├── tariff_id (string, read-only)
│ ├── tariff_content (string/JSON, read-only)
│ └── tariff_content_v2 (string/JSON, read-only)
└── commands
└── storm_mode (boolean, write-only)
```
Expand Down
17 changes: 17 additions & 0 deletions packages/iobroker.teslemetry/lib/StateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ export class StateManager {
await this.createState(`${base}.operation.backup_reserve_percent`, 'Backup Reserve', 'number', 'level.battery', true, true, 20, '%');
await this.createState(`${base}.operation.off_grid_reserve_percent`, 'Off-Grid Reserve', 'number', 'level.battery', true, true, 0, '%');

// Tariff channel (read-only - rate plan is managed through the utility/Tesla app, not this adapter)
await this.createChannel(`${base}.tariff`, 'Tariff');
await this.createState(`${base}.tariff.tariff_id`, 'Tariff Plan Name', 'string', 'text', true, false);
await this.createState(`${base}.tariff.tariff_content`, 'Tariff Content (JSON)', 'string', 'json', true, false);
await this.createState(`${base}.tariff.tariff_content_v2`, 'Tariff Content V2 (JSON)', 'string', 'json', true, false);

// Commands channel
await this.createChannel(`${base}.commands`, 'Commands');
await this.createState(`${base}.commands.storm_mode`, 'Storm Mode', 'boolean', 'button', false, true, false);
Expand Down Expand Up @@ -269,6 +275,17 @@ export class StateManager {
if (data.off_grid_vehicle_charging_reserve_percent !== undefined) {
await this.setStateAsync(`${base}.operation.off_grid_reserve_percent`, data.off_grid_vehicle_charging_reserve_percent);
}

// Update tariff (getSiteInfo() already carries these fields - see TeslemetryEnergyApi.getTariff())
if (data.tariff_id !== undefined) {
await this.setStateAsync(`${base}.tariff.tariff_id`, data.tariff_id);
}
if (data.tariff_content !== undefined) {
await this.setStateAsync(`${base}.tariff.tariff_content`, JSON.stringify(data.tariff_content));
}
if (data.tariff_content_v2 !== undefined) {
await this.setStateAsync(`${base}.tariff.tariff_content_v2`, JSON.stringify(data.tariff_content_v2));
}
} catch (error) {
this.adapter.log.error(`Error updating energy site data for ${siteId}: ${error}`);
}
Expand Down
6 changes: 5 additions & 1 deletion packages/iobroker.teslemetry/test/EnergyHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ test('fetchSiteData calls getLiveStatus()/getSiteInfo() and updates states from

(site as any).getLiveStatus = () =>
Promise.resolve({ response: { solar_power: 500, battery_power: -100, grid_power: 0, grid_status: 'Active', percentage_charged: 80 } });
(site as any).getSiteInfo = () => Promise.resolve({ response: { default_real_mode: 'self_consumption', backup_reserve_percent: 20 } });
(site as any).getSiteInfo = () =>
Promise.resolve({
response: { default_real_mode: 'self_consumption', backup_reserve_percent: 20, tariff_id: 'PGE-EV2-A' },
});

const { adapter, states } = createFakeAdapter();
const handler = new EnergyHandler(adapter, teslemetry, new StateManager(adapter));
Expand All @@ -83,4 +86,5 @@ test('fetchSiteData calls getLiveStatus()/getSiteInfo() and updates states from
assert.equal(states.get(`energy.${SITE_ID}.battery.percentage`), 80);
assert.equal(states.get(`energy.${SITE_ID}.operation.mode`), 'self_consumption');
assert.equal(states.get(`energy.${SITE_ID}.operation.backup_reserve_percent`), 20);
assert.equal(states.get(`energy.${SITE_ID}.tariff.tariff_id`), 'PGE-EV2-A');
});
18 changes: 18 additions & 0 deletions packages/iobroker.teslemetry/test/StateManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,21 @@ test('updateEnergySiteData reads the flat getLiveStatus()/getSiteInfo() response
assert.equal(states.get('energy.123.live.grid_status'), 'Active');
assert.equal(states.get('energy.123.battery.percentage'), 77);
});

test('updateEnergySiteData surfaces tariff_id/tariff_content/tariff_content_v2 from the getSiteInfo() response shape', async () => {
const { adapter, states } = createFakeAdapter();
const stateManager = new StateManager(adapter);

await stateManager.updateEnergySiteData(123, {
tariff_id: 'PGE-EV2-A',
tariff_content: { code: 'PGE-EV2-A' },
tariff_content_v2: { code: 'PGE-EV2-A', utility: 'PG&E', currency: 'USD' },
});

assert.equal(states.get('energy.123.tariff.tariff_id'), 'PGE-EV2-A');
assert.equal(states.get('energy.123.tariff.tariff_content'), JSON.stringify({ code: 'PGE-EV2-A' }));
assert.equal(
states.get('energy.123.tariff.tariff_content_v2'),
JSON.stringify({ code: 'PGE-EV2-A', utility: 'PG&E', currency: 'USD' })
);
});
Loading