hoffmation-base 3.8.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/devices/dachs/dachs.d.ts +79 -2
- package/lib/devices/dachs/dachs.js +214 -18
- package/lib/devices/devices.d.ts +36 -1
- package/lib/devices/devices.js +64 -0
- package/lib/devices/jsObject/jsObjectEnergyManager.d.ts +16 -1
- package/lib/devices/jsObject/jsObjectEnergyManager.js +23 -0
- package/lib/devices/victron/victron-device.d.ts +83 -1
- package/lib/devices/victron/victron-device.js +151 -2
- package/lib/devices/victron/victron-morning-release.d.ts +16 -0
- package/lib/devices/victron/victron-morning-release.js +2 -0
- package/lib/enums/DeviceCapability.d.ts +1 -0
- package/lib/enums/DeviceCapability.js +1 -0
- package/lib/interfaces/baseDevices/iAirQualityCollector.d.ts +2 -1
- package/lib/interfaces/baseDevices/iAirQualityReadings.d.ts +50 -0
- package/lib/interfaces/baseDevices/iAirQualityReadings.js +2 -0
- package/lib/interfaces/baseDevices/iAirQualitySensor.d.ts +1 -54
- package/lib/interfaces/baseDevices/iAirQualitySensor.js +0 -5
- package/lib/interfaces/baseDevices/iEnergyManager.d.ts +43 -0
- package/lib/interfaces/baseDevices/iHumidityCollector.d.ts +0 -1
- package/lib/interfaces/baseDevices/iHumidityCollector.js +0 -2
- package/lib/interfaces/baseDevices/iTemperatureCollector.d.ts +0 -1
- package/lib/interfaces/baseDevices/iTemperatureCollector.js +0 -2
- package/lib/interfaces/baseDevices/index.d.ts +4 -0
- package/lib/interfaces/baseDevices/index.js +4 -0
- package/lib/interfaces/baseDevices/undefinedAirQualityValue.d.ts +4 -0
- package/lib/interfaces/baseDevices/undefinedAirQualityValue.js +7 -0
- package/lib/interfaces/baseDevices/undefinedHumidityValue.d.ts +4 -0
- package/lib/interfaces/baseDevices/undefinedHumidityValue.js +7 -0
- package/lib/interfaces/baseDevices/undefinedTempValue.d.ts +4 -0
- package/lib/interfaces/baseDevices/undefinedTempValue.js +7 -0
- package/lib/interfaces/dachs/KeyListEntity.d.ts +0 -5
- package/lib/interfaces/dachs/KeyListEntityResponse.d.ts +6 -0
- package/lib/interfaces/dachs/KeyListEntityResponse.js +2 -0
- package/lib/interfaces/dachs/index.d.ts +1 -0
- package/lib/interfaces/dachs/index.js +1 -0
- package/lib/interfaces/deviceSettings/iDachsDeviceSettings.d.ts +15 -0
- package/lib/interfaces/iActuatorStateSample.d.ts +13 -0
- package/lib/interfaces/iActuatorStateSample.js +2 -0
- package/lib/interfaces/iBatteryLevelSample.d.ts +13 -0
- package/lib/interfaces/iBatteryLevelSample.js +2 -0
- package/lib/interfaces/iConsumptionWindowSample.d.ts +26 -0
- package/lib/interfaces/iConsumptionWindowSample.js +2 -0
- package/lib/interfaces/iDachsHistoryGateResult.d.ts +29 -0
- package/lib/interfaces/iDachsHistoryGateResult.js +2 -0
- package/lib/interfaces/iEnergyHistoryBasis.d.ts +26 -0
- package/lib/interfaces/iEnergyHistoryBasis.js +2 -0
- package/lib/interfaces/iEnergyHistoryEstimate.d.ts +21 -0
- package/lib/interfaces/iEnergyHistoryEstimate.js +2 -0
- package/lib/interfaces/iEnergyHistoryFeatures.d.ts +21 -0
- package/lib/interfaces/iEnergyHistoryFeatures.js +2 -0
- package/lib/interfaces/iEnergyHistoryModel.d.ts +21 -0
- package/lib/interfaces/iEnergyHistoryModel.js +2 -0
- package/lib/interfaces/iEnergyHistoryOptions.d.ts +29 -0
- package/lib/interfaces/iEnergyHistoryOptions.js +2 -0
- package/lib/interfaces/iEnergyHistoryOutlook.d.ts +41 -0
- package/lib/interfaces/iEnergyHistoryOutlook.js +2 -0
- package/lib/interfaces/iEnergyHistorySample.d.ts +19 -0
- package/lib/interfaces/iEnergyHistorySample.js +2 -0
- package/lib/interfaces/iFossilGeneratorRun.d.ts +22 -0
- package/lib/interfaces/iFossilGeneratorRun.js +2 -0
- package/lib/interfaces/iFossilGeneratorSource.d.ts +21 -0
- package/lib/interfaces/iFossilGeneratorSource.js +2 -0
- package/lib/interfaces/iMorningReserveVerdict.d.ts +38 -0
- package/lib/interfaces/iMorningReserveVerdict.js +2 -0
- package/lib/interfaces/iPersist.d.ts +51 -0
- package/lib/interfaces/iProjectedSocBand.d.ts +7 -0
- package/lib/interfaces/iProjectedSocBand.js +2 -0
- package/lib/interfaces/iWeatherDaySummary.d.ts +21 -0
- package/lib/interfaces/iWeatherDaySummary.js +2 -0
- package/lib/interfaces/index.d.ts +16 -0
- package/lib/interfaces/index.js +16 -0
- package/lib/services/dbo/actuator-state-row.d.ts +14 -0
- package/lib/services/dbo/actuator-state-row.js +2 -0
- package/lib/services/dbo/battery-level-row.d.ts +16 -0
- package/lib/services/dbo/battery-level-row.js +2 -0
- package/lib/services/dbo/energy-consumption-row.d.ts +16 -0
- package/lib/services/dbo/energy-consumption-row.js +2 -0
- package/lib/services/dbo/postgreSqlPersist.d.ts +48 -1
- package/lib/services/dbo/postgreSqlPersist.js +298 -28
- package/lib/services/dbo/weather-day-summary-row.d.ts +18 -0
- package/lib/services/dbo/weather-day-summary-row.js +2 -0
- package/lib/services/energy/energy-history-service.d.ts +168 -0
- package/lib/services/energy/energy-history-service.js +499 -0
- package/lib/services/energy/index.d.ts +1 -0
- package/lib/services/energy/index.js +17 -0
- package/lib/services/energy/plant-energy-dials.d.ts +22 -0
- package/lib/services/energy/plant-energy-dials.js +2 -0
- package/lib/services/https-service.d.ts +48 -2
- package/lib/services/https-service.js +136 -10
- package/lib/services/index.d.ts +1 -0
- package/lib/services/index.js +1 -0
- package/lib/services/time-callback-service.d.ts +12 -0
- package/lib/services/time-callback-service.js +16 -2
- package/lib/services/weather/index.d.ts +1 -0
- package/lib/services/weather/index.js +1 -0
- package/lib/services/weather/open-weather-day-summary.d.ts +21 -0
- package/lib/services/weather/open-weather-day-summary.js +2 -0
- package/lib/services/weather/weather-day-summary-fetcher.d.ts +6 -0
- package/lib/services/weather/weather-day-summary-fetcher.js +2 -0
- package/lib/services/weather/weather-history-backfill.d.ts +88 -0
- package/lib/services/weather/weather-history-backfill.js +228 -0
- package/lib/services/weather/weather-service.js +29 -4
- package/lib/settings-service-instance.js +5 -2
- package/lib/settingsObjects/deviceSettings/dachsSettings.d.ts +6 -0
- package/lib/settingsObjects/deviceSettings/dachsSettings.js +14 -1
- package/lib/settingsObjects/deviceSettings/victronDeviceSettings.d.ts +116 -0
- package/lib/settingsObjects/deviceSettings/victronDeviceSettings.js +150 -5
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/dayMs.d.ts +7 -0
- package/lib/utils/dayMs.js +10 -0
- package/lib/utils/energy-history-dials.d.ts +19 -0
- package/lib/utils/energy-history-dials.js +2 -0
- package/lib/utils/energy-history-utils.d.ts +143 -0
- package/lib/utils/energy-history-utils.js +470 -0
- package/lib/utils/energy-manager-utils.d.ts +123 -1
- package/lib/utils/energy-manager-utils.js +226 -0
- package/lib/utils/index.d.ts +4 -0
- package/lib/utils/index.js +4 -0
- package/lib/utils/model-shadow.d.ts +61 -0
- package/lib/utils/model-shadow.js +83 -0
- package/lib/utils/morning-reserve-dials.d.ts +21 -0
- package/lib/utils/morning-reserve-dials.js +2 -0
- package/lib/utils/morning-verdict.d.ts +5 -0
- package/lib/utils/morning-verdict.js +2 -0
- package/lib/utils/utils.d.ts +0 -1
- package/lib/utils/utils.js +5 -5
- package/package.json +3 -2
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { iBatteryDevice, iEnergyManager, iExcessEnergyConsumer, iJsonOmitKeys } from '../../interfaces';
|
|
1
|
+
import { iBatteryDevice, iEnergyHistoryOutlook, iEnergyManager, iExcessEnergyConsumer, iJsonOmitKeys, iMorningReserveVerdict } from '../../interfaces';
|
|
2
2
|
import { VictronDeviceSettings } from '../../settingsObjects';
|
|
3
3
|
import { Battery } from '../sharedFunctions';
|
|
4
4
|
import { VictronDeviceData, VictronMqttConnectionOptions, VictronMqttConsumer } from 'victron-mqtt-consumer';
|
|
5
5
|
import { BaseDevice } from '../BaseDevice';
|
|
6
6
|
export declare class VictronDevice extends BaseDevice implements iEnergyManager, iBatteryDevice, iJsonOmitKeys {
|
|
7
|
+
/**
|
|
8
|
+
* The shortest horizon the battery reservation is spread over, in hours. Not a setting: it is a bound on
|
|
9
|
+
* the arithmetic rather than a dial of the plant - see {@link VictronDevice.reserveHorizonHours}.
|
|
10
|
+
*/
|
|
11
|
+
private static readonly minimumReserveHorizonHours;
|
|
7
12
|
/** @inheritDoc */
|
|
8
13
|
readonly settings: VictronDeviceSettings;
|
|
9
14
|
/** @inheritDoc */
|
|
@@ -17,11 +22,28 @@ export declare class VictronDevice extends BaseDevice implements iEnergyManager,
|
|
|
17
22
|
private _lastPersistenceCalculation;
|
|
18
23
|
private _nextPersistEntry;
|
|
19
24
|
private _excessEnergy;
|
|
25
|
+
/** The last reported release state, so the five second loop writes a line on change, not on every pass. */
|
|
26
|
+
private _lastReleaseLine;
|
|
20
27
|
constructor(opts: VictronMqttConnectionOptions);
|
|
21
28
|
/** @inheritDoc */
|
|
22
29
|
get acBlocked(): boolean;
|
|
23
30
|
/** @inheritDoc */
|
|
24
31
|
get batteryLevel(): number;
|
|
32
|
+
/**
|
|
33
|
+
* Delegated rather than written here, exactly the way this device delegates its consumer switching to
|
|
34
|
+
* {@link EnergyManagerUtils.turnOnAdditionalConsumer}: the answer is about the **plant**, and every energy
|
|
35
|
+
* manager owes the same one - so it is written once and no manager is left with a copy or a stub. What this
|
|
36
|
+
* device keeps for itself is the policy: the rungs, the reservation and the ac block.
|
|
37
|
+
* @inheritDoc
|
|
38
|
+
*/
|
|
39
|
+
get morningOutlook(): iEnergyHistoryOutlook | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Delegated for the same reason {@link morningOutlook} is: the judgement is about the plant's battery and
|
|
42
|
+
* every manager owes the same one. What this device keeps for itself is what it *does* with the verdict -
|
|
43
|
+
* the reservation and the ac block below.
|
|
44
|
+
* @inheritDoc
|
|
45
|
+
*/
|
|
46
|
+
get morningReserveVerdict(): iMorningReserveVerdict | undefined;
|
|
25
47
|
get victronConsumer(): VictronMqttConsumer;
|
|
26
48
|
get data(): VictronDeviceData;
|
|
27
49
|
get excessEnergy(): number;
|
|
@@ -33,6 +55,7 @@ export declare class VictronDevice extends BaseDevice implements iEnergyManager,
|
|
|
33
55
|
addExcessConsumer(device: iExcessEnergyConsumer): void;
|
|
34
56
|
/** @inheritDoc */
|
|
35
57
|
dispose(): void;
|
|
58
|
+
/** @inheritDoc */
|
|
36
59
|
getReport(): string;
|
|
37
60
|
recalculatePowerSharing(): void;
|
|
38
61
|
toJSON(): Partial<VictronDevice>;
|
|
@@ -42,6 +65,65 @@ export declare class VictronDevice extends BaseDevice implements iEnergyManager,
|
|
|
42
65
|
*/
|
|
43
66
|
setGridSetPoint(setPoint: number): void;
|
|
44
67
|
private calculateExcessEnergy;
|
|
68
|
+
/**
|
|
69
|
+
* Rung 1, the only rung of the recorded history that decides: if the state of charge carries past the
|
|
70
|
+
* coming morning low even when no further yield arrives, nothing has to be held back and no consumer has to
|
|
71
|
+
* be kept off.
|
|
72
|
+
*
|
|
73
|
+
* This manager's own policy and rightly here rather than in the shared utils: what a plant does with the
|
|
74
|
+
* outlook is the manager's judgement, and the reservation and ac block it feeds are this device's.
|
|
75
|
+
*
|
|
76
|
+
* Arithmetic over the plant's own measured consumption - no fit, no weights, no window length. That is what
|
|
77
|
+
* separates it from the rung below, which measures against the lower edge of the fitted band and therefore
|
|
78
|
+
* only records (see {@link EnergyManagerUtils.observeModelShadow}).
|
|
79
|
+
* @param outlook - What the plant said, or undefined while it cannot say anything - which includes the
|
|
80
|
+
* absent charge level, so the guard against it stays in the shared utils alone.
|
|
81
|
+
* @returns The release, or undefined while the measured consumption cannot carry it.
|
|
82
|
+
*/
|
|
83
|
+
private morningRelease;
|
|
84
|
+
/**
|
|
85
|
+
* How much of the production is held back for the battery.
|
|
86
|
+
*
|
|
87
|
+
* With the release nothing is held back: the coming morning clears the reserve on measured consumption
|
|
88
|
+
* alone. Without it the calculation is the one that was here before - it aims at a full battery by sunset,
|
|
89
|
+
* which over-reserves, and that is what an installation without a usable recorded history keeps.
|
|
90
|
+
* @param released - Whether rung 1 carried the coming morning.
|
|
91
|
+
* @param hoursTilSunset - The horizon the shortfall is spread over.
|
|
92
|
+
* @returns The wattage to hold back.
|
|
93
|
+
*/
|
|
94
|
+
private neededBatteryWattage;
|
|
95
|
+
/**
|
|
96
|
+
* The horizon the shortfall is actually spread over: the remaining sun hours, but never less than
|
|
97
|
+
* {@link VictronDevice.minimumReserveHorizonHours}.
|
|
98
|
+
*
|
|
99
|
+
* The raw horizon is a difference against the next sunset and runs to zero, while the part of day this
|
|
100
|
+
* calculation is entered under is derived separately from today's sunset plus its own offsets - so the two
|
|
101
|
+
* do not turn at the same instant. In the gap the divisor is first tiny and then negative: a shortfall of a
|
|
102
|
+
* few percent becomes a rate in the megawatt range, at exactly zero an `Infinity` that leaves the process
|
|
103
|
+
* through `toJSON`, and past the turn a *negative* reservation that is added to the excess and offers
|
|
104
|
+
* energy the plant does not have. Every five seconds, and since the horizon is counted in hours rather than
|
|
105
|
+
* minutes, sixty times as large as when this was harmless.
|
|
106
|
+
*
|
|
107
|
+
* The bound is a rate bound, not a schedule: below half an hour of daylight there is nothing left to charge
|
|
108
|
+
* in, so the quotient stops describing a charging rate and only its magnitude still moves. Half an hour
|
|
109
|
+
* caps the reservation at twice the battery capacity per hour - still above anything the plant can take in,
|
|
110
|
+
* therefore never loosening a reservation that was meaningful, while keeping the value finite and signed
|
|
111
|
+
* the way the rest of the calculation assumes.
|
|
112
|
+
* @param hoursTilSunset - The raw remaining sun hours, which may be zero or negative.
|
|
113
|
+
* @returns The horizon to divide by, at least the minimum.
|
|
114
|
+
*/
|
|
115
|
+
private reserveHorizonHours;
|
|
116
|
+
/**
|
|
117
|
+
* Reports the release whenever it changes, and never otherwise: this runs every five seconds, and a line
|
|
118
|
+
* per pass would bury the change nobody must miss. The very first pass is remembered but not reported while
|
|
119
|
+
* there is no release, so an installation whose recorded history holds nothing stays silent instead of
|
|
120
|
+
* announcing its unchanged behaviour once per start.
|
|
121
|
+
*
|
|
122
|
+
* Named after the reserve rather than after the reservation, because the same release also answers the ac
|
|
123
|
+
* block - and it does so around the clock, while the reservation only applies in daylight.
|
|
124
|
+
* @param release - The release of this pass, or undefined while the measured consumption cannot carry one.
|
|
125
|
+
*/
|
|
126
|
+
private logMorningRelease;
|
|
45
127
|
private turnOnAdditionalConsumer;
|
|
46
128
|
private turnOffAdditionalConsumer;
|
|
47
129
|
private persist;
|
|
@@ -50,6 +50,16 @@ class VictronDevice extends BaseDevice_1.BaseDevice {
|
|
|
50
50
|
if (!this.settings.hasBattery) {
|
|
51
51
|
return false;
|
|
52
52
|
}
|
|
53
|
+
if (this.morningRelease(this.morningOutlook) !== undefined) {
|
|
54
|
+
// Where the plant's measured consumption alone shows the coming morning clears the reserve, that
|
|
55
|
+
// statement decides and the clock does not. The ladder below is a stand-in for the same question, and
|
|
56
|
+
// its sunny morning branch cannot answer the case it was built for: it reads the day's mean cloud
|
|
57
|
+
// cover, which a day that is dull in the morning and clears up at noon never falls below.
|
|
58
|
+
//
|
|
59
|
+
// Only the release direction. Blocking on the model would mean acting on the fitted band, which is
|
|
60
|
+
// exactly what the shadow exists to defer.
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
53
63
|
const hours = new Date().getHours();
|
|
54
64
|
if (hours > 18) {
|
|
55
65
|
return this.batteryLevel < this.settings.minimumNightTimeAcBatteryLevel;
|
|
@@ -81,6 +91,25 @@ class VictronDevice extends BaseDevice_1.BaseDevice {
|
|
|
81
91
|
}
|
|
82
92
|
return level;
|
|
83
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Delegated rather than written here, exactly the way this device delegates its consumer switching to
|
|
96
|
+
* {@link EnergyManagerUtils.turnOnAdditionalConsumer}: the answer is about the **plant**, and every energy
|
|
97
|
+
* manager owes the same one - so it is written once and no manager is left with a copy or a stub. What this
|
|
98
|
+
* device keeps for itself is the policy: the rungs, the reservation and the ac block.
|
|
99
|
+
* @inheritDoc
|
|
100
|
+
*/
|
|
101
|
+
get morningOutlook() {
|
|
102
|
+
return utils_1.EnergyManagerUtils.morningOutlook(this);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Delegated for the same reason {@link morningOutlook} is: the judgement is about the plant's battery and
|
|
106
|
+
* every manager owes the same one. What this device keeps for itself is what it *does* with the verdict -
|
|
107
|
+
* the reservation and the ac block below.
|
|
108
|
+
* @inheritDoc
|
|
109
|
+
*/
|
|
110
|
+
get morningReserveVerdict() {
|
|
111
|
+
return utils_1.EnergyManagerUtils.morningReserveVerdict(this, this.morningOutlook);
|
|
112
|
+
}
|
|
84
113
|
get victronConsumer() {
|
|
85
114
|
return this._victronConsumer;
|
|
86
115
|
}
|
|
@@ -124,8 +153,16 @@ class VictronDevice extends BaseDevice_1.BaseDevice {
|
|
|
124
153
|
this._iCalculationInterval = null;
|
|
125
154
|
}
|
|
126
155
|
}
|
|
156
|
+
/** @inheritDoc */
|
|
127
157
|
getReport() {
|
|
128
|
-
|
|
158
|
+
var _a;
|
|
159
|
+
const response = [];
|
|
160
|
+
response.push(`Production: ${(_a = this.data.pvInverter.power) !== null && _a !== void 0 ? _a : 0}W`);
|
|
161
|
+
response.push(`Drawing Wattage: ${this.drawingWattage}W`);
|
|
162
|
+
response.push(`Injecting Wattage: ${this.injectingWattage}W`);
|
|
163
|
+
response.push(`Self Consuming Wattage: ${this.selfConsumingWattage}W`);
|
|
164
|
+
response.push(`Battery Level: ${this.batteryLevel}%`);
|
|
165
|
+
return response.join('\n');
|
|
129
166
|
}
|
|
130
167
|
recalculatePowerSharing() {
|
|
131
168
|
this.calculateExcessEnergy();
|
|
@@ -167,6 +204,19 @@ class VictronDevice extends BaseDevice_1.BaseDevice {
|
|
|
167
204
|
this.log(enums_1.LogLevel.Debug, 'No data available from Victron device.');
|
|
168
205
|
return;
|
|
169
206
|
}
|
|
207
|
+
// The manager's loop is what drives the plant's one reading, for every consumer of it. Asked, not
|
|
208
|
+
// scheduled: what it costs is decided by the data situation rather than by a setting, and each of the
|
|
209
|
+
// reads behind this throttles itself, so asking on every pass of the five second loop costs the same
|
|
210
|
+
// number of queries as asking once an hour.
|
|
211
|
+
utils_1.EnergyManagerUtils.refreshEnergyHistory(this);
|
|
212
|
+
// Read once and handed on, rather than each of the three asking again: the projection is arithmetic over
|
|
213
|
+
// one read, and three passes over it would still have to agree with each other.
|
|
214
|
+
const outlook = this.morningOutlook;
|
|
215
|
+
const release = this.morningRelease(outlook);
|
|
216
|
+
this.logMorningRelease(release);
|
|
217
|
+
// Fed from here and from nowhere else. `acBlocked` is a getter anyone may read at any rate, and a tally
|
|
218
|
+
// that counts reads rather than evaluations measures the callers instead of the model.
|
|
219
|
+
utils_1.EnergyManagerUtils.observeModelShadow(this, outlook, release !== undefined);
|
|
170
220
|
// Step 1: Calculate battery need
|
|
171
221
|
const hoursTilSunset = services_1.TimeCallbackService.hoursTilSunset();
|
|
172
222
|
let neededBatteryWattage = 0;
|
|
@@ -176,7 +226,7 @@ class VictronDevice extends BaseDevice_1.BaseDevice {
|
|
|
176
226
|
this.log(enums_1.LogLevel.Debug, 'No battery data available from Victron device.');
|
|
177
227
|
return;
|
|
178
228
|
}
|
|
179
|
-
neededBatteryWattage =
|
|
229
|
+
neededBatteryWattage = this.neededBatteryWattage(release !== undefined, hoursTilSunset);
|
|
180
230
|
// Step 2: Calculate expected solar output
|
|
181
231
|
const solarOutput = (_a = this.data.pvInverter.power) !== null && _a !== void 0 ? _a : 0;
|
|
182
232
|
// Step 3: Calculate expected base consumption
|
|
@@ -205,6 +255,100 @@ class VictronDevice extends BaseDevice_1.BaseDevice {
|
|
|
205
255
|
}
|
|
206
256
|
this.calculatePersistenceValues();
|
|
207
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Rung 1, the only rung of the recorded history that decides: if the state of charge carries past the
|
|
260
|
+
* coming morning low even when no further yield arrives, nothing has to be held back and no consumer has to
|
|
261
|
+
* be kept off.
|
|
262
|
+
*
|
|
263
|
+
* This manager's own policy and rightly here rather than in the shared utils: what a plant does with the
|
|
264
|
+
* outlook is the manager's judgement, and the reservation and ac block it feeds are this device's.
|
|
265
|
+
*
|
|
266
|
+
* Arithmetic over the plant's own measured consumption - no fit, no weights, no window length. That is what
|
|
267
|
+
* separates it from the rung below, which measures against the lower edge of the fitted band and therefore
|
|
268
|
+
* only records (see {@link EnergyManagerUtils.observeModelShadow}).
|
|
269
|
+
* @param outlook - What the plant said, or undefined while it cannot say anything - which includes the
|
|
270
|
+
* absent charge level, so the guard against it stays in the shared utils alone.
|
|
271
|
+
* @returns The release, or undefined while the measured consumption cannot carry it.
|
|
272
|
+
*/
|
|
273
|
+
morningRelease(outlook) {
|
|
274
|
+
if (outlook === undefined) {
|
|
275
|
+
// The fallback holds back more, not less.
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
const reserve = this.settings.minimumMorningSocReserve;
|
|
279
|
+
const worstCaseLowSoc = outlook.worstCaseLowSoc;
|
|
280
|
+
if (worstCaseLowSoc === undefined || worstCaseLowSoc < reserve) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
reason: `worst case low ${utils_1.Utils.round(worstCaseLowSoc, 2)}% holds the reserve ${reserve}% ` +
|
|
285
|
+
'without any further yield',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* How much of the production is held back for the battery.
|
|
290
|
+
*
|
|
291
|
+
* With the release nothing is held back: the coming morning clears the reserve on measured consumption
|
|
292
|
+
* alone. Without it the calculation is the one that was here before - it aims at a full battery by sunset,
|
|
293
|
+
* which over-reserves, and that is what an installation without a usable recorded history keeps.
|
|
294
|
+
* @param released - Whether rung 1 carried the coming morning.
|
|
295
|
+
* @param hoursTilSunset - The horizon the shortfall is spread over.
|
|
296
|
+
* @returns The wattage to hold back.
|
|
297
|
+
*/
|
|
298
|
+
neededBatteryWattage(released, hoursTilSunset) {
|
|
299
|
+
if (released) {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
const missingShare = 1 - this.batteryLevel / 100.0;
|
|
303
|
+
return (missingShare * this.settings.batteryCapacityWattage) / this.reserveHorizonHours(hoursTilSunset);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The horizon the shortfall is actually spread over: the remaining sun hours, but never less than
|
|
307
|
+
* {@link VictronDevice.minimumReserveHorizonHours}.
|
|
308
|
+
*
|
|
309
|
+
* The raw horizon is a difference against the next sunset and runs to zero, while the part of day this
|
|
310
|
+
* calculation is entered under is derived separately from today's sunset plus its own offsets - so the two
|
|
311
|
+
* do not turn at the same instant. In the gap the divisor is first tiny and then negative: a shortfall of a
|
|
312
|
+
* few percent becomes a rate in the megawatt range, at exactly zero an `Infinity` that leaves the process
|
|
313
|
+
* through `toJSON`, and past the turn a *negative* reservation that is added to the excess and offers
|
|
314
|
+
* energy the plant does not have. Every five seconds, and since the horizon is counted in hours rather than
|
|
315
|
+
* minutes, sixty times as large as when this was harmless.
|
|
316
|
+
*
|
|
317
|
+
* The bound is a rate bound, not a schedule: below half an hour of daylight there is nothing left to charge
|
|
318
|
+
* in, so the quotient stops describing a charging rate and only its magnitude still moves. Half an hour
|
|
319
|
+
* caps the reservation at twice the battery capacity per hour - still above anything the plant can take in,
|
|
320
|
+
* therefore never loosening a reservation that was meaningful, while keeping the value finite and signed
|
|
321
|
+
* the way the rest of the calculation assumes.
|
|
322
|
+
* @param hoursTilSunset - The raw remaining sun hours, which may be zero or negative.
|
|
323
|
+
* @returns The horizon to divide by, at least the minimum.
|
|
324
|
+
*/
|
|
325
|
+
reserveHorizonHours(hoursTilSunset) {
|
|
326
|
+
return Math.max(hoursTilSunset, VictronDevice.minimumReserveHorizonHours);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Reports the release whenever it changes, and never otherwise: this runs every five seconds, and a line
|
|
330
|
+
* per pass would bury the change nobody must miss. The very first pass is remembered but not reported while
|
|
331
|
+
* there is no release, so an installation whose recorded history holds nothing stays silent instead of
|
|
332
|
+
* announcing its unchanged behaviour once per start.
|
|
333
|
+
*
|
|
334
|
+
* Named after the reserve rather than after the reservation, because the same release also answers the ac
|
|
335
|
+
* block - and it does so around the clock, while the reservation only applies in daylight.
|
|
336
|
+
* @param release - The release of this pass, or undefined while the measured consumption cannot carry one.
|
|
337
|
+
*/
|
|
338
|
+
logMorningRelease(release) {
|
|
339
|
+
const line = release === undefined
|
|
340
|
+
? 'no model free release, aiming at a full battery by sunset'
|
|
341
|
+
: `released: ${release.reason}`;
|
|
342
|
+
if (line === this._lastReleaseLine) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const first = this._lastReleaseLine === undefined;
|
|
346
|
+
this._lastReleaseLine = line;
|
|
347
|
+
if (first && release === undefined) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
this.log(enums_1.LogLevel.Info, `Morning reserve decision: ${line}`);
|
|
351
|
+
}
|
|
208
352
|
turnOnAdditionalConsumer() {
|
|
209
353
|
const result = utils_1.EnergyManagerUtils.turnOnAdditionalConsumer(this._excessEnergyConsumer, this._lastDeviceChange);
|
|
210
354
|
if (result == undefined) {
|
|
@@ -249,3 +393,8 @@ class VictronDevice extends BaseDevice_1.BaseDevice {
|
|
|
249
393
|
}
|
|
250
394
|
}
|
|
251
395
|
exports.VictronDevice = VictronDevice;
|
|
396
|
+
/**
|
|
397
|
+
* The shortest horizon the battery reservation is spread over, in hours. Not a setting: it is a bound on
|
|
398
|
+
* the arithmetic rather than a dial of the plant - see {@link VictronDevice.reserveHorizonHours}.
|
|
399
|
+
*/
|
|
400
|
+
VictronDevice.minimumReserveHorizonHours = 0.5;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one statement the plant's recorded history is allowed to act on in the victron manager: that the coming
|
|
3
|
+
* morning low clears the reserve on measured consumption alone, with no further yield assumed and no model
|
|
4
|
+
* involved.
|
|
5
|
+
*
|
|
6
|
+
* There is no counterpart for "the morning falls short". Saying so needs the fitted model, and the model runs
|
|
7
|
+
* in the shadow; the absent value therefore means the previous calculation applies, which needs nothing from
|
|
8
|
+
* the history.
|
|
9
|
+
*
|
|
10
|
+
* Not part of the published surface: it is how this manager carries its own decision from one private method
|
|
11
|
+
* to the next, not something a consumer of the library reads.
|
|
12
|
+
*/
|
|
13
|
+
export interface iVictronMorningRelease {
|
|
14
|
+
/** What an operator reads the release by. */
|
|
15
|
+
reason: string;
|
|
16
|
+
}
|
|
@@ -27,6 +27,7 @@ var DeviceCapability;
|
|
|
27
27
|
DeviceCapability[DeviceCapability["garageDoorOpener"] = 21] = "garageDoorOpener";
|
|
28
28
|
DeviceCapability[DeviceCapability["magnetSensor"] = 22] = "magnetSensor";
|
|
29
29
|
DeviceCapability[DeviceCapability["airQualitySensor"] = 23] = "airQualitySensor";
|
|
30
|
+
DeviceCapability[DeviceCapability["fossilGenerator"] = 24] = "fossilGenerator";
|
|
30
31
|
DeviceCapability[DeviceCapability["bluetoothDetector"] = 101] = "bluetoothDetector";
|
|
31
32
|
DeviceCapability[DeviceCapability["trackableDevice"] = 102] = "trackableDevice";
|
|
32
33
|
DeviceCapability[DeviceCapability["scene"] = 103] = "scene";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { iRoomDevice } from './iRoomDevice';
|
|
2
2
|
import { AirQualitySensorChangeAction } from '../../action';
|
|
3
|
-
import { iAirQualityReadings
|
|
3
|
+
import { iAirQualityReadings } from './iAirQualityReadings';
|
|
4
|
+
import { iAirQualitySensor } from './iAirQualitySensor';
|
|
4
5
|
/**
|
|
5
6
|
* This interface represents a device measuring air quality.
|
|
6
7
|
*
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The set of air quality metrics a sensor can report.
|
|
3
|
+
*
|
|
4
|
+
* Devices differ widely in what they measure, so every member is always present but defaults to
|
|
5
|
+
* `UNDEFINED_AIR_QUALITY_VALUE` until the device reports it. This mirrors the sentinel approach of
|
|
6
|
+
* `UNDEFINED_TEMP_VALUE` and spares consumers a null check on every metric; use
|
|
7
|
+
* {@link iAirQualitySensor.reports} to tell "not measured" apart from a measured zero.
|
|
8
|
+
*/
|
|
9
|
+
export interface iAirQualityReadings {
|
|
10
|
+
/**
|
|
11
|
+
* The air quality index as calculated by the device (lower is better)
|
|
12
|
+
*/
|
|
13
|
+
aqi: number;
|
|
14
|
+
/**
|
|
15
|
+
* The carbon dioxide concentration in ppm
|
|
16
|
+
*/
|
|
17
|
+
co2: number;
|
|
18
|
+
/**
|
|
19
|
+
* The nitrogen oxide index (1 equals background level)
|
|
20
|
+
*/
|
|
21
|
+
nox: number;
|
|
22
|
+
/**
|
|
23
|
+
* Particulate matter up to 1.0 µm in µg/m³
|
|
24
|
+
*/
|
|
25
|
+
pm1p0: number;
|
|
26
|
+
/**
|
|
27
|
+
* Particulate matter up to 2.5 µm in µg/m³
|
|
28
|
+
*/
|
|
29
|
+
pm2p5: number;
|
|
30
|
+
/**
|
|
31
|
+
* Particulate matter up to 4.0 µm in µg/m³
|
|
32
|
+
*/
|
|
33
|
+
pm4p0: number;
|
|
34
|
+
/**
|
|
35
|
+
* Particulate matter up to 10.0 µm in µg/m³
|
|
36
|
+
*/
|
|
37
|
+
pm10p0: number;
|
|
38
|
+
/**
|
|
39
|
+
* The total volatile organic compounds in ppb
|
|
40
|
+
*/
|
|
41
|
+
tvoc: number;
|
|
42
|
+
/**
|
|
43
|
+
* The vape detection reading of the device
|
|
44
|
+
*/
|
|
45
|
+
vape: number;
|
|
46
|
+
/**
|
|
47
|
+
* The volatile organic compounds index (100 equals background level)
|
|
48
|
+
*/
|
|
49
|
+
voc: number;
|
|
50
|
+
}
|
|
@@ -1,59 +1,6 @@
|
|
|
1
1
|
import { iJsonOmitKeys } from '../iJsonOmitKeys';
|
|
2
2
|
import { AirQualitySensorChangeAction } from '../../action';
|
|
3
|
-
|
|
4
|
-
* The value reported for a metric the sensor does not measure or has not reported yet.
|
|
5
|
-
*/
|
|
6
|
-
export declare const UNDEFINED_AIR_QUALITY_VALUE = -1;
|
|
7
|
-
/**
|
|
8
|
-
* The set of air quality metrics a sensor can report.
|
|
9
|
-
*
|
|
10
|
-
* Devices differ widely in what they measure, so every member is always present but defaults to
|
|
11
|
-
* {@link UNDEFINED_AIR_QUALITY_VALUE} until the device reports it. This mirrors the sentinel approach of
|
|
12
|
-
* {@link UNDEFINED_TEMP_VALUE} and spares consumers a null check on every metric; use
|
|
13
|
-
* {@link iAirQualitySensor.reports} to tell "not measured" apart from a measured zero.
|
|
14
|
-
*/
|
|
15
|
-
export interface iAirQualityReadings {
|
|
16
|
-
/**
|
|
17
|
-
* The air quality index as calculated by the device (lower is better)
|
|
18
|
-
*/
|
|
19
|
-
aqi: number;
|
|
20
|
-
/**
|
|
21
|
-
* The carbon dioxide concentration in ppm
|
|
22
|
-
*/
|
|
23
|
-
co2: number;
|
|
24
|
-
/**
|
|
25
|
-
* The nitrogen oxide index (1 equals background level)
|
|
26
|
-
*/
|
|
27
|
-
nox: number;
|
|
28
|
-
/**
|
|
29
|
-
* Particulate matter up to 1.0 µm in µg/m³
|
|
30
|
-
*/
|
|
31
|
-
pm1p0: number;
|
|
32
|
-
/**
|
|
33
|
-
* Particulate matter up to 2.5 µm in µg/m³
|
|
34
|
-
*/
|
|
35
|
-
pm2p5: number;
|
|
36
|
-
/**
|
|
37
|
-
* Particulate matter up to 4.0 µm in µg/m³
|
|
38
|
-
*/
|
|
39
|
-
pm4p0: number;
|
|
40
|
-
/**
|
|
41
|
-
* Particulate matter up to 10.0 µm in µg/m³
|
|
42
|
-
*/
|
|
43
|
-
pm10p0: number;
|
|
44
|
-
/**
|
|
45
|
-
* The total volatile organic compounds in ppb
|
|
46
|
-
*/
|
|
47
|
-
tvoc: number;
|
|
48
|
-
/**
|
|
49
|
-
* The vape detection reading of the device
|
|
50
|
-
*/
|
|
51
|
-
vape: number;
|
|
52
|
-
/**
|
|
53
|
-
* The volatile organic compounds index (100 equals background level)
|
|
54
|
-
*/
|
|
55
|
-
voc: number;
|
|
56
|
-
}
|
|
3
|
+
import { iAirQualityReadings } from './iAirQualityReadings';
|
|
57
4
|
/**
|
|
58
5
|
* Common handling for a device measuring air quality.
|
|
59
6
|
*/
|
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UNDEFINED_AIR_QUALITY_VALUE = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* The value reported for a metric the sensor does not measure or has not reported yet.
|
|
6
|
-
*/
|
|
7
|
-
exports.UNDEFINED_AIR_QUALITY_VALUE = -1;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { iBaseDevice } from './iBaseDevice';
|
|
2
2
|
import { iDisposable } from '../iDisposeable';
|
|
3
|
+
import { iEnergyHistoryOutlook } from '../iEnergyHistoryOutlook';
|
|
3
4
|
import { iExcessEnergyConsumer } from './iExcessEnergyConsumer';
|
|
5
|
+
import { iMorningReserveVerdict } from '../iMorningReserveVerdict';
|
|
4
6
|
/**
|
|
5
7
|
* Interface for devices that can manage energy consumption and production.
|
|
6
8
|
*
|
|
@@ -17,6 +19,47 @@ export interface iEnergyManager extends iBaseDevice, iDisposable {
|
|
|
17
19
|
* Whether the Energy Manager actively blocks AC
|
|
18
20
|
*/
|
|
19
21
|
readonly acBlocked: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* What the plant's own recorded history says about the coming morning low, or `undefined` while this
|
|
24
|
+
* manager cannot say anything about it - no battery, no stated capacity, or no charge level to project
|
|
25
|
+
* from.
|
|
26
|
+
*
|
|
27
|
+
* **Read, not asked with a charge level.** The level of the plant's battery is the manager's own, and a
|
|
28
|
+
* consumer that handed one in could hand in a different number than the manager itself reads. It is also
|
|
29
|
+
* why the guard against the "no reading" marker of {@link iBatteryDevice.batteryLevel} lives here, once,
|
|
30
|
+
* rather than in every consumer.
|
|
31
|
+
*
|
|
32
|
+
* **Free to read.** This getter must stay pure arithmetic over what was already read, so that a consumer
|
|
33
|
+
* asking on every one of its own decisions costs no query and no request quota. The reading itself is driven
|
|
34
|
+
* separately, from the manager's own loop.
|
|
35
|
+
*
|
|
36
|
+
* **Delegate, do not implement.** `EnergyManagerUtils.morningOutlook(this)` answers it for any manager, and
|
|
37
|
+
* `EnergyManagerUtils.refreshEnergyHistory(this)` in the manager's loop drives the reading - the same pair
|
|
38
|
+
* both managers in this repository use. There is one energy manager per installation, so the shared
|
|
39
|
+
* implementation keeps one reading and one shadow record; a manager that builds its own would pay the
|
|
40
|
+
* bounded weather backfill twice and split that record in half.
|
|
41
|
+
*
|
|
42
|
+
* The answer states facts about the plant and no arithmetic on them. The judgement that goes with it is
|
|
43
|
+
* {@link morningReserveVerdict}; read that one unless you genuinely need the raw quantities.
|
|
44
|
+
*/
|
|
45
|
+
readonly morningOutlook: iEnergyHistoryOutlook | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the plant expects the coming morning to hold the reserve its battery must not fall below, or
|
|
48
|
+
* `undefined` while it can say neither.
|
|
49
|
+
*
|
|
50
|
+
* **The judgement belongs here, not to the asking consumer.** How low the morning may get is a property of
|
|
51
|
+
* the battery, and how little sun still counts as none a property of the plant's photovoltaic - a fuel
|
|
52
|
+
* burning generator and an air conditioner do not want different answers to that. Where they differ is how
|
|
53
|
+
* sure they want to be before they act, and that is {@link iMorningReserveVerdict.measured}: the verdicts
|
|
54
|
+
* that need no fitted model may move something, the modelled ones are reported and left alone.
|
|
55
|
+
*
|
|
56
|
+
* **Free to read**, for the same reason {@link morningOutlook} is - pure arithmetic over what was already
|
|
57
|
+
* read.
|
|
58
|
+
*
|
|
59
|
+
* **Delegate, do not implement.** `EnergyManagerUtils.morningReserveVerdict(this, this.morningOutlook)`
|
|
60
|
+
* answers it for any manager.
|
|
61
|
+
*/
|
|
62
|
+
readonly morningReserveVerdict: iMorningReserveVerdict | undefined;
|
|
20
63
|
/**
|
|
21
64
|
* The total wattaage being injected into the grid at the moment of last calculation.
|
|
22
65
|
*/
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { iRoomDevice } from './iRoomDevice';
|
|
2
2
|
import { HumiditySensorChangeAction } from '../../action';
|
|
3
3
|
import { iHumiditySensor } from './iHumiditySensor';
|
|
4
|
-
export declare const UNDEFINED_HUMIDITY_VALUE = -1;
|
|
5
4
|
/**
|
|
6
5
|
* This interface represents a humidity sensor device.
|
|
7
6
|
*
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { iRoomDevice } from './iRoomDevice';
|
|
2
2
|
import { TemperatureSensorChangeAction } from '../../action';
|
|
3
3
|
import { iTemperatureSensor } from './iTemperatureSensor';
|
|
4
|
-
export declare const UNDEFINED_TEMP_VALUE = -99;
|
|
5
4
|
/**
|
|
6
5
|
* This interface represents a temperature sensor device.
|
|
7
6
|
*
|
|
@@ -13,7 +13,11 @@ export * from './iGarageDoorOpener';
|
|
|
13
13
|
export * from './iHandle';
|
|
14
14
|
export * from './iHeater';
|
|
15
15
|
export * from './iAirQualityCollector';
|
|
16
|
+
export * from './iAirQualityReadings';
|
|
16
17
|
export * from './iAirQualitySensor';
|
|
18
|
+
export * from './undefinedAirQualityValue';
|
|
19
|
+
export * from './undefinedHumidityValue';
|
|
20
|
+
export * from './undefinedTempValue';
|
|
17
21
|
export * from './iHumidityCollector';
|
|
18
22
|
export * from './iIlluminationSensor';
|
|
19
23
|
export * from './iLamp';
|
|
@@ -28,7 +28,11 @@ __exportStar(require("./iGarageDoorOpener"), exports);
|
|
|
28
28
|
__exportStar(require("./iHandle"), exports);
|
|
29
29
|
__exportStar(require("./iHeater"), exports);
|
|
30
30
|
__exportStar(require("./iAirQualityCollector"), exports);
|
|
31
|
+
__exportStar(require("./iAirQualityReadings"), exports);
|
|
31
32
|
__exportStar(require("./iAirQualitySensor"), exports);
|
|
33
|
+
__exportStar(require("./undefinedAirQualityValue"), exports);
|
|
34
|
+
__exportStar(require("./undefinedHumidityValue"), exports);
|
|
35
|
+
__exportStar(require("./undefinedTempValue"), exports);
|
|
32
36
|
__exportStar(require("./iHumidityCollector"), exports);
|
|
33
37
|
__exportStar(require("./iIlluminationSensor"), exports);
|
|
34
38
|
__exportStar(require("./iLamp"), exports);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UNDEFINED_AIR_QUALITY_VALUE = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The value reported for a metric the sensor does not measure or has not reported yet.
|
|
6
|
+
*/
|
|
7
|
+
exports.UNDEFINED_AIR_QUALITY_VALUE = -1;
|