hoffmation-base 3.8.0 → 4.1.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.
Files changed (160) hide show
  1. package/lib/action/index.d.ts +1 -0
  2. package/lib/action/index.js +3 -1
  3. package/lib/action/soilSensorChangeAction.d.ts +16 -0
  4. package/lib/action/soilSensorChangeAction.js +15 -0
  5. package/lib/devices/dachs/dachs.d.ts +79 -2
  6. package/lib/devices/dachs/dachs.js +214 -18
  7. package/lib/devices/device-cluster.js +7 -0
  8. package/lib/devices/devices.d.ts +36 -1
  9. package/lib/devices/devices.js +67 -0
  10. package/lib/devices/jsObject/jsObjectEnergyManager.d.ts +16 -1
  11. package/lib/devices/jsObject/jsObjectEnergyManager.js +23 -0
  12. package/lib/devices/sharedFunctions/index.d.ts +1 -0
  13. package/lib/devices/sharedFunctions/index.js +1 -0
  14. package/lib/devices/sharedFunctions/soilSensor.d.ts +23 -0
  15. package/lib/devices/sharedFunctions/soilSensor.js +58 -0
  16. package/lib/devices/victron/victron-device.d.ts +83 -1
  17. package/lib/devices/victron/victron-device.js +151 -2
  18. package/lib/devices/victron/victron-morning-release.d.ts +16 -0
  19. package/lib/devices/victron/victron-morning-release.js +2 -0
  20. package/lib/devices/zigbee/BaseDevices/index.d.ts +1 -0
  21. package/lib/devices/zigbee/BaseDevices/index.js +1 -0
  22. package/lib/devices/zigbee/BaseDevices/zigbeeSoilSensor.d.ts +33 -0
  23. package/lib/devices/zigbee/BaseDevices/zigbeeSoilSensor.js +62 -0
  24. package/lib/devices/zigbee/index.d.ts +1 -0
  25. package/lib/devices/zigbee/index.js +1 -0
  26. package/lib/devices/zigbee/zigbeeCooloSoilSensor.d.ts +44 -0
  27. package/lib/devices/zigbee/zigbeeCooloSoilSensor.js +81 -0
  28. package/lib/enums/DeviceCapability.d.ts +2 -0
  29. package/lib/enums/DeviceCapability.js +2 -0
  30. package/lib/enums/commandType.d.ts +1 -0
  31. package/lib/enums/commandType.js +1 -0
  32. package/lib/enums/device-cluster-type.d.ts +2 -1
  33. package/lib/enums/device-cluster-type.js +1 -0
  34. package/lib/enums/deviceType.d.ts +1 -0
  35. package/lib/enums/deviceType.js +1 -0
  36. package/lib/interfaces/baseDevices/iAirQualityCollector.d.ts +2 -1
  37. package/lib/interfaces/baseDevices/iAirQualityReadings.d.ts +50 -0
  38. package/lib/interfaces/baseDevices/iAirQualityReadings.js +2 -0
  39. package/lib/interfaces/baseDevices/iAirQualitySensor.d.ts +1 -54
  40. package/lib/interfaces/baseDevices/iAirQualitySensor.js +0 -5
  41. package/lib/interfaces/baseDevices/iEnergyManager.d.ts +43 -0
  42. package/lib/interfaces/baseDevices/iHumidityCollector.d.ts +0 -1
  43. package/lib/interfaces/baseDevices/iHumidityCollector.js +0 -2
  44. package/lib/interfaces/baseDevices/iSoilCollector.d.ts +23 -0
  45. package/lib/interfaces/baseDevices/iSoilCollector.js +2 -0
  46. package/lib/interfaces/baseDevices/iSoilSensor.d.ts +32 -0
  47. package/lib/interfaces/baseDevices/iSoilSensor.js +2 -0
  48. package/lib/interfaces/baseDevices/iTemperatureCollector.d.ts +0 -1
  49. package/lib/interfaces/baseDevices/iTemperatureCollector.js +0 -2
  50. package/lib/interfaces/baseDevices/index.d.ts +7 -0
  51. package/lib/interfaces/baseDevices/index.js +7 -0
  52. package/lib/interfaces/baseDevices/undefinedAirQualityValue.d.ts +4 -0
  53. package/lib/interfaces/baseDevices/undefinedAirQualityValue.js +7 -0
  54. package/lib/interfaces/baseDevices/undefinedHumidityValue.d.ts +4 -0
  55. package/lib/interfaces/baseDevices/undefinedHumidityValue.js +7 -0
  56. package/lib/interfaces/baseDevices/undefinedSoilMoistureValue.d.ts +6 -0
  57. package/lib/interfaces/baseDevices/undefinedSoilMoistureValue.js +9 -0
  58. package/lib/interfaces/baseDevices/undefinedTempValue.d.ts +4 -0
  59. package/lib/interfaces/baseDevices/undefinedTempValue.js +7 -0
  60. package/lib/interfaces/dachs/KeyListEntity.d.ts +0 -5
  61. package/lib/interfaces/dachs/KeyListEntityResponse.d.ts +6 -0
  62. package/lib/interfaces/dachs/KeyListEntityResponse.js +2 -0
  63. package/lib/interfaces/dachs/index.d.ts +1 -0
  64. package/lib/interfaces/dachs/index.js +1 -0
  65. package/lib/interfaces/deviceSettings/iDachsDeviceSettings.d.ts +15 -0
  66. package/lib/interfaces/iActuatorStateSample.d.ts +13 -0
  67. package/lib/interfaces/iActuatorStateSample.js +2 -0
  68. package/lib/interfaces/iBatteryLevelSample.d.ts +13 -0
  69. package/lib/interfaces/iBatteryLevelSample.js +2 -0
  70. package/lib/interfaces/iConsumptionWindowSample.d.ts +26 -0
  71. package/lib/interfaces/iConsumptionWindowSample.js +2 -0
  72. package/lib/interfaces/iDachsHistoryGateResult.d.ts +29 -0
  73. package/lib/interfaces/iDachsHistoryGateResult.js +2 -0
  74. package/lib/interfaces/iEnergyHistoryBasis.d.ts +26 -0
  75. package/lib/interfaces/iEnergyHistoryBasis.js +2 -0
  76. package/lib/interfaces/iEnergyHistoryEstimate.d.ts +21 -0
  77. package/lib/interfaces/iEnergyHistoryEstimate.js +2 -0
  78. package/lib/interfaces/iEnergyHistoryFeatures.d.ts +21 -0
  79. package/lib/interfaces/iEnergyHistoryFeatures.js +2 -0
  80. package/lib/interfaces/iEnergyHistoryModel.d.ts +21 -0
  81. package/lib/interfaces/iEnergyHistoryModel.js +2 -0
  82. package/lib/interfaces/iEnergyHistoryOptions.d.ts +29 -0
  83. package/lib/interfaces/iEnergyHistoryOptions.js +2 -0
  84. package/lib/interfaces/iEnergyHistoryOutlook.d.ts +41 -0
  85. package/lib/interfaces/iEnergyHistoryOutlook.js +2 -0
  86. package/lib/interfaces/iEnergyHistorySample.d.ts +19 -0
  87. package/lib/interfaces/iEnergyHistorySample.js +2 -0
  88. package/lib/interfaces/iFossilGeneratorRun.d.ts +22 -0
  89. package/lib/interfaces/iFossilGeneratorRun.js +2 -0
  90. package/lib/interfaces/iFossilGeneratorSource.d.ts +21 -0
  91. package/lib/interfaces/iFossilGeneratorSource.js +2 -0
  92. package/lib/interfaces/iMorningReserveVerdict.d.ts +38 -0
  93. package/lib/interfaces/iMorningReserveVerdict.js +2 -0
  94. package/lib/interfaces/iPersist.d.ts +69 -1
  95. package/lib/interfaces/iProjectedSocBand.d.ts +7 -0
  96. package/lib/interfaces/iProjectedSocBand.js +2 -0
  97. package/lib/interfaces/iSoilMoistureSample.d.ts +13 -0
  98. package/lib/interfaces/iSoilMoistureSample.js +2 -0
  99. package/lib/interfaces/iWeatherDaySummary.d.ts +33 -0
  100. package/lib/interfaces/iWeatherDaySummary.js +2 -0
  101. package/lib/interfaces/index.d.ts +17 -0
  102. package/lib/interfaces/index.js +17 -0
  103. package/lib/services/dbo/actuator-state-row.d.ts +14 -0
  104. package/lib/services/dbo/actuator-state-row.js +2 -0
  105. package/lib/services/dbo/battery-level-row.d.ts +16 -0
  106. package/lib/services/dbo/battery-level-row.js +2 -0
  107. package/lib/services/dbo/energy-consumption-row.d.ts +16 -0
  108. package/lib/services/dbo/energy-consumption-row.js +2 -0
  109. package/lib/services/dbo/postgreSqlPersist.d.ts +52 -1
  110. package/lib/services/dbo/postgreSqlPersist.js +392 -27
  111. package/lib/services/dbo/soil-moisture-row.d.ts +14 -0
  112. package/lib/services/dbo/soil-moisture-row.js +2 -0
  113. package/lib/services/dbo/weather-day-summary-row.d.ts +20 -0
  114. package/lib/services/dbo/weather-day-summary-row.js +2 -0
  115. package/lib/services/energy/energy-history-service.d.ts +168 -0
  116. package/lib/services/energy/energy-history-service.js +499 -0
  117. package/lib/services/energy/index.d.ts +1 -0
  118. package/lib/services/energy/index.js +17 -0
  119. package/lib/services/energy/plant-energy-dials.d.ts +22 -0
  120. package/lib/services/energy/plant-energy-dials.js +2 -0
  121. package/lib/services/https-service.d.ts +48 -2
  122. package/lib/services/https-service.js +136 -10
  123. package/lib/services/index.d.ts +1 -0
  124. package/lib/services/index.js +1 -0
  125. package/lib/services/time-callback-service.d.ts +12 -0
  126. package/lib/services/time-callback-service.js +16 -2
  127. package/lib/services/weather/index.d.ts +1 -0
  128. package/lib/services/weather/index.js +1 -0
  129. package/lib/services/weather/open-weather-day-summary.d.ts +25 -0
  130. package/lib/services/weather/open-weather-day-summary.js +2 -0
  131. package/lib/services/weather/weather-day-summary-fetcher.d.ts +6 -0
  132. package/lib/services/weather/weather-day-summary-fetcher.js +2 -0
  133. package/lib/services/weather/weather-history-backfill.d.ts +94 -0
  134. package/lib/services/weather/weather-history-backfill.js +247 -0
  135. package/lib/services/weather/weather-service.js +29 -4
  136. package/lib/settings-service-instance.js +5 -2
  137. package/lib/settingsObjects/deviceSettings/dachsSettings.d.ts +6 -0
  138. package/lib/settingsObjects/deviceSettings/dachsSettings.js +14 -1
  139. package/lib/settingsObjects/deviceSettings/victronDeviceSettings.d.ts +116 -0
  140. package/lib/settingsObjects/deviceSettings/victronDeviceSettings.js +150 -5
  141. package/lib/tsconfig.tsbuildinfo +1 -1
  142. package/lib/utils/dayMs.d.ts +7 -0
  143. package/lib/utils/dayMs.js +10 -0
  144. package/lib/utils/energy-history-dials.d.ts +19 -0
  145. package/lib/utils/energy-history-dials.js +2 -0
  146. package/lib/utils/energy-history-utils.d.ts +143 -0
  147. package/lib/utils/energy-history-utils.js +470 -0
  148. package/lib/utils/energy-manager-utils.d.ts +123 -1
  149. package/lib/utils/energy-manager-utils.js +226 -0
  150. package/lib/utils/index.d.ts +4 -0
  151. package/lib/utils/index.js +4 -0
  152. package/lib/utils/model-shadow.d.ts +61 -0
  153. package/lib/utils/model-shadow.js +83 -0
  154. package/lib/utils/morning-reserve-dials.d.ts +21 -0
  155. package/lib/utils/morning-reserve-dials.js +2 -0
  156. package/lib/utils/morning-verdict.d.ts +5 -0
  157. package/lib/utils/morning-verdict.js +2 -0
  158. package/lib/utils/utils.d.ts +0 -1
  159. package/lib/utils/utils.js +5 -5
  160. package/package.json +3 -2
