hoffmation-base 3.7.3 → 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/interfaces/settings/iShutterSettings.d.ts +22 -1
- 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.d.ts +31 -0
- package/lib/services/weather/weather-service.js +117 -20
- 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/shutterSettings.d.ts +6 -0
- package/lib/settingsObjects/deviceSettings/shutterSettings.js +12 -2
- 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 +7 -1
- package/lib/utils/utils.js +15 -5
- package/package.json +3 -2
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WeatherHistoryBackfill = void 0;
|
|
4
|
+
const logging_1 = require("../../logging");
|
|
5
|
+
const enums_1 = require("../../enums");
|
|
6
|
+
const HTTPSOptions_1 = require("../HTTPSOptions");
|
|
7
|
+
const https_service_1 = require("../https-service");
|
|
8
|
+
const settings_service_1 = require("../../settings-service");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
/**
|
|
11
|
+
* Fills the gaps in the stored daily weather aggregates from the One Call 3.0 day summary endpoint.
|
|
12
|
+
*
|
|
13
|
+
* Its own service instead of a method on the weather service: that one serves the live forecast, while this
|
|
14
|
+
* one walks a window of past days and must stay bounded and throttled. The full recorded history is far larger
|
|
15
|
+
* than a day's request quota, so the fitting window is the hard limit and the throttle keeps an accidentally
|
|
16
|
+
* widened window from draining the quota in one burst.
|
|
17
|
+
*/
|
|
18
|
+
class WeatherHistoryBackfill {
|
|
19
|
+
/**
|
|
20
|
+
* Fetches the daily weather aggregates that are missing within the fitting window and stores them, plus the
|
|
21
|
+
* running day's aggregate on every run. Past days that are already stored are not fetched again, so a
|
|
22
|
+
* service restart does not cost quota; the running day always is, because it is a forecast that moves. A past
|
|
23
|
+
* day that was already fetched on the running day is not fetched again until the next calendar day.
|
|
24
|
+
* @param persist - The persistence to read the present days from and to write the fetched ones to
|
|
25
|
+
* @param referenceDate - The running day; the window ends on it, inclusive
|
|
26
|
+
* @param historyWindowDays - Length of the history window in days; calls are bounded by it plus one
|
|
27
|
+
* @param fetcher - The fetcher to use, defaults to the configured One Call 3.0 day summary endpoint
|
|
28
|
+
* @param throttleMs - Milliseconds to wait between two calls
|
|
29
|
+
* @returns - The number of days that were fetched and handed to the persistence. Not the number that
|
|
30
|
+
* arrived: `persistWeatherDaySummary` reports nothing back, so whether a row landed cannot be seen from
|
|
31
|
+
* here. Do not read this as a count of stored rows - look in the table for that.
|
|
32
|
+
*/
|
|
33
|
+
static async run(persist, referenceDate, historyWindowDays, fetcher, throttleMs = WeatherHistoryBackfill.defaultThrottleMs) {
|
|
34
|
+
if (persist === undefined) {
|
|
35
|
+
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Debug, 'WeatherHistoryBackfill: no persistence configured --> nothing to do');
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
if (historyWindowDays < 1) {
|
|
39
|
+
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Debug, `WeatherHistoryBackfill: window of ${historyWindowDays} days --> nothing to do`);
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
const activeFetcher = fetcher !== null && fetcher !== void 0 ? fetcher : WeatherHistoryBackfill.createOpenWeatherFetcher();
|
|
43
|
+
if (activeFetcher === undefined) {
|
|
44
|
+
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Warn, 'WeatherHistoryBackfill: no usable weather configuration --> skipped');
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
const days = WeatherHistoryBackfill.windowDays(referenceDate, historyWindowDays);
|
|
48
|
+
const present = new Set((await persist.getWeatherDaySummaries(days[0], days[days.length - 1])).map((summary) => WeatherHistoryBackfill.dayKey(summary.date)));
|
|
49
|
+
// The last day of the window is the running one. Its aggregate is a forecast and moves through the day,
|
|
50
|
+
// so it is fetched on every run and the upsert overwrites it; a past day is archive and does not change,
|
|
51
|
+
// so a stored one is never paid for twice.
|
|
52
|
+
const runningDay = WeatherHistoryBackfill.dayKey(days[days.length - 1]);
|
|
53
|
+
if (WeatherHistoryBackfill.attemptedPastDaysRecordedOn !== runningDay) {
|
|
54
|
+
WeatherHistoryBackfill.resetAttemptedPastDays();
|
|
55
|
+
WeatherHistoryBackfill.attemptedPastDaysRecordedOn = runningDay;
|
|
56
|
+
}
|
|
57
|
+
let fetched = 0;
|
|
58
|
+
// Handed to the persistence, not confirmed as arrived - persistWeatherDaySummary reports nothing back.
|
|
59
|
+
let handedOver = 0;
|
|
60
|
+
let heldBack = 0;
|
|
61
|
+
for (const day of days) {
|
|
62
|
+
const key = WeatherHistoryBackfill.dayKey(day);
|
|
63
|
+
if (key !== runningDay && present.has(key)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (key !== runningDay && WeatherHistoryBackfill.attemptedPastDays.has(key)) {
|
|
67
|
+
heldBack++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (fetched > 0 && throttleMs > 0) {
|
|
71
|
+
await utils_1.Utils.delay(throttleMs);
|
|
72
|
+
}
|
|
73
|
+
fetched++;
|
|
74
|
+
if (key !== runningDay) {
|
|
75
|
+
// Noted before the outcome is known, on purpose - see {@link attemptedPastDays}.
|
|
76
|
+
WeatherHistoryBackfill.attemptedPastDays.add(key);
|
|
77
|
+
}
|
|
78
|
+
const summary = await activeFetcher(day);
|
|
79
|
+
if (summary === undefined) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
persist.persistWeatherDaySummary(summary);
|
|
83
|
+
handedOver++;
|
|
84
|
+
}
|
|
85
|
+
logging_1.ServerLogService.writeLog(handedOver > 0 ? enums_1.LogLevel.Info : enums_1.LogLevel.Debug, `WeatherHistoryBackfill: handed ${handedOver} of ${fetched} fetched day(s) to the persistence within the last ` +
|
|
86
|
+
`${historyWindowDays} days plus today, ${heldBack} day(s) held back after an earlier fetch today`);
|
|
87
|
+
return handedOver;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Forgets which past days were already fetched, so the next run offers the whole window again. Called by
|
|
91
|
+
* `run` itself on a change of the running day; separate so a test can start from a known state.
|
|
92
|
+
*/
|
|
93
|
+
static resetAttemptedPastDays() {
|
|
94
|
+
WeatherHistoryBackfill.attemptedPastDays.clear();
|
|
95
|
+
WeatherHistoryBackfill.attemptedPastDaysRecordedOn = undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Builds the fetcher against the One Call 3.0 day summary endpoint.
|
|
99
|
+
* @param settings - The weather settings to use, defaults to the configured ones
|
|
100
|
+
* @returns - The fetcher, or `undefined` when there is no location or no key to work with
|
|
101
|
+
*/
|
|
102
|
+
static createOpenWeatherFetcher(settings) {
|
|
103
|
+
var _a;
|
|
104
|
+
if (settings === void 0) { settings = (_a = settings_service_1.SettingsService.settings) === null || _a === void 0 ? void 0 : _a.weather; }
|
|
105
|
+
if (settings === undefined || !settings.appid || !settings.lattitude || !settings.longitude) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const location = settings;
|
|
109
|
+
return (date) => WeatherHistoryBackfill.fetchDaySummary(location, date);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The days of the fitting window, oldest first, **including the running day**. The running day is in there
|
|
113
|
+
* on purpose: the gate reads today's cloud cover and maximum temperature out of the same table and through
|
|
114
|
+
* the same reader as the history, so that both sides of the fit are the same quantity from the same field.
|
|
115
|
+
* That is one extra call per run - the alternative was the gate deriving today's figures from a different
|
|
116
|
+
* product, which measurably is not the same number.
|
|
117
|
+
* @param referenceDate - The running day
|
|
118
|
+
* @param historyWindowDays - Length of the history window in days
|
|
119
|
+
* @returns - `historyWindowDays + 1` day starts, the last of which is the running day
|
|
120
|
+
*/
|
|
121
|
+
static windowDays(referenceDate, historyWindowDays) {
|
|
122
|
+
const midnight = new Date(referenceDate);
|
|
123
|
+
midnight.setHours(0, 0, 0, 0);
|
|
124
|
+
const days = [];
|
|
125
|
+
for (let offset = historyWindowDays; offset >= 0; offset--) {
|
|
126
|
+
const day = new Date(midnight);
|
|
127
|
+
// Calendar arithmetic instead of subtracting milliseconds, so a daylight saving change does not shift
|
|
128
|
+
// every older day of the window by an hour.
|
|
129
|
+
day.setDate(day.getDate() - offset);
|
|
130
|
+
days.push(day);
|
|
131
|
+
}
|
|
132
|
+
return days;
|
|
133
|
+
}
|
|
134
|
+
static async fetchDaySummary(settings, date) {
|
|
135
|
+
const day = WeatherHistoryBackfill.dayKey(date);
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
// HTTPSOptions logs the path it is constructed with, so it is constructed with a redacted one and the
|
|
138
|
+
// real query - which carries location and key - is set afterwards. The repository is public.
|
|
139
|
+
// Do NOT collapse these two into one constructor call: that puts the key into a log line. The guard is
|
|
140
|
+
// "keeps key and location out of the path HTTPSOptions is constructed with" in
|
|
141
|
+
// test/services/dachs-history-persistence.test.ts.
|
|
142
|
+
const options = new HTTPSOptions_1.HTTPSOptions(WeatherHistoryBackfill.host, `/data/3.0/onecall/day_summary (${day})`, {}, 'GET', 443);
|
|
143
|
+
options.path =
|
|
144
|
+
`/data/3.0/onecall/day_summary?lat=${settings.lattitude}&lon=${settings.longitude}` +
|
|
145
|
+
`&date=${day}&appid=${settings.appid}&units=metric`;
|
|
146
|
+
// This promise settles because the callback always comes: `HTTPSService.request` reports every way a
|
|
147
|
+
// request can end - an answer, an exhausted retry chain, a failing socket, an endpoint that accepts the
|
|
148
|
+
// connection and then stays silent - to its callback exactly once, and bounds each attempt by its own
|
|
149
|
+
// time limit. A failure arrives as `HTTPSService.failureStatusCode`, which is not 200 and is discarded
|
|
150
|
+
// below like any other unusable answer. Nothing here has to watch the clock as well; a second deadline
|
|
151
|
+
// would only be able to fire before the retry does and throw away the answer it was about to bring.
|
|
152
|
+
https_service_1.HTTPSService.request(options, '', 1, (response, statusCode) => {
|
|
153
|
+
resolve(WeatherHistoryBackfill.parseDaySummary(response, statusCode, date, day));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
static parseDaySummary(response, statusCode, date, day) {
|
|
158
|
+
var _a, _b, _c;
|
|
159
|
+
if (statusCode !== 200) {
|
|
160
|
+
// Neither the answer nor the request is logged: both carry the key and the location.
|
|
161
|
+
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Warn, `WeatherHistoryBackfill: day summary for ${day} answered ${statusCode}`);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const parsed = utils_1.Utils.guardedFunction(() => JSON.parse(response), undefined, `WeatherHistoryBackfill: unreadable day summary for ${day}`);
|
|
165
|
+
// Each leaf is established as a number in a plausible range here rather than trusted from the shape
|
|
166
|
+
// above. This is the boundary: what passes is written into the table the start decision is read from,
|
|
167
|
+
// and nothing between here and that decision looks at the values again.
|
|
168
|
+
const cloudCover = WeatherHistoryBackfill.plausibleReading((_a = parsed === null || parsed === void 0 ? void 0 : parsed.cloud_cover) === null || _a === void 0 ? void 0 : _a.afternoon, 0, 100);
|
|
169
|
+
const tempMin = WeatherHistoryBackfill.plausibleReading((_b = parsed === null || parsed === void 0 ? void 0 : parsed.temperature) === null || _b === void 0 ? void 0 : _b.min, WeatherHistoryBackfill.minTemperature, WeatherHistoryBackfill.maxTemperature);
|
|
170
|
+
const tempMax = WeatherHistoryBackfill.plausibleReading((_c = parsed === null || parsed === void 0 ? void 0 : parsed.temperature) === null || _c === void 0 ? void 0 : _c.max, WeatherHistoryBackfill.minTemperature, WeatherHistoryBackfill.maxTemperature);
|
|
171
|
+
if (cloudCover === undefined || tempMin === undefined || tempMax === undefined) {
|
|
172
|
+
// No substitute values: a made up cloud cover would be fitted as if it had been measured. A value
|
|
173
|
+
// outside its band is discarded exactly like a missing one, for the same reason.
|
|
174
|
+
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Warn, `WeatherHistoryBackfill: incomplete day summary for ${day}`);
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const dayStart = new Date(date);
|
|
178
|
+
dayStart.setHours(0, 0, 0, 0);
|
|
179
|
+
return { date: dayStart, cloudCover: cloudCover, tempMin: tempMin, tempMax: tempMax };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Establishes one field of the answer as a reading: a real number within the band its quantity can occupy.
|
|
183
|
+
*
|
|
184
|
+
* `typeof` rather than `Number(...)`: a value that has to be converted first is not a reading of the
|
|
185
|
+
* quantity, and converting would turn `null` into 0 and `""` into 0 - both of which pass every band. The
|
|
186
|
+
* band itself catches what is numeric but cannot be the quantity, `NaN` and the infinities included, since
|
|
187
|
+
* neither compares inside it.
|
|
188
|
+
* @param value - The field as it arrived
|
|
189
|
+
* @param min - Lowest value the quantity can take
|
|
190
|
+
* @param max - Highest value the quantity can take
|
|
191
|
+
* @returns - The reading, or undefined when the field is not one
|
|
192
|
+
*/
|
|
193
|
+
static plausibleReading(value, min, max) {
|
|
194
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < min || value > max) {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
199
|
+
static dayKey(date) {
|
|
200
|
+
const month = `${date.getMonth() + 1}`.padStart(2, '0');
|
|
201
|
+
const day = `${date.getDate()}`.padStart(2, '0');
|
|
202
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.WeatherHistoryBackfill = WeatherHistoryBackfill;
|
|
206
|
+
/** Milliseconds between two calls to the weather service. */
|
|
207
|
+
WeatherHistoryBackfill.defaultThrottleMs = 1500;
|
|
208
|
+
/**
|
|
209
|
+
* The band a daily air temperature in degrees Celsius can occupy. Wide on purpose - it is there to reject
|
|
210
|
+
* what cannot be a temperature at all, not to second guess the weather service on a hot afternoon. The
|
|
211
|
+
* bounds sit outside the coldest and the hottest reading ever recorded on the planet.
|
|
212
|
+
*/
|
|
213
|
+
WeatherHistoryBackfill.minTemperature = -95;
|
|
214
|
+
WeatherHistoryBackfill.maxTemperature = 60;
|
|
215
|
+
WeatherHistoryBackfill.host = 'api.openweathermap.org';
|
|
216
|
+
/**
|
|
217
|
+
* The past days of the window that were already fetched on the running day, and the running day that was
|
|
218
|
+
* recorded on.
|
|
219
|
+
*
|
|
220
|
+
* The attempt is remembered, not its outcome: a past day is archive, so a second fetch on the same calendar
|
|
221
|
+
* day cannot yield anything the first one did not, whatever became of the first. That also covers the fetch
|
|
222
|
+
* that succeeded while the write did not land - `persistWeatherDaySummary` reports nothing back, so a
|
|
223
|
+
* failing write is invisible from here and would otherwise cost the full window once per run, and the
|
|
224
|
+
* backfill runs far more often than once a day. The running day is remembered alongside, so a passing outage
|
|
225
|
+
* heals on the next calendar day instead of staying suppressed until a restart.
|
|
226
|
+
*/
|
|
227
|
+
WeatherHistoryBackfill.attemptedPastDays = new Set();
|
|
228
|
+
WeatherHistoryBackfill.attemptedPastDaysRecordedOn = undefined;
|
|
@@ -18,6 +18,11 @@ export declare class WeatherService {
|
|
|
18
18
|
* The sun horizontal degree (0 is North)
|
|
19
19
|
*/
|
|
20
20
|
static sunDirection: number;
|
|
21
|
+
/**
|
|
22
|
+
* How many degrees a room has to exceed its desired temperature, before the sun shading
|
|
23
|
+
* triggers on the room temperature alone.
|
|
24
|
+
*/
|
|
25
|
+
private static readonly roomOverheatOffset;
|
|
21
26
|
private static _dataUpdateCbs;
|
|
22
27
|
private static _refreshInterval;
|
|
23
28
|
private static latitude;
|
|
@@ -48,6 +53,32 @@ export declare class WeatherService {
|
|
|
48
53
|
static get currentTemp(): number;
|
|
49
54
|
static willOutsideBeWarmer(referenceTemperature: number, logger: (level: LogLevel, message: string, debugType?: LogDebugType) => void): boolean;
|
|
50
55
|
static weatherRolloPosition(normalPos: number, desiredTemperatur: number, currentTemperatur: number, logger: (level: LogLevel, message: string, debugType?: LogDebugType) => void, shutterSettings: ShutterSettings): number;
|
|
56
|
+
/**
|
|
57
|
+
* Determines whether the room is already warmer than wanted.
|
|
58
|
+
*
|
|
59
|
+
* The outside temperature alone cannot answer whether a room needs shading: a low autumn sun
|
|
60
|
+
* shines deep into a south facing room and heats it up considerably while it stays cool outside.
|
|
61
|
+
* The room's own temperature is the honest measure for that, so it may trigger the shading on its own.
|
|
62
|
+
* @param desiredTemperatur - The temperature the room is supposed to have
|
|
63
|
+
* @param currentTemperatur - The temperature the room currently has
|
|
64
|
+
* @returns True if both values are known and the room exceeds the desired temperature noticeably
|
|
65
|
+
*/
|
|
66
|
+
private static isRoomOverheated;
|
|
67
|
+
/**
|
|
68
|
+
* Determines whether the sun currently shines onto this particular window,
|
|
69
|
+
* based on the angle between sun and window direction.
|
|
70
|
+
* @param shutterSettings - The settings of the shutter in question
|
|
71
|
+
* @param logger - The logging function to use
|
|
72
|
+
* @returns 1 while the sun faces the window, 0 otherwise
|
|
73
|
+
*/
|
|
74
|
+
private static solarExposureShare;
|
|
75
|
+
/**
|
|
76
|
+
* Determines how much solar gain the current sky lets through, as an overcast sky provides
|
|
77
|
+
* too little of it to justify a darkened room.
|
|
78
|
+
* @param shutterSettings - The settings of the shutter in question
|
|
79
|
+
* @returns The share of clear sky, between 0 and 1
|
|
80
|
+
*/
|
|
81
|
+
private static skyClearnessShare;
|
|
51
82
|
static getCurrentCloudiness(): number;
|
|
52
83
|
private static getRainNextMinutes;
|
|
53
84
|
private static getActiveAlerts;
|
|
@@ -168,37 +168,104 @@ class WeatherService {
|
|
|
168
168
|
return referenceTemperature < wData.daily[0].temp.max;
|
|
169
169
|
}
|
|
170
170
|
static weatherRolloPosition(normalPos, desiredTemperatur, currentTemperatur, logger, shutterSettings) {
|
|
171
|
-
|
|
171
|
+
const result = normalPos;
|
|
172
172
|
if (currentTemperatur < desiredTemperatur && currentTemperatur < shutterSettings.heatReductionDirectionThreshold) {
|
|
173
173
|
logger(enums_1.LogLevel.Trace, 'RolloWeatherPosition: Room needs to heat up anyways.');
|
|
174
174
|
return result;
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
if (normalPos < shutterSettings.heatReductionPosition) {
|
|
177
177
|
logger(enums_1.LogLevel.Trace, 'RolloWeatherPosition: Shutter should be down anyways.');
|
|
178
178
|
return result;
|
|
179
179
|
}
|
|
180
|
-
|
|
181
|
-
result = shutterSettings.heatReductionPosition;
|
|
182
|
-
}
|
|
183
|
-
else if (this.hoursTilSunset() < 1) {
|
|
180
|
+
if (this.hoursTilSunset() < 1) {
|
|
184
181
|
logger(enums_1.LogLevel.Trace, "RolloWeatherPosition: It's close to or after todays sunset");
|
|
185
182
|
return result;
|
|
186
183
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
// How much of the full reduction is warranted right now, between 0 (none) and 1 (full).
|
|
185
|
+
let reductionShare;
|
|
186
|
+
if (this.willOutsideBeWarmer(shutterSettings.heatReductionThreshold, logger)) {
|
|
187
|
+
// Insulation regime: at this point the ambient heat outweighs the solar gain, so the closed
|
|
188
|
+
// shutter is worth it as additional window insulation - no matter where the sun stands.
|
|
189
|
+
reductionShare = 1;
|
|
190
|
+
}
|
|
191
|
+
else if (this.willOutsideBeWarmer(shutterSettings.heatReductionDirectionThreshold, logger) ||
|
|
192
|
+
this.isRoomOverheated(desiredTemperatur, currentTemperatur)) {
|
|
193
|
+
// Sun shading regime: only the window the sun actually shines on is worth darkening.
|
|
194
|
+
reductionShare = this.solarExposureShare(shutterSettings, logger) * this.skyClearnessShare(shutterSettings);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
logger(enums_1.LogLevel.Trace, "RolloWeatherPosition: It won't get warm enough today.");
|
|
190
198
|
return result;
|
|
191
199
|
}
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
if (reductionShare <= 0) {
|
|
201
|
+
return result;
|
|
194
202
|
}
|
|
195
|
-
|
|
196
|
-
|
|
203
|
+
const span = normalPos - shutterSettings.heatReductionPosition;
|
|
204
|
+
// Quantized to 10% steps, so slight weather changes don't cause constant shutter movement.
|
|
205
|
+
// A full reduction keeps the configured position verbatim, as that one is a deliberate choice.
|
|
206
|
+
const target = reductionShare >= 1
|
|
207
|
+
? shutterSettings.heatReductionPosition
|
|
208
|
+
: Math.min(normalPos, Math.max(shutterSettings.heatReductionPosition, Math.round((normalPos - span * reductionShare) / 10) * 10));
|
|
209
|
+
if (target !== normalPos) {
|
|
210
|
+
logger(enums_1.LogLevel.Info, `weatherRolloPosition(${normalPos}, ${desiredTemperatur}, ${currentTemperatur}) --> Target: ${target} ` +
|
|
211
|
+
`(share: ${utils_1.Utils.round(reductionShare, 2)}, cloudiness: ${this.getCurrentCloudiness()}%, ` +
|
|
212
|
+
`sunDirection: ${Math.round(this.sunDirection)}°, windowDirection: ${shutterSettings.direction}°)`);
|
|
197
213
|
}
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
return target;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Determines whether the room is already warmer than wanted.
|
|
218
|
+
*
|
|
219
|
+
* The outside temperature alone cannot answer whether a room needs shading: a low autumn sun
|
|
220
|
+
* shines deep into a south facing room and heats it up considerably while it stays cool outside.
|
|
221
|
+
* The room's own temperature is the honest measure for that, so it may trigger the shading on its own.
|
|
222
|
+
* @param desiredTemperatur - The temperature the room is supposed to have
|
|
223
|
+
* @param currentTemperatur - The temperature the room currently has
|
|
224
|
+
* @returns True if both values are known and the room exceeds the desired temperature noticeably
|
|
225
|
+
*/
|
|
226
|
+
static isRoomOverheated(desiredTemperatur, currentTemperatur) {
|
|
227
|
+
if (desiredTemperatur <= interfaces_1.UNDEFINED_TEMP_VALUE || currentTemperatur <= interfaces_1.UNDEFINED_TEMP_VALUE) {
|
|
228
|
+
// Without a heat group we have no room temperature to judge by.
|
|
229
|
+
return false;
|
|
200
230
|
}
|
|
201
|
-
return
|
|
231
|
+
return currentTemperatur >= desiredTemperatur + WeatherService.roomOverheatOffset;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Determines whether the sun currently shines onto this particular window,
|
|
235
|
+
* based on the angle between sun and window direction.
|
|
236
|
+
* @param shutterSettings - The settings of the shutter in question
|
|
237
|
+
* @param logger - The logging function to use
|
|
238
|
+
* @returns 1 while the sun faces the window, 0 otherwise
|
|
239
|
+
*/
|
|
240
|
+
static solarExposureShare(shutterSettings, logger) {
|
|
241
|
+
if (shutterSettings.direction === undefined) {
|
|
242
|
+
// Without a known direction we have to assume the worst case of a fully exposed window.
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
const delta = utils_1.Utils.degreeDistance(shutterSettings.direction, this.sunDirection);
|
|
246
|
+
if (delta > shutterSettings.heatReductionDirectionTolerance) {
|
|
247
|
+
logger(enums_1.LogLevel.Trace, `RolloWeatherPosition: Sun is facing a different direction (${Math.round(delta)}° off).`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
return 1;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Determines how much solar gain the current sky lets through, as an overcast sky provides
|
|
254
|
+
* too little of it to justify a darkened room.
|
|
255
|
+
* @param shutterSettings - The settings of the shutter in question
|
|
256
|
+
* @returns The share of clear sky, between 0 and 1
|
|
257
|
+
*/
|
|
258
|
+
static skyClearnessShare(shutterSettings) {
|
|
259
|
+
const cloudiness = this.getCurrentCloudiness();
|
|
260
|
+
const fullReductionMax = shutterSettings.heatReductionCloudinessThreshold;
|
|
261
|
+
const noReductionMin = shutterSettings.heatReductionMaxCloudiness;
|
|
262
|
+
if (cloudiness <= fullReductionMax || noReductionMin <= fullReductionMax) {
|
|
263
|
+
return 1;
|
|
264
|
+
}
|
|
265
|
+
if (cloudiness >= noReductionMin) {
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
return 1 - (cloudiness - fullReductionMax) / (noReductionMin - fullReductionMax);
|
|
202
269
|
}
|
|
203
270
|
static getCurrentCloudiness() {
|
|
204
271
|
const wData = WeatherService.lastResponse;
|
|
@@ -247,18 +314,43 @@ class WeatherService {
|
|
|
247
314
|
return;
|
|
248
315
|
}
|
|
249
316
|
const host = 'api.openweathermap.org';
|
|
250
|
-
|
|
317
|
+
// HTTPSOptions logs the path it is constructed with, so it is constructed with a redacted one and the
|
|
318
|
+
// real query - which carries key and location - is set afterwards. The repository is public and an
|
|
319
|
+
// operator runs at Debug, which is the level that log line goes out on.
|
|
320
|
+
// Do NOT collapse these two into one constructor call: that puts the key into a log line. The guard is
|
|
321
|
+
// "carries key and location in the request and in no log line" in test/services/weather-service.test.ts.
|
|
322
|
+
const options = new HTTPSOptions_1.HTTPSOptions(host, '/data/3.0/onecall (forecast)', {}, 'GET', 443);
|
|
323
|
+
options.path =
|
|
324
|
+
`/data/3.0/onecall?lat=${WeatherService.latitude}&lon=${WeatherService.longitude}` +
|
|
325
|
+
`&appid=${WeatherService.appID}&units=metric&lang=de`;
|
|
251
326
|
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Debug, 'Send WeatherAPi Request for data update.');
|
|
252
|
-
https_service_1.HTTPSService.request(
|
|
327
|
+
https_service_1.HTTPSService.request(options, '', 5, (response, statusCode) => {
|
|
328
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
329
|
+
// Everything outside the successful range ends the update here, and the last forecast stays in place:
|
|
330
|
+
// an old forecast is a worse answer than a fresh one, an error page is none at all.
|
|
331
|
+
// This covers both ways a request can miss its forecast. `HTTPSService.failureStatusCode` means there
|
|
332
|
+
// was no answer to begin with, and any other code means the endpoint answered something that is not
|
|
333
|
+
// one - a refused key answers 401, an exhausted quota 429. The body cannot be what decides that:
|
|
334
|
+
// those answers are readable JSON as well, so parsing them succeeds and yields a WeatherResponse
|
|
335
|
+
// whose every field is undefined.
|
|
336
|
+
// Only the code is named: the request carries the key and the answer mirrors the coordinate back.
|
|
337
|
+
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Warn, `WeatherAPi request answered ${statusCode} --> no forecast update`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
253
340
|
logging_1.ServerLogService.writeLog(enums_1.LogLevel.Debug, 'WeatherAPi Response erhalten');
|
|
254
|
-
|
|
341
|
+
// The length rather than the body: the answer mirrors the coordinate of the request back, so a raw
|
|
342
|
+
// dump writes the location of the plant into the log even though it carries no key itself.
|
|
343
|
+
logging_1.ServerLogService.writeLog(enums_1.LogLevel.DeepTrace, `WeatherAPi Response of ${response.length} characters received`);
|
|
255
344
|
utils_1.Utils.guardedFunction(() => {
|
|
256
345
|
const responseObj = JSON.parse(response);
|
|
257
346
|
WeatherService.processHourlyWeather(responseObj);
|
|
258
347
|
for (const dataUpdateCbsKey in this._dataUpdateCbs) {
|
|
259
348
|
this._dataUpdateCbs[dataUpdateCbsKey]();
|
|
260
349
|
}
|
|
261
|
-
}, this,
|
|
350
|
+
}, this,
|
|
351
|
+
// Neither the request nor the answer is named here: the first carries the key, the second mirrors
|
|
352
|
+
// the location back. The status of the call is already reported by the two lines above.
|
|
353
|
+
'WeatherService: the answer of the weather API could not be processed');
|
|
262
354
|
});
|
|
263
355
|
}
|
|
264
356
|
static recalcAzimuth() {
|
|
@@ -285,6 +377,11 @@ WeatherService.active = false;
|
|
|
285
377
|
* The milliseconds of one day
|
|
286
378
|
*/
|
|
287
379
|
WeatherService.oneDay = 1000 * 60 * 60 * 24;
|
|
380
|
+
/**
|
|
381
|
+
* How many degrees a room has to exceed its desired temperature, before the sun shading
|
|
382
|
+
* triggers on the room temperature alone.
|
|
383
|
+
*/
|
|
384
|
+
WeatherService.roomOverheatOffset = 1;
|
|
288
385
|
WeatherService._dataUpdateCbs = {};
|
|
289
386
|
/**
|
|
290
387
|
* The highest maximum temperature forecast for today (prevents oscillation)
|
|
@@ -32,7 +32,9 @@ class SettingsServiceInstance {
|
|
|
32
32
|
return lat;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
// A coarse fallback so the sun times resolve at all when no location is configured. Deliberately not a
|
|
36
|
+
// site: one decimal is roughly city scale, and this default is public.
|
|
37
|
+
return 51.0;
|
|
36
38
|
}
|
|
37
39
|
get longitude() {
|
|
38
40
|
var _a, _b;
|
|
@@ -42,7 +44,8 @@ class SettingsServiceInstance {
|
|
|
42
44
|
return longitude;
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
|
-
|
|
47
|
+
// Coarse for the same reason as the latitude above.
|
|
48
|
+
return 7.0;
|
|
46
49
|
}
|
|
47
50
|
initialize(config) {
|
|
48
51
|
this.settings = config;
|
|
@@ -31,6 +31,12 @@ export declare class DachsDeviceSettings extends ActuatorSettings implements iDa
|
|
|
31
31
|
winterMinimumHeatStorageTemp: number;
|
|
32
32
|
/** @inheritDoc */
|
|
33
33
|
winterMinimumPreNightHeatStorageTemp: number;
|
|
34
|
+
/** @inheritDoc */
|
|
35
|
+
summerWarmWaterDesiredMaxTemp: number;
|
|
36
|
+
/** @inheritDoc */
|
|
37
|
+
dachsRatedElectricalWattage: number;
|
|
38
|
+
/** @inheritDoc */
|
|
39
|
+
dachsConversionFactor: number;
|
|
34
40
|
fromPartialObject(data: Partial<DachsDeviceSettings>): void;
|
|
35
41
|
toJSON(): Partial<DachsDeviceSettings>;
|
|
36
42
|
}
|
|
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DachsDeviceSettings = void 0;
|
|
4
4
|
const utils_1 = require("../../utils");
|
|
5
5
|
const actuatorSettings_1 = require("./actuatorSettings");
|
|
6
|
+
// Shared with the energy manager's settings rather than copied: the two would otherwise drift apart on what
|
|
7
|
+
// counts as an unusable share, and an installation would be answered differently depending on which device
|
|
8
|
+
// happened to read the value.
|
|
9
|
+
const victronDeviceSettings_1 = require("./victronDeviceSettings");
|
|
6
10
|
class DachsDeviceSettings extends actuatorSettings_1.ActuatorSettings {
|
|
7
11
|
constructor() {
|
|
8
12
|
super(...arguments);
|
|
@@ -36,9 +40,15 @@ class DachsDeviceSettings extends actuatorSettings_1.ActuatorSettings {
|
|
|
36
40
|
this.winterMinimumHeatStorageTemp = 55;
|
|
37
41
|
/** @inheritDoc */
|
|
38
42
|
this.winterMinimumPreNightHeatStorageTemp = 65;
|
|
43
|
+
/** @inheritDoc */
|
|
44
|
+
this.summerWarmWaterDesiredMaxTemp = 58;
|
|
45
|
+
/** @inheritDoc */
|
|
46
|
+
this.dachsRatedElectricalWattage = 5500;
|
|
47
|
+
/** @inheritDoc */
|
|
48
|
+
this.dachsConversionFactor = 0.8;
|
|
39
49
|
}
|
|
40
50
|
fromPartialObject(data) {
|
|
41
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
51
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
42
52
|
this.disableHeatingRod = (_a = data.disableHeatingRod) !== null && _a !== void 0 ? _a : this.disableHeatingRod;
|
|
43
53
|
this.disableDachsOwnWW = (_b = data.disableDachsOwnWW) !== null && _b !== void 0 ? _b : this.disableDachsOwnWW;
|
|
44
54
|
this.disableDachsTemporarily = (_c = data.disableDachsTemporarily) !== null && _c !== void 0 ? _c : this.disableDachsTemporarily;
|
|
@@ -58,6 +68,9 @@ class DachsDeviceSettings extends actuatorSettings_1.ActuatorSettings {
|
|
|
58
68
|
this.winterMinimumHeatStorageTemp = (_p = data.winterMinimumHeatStorageTemp) !== null && _p !== void 0 ? _p : this.winterMinimumHeatStorageTemp;
|
|
59
69
|
this.winterMinimumPreNightHeatStorageTemp =
|
|
60
70
|
(_q = data.winterMinimumPreNightHeatStorageTemp) !== null && _q !== void 0 ? _q : this.winterMinimumPreNightHeatStorageTemp;
|
|
71
|
+
this.summerWarmWaterDesiredMaxTemp = (_r = data.summerWarmWaterDesiredMaxTemp) !== null && _r !== void 0 ? _r : this.summerWarmWaterDesiredMaxTemp;
|
|
72
|
+
this.dachsRatedElectricalWattage = (_s = data.dachsRatedElectricalWattage) !== null && _s !== void 0 ? _s : this.dachsRatedElectricalWattage;
|
|
73
|
+
this.dachsConversionFactor = (0, victronDeviceSettings_1.acceptedShare)('dachsConversionFactor', data.dachsConversionFactor, this.dachsConversionFactor);
|
|
61
74
|
super.fromPartialObject(data);
|
|
62
75
|
}
|
|
63
76
|
toJSON() {
|
|
@@ -25,6 +25,12 @@ export declare class ShutterSettings extends DeviceSettings implements iShutterS
|
|
|
25
25
|
heatReductionDirectionThreshold: number;
|
|
26
26
|
/** @inheritDoc */
|
|
27
27
|
heatReductionThreshold: number;
|
|
28
|
+
/** @inheritDoc */
|
|
29
|
+
heatReductionDirectionTolerance: number;
|
|
30
|
+
/** @inheritDoc */
|
|
31
|
+
heatReductionCloudinessThreshold: number;
|
|
32
|
+
/** @inheritDoc */
|
|
33
|
+
heatReductionMaxCloudiness: number;
|
|
28
34
|
/**
|
|
29
35
|
* Some shutter give no position feedback on their own, so by knowing the durations in either direction,
|
|
30
36
|
* we can programmatically trigger the callbacks.
|
|
@@ -30,6 +30,12 @@ class ShutterSettings extends deviceSettings_1.DeviceSettings {
|
|
|
30
30
|
this.heatReductionDirectionThreshold = 24;
|
|
31
31
|
/** @inheritDoc */
|
|
32
32
|
this.heatReductionThreshold = 27;
|
|
33
|
+
/** @inheritDoc */
|
|
34
|
+
this.heatReductionDirectionTolerance = 50;
|
|
35
|
+
/** @inheritDoc */
|
|
36
|
+
this.heatReductionCloudinessThreshold = 40;
|
|
37
|
+
/** @inheritDoc */
|
|
38
|
+
this.heatReductionMaxCloudiness = 80;
|
|
33
39
|
/**
|
|
34
40
|
* Some shutter give no position feedback on their own, so by knowing the durations in either direction,
|
|
35
41
|
* we can programmatically trigger the callbacks.
|
|
@@ -38,14 +44,18 @@ class ShutterSettings extends deviceSettings_1.DeviceSettings {
|
|
|
38
44
|
this.triggerPositionUpdateByTime = false;
|
|
39
45
|
}
|
|
40
46
|
fromPartialObject(data) {
|
|
41
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
47
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
42
48
|
this.msTilTop = (_a = data.msTilTop) !== null && _a !== void 0 ? _a : this.msTilTop;
|
|
43
49
|
this.msTilBot = (_b = data.msTilBot) !== null && _b !== void 0 ? _b : this.msTilBot;
|
|
44
50
|
this.direction = (_c = data.direction) !== null && _c !== void 0 ? _c : this.direction;
|
|
45
51
|
this.heatReductionPosition = (_d = data.heatReductionPosition) !== null && _d !== void 0 ? _d : this.heatReductionPosition;
|
|
46
52
|
this.heatReductionThreshold = (_e = data.heatReductionThreshold) !== null && _e !== void 0 ? _e : this.heatReductionThreshold;
|
|
47
53
|
this.heatReductionDirectionThreshold = (_f = data.heatReductionDirectionThreshold) !== null && _f !== void 0 ? _f : this.heatReductionDirectionThreshold;
|
|
48
|
-
this.
|
|
54
|
+
this.heatReductionDirectionTolerance = (_g = data.heatReductionDirectionTolerance) !== null && _g !== void 0 ? _g : this.heatReductionDirectionTolerance;
|
|
55
|
+
this.heatReductionCloudinessThreshold =
|
|
56
|
+
(_h = data.heatReductionCloudinessThreshold) !== null && _h !== void 0 ? _h : this.heatReductionCloudinessThreshold;
|
|
57
|
+
this.heatReductionMaxCloudiness = (_j = data.heatReductionMaxCloudiness) !== null && _j !== void 0 ? _j : this.heatReductionMaxCloudiness;
|
|
58
|
+
this.triggerPositionUpdateByTime = (_k = data.triggerPositionUpdateByTime) !== null && _k !== void 0 ? _k : this.triggerPositionUpdateByTime;
|
|
49
59
|
super.fromPartialObject(data);
|
|
50
60
|
}
|
|
51
61
|
toJSON() {
|