mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 11:56:01 +02:00
fix(smart-plugs): read a REST plug's lifetime counter, and derive Today/Yesterday from it (issue #2539)
A Shelly reports one energy figure — aenergy.total, a lifetime counter in Wh that never resets. Bambuddy had a single REST energy field and filed whatever it found under "today", so the value never reset at midnight, and Yesterday and Total stayed at zero: get_energy() simply never set those keys. With `total` unpopulated, the hourly snapshot recorder skipped the plug, so the Statistics page's energy figure was zero as well, not just the Settings card. Split the REST energy config in two: rest_energy_path still means "used today", rest_energy_total_path means "lifetime counter". A Shelly has only the latter; a Tasmota behind a REST bridge has both; sharing a URL costs one fetch, not two. Then derive Today and Yesterday from that counter using the snapshots we were already taking: today = counter now - counter at the last local midnight; yesterday = the gap between the two previous midnights. Local midnight, not UTC — a UTC boundary rolls Today over at 02:00 in Berlin. The snapshot loop now ticks on the local hour so a reading lands on the boundary instead of up to an hour early. A counter that goes backwards (factory reset) reports nothing rather than a negative. Collateral, found while verifying on both engines: the smart-plug DateTime columns are naive UTC but the code wrote aware datetimes into them. SQLite drops the offset; asyncpg raises DataError. So on Postgres every snapshot capture raised inside the loop's except, and every status poll raised on last_checked — the whole subsystem was dead on the database we recommend for multi-printer installs. All plug timestamps are naive UTC now. Existing REST users with a cumulative path in the today field must move it to the new lifetime field; the form and wiki now name which counter each wants.
This commit is contained in:
@@ -58,6 +58,8 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
|
||||
const [restEnergyUrl, setRestEnergyUrl] = useState(plug?.rest_energy_url || '');
|
||||
const [restEnergyPath, setRestEnergyPath] = useState(plug?.rest_energy_path || '');
|
||||
const [restEnergyMultiplier, setRestEnergyMultiplier] = useState<string>((plug?.rest_energy_multiplier ?? 1).toString());
|
||||
const [restEnergyTotalPath, setRestEnergyTotalPath] = useState(plug?.rest_energy_total_path || '');
|
||||
const [restEnergyTotalMultiplier, setRestEnergyTotalMultiplier] = useState<string>((plug?.rest_energy_total_multiplier ?? 1).toString());
|
||||
// HA energy sensor entities (optional)
|
||||
const [haPowerEntity, setHaPowerEntity] = useState(plug?.ha_power_entity || '');
|
||||
const [haEnergyTodayEntity, setHaEnergyTodayEntity] = useState(plug?.ha_energy_today_entity || '');
|
||||
@@ -381,6 +383,8 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
|
||||
rest_energy_url: plugType === 'rest' ? (restEnergyUrl.trim() || null) : null,
|
||||
rest_energy_path: plugType === 'rest' ? (restEnergyPath.trim() || null) : null,
|
||||
rest_energy_multiplier: plugType === 'rest' ? (parseFloat(restEnergyMultiplier) || 1) : 1,
|
||||
rest_energy_total_path: plugType === 'rest' ? (restEnergyTotalPath.trim() || null) : null,
|
||||
rest_energy_total_multiplier: plugType === 'rest' ? (parseFloat(restEnergyTotalMultiplier) || 1) : 1,
|
||||
username: plugType === 'tasmota' ? (username.trim() || null) : null,
|
||||
password: plugType === 'tasmota' ? (password.trim() || null) : null,
|
||||
printer_id: printerId,
|
||||
@@ -1312,10 +1316,36 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-bambu-gray">
|
||||
{t('smartPlugs.restEnergyHint')}
|
||||
</p>
|
||||
|
||||
{/* Lifetime counter (#2539) — the only energy figure a Shelly has. */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restEnergyTotalPath')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={restEnergyTotalPath}
|
||||
onChange={(e) => setRestEnergyTotalPath(e.target.value)}
|
||||
placeholder={t('smartPlugs.restEnergyTotalPathHint')}
|
||||
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm text-bambu-gray mb-1">{t('smartPlugs.restEnergyTotalMultiplier')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={restEnergyTotalMultiplier}
|
||||
onChange={(e) => setRestEnergyTotalMultiplier(e.target.value)}
|
||||
placeholder="1"
|
||||
className="w-full px-3 py-2 bg-bambu-dark-secondary border border-bambu-dark-tertiary rounded-lg text-white placeholder-bambu-gray focus:border-bambu-green focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-bambu-gray">
|
||||
{t('smartPlugs.restEnergyTotalHint')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Test Connection */}
|
||||
|
||||
Reference in New Issue
Block a user