iobroker.assistant 0.1.2
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/LICENSE +21 -0
- package/README.md +176 -0
- package/admin/assistant.svg +19 -0
- package/admin/custom/.vite/manifest.json +169 -0
- package/admin/custom/assets/Components-DcnuVFM6.js +1 -0
- package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomAssistant__mf_owner__1-DogEjv3U.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-Dw5TkU1t.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-BEFjTs9S.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-C4r3udVz.js +1243 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomAssistant__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-DlOBHPL0.js +1494 -0
- package/admin/custom/assets/hostInit-DKMI-sLp.js +1 -0
- package/admin/custom/assets/index-B8wW0kQx.js +1 -0
- package/admin/custom/assets/lib-DpE8sc4h.js +3 -0
- package/admin/custom/assets/mf-entry-bootstrap-0-e7fa582d.js +36 -0
- package/admin/custom/assets/rolldown-runtime-C0FnF6B9.js +1 -0
- package/admin/custom/assets/virtualExposes-DOTO22UK.js +1 -0
- package/admin/custom/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__ConfigCustomAssistant__customComponents_js-CQa59yVi.js +1 -0
- package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__ConfigCustomAssistant__customComponents_js-9g2Fj8tk.js +2 -0
- package/admin/custom/assets/virtual_mf-exposes___mfe_internal__ConfigCustomAssistant__customComponents_js-BTUQTbYo.js +2 -0
- package/admin/custom/assets/vite-preload-helper-B7qeedMF.js +1 -0
- package/admin/custom/customComponents.js +1 -0
- package/admin/custom/customComponents.ssr.js +1 -0
- package/admin/custom/i18n/de.json +58 -0
- package/admin/custom/i18n/en.json +58 -0
- package/admin/custom/i18n/es.json +34 -0
- package/admin/custom/i18n/fr.json +34 -0
- package/admin/custom/i18n/it.json +34 -0
- package/admin/custom/i18n/nl.json +34 -0
- package/admin/custom/i18n/pl.json +34 -0
- package/admin/custom/i18n/pt.json +34 -0
- package/admin/custom/i18n/ru.json +34 -0
- package/admin/custom/i18n/uk.json +34 -0
- package/admin/custom/i18n/zh-cn.json +34 -0
- package/admin/custom/index.html +32 -0
- package/admin/custom/mf-manifest.json +1 -0
- package/admin/custom/mf-stats.json +1 -0
- package/admin/i18n/de.json +130 -0
- package/admin/i18n/en.json +130 -0
- package/admin/i18n/es.json +106 -0
- package/admin/i18n/fr.json +106 -0
- package/admin/i18n/it.json +106 -0
- package/admin/i18n/nl.json +106 -0
- package/admin/i18n/pl.json +106 -0
- package/admin/i18n/pt.json +106 -0
- package/admin/i18n/ru.json +106 -0
- package/admin/i18n/uk.json +106 -0
- package/admin/i18n/zh-cn.json +106 -0
- package/admin/jsonConfig.json +743 -0
- package/admin/sounds/alarm.wav +0 -0
- package/admin/sounds/timer.wav +0 -0
- package/build/lib/alarms.js +281 -0
- package/build/lib/alarms.js.map +1 -0
- package/build/lib/context.js +52 -0
- package/build/lib/context.js.map +1 -0
- package/build/lib/credentials.js +121 -0
- package/build/lib/credentials.js.map +1 -0
- package/build/lib/llm.js +303 -0
- package/build/lib/llm.js.map +1 -0
- package/build/lib/localLlm.js +221 -0
- package/build/lib/localLlm.js.map +1 -0
- package/build/lib/memory.js +190 -0
- package/build/lib/memory.js.map +1 -0
- package/build/lib/nlu.js +929 -0
- package/build/lib/nlu.js.map +1 -0
- package/build/lib/timers.js +245 -0
- package/build/lib/timers.js.map +1 -0
- package/build/lib/tools.js +299 -0
- package/build/lib/tools.js.map +1 -0
- package/build/lib/voice/aws.js +102 -0
- package/build/lib/voice/aws.js.map +1 -0
- package/build/lib/voice/azure.js +144 -0
- package/build/lib/voice/azure.js.map +1 -0
- package/build/lib/voice/download.js +76 -0
- package/build/lib/voice/download.js.map +1 -0
- package/build/lib/voice/engines.js +89 -0
- package/build/lib/voice/engines.js.map +1 -0
- package/build/lib/voice/lang.js +33 -0
- package/build/lib/voice/lang.js.map +1 -0
- package/build/lib/voice/piper.js +191 -0
- package/build/lib/voice/piper.js.map +1 -0
- package/build/lib/voice/protocol.js +35 -0
- package/build/lib/voice/protocol.js.map +1 -0
- package/build/lib/voice/stt.js +116 -0
- package/build/lib/voice/stt.js.map +1 -0
- package/build/lib/voice/tts.js +36 -0
- package/build/lib/voice/tts.js.map +1 -0
- package/build/lib/voice/voiceServer.js +249 -0
- package/build/lib/voice/voiceServer.js.map +1 -0
- package/build/lib/voice/vosk.js +260 -0
- package/build/lib/voice/vosk.js.map +1 -0
- package/build/lib/voice/wyoming.js +320 -0
- package/build/lib/voice/wyoming.js.map +1 -0
- package/build/lib/weather.js +423 -0
- package/build/lib/weather.js.map +1 -0
- package/build/main.js +3152 -0
- package/build/main.js.map +1 -0
- package/io-package.json +528 -0
- package/package.json +68 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Weather understanding — reads a user-selected ioBroker weather adapter's states and normalizes their
|
|
4
|
+
* (wildly different) state trees into one compact {@link WeatherReport} the LLM can answer from.
|
|
5
|
+
*
|
|
6
|
+
* Each weather adapter names its states differently, so there is one small mapper per adapter "kind"
|
|
7
|
+
* (verified against the adapters' source):
|
|
8
|
+
* - `openmeteo` → `open-meteo-weather` (H5N1v2): `<root>.weather.current.*`, `<root>.weather.forecast.dayN.*`
|
|
9
|
+
* - `wunderground` → `weatherunderground`: `<root>.forecast.current.*`, `<root>.forecast.Nd.*`
|
|
10
|
+
* - `openweathermap` → `openweathermap`: `<root>.forecast.current.*`, `<root>.forecast.dayN.*`
|
|
11
|
+
* Adapters without a mapper fall back to a raw state dump (handled by the caller), so the dropdown can still
|
|
12
|
+
* offer them and the LLM can attempt an answer.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.WEATHER_ADAPTERS = void 0;
|
|
16
|
+
exports.wmoText = wmoText;
|
|
17
|
+
exports.buildWeatherReport = buildWeatherReport;
|
|
18
|
+
exports.trimReport = trimReport;
|
|
19
|
+
/**
|
|
20
|
+
* Registry of weather adapters offered in the settings dropdown. `kind` present = fully normalized.
|
|
21
|
+
* `perLocationProbe` (a state path that exists once per location) marks adapters that namespace their data
|
|
22
|
+
* under a per-location device, so the dropdown lists each location separately (its first path segment is
|
|
23
|
+
* used to split the location out of the state id).
|
|
24
|
+
*/
|
|
25
|
+
exports.WEATHER_ADAPTERS = {
|
|
26
|
+
'open-meteo-weather': {
|
|
27
|
+
label: 'Open-Meteo',
|
|
28
|
+
kind: 'openmeteo',
|
|
29
|
+
perLocationProbe: 'weather.current.temperature_2m',
|
|
30
|
+
},
|
|
31
|
+
weatherunderground: { label: 'Weather Underground', kind: 'wunderground' },
|
|
32
|
+
openweathermap: { label: 'OpenWeatherMap', kind: 'openweathermap' },
|
|
33
|
+
brightsky: { label: 'Bright Sky (DWD, free)', kind: 'brightsky' },
|
|
34
|
+
'pirate-weather': { label: 'Pirate Weather', kind: 'pirate' },
|
|
35
|
+
accuweather: { label: 'AccuWeather', kind: 'accuweather' },
|
|
36
|
+
daswetter: {
|
|
37
|
+
label: 'DasWetter (Meteored)',
|
|
38
|
+
kind: 'daswetter',
|
|
39
|
+
perLocationProbe: 'ForecastDaily.Day_1.Temperature_Max',
|
|
40
|
+
},
|
|
41
|
+
yr: { label: 'Yr / met.no', kind: 'yr' },
|
|
42
|
+
dwd: { label: 'DWD (warnings only)' },
|
|
43
|
+
};
|
|
44
|
+
/** WMO weather-code → short English text, used when an adapter gives only a numeric code. */
|
|
45
|
+
const WMO_TEXT = {
|
|
46
|
+
0: 'clear sky',
|
|
47
|
+
1: 'mainly clear',
|
|
48
|
+
2: 'partly cloudy',
|
|
49
|
+
3: 'overcast',
|
|
50
|
+
45: 'fog',
|
|
51
|
+
48: 'depositing rime fog',
|
|
52
|
+
51: 'light drizzle',
|
|
53
|
+
53: 'drizzle',
|
|
54
|
+
55: 'dense drizzle',
|
|
55
|
+
56: 'freezing drizzle',
|
|
56
|
+
57: 'dense freezing drizzle',
|
|
57
|
+
61: 'slight rain',
|
|
58
|
+
63: 'rain',
|
|
59
|
+
65: 'heavy rain',
|
|
60
|
+
66: 'freezing rain',
|
|
61
|
+
67: 'heavy freezing rain',
|
|
62
|
+
71: 'slight snow',
|
|
63
|
+
73: 'snow',
|
|
64
|
+
75: 'heavy snow',
|
|
65
|
+
77: 'snow grains',
|
|
66
|
+
80: 'slight rain showers',
|
|
67
|
+
81: 'rain showers',
|
|
68
|
+
82: 'violent rain showers',
|
|
69
|
+
85: 'snow showers',
|
|
70
|
+
86: 'heavy snow showers',
|
|
71
|
+
95: 'thunderstorm',
|
|
72
|
+
96: 'thunderstorm with slight hail',
|
|
73
|
+
99: 'thunderstorm with heavy hail',
|
|
74
|
+
};
|
|
75
|
+
/** Human text for a WMO code (or '' if unknown). */
|
|
76
|
+
function wmoText(code) {
|
|
77
|
+
const n = typeof code === 'number' ? code : parseInt(String(code), 10);
|
|
78
|
+
return Number.isFinite(n) ? WMO_TEXT[n] || '' : '';
|
|
79
|
+
}
|
|
80
|
+
function num(states, id) {
|
|
81
|
+
const v = states[id];
|
|
82
|
+
if (v === undefined || v === null || v === '') {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const n = typeof v === 'number' ? v : parseFloat(v == null ? '0' : v.toString());
|
|
86
|
+
return Number.isFinite(n) ? n : undefined;
|
|
87
|
+
}
|
|
88
|
+
function str(states, id) {
|
|
89
|
+
const v = states[id];
|
|
90
|
+
if (v == null || v === '') {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return v.toString();
|
|
94
|
+
}
|
|
95
|
+
/** First defined number among several candidate ids (for adapters with windowed/variant fields). */
|
|
96
|
+
function firstNum(states, ids) {
|
|
97
|
+
for (const id of ids) {
|
|
98
|
+
const n = num(states, id);
|
|
99
|
+
if (n !== undefined) {
|
|
100
|
+
return n;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
/** First defined string among several candidate ids. */
|
|
106
|
+
function firstStr(states, ids) {
|
|
107
|
+
for (const id of ids) {
|
|
108
|
+
const s = str(states, id);
|
|
109
|
+
if (s !== undefined) {
|
|
110
|
+
return s;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
/** Number of forecast days present under `dayId(i)` (probe until a day has no max-temp). */
|
|
116
|
+
function countDays(states, maxId, cap = 8) {
|
|
117
|
+
let n = 0;
|
|
118
|
+
for (let i = 0; i < cap; i++) {
|
|
119
|
+
if (num(states, maxId(i)) === undefined && str(states, maxId(i)) === undefined) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
n++;
|
|
123
|
+
}
|
|
124
|
+
return n;
|
|
125
|
+
}
|
|
126
|
+
function mapOpenMeteo(root, states) {
|
|
127
|
+
const c = `${root}.weather.current`;
|
|
128
|
+
const current = {
|
|
129
|
+
temperature: num(states, `${c}.temperature_2m`),
|
|
130
|
+
feelsLike: num(states, `${c}.apparent_temperature`),
|
|
131
|
+
humidity: num(states, `${c}.relative_humidity_2m`),
|
|
132
|
+
condition: str(states, `${c}.weather_text`) || wmoText(states[`${c}.weather_code`]),
|
|
133
|
+
windSpeed: num(states, `${c}.wind_speed_10m`),
|
|
134
|
+
windDir: str(states, `${c}.wind_direction_text`) ?? num(states, `${c}.wind_direction_10m`),
|
|
135
|
+
pressure: num(states, `${c}.pressure_msl`),
|
|
136
|
+
precipitation: num(states, `${c}.precipitation`),
|
|
137
|
+
cloudCover: num(states, `${c}.cloud_cover`),
|
|
138
|
+
};
|
|
139
|
+
const dayBase = (i) => `${root}.weather.forecast.day${i}`;
|
|
140
|
+
const days = countDays(states, i => `${dayBase(i)}.temperature_2m_max`);
|
|
141
|
+
const forecast = [];
|
|
142
|
+
for (let i = 0; i < days; i++) {
|
|
143
|
+
const d = dayBase(i);
|
|
144
|
+
forecast.push({
|
|
145
|
+
day: i,
|
|
146
|
+
date: str(states, `${d}.name_day`) || str(states, `${d}.time`),
|
|
147
|
+
tempMax: num(states, `${d}.temperature_2m_max`),
|
|
148
|
+
tempMin: num(states, `${d}.temperature_2m_min`),
|
|
149
|
+
condition: str(states, `${d}.weather_text`) || wmoText(states[`${d}.weather_code`]),
|
|
150
|
+
precipProbability: num(states, `${d}.precipitation_probability_max`),
|
|
151
|
+
precipitation: num(states, `${d}.precipitation_sum`),
|
|
152
|
+
windSpeed: num(states, `${d}.wind_speed_10m_max`),
|
|
153
|
+
windDir: num(states, `${d}.wind_direction_10m_dominant`),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// Location = the segment between the instance (adapter.N) and the fixed ".weather" suffix.
|
|
157
|
+
const m = root.match(/^[^.]+\.\d+\.(.+)$/);
|
|
158
|
+
return {
|
|
159
|
+
source: 'open-meteo-weather',
|
|
160
|
+
location: m ? m[1].replace(/_/g, ' ') : undefined,
|
|
161
|
+
units: { temp: '°C', wind: 'km/h', precip: 'mm' },
|
|
162
|
+
current,
|
|
163
|
+
forecast,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function mapWunderground(root, states) {
|
|
167
|
+
const c = `${root}.forecast.current`;
|
|
168
|
+
const current = {
|
|
169
|
+
temperature: num(states, `${c}.temp`),
|
|
170
|
+
feelsLike: num(states, `${c}.feelsLike`),
|
|
171
|
+
humidity: num(states, `${c}.relativeHumidity`),
|
|
172
|
+
condition: str(states, `${c}.weather`),
|
|
173
|
+
windSpeed: num(states, `${c}.wind`),
|
|
174
|
+
windDir: str(states, `${c}.windDirection`) ?? num(states, `${c}.windDegrees`),
|
|
175
|
+
pressure: num(states, `${c}.pressure`),
|
|
176
|
+
precipitation: num(states, `${c}.precipitationHour`),
|
|
177
|
+
};
|
|
178
|
+
const dayBase = (i) => `${root}.forecast.${i}d`;
|
|
179
|
+
const days = countDays(states, i => `${dayBase(i)}.tempMax`);
|
|
180
|
+
const forecast = [];
|
|
181
|
+
for (let i = 0; i < days; i++) {
|
|
182
|
+
const d = dayBase(i);
|
|
183
|
+
forecast.push({
|
|
184
|
+
day: i,
|
|
185
|
+
date: str(states, `${d}.date`),
|
|
186
|
+
tempMax: num(states, `${d}.tempMax`),
|
|
187
|
+
tempMin: num(states, `${d}.tempMin`),
|
|
188
|
+
condition: str(states, `${d}.state`),
|
|
189
|
+
precipProbability: num(states, `${d}.precipitationChance`),
|
|
190
|
+
precipitation: num(states, `${d}.precipitationAllDay`),
|
|
191
|
+
windSpeed: num(states, `${d}.windSpeed`),
|
|
192
|
+
windDir: str(states, `${d}.windDirection`),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return { source: 'weatherunderground', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };
|
|
196
|
+
}
|
|
197
|
+
function mapOpenWeatherMap(root, states) {
|
|
198
|
+
const c = `${root}.forecast.current`;
|
|
199
|
+
const current = {
|
|
200
|
+
temperature: num(states, `${c}.temperature`),
|
|
201
|
+
feelsLike: num(states, `${c}.feelsLike`),
|
|
202
|
+
humidity: num(states, `${c}.humidity`),
|
|
203
|
+
condition: str(states, `${c}.state`) || str(states, `${c}.title`),
|
|
204
|
+
windSpeed: num(states, `${c}.windSpeed`),
|
|
205
|
+
windDir: str(states, `${c}.windDirectionText`) ?? num(states, `${c}.windDirection`),
|
|
206
|
+
pressure: num(states, `${c}.pressure`),
|
|
207
|
+
precipitation: num(states, `${c}.precipitationRain`),
|
|
208
|
+
};
|
|
209
|
+
const dayBase = (i) => `${root}.forecast.day${i}`;
|
|
210
|
+
const days = countDays(states, i => `${dayBase(i)}.temperatureMax`);
|
|
211
|
+
const forecast = [];
|
|
212
|
+
for (let i = 0; i < days; i++) {
|
|
213
|
+
const d = dayBase(i);
|
|
214
|
+
forecast.push({
|
|
215
|
+
day: i,
|
|
216
|
+
date: str(states, `${d}.date`),
|
|
217
|
+
tempMax: num(states, `${d}.temperatureMax`),
|
|
218
|
+
tempMin: num(states, `${d}.temperatureMin`),
|
|
219
|
+
condition: str(states, `${d}.state`) || str(states, `${d}.title`),
|
|
220
|
+
precipitation: num(states, `${d}.precipitationRain`),
|
|
221
|
+
windSpeed: num(states, `${d}.windSpeed`),
|
|
222
|
+
windDir: str(states, `${d}.windDirectionText`),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
// OpenWeatherMap reports wind in m/s.
|
|
226
|
+
return { source: 'openweathermap', units: { temp: '°C', wind: 'm/s', precip: 'mm' }, current, forecast };
|
|
227
|
+
}
|
|
228
|
+
function mapBrightsky(root, states) {
|
|
229
|
+
const c = `${root}.weather.current`;
|
|
230
|
+
const current = {
|
|
231
|
+
temperature: num(states, `${c}.temperature`),
|
|
232
|
+
feelsLike: num(states, `${c}.apparent_temperature`),
|
|
233
|
+
humidity: num(states, `${c}.relative_humidity`),
|
|
234
|
+
condition: firstStr(states, [`${c}.conditionUI`, `${c}.condition`]),
|
|
235
|
+
windSpeed: firstNum(states, [`${c}.wind_speed_60`, `${c}.wind_speed_10`, `${c}.wind_speed_30`]),
|
|
236
|
+
windDir: firstStr(states, [`${c}.wind_bearing_text`]) ??
|
|
237
|
+
firstNum(states, [`${c}.wind_direction_60`, `${c}.wind_direction_10`]),
|
|
238
|
+
pressure: num(states, `${c}.pressure_msl`),
|
|
239
|
+
precipitation: firstNum(states, [`${c}.precipitation_60`, `${c}.precipitation_10`]),
|
|
240
|
+
cloudCover: num(states, `${c}.cloud_cover`),
|
|
241
|
+
};
|
|
242
|
+
const dayBase = (i) => `${root}.weather.daily.${i}`;
|
|
243
|
+
const days = countDays(states, i => `${dayBase(i)}.temperature_max`);
|
|
244
|
+
const forecast = [];
|
|
245
|
+
for (let i = 0; i < days; i++) {
|
|
246
|
+
const d = dayBase(i);
|
|
247
|
+
forecast.push({
|
|
248
|
+
day: i,
|
|
249
|
+
date: firstStr(states, [`${d}.dayName_long`, `${d}.dayName_short`]),
|
|
250
|
+
tempMax: num(states, `${d}.temperature_max`),
|
|
251
|
+
tempMin: num(states, `${d}.temperature_min`),
|
|
252
|
+
condition: str(states, `${d}.condition`),
|
|
253
|
+
precipProbability: num(states, `${d}.precipitation_probability_median`),
|
|
254
|
+
precipitation: num(states, `${d}.precipitation`),
|
|
255
|
+
windSpeed: firstNum(states, [`${d}.wind_speed_max`, `${d}.wind_speed_median`]),
|
|
256
|
+
windDir: num(states, `${d}.wind_direction_median`),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return { source: 'brightsky', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };
|
|
260
|
+
}
|
|
261
|
+
function mapPirate(root, states) {
|
|
262
|
+
const c = `${root}.weather.currently`;
|
|
263
|
+
const current = {
|
|
264
|
+
temperature: num(states, `${c}.temperature`),
|
|
265
|
+
feelsLike: num(states, `${c}.apparentTemperature`),
|
|
266
|
+
humidity: num(states, `${c}.humidity`),
|
|
267
|
+
condition: str(states, `${c}.summary`),
|
|
268
|
+
windSpeed: num(states, `${c}.windSpeed`),
|
|
269
|
+
windDir: str(states, `${c}.windBearingText`) ?? num(states, `${c}.windBearing`),
|
|
270
|
+
pressure: num(states, `${c}.pressure`),
|
|
271
|
+
precipitation: num(states, `${c}.precipIntensity`),
|
|
272
|
+
cloudCover: num(states, `${c}.cloudCover`),
|
|
273
|
+
};
|
|
274
|
+
const dayBase = (i) => `${root}.weather.daily.${i}`;
|
|
275
|
+
const days = countDays(states, i => `${dayBase(i)}.temperatureHigh`);
|
|
276
|
+
const forecast = [];
|
|
277
|
+
for (let i = 0; i < days; i++) {
|
|
278
|
+
const d = dayBase(i);
|
|
279
|
+
forecast.push({
|
|
280
|
+
day: i,
|
|
281
|
+
date: str(states, `${d}.time`),
|
|
282
|
+
tempMax: num(states, `${d}.temperatureHigh`),
|
|
283
|
+
tempMin: num(states, `${d}.temperatureLow`),
|
|
284
|
+
condition: str(states, `${d}.summary`),
|
|
285
|
+
precipProbability: num(states, `${d}.precipProbability`),
|
|
286
|
+
precipitation: firstNum(states, [`${d}.precipAccumulation`, `${d}.precipIntensity`]),
|
|
287
|
+
windSpeed: num(states, `${d}.windSpeed`),
|
|
288
|
+
windDir: str(states, `${d}.windBearingText`) ?? num(states, `${d}.windBearing`),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Pirate Weather defaults to SI units (wind m/s).
|
|
292
|
+
return { source: 'pirate-weather', units: { temp: '°C', wind: 'm/s', precip: 'mm' }, current, forecast };
|
|
293
|
+
}
|
|
294
|
+
function mapAccuweather(root, states) {
|
|
295
|
+
const c = `${root}.Current`;
|
|
296
|
+
const current = {
|
|
297
|
+
temperature: num(states, `${c}.Temperature`),
|
|
298
|
+
feelsLike: num(states, `${c}.RealFeelTemperature`),
|
|
299
|
+
humidity: num(states, `${c}.RelativeHumidity`),
|
|
300
|
+
condition: str(states, `${c}.WeatherText`),
|
|
301
|
+
windSpeed: num(states, `${c}.WindSpeed`),
|
|
302
|
+
windDir: str(states, `${c}.WindDirectionText`) ?? num(states, `${c}.WindDirection`),
|
|
303
|
+
pressure: num(states, `${c}.Pressure`),
|
|
304
|
+
cloudCover: num(states, `${c}.CloudCover`),
|
|
305
|
+
uvIndex: num(states, `${c}.UVIndex`),
|
|
306
|
+
};
|
|
307
|
+
// AccuWeather days are the fixed folders Day1..Day5 (i logical → Day${i+1}).
|
|
308
|
+
const dayBase = (i) => `${root}.Daily.Day${i + 1}`;
|
|
309
|
+
const days = countDays(states, i => `${dayBase(i)}.Temperature.Maximum`, 6);
|
|
310
|
+
const forecast = [];
|
|
311
|
+
for (let i = 0; i < days; i++) {
|
|
312
|
+
const d = dayBase(i);
|
|
313
|
+
forecast.push({
|
|
314
|
+
day: i,
|
|
315
|
+
date: str(states, `${d}.Date`),
|
|
316
|
+
tempMax: num(states, `${d}.Temperature.Maximum`),
|
|
317
|
+
tempMin: num(states, `${d}.Temperature.Minimum`),
|
|
318
|
+
condition: firstStr(states, [`${d}.Day.IconPhrase`, `${d}.Day.ShortPhrase`]),
|
|
319
|
+
precipProbability: num(states, `${d}.Day.PrecipitationProbability`),
|
|
320
|
+
windSpeed: num(states, `${d}.Day.WindSpeed`),
|
|
321
|
+
windDir: str(states, `${d}.Day.WindDirection`),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
// Metric config is the default (°C, km/h).
|
|
325
|
+
return { source: 'accuweather', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };
|
|
326
|
+
}
|
|
327
|
+
function mapDasWetter(root, states) {
|
|
328
|
+
// Current is a copy of an hourly record (only present when CopyCurrentHour is enabled).
|
|
329
|
+
const c = `${root}.ForecastHourly.Current`;
|
|
330
|
+
const current = {
|
|
331
|
+
temperature: num(states, `${c}.temperature`),
|
|
332
|
+
feelsLike: num(states, `${c}.temperature_feels_like`),
|
|
333
|
+
humidity: num(states, `${c}.humidity`),
|
|
334
|
+
condition: str(states, `${c}.symbol_description`),
|
|
335
|
+
windSpeed: num(states, `${c}.wind_speed`),
|
|
336
|
+
windDir: str(states, `${c}.wind_direction`),
|
|
337
|
+
pressure: num(states, `${c}.pressure`),
|
|
338
|
+
precipitation: num(states, `${c}.rain`),
|
|
339
|
+
cloudCover: num(states, `${c}.clouds`),
|
|
340
|
+
};
|
|
341
|
+
// Daily folders are Day_1..Day_5 (i logical → Day_${i+1}).
|
|
342
|
+
const dayBase = (i) => `${root}.ForecastDaily.Day_${i + 1}`;
|
|
343
|
+
const days = countDays(states, i => `${dayBase(i)}.Temperature_Max`, 6);
|
|
344
|
+
const forecast = [];
|
|
345
|
+
for (let i = 0; i < days; i++) {
|
|
346
|
+
const d = dayBase(i);
|
|
347
|
+
forecast.push({
|
|
348
|
+
day: i,
|
|
349
|
+
date: firstStr(states, [`${d}.NameOfDay`, `${d}.date`]),
|
|
350
|
+
tempMax: num(states, `${d}.Temperature_Max`),
|
|
351
|
+
tempMin: num(states, `${d}.Temperature_Min`),
|
|
352
|
+
condition: str(states, `${d}.symbol_description`),
|
|
353
|
+
precipProbability: num(states, `${d}.Rain_Probability`),
|
|
354
|
+
precipitation: num(states, `${d}.Rain`),
|
|
355
|
+
windSpeed: num(states, `${d}.Wind_Speed`),
|
|
356
|
+
windDir: str(states, `${d}.Wind_Direction`),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return { source: 'daswetter', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };
|
|
360
|
+
}
|
|
361
|
+
function mapYr(root, states) {
|
|
362
|
+
// met.no is hourly-only; the nearest hour (`forecastHourly.0h`) is the closest thing to "current".
|
|
363
|
+
const c = `${root}.forecastHourly.0h`;
|
|
364
|
+
const current = {
|
|
365
|
+
temperature: num(states, `${c}.air_temperature`),
|
|
366
|
+
humidity: num(states, `${c}.relative_humidity`),
|
|
367
|
+
condition: firstStr(states, [`${c}.1h_summary_text`, `${c}.6h_summary_text`]),
|
|
368
|
+
windSpeed: num(states, `${c}.wind_speed`),
|
|
369
|
+
windDir: num(states, `${c}.wind_from_direction`),
|
|
370
|
+
pressure: num(states, `${c}.air_pressure_at_sea_level`),
|
|
371
|
+
precipitation: num(states, `${c}.1h_precipitation_amount`),
|
|
372
|
+
cloudCover: num(states, `${c}.cloud_area_fraction`),
|
|
373
|
+
};
|
|
374
|
+
// No daily/multi-day branch in the yr adapter → forecast stays empty.
|
|
375
|
+
return { source: 'yr', units: { temp: '°C', wind: 'm/s', precip: 'mm' }, current, forecast: [] };
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Build a normalized {@link WeatherReport} from a weather adapter's flat state map. `adapter` is the adapter
|
|
379
|
+
* name (segment 0 of the instance id); `root` is the state prefix the data lives under (for Open-Meteo this
|
|
380
|
+
* includes the location device, e.g. `open-meteo-weather.0.Berlin`). Returns null for adapters without a
|
|
381
|
+
* mapper (the caller then falls back to a raw dump) or when no data is present.
|
|
382
|
+
*/
|
|
383
|
+
function buildWeatherReport(adapter, root, states) {
|
|
384
|
+
const kind = exports.WEATHER_ADAPTERS[adapter]?.kind;
|
|
385
|
+
if (!kind) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const mappers = {
|
|
389
|
+
openmeteo: mapOpenMeteo,
|
|
390
|
+
wunderground: mapWunderground,
|
|
391
|
+
openweathermap: mapOpenWeatherMap,
|
|
392
|
+
brightsky: mapBrightsky,
|
|
393
|
+
pirate: mapPirate,
|
|
394
|
+
accuweather: mapAccuweather,
|
|
395
|
+
daswetter: mapDasWetter,
|
|
396
|
+
yr: mapYr,
|
|
397
|
+
};
|
|
398
|
+
const report = mappers[kind](root, states);
|
|
399
|
+
// Drop empty/undefined fields from `current` so the report is compact (and a data-less selection
|
|
400
|
+
// collapses to no current at all, rather than a stray empty condition string).
|
|
401
|
+
if (report.current) {
|
|
402
|
+
report.current = Object.fromEntries(Object.entries(report.current).filter(([, v]) => v !== undefined && v !== ''));
|
|
403
|
+
if (!Object.keys(report.current).length) {
|
|
404
|
+
report.current = undefined;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (!report.current && !report.forecast.length) {
|
|
408
|
+
return null; // adapter selected but no data yet
|
|
409
|
+
}
|
|
410
|
+
return report;
|
|
411
|
+
}
|
|
412
|
+
/** Keep only the forecast days a question needs: 'current'/'today' → day 0, 'tomorrow' → day 1, 'week' → all. */
|
|
413
|
+
function trimReport(report, when) {
|
|
414
|
+
const w = (when || '').toLowerCase();
|
|
415
|
+
if (w === 'tomorrow') {
|
|
416
|
+
return { ...report, forecast: report.forecast.filter(d => d.day === 1) };
|
|
417
|
+
}
|
|
418
|
+
if (w === 'today' || w === 'current' || w === 'now') {
|
|
419
|
+
return { ...report, forecast: report.forecast.filter(d => d.day === 0) };
|
|
420
|
+
}
|
|
421
|
+
return report; // 'week' / unspecified → everything
|
|
422
|
+
}
|
|
423
|
+
//# sourceMappingURL=weather.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weather.js","sourceRoot":"","sources":["../../src/lib/weather.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Weather understanding — reads a user-selected ioBroker weather adapter's states and normalizes their\n * (wildly different) state trees into one compact {@link WeatherReport} the LLM can answer from.\n *\n * Each weather adapter names its states differently, so there is one small mapper per adapter \"kind\"\n * (verified against the adapters' source):\n * - `openmeteo` → `open-meteo-weather` (H5N1v2): `<root>.weather.current.*`, `<root>.weather.forecast.dayN.*`\n * - `wunderground` → `weatherunderground`: `<root>.forecast.current.*`, `<root>.forecast.Nd.*`\n * - `openweathermap` → `openweathermap`: `<root>.forecast.current.*`, `<root>.forecast.dayN.*`\n * Adapters without a mapper fall back to a raw state dump (handled by the caller), so the dropdown can still\n * offer them and the LLM can attempt an answer.\n */\n\n/** How to read a given weather adapter's state tree. */\nexport type WeatherKind =\n 'openmeteo' | 'wunderground' | 'openweathermap' | 'brightsky' | 'pirate' | 'accuweather' | 'daswetter' | 'yr';\n\n/**\n * Registry of weather adapters offered in the settings dropdown. `kind` present = fully normalized.\n * `perLocationProbe` (a state path that exists once per location) marks adapters that namespace their data\n * under a per-location device, so the dropdown lists each location separately (its first path segment is\n * used to split the location out of the state id).\n */\nexport const WEATHER_ADAPTERS: Record<string, { label: string; kind?: WeatherKind; perLocationProbe?: string }> = {\n 'open-meteo-weather': {\n label: 'Open-Meteo',\n kind: 'openmeteo',\n perLocationProbe: 'weather.current.temperature_2m',\n },\n weatherunderground: { label: 'Weather Underground', kind: 'wunderground' },\n openweathermap: { label: 'OpenWeatherMap', kind: 'openweathermap' },\n brightsky: { label: 'Bright Sky (DWD, free)', kind: 'brightsky' },\n 'pirate-weather': { label: 'Pirate Weather', kind: 'pirate' },\n accuweather: { label: 'AccuWeather', kind: 'accuweather' },\n daswetter: {\n label: 'DasWetter (Meteored)',\n kind: 'daswetter',\n perLocationProbe: 'ForecastDaily.Day_1.Temperature_Max',\n },\n yr: { label: 'Yr / met.no', kind: 'yr' },\n dwd: { label: 'DWD (warnings only)' },\n};\n\n/** Current conditions, normalized. All fields optional (an adapter may not provide them). */\nexport interface WeatherCurrent {\n temperature?: number;\n feelsLike?: number;\n humidity?: number;\n condition?: string;\n windSpeed?: number;\n windDir?: string | number;\n pressure?: number;\n precipitation?: number;\n cloudCover?: number;\n uvIndex?: number;\n}\n\n/** One forecast day, normalized. */\nexport interface WeatherDay {\n day: number;\n date?: string;\n tempMax?: number;\n tempMin?: number;\n condition?: string;\n precipProbability?: number;\n precipitation?: number;\n windSpeed?: number;\n windDir?: string | number;\n}\n\nexport interface WeatherReport {\n /** Adapter name the data came from. */\n source: string;\n /** Location name, if the root path carries one (Open-Meteo). */\n location?: string;\n /** Unit labels for the numeric values (the weather adapter's default units). */\n units: { temp: string; wind: string; precip: string };\n current?: WeatherCurrent;\n forecast: WeatherDay[];\n}\n\n/** A flat map of state id → value (as returned by getForeignStates, `.val` already unwrapped by the caller). */\nexport type StateValues = Record<string, unknown>;\n\n/** WMO weather-code → short English text, used when an adapter gives only a numeric code. */\nconst WMO_TEXT: Record<number, string> = {\n 0: 'clear sky',\n 1: 'mainly clear',\n 2: 'partly cloudy',\n 3: 'overcast',\n 45: 'fog',\n 48: 'depositing rime fog',\n 51: 'light drizzle',\n 53: 'drizzle',\n 55: 'dense drizzle',\n 56: 'freezing drizzle',\n 57: 'dense freezing drizzle',\n 61: 'slight rain',\n 63: 'rain',\n 65: 'heavy rain',\n 66: 'freezing rain',\n 67: 'heavy freezing rain',\n 71: 'slight snow',\n 73: 'snow',\n 75: 'heavy snow',\n 77: 'snow grains',\n 80: 'slight rain showers',\n 81: 'rain showers',\n 82: 'violent rain showers',\n 85: 'snow showers',\n 86: 'heavy snow showers',\n 95: 'thunderstorm',\n 96: 'thunderstorm with slight hail',\n 99: 'thunderstorm with heavy hail',\n};\n\n/** Human text for a WMO code (or '' if unknown). */\nexport function wmoText(code: unknown): string {\n const n = typeof code === 'number' ? code : parseInt(String(code), 10);\n return Number.isFinite(n) ? WMO_TEXT[n] || '' : '';\n}\n\nfunction num(states: StateValues, id: string): number | undefined {\n const v = states[id];\n if (v === undefined || v === null || v === '') {\n return undefined;\n }\n const n = typeof v === 'number' ? v : parseFloat(v == null ? '0' : (v as string).toString());\n return Number.isFinite(n) ? n : undefined;\n}\n\nfunction str(states: StateValues, id: string): string | undefined {\n const v = states[id];\n if (v == null || v === '') {\n return undefined;\n }\n return (v as string).toString();\n}\n\n/** First defined number among several candidate ids (for adapters with windowed/variant fields). */\nfunction firstNum(states: StateValues, ids: string[]): number | undefined {\n for (const id of ids) {\n const n = num(states, id);\n if (n !== undefined) {\n return n;\n }\n }\n return undefined;\n}\n\n/** First defined string among several candidate ids. */\nfunction firstStr(states: StateValues, ids: string[]): string | undefined {\n for (const id of ids) {\n const s = str(states, id);\n if (s !== undefined) {\n return s;\n }\n }\n return undefined;\n}\n\n/** Number of forecast days present under `dayId(i)` (probe until a day has no max-temp). */\nfunction countDays(states: StateValues, maxId: (i: number) => string, cap = 8): number {\n let n = 0;\n for (let i = 0; i < cap; i++) {\n if (num(states, maxId(i)) === undefined && str(states, maxId(i)) === undefined) {\n break;\n }\n n++;\n }\n return n;\n}\n\nfunction mapOpenMeteo(root: string, states: StateValues): WeatherReport {\n const c = `${root}.weather.current`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.temperature_2m`),\n feelsLike: num(states, `${c}.apparent_temperature`),\n humidity: num(states, `${c}.relative_humidity_2m`),\n condition: str(states, `${c}.weather_text`) || wmoText(states[`${c}.weather_code`]),\n windSpeed: num(states, `${c}.wind_speed_10m`),\n windDir: str(states, `${c}.wind_direction_text`) ?? num(states, `${c}.wind_direction_10m`),\n pressure: num(states, `${c}.pressure_msl`),\n precipitation: num(states, `${c}.precipitation`),\n cloudCover: num(states, `${c}.cloud_cover`),\n };\n const dayBase = (i: number): string => `${root}.weather.forecast.day${i}`;\n const days = countDays(states, i => `${dayBase(i)}.temperature_2m_max`);\n const forecast: WeatherDay[] = [];\n for (let i = 0; i < days; i++) {\n const d = dayBase(i);\n forecast.push({\n day: i,\n date: str(states, `${d}.name_day`) || str(states, `${d}.time`),\n tempMax: num(states, `${d}.temperature_2m_max`),\n tempMin: num(states, `${d}.temperature_2m_min`),\n condition: str(states, `${d}.weather_text`) || wmoText(states[`${d}.weather_code`]),\n precipProbability: num(states, `${d}.precipitation_probability_max`),\n precipitation: num(states, `${d}.precipitation_sum`),\n windSpeed: num(states, `${d}.wind_speed_10m_max`),\n windDir: num(states, `${d}.wind_direction_10m_dominant`),\n });\n }\n // Location = the segment between the instance (adapter.N) and the fixed \".weather\" suffix.\n const m = root.match(/^[^.]+\\.\\d+\\.(.+)$/);\n return {\n source: 'open-meteo-weather',\n location: m ? m[1].replace(/_/g, ' ') : undefined,\n units: { temp: '°C', wind: 'km/h', precip: 'mm' },\n current,\n forecast,\n };\n}\n\nfunction mapWunderground(root: string, states: StateValues): WeatherReport {\n const c = `${root}.forecast.current`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.temp`),\n feelsLike: num(states, `${c}.feelsLike`),\n humidity: num(states, `${c}.relativeHumidity`),\n condition: str(states, `${c}.weather`),\n windSpeed: num(states, `${c}.wind`),\n windDir: str(states, `${c}.windDirection`) ?? num(states, `${c}.windDegrees`),\n pressure: num(states, `${c}.pressure`),\n precipitation: num(states, `${c}.precipitationHour`),\n };\n const dayBase = (i: number): string => `${root}.forecast.${i}d`;\n const days = countDays(states, i => `${dayBase(i)}.tempMax`);\n const forecast: WeatherDay[] = [];\n for (let i = 0; i < days; i++) {\n const d = dayBase(i);\n forecast.push({\n day: i,\n date: str(states, `${d}.date`),\n tempMax: num(states, `${d}.tempMax`),\n tempMin: num(states, `${d}.tempMin`),\n condition: str(states, `${d}.state`),\n precipProbability: num(states, `${d}.precipitationChance`),\n precipitation: num(states, `${d}.precipitationAllDay`),\n windSpeed: num(states, `${d}.windSpeed`),\n windDir: str(states, `${d}.windDirection`),\n });\n }\n return { source: 'weatherunderground', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };\n}\n\nfunction mapOpenWeatherMap(root: string, states: StateValues): WeatherReport {\n const c = `${root}.forecast.current`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.temperature`),\n feelsLike: num(states, `${c}.feelsLike`),\n humidity: num(states, `${c}.humidity`),\n condition: str(states, `${c}.state`) || str(states, `${c}.title`),\n windSpeed: num(states, `${c}.windSpeed`),\n windDir: str(states, `${c}.windDirectionText`) ?? num(states, `${c}.windDirection`),\n pressure: num(states, `${c}.pressure`),\n precipitation: num(states, `${c}.precipitationRain`),\n };\n const dayBase = (i: number): string => `${root}.forecast.day${i}`;\n const days = countDays(states, i => `${dayBase(i)}.temperatureMax`);\n const forecast: WeatherDay[] = [];\n for (let i = 0; i < days; i++) {\n const d = dayBase(i);\n forecast.push({\n day: i,\n date: str(states, `${d}.date`),\n tempMax: num(states, `${d}.temperatureMax`),\n tempMin: num(states, `${d}.temperatureMin`),\n condition: str(states, `${d}.state`) || str(states, `${d}.title`),\n precipitation: num(states, `${d}.precipitationRain`),\n windSpeed: num(states, `${d}.windSpeed`),\n windDir: str(states, `${d}.windDirectionText`),\n });\n }\n // OpenWeatherMap reports wind in m/s.\n return { source: 'openweathermap', units: { temp: '°C', wind: 'm/s', precip: 'mm' }, current, forecast };\n}\n\nfunction mapBrightsky(root: string, states: StateValues): WeatherReport {\n const c = `${root}.weather.current`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.temperature`),\n feelsLike: num(states, `${c}.apparent_temperature`),\n humidity: num(states, `${c}.relative_humidity`),\n condition: firstStr(states, [`${c}.conditionUI`, `${c}.condition`]),\n windSpeed: firstNum(states, [`${c}.wind_speed_60`, `${c}.wind_speed_10`, `${c}.wind_speed_30`]),\n windDir:\n firstStr(states, [`${c}.wind_bearing_text`]) ??\n firstNum(states, [`${c}.wind_direction_60`, `${c}.wind_direction_10`]),\n pressure: num(states, `${c}.pressure_msl`),\n precipitation: firstNum(states, [`${c}.precipitation_60`, `${c}.precipitation_10`]),\n cloudCover: num(states, `${c}.cloud_cover`),\n };\n const dayBase = (i: number): string => `${root}.weather.daily.${i}`;\n const days = countDays(states, i => `${dayBase(i)}.temperature_max`);\n const forecast: WeatherDay[] = [];\n for (let i = 0; i < days; i++) {\n const d = dayBase(i);\n forecast.push({\n day: i,\n date: firstStr(states, [`${d}.dayName_long`, `${d}.dayName_short`]),\n tempMax: num(states, `${d}.temperature_max`),\n tempMin: num(states, `${d}.temperature_min`),\n condition: str(states, `${d}.condition`),\n precipProbability: num(states, `${d}.precipitation_probability_median`),\n precipitation: num(states, `${d}.precipitation`),\n windSpeed: firstNum(states, [`${d}.wind_speed_max`, `${d}.wind_speed_median`]),\n windDir: num(states, `${d}.wind_direction_median`),\n });\n }\n return { source: 'brightsky', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };\n}\n\nfunction mapPirate(root: string, states: StateValues): WeatherReport {\n const c = `${root}.weather.currently`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.temperature`),\n feelsLike: num(states, `${c}.apparentTemperature`),\n humidity: num(states, `${c}.humidity`),\n condition: str(states, `${c}.summary`),\n windSpeed: num(states, `${c}.windSpeed`),\n windDir: str(states, `${c}.windBearingText`) ?? num(states, `${c}.windBearing`),\n pressure: num(states, `${c}.pressure`),\n precipitation: num(states, `${c}.precipIntensity`),\n cloudCover: num(states, `${c}.cloudCover`),\n };\n const dayBase = (i: number): string => `${root}.weather.daily.${i}`;\n const days = countDays(states, i => `${dayBase(i)}.temperatureHigh`);\n const forecast: WeatherDay[] = [];\n for (let i = 0; i < days; i++) {\n const d = dayBase(i);\n forecast.push({\n day: i,\n date: str(states, `${d}.time`),\n tempMax: num(states, `${d}.temperatureHigh`),\n tempMin: num(states, `${d}.temperatureLow`),\n condition: str(states, `${d}.summary`),\n precipProbability: num(states, `${d}.precipProbability`),\n precipitation: firstNum(states, [`${d}.precipAccumulation`, `${d}.precipIntensity`]),\n windSpeed: num(states, `${d}.windSpeed`),\n windDir: str(states, `${d}.windBearingText`) ?? num(states, `${d}.windBearing`),\n });\n }\n // Pirate Weather defaults to SI units (wind m/s).\n return { source: 'pirate-weather', units: { temp: '°C', wind: 'm/s', precip: 'mm' }, current, forecast };\n}\n\nfunction mapAccuweather(root: string, states: StateValues): WeatherReport {\n const c = `${root}.Current`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.Temperature`),\n feelsLike: num(states, `${c}.RealFeelTemperature`),\n humidity: num(states, `${c}.RelativeHumidity`),\n condition: str(states, `${c}.WeatherText`),\n windSpeed: num(states, `${c}.WindSpeed`),\n windDir: str(states, `${c}.WindDirectionText`) ?? num(states, `${c}.WindDirection`),\n pressure: num(states, `${c}.Pressure`),\n cloudCover: num(states, `${c}.CloudCover`),\n uvIndex: num(states, `${c}.UVIndex`),\n };\n // AccuWeather days are the fixed folders Day1..Day5 (i logical → Day${i+1}).\n const dayBase = (i: number): string => `${root}.Daily.Day${i + 1}`;\n const days = countDays(states, i => `${dayBase(i)}.Temperature.Maximum`, 6);\n const forecast: WeatherDay[] = [];\n for (let i = 0; i < days; i++) {\n const d = dayBase(i);\n forecast.push({\n day: i,\n date: str(states, `${d}.Date`),\n tempMax: num(states, `${d}.Temperature.Maximum`),\n tempMin: num(states, `${d}.Temperature.Minimum`),\n condition: firstStr(states, [`${d}.Day.IconPhrase`, `${d}.Day.ShortPhrase`]),\n precipProbability: num(states, `${d}.Day.PrecipitationProbability`),\n windSpeed: num(states, `${d}.Day.WindSpeed`),\n windDir: str(states, `${d}.Day.WindDirection`),\n });\n }\n // Metric config is the default (°C, km/h).\n return { source: 'accuweather', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };\n}\n\nfunction mapDasWetter(root: string, states: StateValues): WeatherReport {\n // Current is a copy of an hourly record (only present when CopyCurrentHour is enabled).\n const c = `${root}.ForecastHourly.Current`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.temperature`),\n feelsLike: num(states, `${c}.temperature_feels_like`),\n humidity: num(states, `${c}.humidity`),\n condition: str(states, `${c}.symbol_description`),\n windSpeed: num(states, `${c}.wind_speed`),\n windDir: str(states, `${c}.wind_direction`),\n pressure: num(states, `${c}.pressure`),\n precipitation: num(states, `${c}.rain`),\n cloudCover: num(states, `${c}.clouds`),\n };\n // Daily folders are Day_1..Day_5 (i logical → Day_${i+1}).\n const dayBase = (i: number): string => `${root}.ForecastDaily.Day_${i + 1}`;\n const days = countDays(states, i => `${dayBase(i)}.Temperature_Max`, 6);\n const forecast: WeatherDay[] = [];\n for (let i = 0; i < days; i++) {\n const d = dayBase(i);\n forecast.push({\n day: i,\n date: firstStr(states, [`${d}.NameOfDay`, `${d}.date`]),\n tempMax: num(states, `${d}.Temperature_Max`),\n tempMin: num(states, `${d}.Temperature_Min`),\n condition: str(states, `${d}.symbol_description`),\n precipProbability: num(states, `${d}.Rain_Probability`),\n precipitation: num(states, `${d}.Rain`),\n windSpeed: num(states, `${d}.Wind_Speed`),\n windDir: str(states, `${d}.Wind_Direction`),\n });\n }\n return { source: 'daswetter', units: { temp: '°C', wind: 'km/h', precip: 'mm' }, current, forecast };\n}\n\nfunction mapYr(root: string, states: StateValues): WeatherReport {\n // met.no is hourly-only; the nearest hour (`forecastHourly.0h`) is the closest thing to \"current\".\n const c = `${root}.forecastHourly.0h`;\n const current: WeatherCurrent = {\n temperature: num(states, `${c}.air_temperature`),\n humidity: num(states, `${c}.relative_humidity`),\n condition: firstStr(states, [`${c}.1h_summary_text`, `${c}.6h_summary_text`]),\n windSpeed: num(states, `${c}.wind_speed`),\n windDir: num(states, `${c}.wind_from_direction`),\n pressure: num(states, `${c}.air_pressure_at_sea_level`),\n precipitation: num(states, `${c}.1h_precipitation_amount`),\n cloudCover: num(states, `${c}.cloud_area_fraction`),\n };\n // No daily/multi-day branch in the yr adapter → forecast stays empty.\n return { source: 'yr', units: { temp: '°C', wind: 'm/s', precip: 'mm' }, current, forecast: [] };\n}\n\n/**\n * Build a normalized {@link WeatherReport} from a weather adapter's flat state map. `adapter` is the adapter\n * name (segment 0 of the instance id); `root` is the state prefix the data lives under (for Open-Meteo this\n * includes the location device, e.g. `open-meteo-weather.0.Berlin`). Returns null for adapters without a\n * mapper (the caller then falls back to a raw dump) or when no data is present.\n */\nexport function buildWeatherReport(adapter: string, root: string, states: StateValues): WeatherReport | null {\n const kind = WEATHER_ADAPTERS[adapter]?.kind;\n if (!kind) {\n return null;\n }\n const mappers: Record<WeatherKind, (root: string, states: StateValues) => WeatherReport> = {\n openmeteo: mapOpenMeteo,\n wunderground: mapWunderground,\n openweathermap: mapOpenWeatherMap,\n brightsky: mapBrightsky,\n pirate: mapPirate,\n accuweather: mapAccuweather,\n daswetter: mapDasWetter,\n yr: mapYr,\n };\n const report = mappers[kind](root, states);\n // Drop empty/undefined fields from `current` so the report is compact (and a data-less selection\n // collapses to no current at all, rather than a stray empty condition string).\n if (report.current) {\n report.current = Object.fromEntries(\n Object.entries(report.current).filter(([, v]) => v !== undefined && v !== ''),\n );\n if (!Object.keys(report.current).length) {\n report.current = undefined;\n }\n }\n if (!report.current && !report.forecast.length) {\n return null; // adapter selected but no data yet\n }\n return report;\n}\n\n/** Keep only the forecast days a question needs: 'current'/'today' → day 0, 'tomorrow' → day 1, 'week' → all. */\nexport function trimReport(report: WeatherReport, when?: string): WeatherReport {\n const w = (when || '').toLowerCase();\n if (w === 'tomorrow') {\n return { ...report, forecast: report.forecast.filter(d => d.day === 1) };\n }\n if (w === 'today' || w === 'current' || w === 'now') {\n return { ...report, forecast: report.forecast.filter(d => d.day === 0) };\n }\n return report; // 'week' / unspecified → everything\n}\n"]}
|