@@ -1,5 +1,5 @@
1
1
  import { IoBrokerBaseDevice } from '../IoBrokerBaseDevice';
2
- import { iDisposable, iEnergyManager, iExcessEnergyConsumer } from '../../interfaces';
2
+ import { iDisposable, iEnergyHistoryOutlook, iEnergyManager, iExcessEnergyConsumer, iMorningReserveVerdict } from '../../interfaces';
3
3
  import { IoBrokerDeviceInfo } from '../IoBrokerDeviceInfo';
4
4
  import { PhaseState } from '../models';
5
5
  export declare class JsObjectEnergyManager extends IoBrokerBaseDevice implements iEnergyManager, iDisposable {
@@ -24,6 +24,21 @@ export declare class JsObjectEnergyManager extends IoBrokerBaseDevice implements
24
24
  get excessEnergy(): number;
25
25
  /** @inheritDoc */
26
26
  get acBlocked(): boolean;
27
+ /**
28
+ * The same shared implementation the other energy manager delegates to - not a stub, and not a copy. This
29
+ * manager measures three phases and carries neither a battery nor the dials that say how a history is read,
30
+ * so the shared code answers nothing for it and reads nothing for it either. That is the honest answer and
31
+ * the one an installation on this manager already got; it is reached by running the real path rather than
32
+ * by declaring it here.
33
+ * @inheritDoc
34
+ */
35
+ get morningOutlook(): iEnergyHistoryOutlook | undefined;
36
+ /**
37
+ * Delegated for the same reason {@link morningOutlook} is - and it answers nothing here for the same reason
38
+ * too: this manager states neither the reserve nor the sun threshold a verdict would be measured against.
39
+ * @inheritDoc
40
+ */
41
+ get morningReserveVerdict(): iMorningReserveVerdict | undefined;
27
42
  private _excessEnergyConsumerConsumption;
28
43
  /**
29
44
  * Example:
@@ -62,6 +62,25 @@ class JsObjectEnergyManager extends IoBrokerBaseDevice_1.IoBrokerBaseDevice {
62
62
  get acBlocked() {
63
63
  return this.excessEnergy < 200;
64
64
  }
65
+ /**
66
+ * The same shared implementation the other energy manager delegates to - not a stub, and not a copy. This
67
+ * manager measures three phases and carries neither a battery nor the dials that say how a history is read,
68
+ * so the shared code answers nothing for it and reads nothing for it either. That is the honest answer and
69
+ * the one an installation on this manager already got; it is reached by running the real path rather than
70
+ * by declaring it here.
71
+ * @inheritDoc
72
+ */
73
+ get morningOutlook() {
74
+ return utils_1.EnergyManagerUtils.morningOutlook(this);
75
+ }
76
+ /**
77
+ * Delegated for the same reason {@link morningOutlook} is - and it answers nothing here for the same reason
78
+ * too: this manager states neither the reserve nor the sun threshold a verdict would be measured against.
79
+ * @inheritDoc
80
+ */
81
+ get morningReserveVerdict() {
82
+ return utils_1.EnergyManagerUtils.morningReserveVerdict(this, this.morningOutlook);
83
+ }
65
84
  /**
66
85
  * Example:
67
86
  * ________________________________________
@@ -137,6 +156,10 @@ class JsObjectEnergyManager extends IoBrokerBaseDevice_1.IoBrokerBaseDevice {
137
156
  }
138
157
  }
139
158
  calculateExcessEnergy() {
159
+ // The same call the other manager's loop makes, so whether anything is read is decided in one place
160
+ // rather than by which manager happens to ask. This one states no dials, so nothing is read and no
161
+ // request quota is spent.
162
+ utils_1.EnergyManagerUtils.refreshEnergyHistory(this);
140
163
  const phaseProduction = this._currentProduction / 3.0;
141
164
  this._phaseAState = new models_2.PhaseState(this._powerValuePhaseA, phaseProduction);
142
165
  this._phaseBState = new models_2.PhaseState(this._powerValuePhaseB, phaseProduction);
@@ -4,4 +4,5 @@ export * from './handleSensor';
4
4
  export * from './humiditySensor';
5
5
  export * from './lampUtils';
6
6
  export * from './shutterUtils';
7
+ export * from './soilSensor';
7
8
  export * from './temperatureSensor';
@@ -20,4 +20,5 @@ __exportStar(require("./handleSensor"), exports);
20
20
  __exportStar(require("./humiditySensor"), exports);
21
21
  __exportStar(require("./lampUtils"), exports);
22
22
  __exportStar(require("./shutterUtils"), exports);
23
+ __exportStar(require("./soilSensor"), exports);
23
24
  __exportStar(require("./temperatureSensor"), exports);
@@ -0,0 +1,23 @@
1
+ import { iSoilCollector, iSoilSensor } from '../../interfaces';
2
+ import { SoilSensorChangeAction } from '../../action';
3
+ export declare class SoilSensor implements iSoilSensor {
4
+ private readonly _device;
5
+ /** @inheritDoc */
6
+ readonly jsonOmitKeys: string[];
7
+ private _soilMoistureCallbacks;
8
+ private _soilMoisture;
9
+ private readonly _persistSoilSensorInterval;
10
+ constructor(_device: iSoilCollector);
11
+ /** @inheritDoc */
12
+ get soilMoisture(): number;
13
+ /** @inheritDoc */
14
+ set soilMoisture(val: number);
15
+ /** @inheritDoc */
16
+ persist(): void;
17
+ /** @inheritDoc */
18
+ addSoilMoistureCallback(pCallback: (action: SoilSensorChangeAction) => void): void;
19
+ /** @inheritDoc */
20
+ dispose(): void;
21
+ /** @inheritDoc */
22
+ toJSON(): Partial<SoilSensor>;
23
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SoilSensor = void 0;
4
+ const interfaces_1 = require("../../interfaces");
5
+ const action_1 = require("../../action");
6
+ const utils_1 = require("../../utils");
7
+ const services_1 = require("../../services");
8
+ class SoilSensor {
9
+ constructor(_device) {
10
+ this._device = _device;
11
+ /** @inheritDoc */
12
+ this.jsonOmitKeys = ['_device'];
13
+ this._soilMoistureCallbacks = [];
14
+ this._soilMoisture = interfaces_1.UNDEFINED_SOIL_MOISTURE_VALUE;
15
+ this._persistSoilSensorInterval = utils_1.Utils.guardedInterval(() => {
16
+ this.persist();
17
+ }, 5 * 60 * 1000, this, false);
18
+ }
19
+ /** @inheritDoc */
20
+ get soilMoisture() {
21
+ return this._soilMoisture;
22
+ }
23
+ /** @inheritDoc */
24
+ set soilMoisture(val) {
25
+ this._soilMoisture = val;
26
+ for (const cb of this._soilMoistureCallbacks) {
27
+ cb(new action_1.SoilSensorChangeAction(this._device, val));
28
+ }
29
+ }
30
+ /** @inheritDoc */
31
+ persist() {
32
+ var _a;
33
+ if (this._soilMoisture === interfaces_1.UNDEFINED_SOIL_MOISTURE_VALUE) {
34
+ // Nothing was ever reported --> persisting would only write a row of the sentinel, which reads like a
35
+ // measured -1 % in every later query.
36
+ return;
37
+ }
38
+ (_a = services_1.Persistence.dbo) === null || _a === void 0 ? void 0 : _a.persistSoilSensor(this._device);
39
+ }
40
+ /** @inheritDoc */
41
+ addSoilMoistureCallback(pCallback) {
42
+ this._soilMoistureCallbacks.push(pCallback);
43
+ if (this._soilMoisture !== interfaces_1.UNDEFINED_SOIL_MOISTURE_VALUE) {
44
+ pCallback(new action_1.SoilSensorChangeAction(this._device, this._soilMoisture));
45
+ }
46
+ }
47
+ /** @inheritDoc */
48
+ dispose() {
49
+ if (this._persistSoilSensorInterval) {
50
+ clearInterval(this._persistSoilSensorInterval);
51
+ }
52
+ }
53
+ /** @inheritDoc */
54
+ toJSON() {
55
+ return utils_1.Utils.jsonFilter(this, this.jsonOmitKeys);
56
+ }
57
+ }
58
+ exports.SoilSensor = SoilSensor;
@@ -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
- return '';
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 = ((1 - this.batteryLevel / 100.0) * this.settings.batteryCapacityWattage) / hoursTilSunset;
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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -6,6 +6,7 @@ export * from './zigbeeLedRGBCCT';
6
6
  export * from './zigbeeMagnetContact';
7
7
  export * from './zigbeeMotionSensor';
8
8
  export * from './zigbeeShutter';
9
+ export * from './zigbeeSoilSensor';
9
10
  export * from './zigbeeSwitch';
10
11
  export { ZigbeeLamp } from './zigbeeLamp';
11
12
  export { ZigbeeWindowHandle } from './zigbeeWindowHandle';
@@ -23,6 +23,7 @@ __exportStar(require("./zigbeeLedRGBCCT"), exports);
23
23
  __exportStar(require("./zigbeeMagnetContact"), exports);
24
24
  __exportStar(require("./zigbeeMotionSensor"), exports);
25
25
  __exportStar(require("./zigbeeShutter"), exports);
26
+ __exportStar(require("./zigbeeSoilSensor"), exports);
26
27
  __exportStar(require("./zigbeeSwitch"), exports);
27
28
  var zigbeeLamp_1 = require("./zigbeeLamp");
28
29
  Object.defineProperty(exports, "ZigbeeLamp", { enumerable: true, get: function () { return zigbeeLamp_1.ZigbeeLamp; } });
@@ -0,0 +1,33 @@
1
+ import { iBatteryDevice, iSoilCollector, iSoilSensor } from '../../../interfaces';
2
+ import { BatteryLevelChangeAction, SoilSensorChangeAction } from '../../../action';
3
+ import { ZigbeeDevice } from './zigbeeDevice';
4
+ import { Battery } from '../../sharedFunctions';
5
+ import { IoBrokerDeviceInfo } from '../../IoBrokerDeviceInfo';
6
+ import { DeviceType } from '../../../enums';
7
+ /**
8
+ * The common part of every zigbee device measuring the moisture of the soil it sits in.
9
+ *
10
+ * Carries the smallest honest contract: soil moisture and battery, the two states every such sensor reports.
11
+ * Ambient temperature and humidity are deliberately NOT here - a sensor that only reads the soil would then
12
+ * claim capabilities it does not have. A device that measures them adds those axes itself, see
13
+ * {@link ZigbeeCooloSoilSensor}.
14
+ */
15
+ export declare class ZigbeeSoilSensor extends ZigbeeDevice implements iSoilCollector, iBatteryDevice {
16
+ /** @inheritDoc */
17
+ soilSensor: iSoilSensor;
18
+ /** @inheritDoc */
19
+ battery: Battery;
20
+ constructor(pInfo: IoBrokerDeviceInfo, pType: DeviceType);
21
+ /** @inheritDoc */
22
+ get batteryLevel(): number;
23
+ /** @inheritDoc */
24
+ get soilMoisture(): number;
25
+ /** @inheritDoc */
26
+ addBatteryLevelCallback(pCallback: (action: BatteryLevelChangeAction) => void): void;
27
+ /** @inheritDoc */
28
+ addSoilMoistureCallback(pCallback: (action: SoilSensorChangeAction) => void): void;
29
+ /** @inheritDoc */
30
+ update(idSplit: string[], state: ioBroker.State, initial?: boolean): void;
31
+ /** @inheritDoc */
32
+ dispose(): void;
33
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZigbeeSoilSensor = void 0;
4
+ const zigbeeDevice_1 = require("./zigbeeDevice");
5
+ const sharedFunctions_1 = require("../../sharedFunctions");
6
+ const enums_1 = require("../../../enums");
7
+ /**
8
+ * The common part of every zigbee device measuring the moisture of the soil it sits in.
9
+ *
10
+ * Carries the smallest honest contract: soil moisture and battery, the two states every such sensor reports.
11
+ * Ambient temperature and humidity are deliberately NOT here - a sensor that only reads the soil would then
12
+ * claim capabilities it does not have. A device that measures them adds those axes itself, see
13
+ * {@link ZigbeeCooloSoilSensor}.
14
+ */
15
+ class ZigbeeSoilSensor extends zigbeeDevice_1.ZigbeeDevice {
16
+ constructor(pInfo, pType) {
17
+ super(pInfo, pType);
18
+ /** @inheritDoc */
19
+ this.soilSensor = new sharedFunctions_1.SoilSensor(this);
20
+ /** @inheritDoc */
21
+ this.battery = new sharedFunctions_1.Battery(this);
22
+ this.deviceCapabilities.push(enums_1.DeviceCapability.soilSensor);
23
+ this.deviceCapabilities.push(enums_1.DeviceCapability.batteryDriven);
24
+ }
25
+ /** @inheritDoc */
26
+ get batteryLevel() {
27
+ return this.battery.level;
28
+ }
29
+ /** @inheritDoc */
30
+ get soilMoisture() {
31
+ return this.soilSensor.soilMoisture;
32
+ }
33
+ /** @inheritDoc */
34
+ addBatteryLevelCallback(pCallback) {
35
+ this.battery.addBatteryLevelCallback(pCallback);
36
+ }
37
+ /** @inheritDoc */
38
+ addSoilMoistureCallback(pCallback) {
39
+ this.soilSensor.addSoilMoistureCallback(pCallback);
40
+ }
41
+ /** @inheritDoc */
42
+ update(idSplit, state, initial = false) {
43
+ super.update(idSplit, state, initial, true);
44
+ switch (idSplit[3]) {
45
+ case 'battery':
46
+ this.battery.level = state.val;
47
+ if (this.batteryLevel < 20) {
48
+ this.log(enums_1.LogLevel.Warn, 'Das Zigbee Gerät hat unter 20% Batterie.');
49
+ }
50
+ break;
51
+ case 'soil_moisture':
52
+ this.soilSensor.soilMoisture = state.val;
53
+ break;
54
+ }
55
+ }
56
+ /** @inheritDoc */
57
+ dispose() {
58
+ this.soilSensor.dispose();
59
+ super.dispose();
60
+ }
61
+ }
62
+ exports.ZigbeeSoilSensor = ZigbeeSoilSensor;
@@ -6,6 +6,7 @@ export * from './zigbeeAqaraPresence';
6
6
  export * from './zigbeeAquaraVibra';
7
7
  export * from './zigbeeAquaraWater';
8
8
  export * from './zigbeeBlitzShp';
9
+ export * from './zigbeeCooloSoilSensor';
9
10
  export * from './zigbeeEuroHeater';
10
11
  export * from './zigbeeHeimanSmoke';
11
12
  export * from './zigbeeIkeaSteckdose';
@@ -23,6 +23,7 @@ __exportStar(require("./zigbeeAqaraPresence"), exports);
23
23
  __exportStar(require("./zigbeeAquaraVibra"), exports);
24
24
  __exportStar(require("./zigbeeAquaraWater"), exports);
25
25
  __exportStar(require("./zigbeeBlitzShp"), exports);
26
+ __exportStar(require("./zigbeeCooloSoilSensor"), exports);
26
27
  __exportStar(require("./zigbeeEuroHeater"), exports);
27
28
  __exportStar(require("./zigbeeHeimanSmoke"), exports);
28
29
  __exportStar(require("./zigbeeIkeaSteckdose"), exports);