@mxtommy/kip 4.7.0 → 4.8.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 (50) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/LICENSE +3 -0
  3. package/README.md +37 -29
  4. package/package.json +23 -34
  5. package/plugin/history-series.service.js +108 -11
  6. package/plugin/index.js +170 -44
  7. package/plugin/kip-series-contract.js +18 -1
  8. package/plugin/openApi.json +75 -7
  9. package/public/3rdpartylicenses.txt +0 -26
  10. package/public/assets/help-docs/contributing-widgets.md +40 -0
  11. package/public/assets/help-docs/dashboards.md +8 -0
  12. package/public/assets/help-docs/menu.json +4 -2
  13. package/public/assets/svg/icons.svg +48 -1
  14. package/public/assets/svg/symbols.svg +68 -0
  15. package/public/{chunk-PKB75FW2.js → chunk-2TP7C66X.js} +1 -1
  16. package/public/chunk-3SJSLBCV.js +6 -0
  17. package/public/chunk-5SMHTIVS.js +50 -0
  18. package/public/{chunk-E3ROX3JL.js → chunk-6ISGIPNP.js} +1 -1
  19. package/public/chunk-6ZR3M3IQ.js +3 -0
  20. package/public/{chunk-6LPU67GV.js → chunk-ACY7HYMZ.js} +1 -1
  21. package/public/{chunk-E3VC75HO.js → chunk-C23GLDFH.js} +1 -1
  22. package/public/{chunk-W5RYIISE.js → chunk-CB4E7PPA.js} +1 -1
  23. package/public/{chunk-JL7DODKZ.js → chunk-DNM5XUIF.js} +1 -1
  24. package/public/{chunk-2BKBAKG4.js → chunk-GSM7FZE2.js} +2 -2
  25. package/public/{chunk-OBMTAHXF.js → chunk-HF7V6XFA.js} +1 -1
  26. package/public/{chunk-2BY2XRFX.js → chunk-HUCDJ6CS.js} +7 -7
  27. package/public/{chunk-CSJ2TU72.js → chunk-ILQQCGMJ.js} +1 -1
  28. package/public/chunk-IXUKD73N.js +5 -0
  29. package/public/{chunk-BMEO4VRV.js → chunk-JMZYPL54.js} +1 -1
  30. package/public/chunk-KG4C6HZC.js +1 -0
  31. package/public/{chunk-UBBLNNPA.js → chunk-LEY6MANN.js} +13 -13
  32. package/public/chunk-NS2OV2OW.js +9 -0
  33. package/public/{chunk-3JK476CX.js → chunk-O5BTKN5D.js} +1 -1
  34. package/public/{chunk-PDJFHMII.js → chunk-Q32FSCUX.js} +1 -1
  35. package/public/{chunk-3B5RVPSS.js → chunk-SHJMXSDM.js} +1 -1
  36. package/public/{chunk-GWAFO3MC.js → chunk-T4VTC6GW.js} +1 -1
  37. package/public/{chunk-GHPW4BPE.js → chunk-VFJD3XKT.js} +1 -1
  38. package/public/{chunk-KHZPPOSI.js → chunk-VFZSH4TC.js} +1 -1
  39. package/public/{chunk-S4UHQERA.js → chunk-W6MCE3GH.js} +1 -1
  40. package/public/index.html +1 -1
  41. package/public/main-UBENKC2D.js +1 -0
  42. package/public/polyfills-BWA36QKG.js +1 -0
  43. package/public/chunk-B2CYEVGO.js +0 -9
  44. package/public/chunk-HIXACNNV.js +0 -6
  45. package/public/chunk-QN4XMV3X.js +0 -50
  46. package/public/chunk-U5QHLGHD.js +0 -1
  47. package/public/chunk-WYC65OAX.js +0 -5
  48. package/public/chunk-XFHXLFTX.js +0 -3
  49. package/public/main-WGYSOKJC.js +0 -1
  50. package/public/polyfills-L4FJGPOC.js +0 -2
package/plugin/index.js CHANGED
@@ -155,13 +155,105 @@ const start = (server) => {
155
155
  .replace(/[^a-z0-9]+/g, '-')
156
156
  .replace(/^-+|-+$/g, '');
157
157
  }
158
- function resolveBmsBatteryIdsFromSelfPath() {
159
- const batteriesPath = server.getSelfPath('electrical.batteries');
160
- const readCandidate = (node) => {
161
- if (!node || typeof node !== 'object' || Array.isArray(node)) {
158
+ const ELECTRICAL_SERIES_PREFIX_BY_FAMILY = {
159
+ batteries: 'bms',
160
+ solar: 'solar',
161
+ chargers: 'charger',
162
+ inverters: 'inverter',
163
+ alternators: 'alternator',
164
+ ac: 'ac'
165
+ };
166
+ const ELECTRICAL_SELF_ROOT_BY_FAMILY = {
167
+ batteries: 'electrical.batteries',
168
+ solar: 'electrical.solar',
169
+ chargers: 'electrical.chargers',
170
+ inverters: 'electrical.inverters',
171
+ alternators: 'electrical.alternators',
172
+ ac: 'electrical.ac'
173
+ };
174
+ const ELECTRICAL_METRICS_BY_FAMILY = {
175
+ batteries: ['capacity.stateOfCharge', 'current'],
176
+ solar: ['current', 'panelPower'],
177
+ chargers: ['voltage', 'current'],
178
+ inverters: ['voltage', 'current'],
179
+ alternators: ['voltage', 'current'],
180
+ ac: [
181
+ 'line1.voltage',
182
+ 'line1.current',
183
+ 'line1.frequency',
184
+ 'line2.voltage',
185
+ 'line2.current',
186
+ 'line2.frequency',
187
+ 'line3.voltage',
188
+ 'line3.current',
189
+ 'line3.frequency'
190
+ ]
191
+ };
192
+ function resolveFamilyKeyFromTemplateSeries(series) {
193
+ if (!(0, history_series_service_1.isKipElectricalTemplateSeriesDefinition)(series)) {
194
+ return null;
195
+ }
196
+ if (series.familyKey) {
197
+ return series.familyKey;
198
+ }
199
+ switch (series.expansionMode) {
200
+ case 'bms-battery-tree':
201
+ return 'batteries';
202
+ case 'solar-tree':
203
+ return 'solar';
204
+ case 'charger-tree':
205
+ return 'chargers';
206
+ case 'inverter-tree':
207
+ return 'inverters';
208
+ case 'alternator-tree':
209
+ return 'alternators';
210
+ case 'ac-tree':
211
+ return 'ac';
212
+ default:
213
+ return null;
214
+ }
215
+ }
216
+ function normalizeAllowedIds(series) {
217
+ const rawAllowedIds = Array.isArray(series.allowedIds)
218
+ ? series.allowedIds
219
+ : null;
220
+ if (!Array.isArray(rawAllowedIds)) {
221
+ return [];
222
+ }
223
+ return rawAllowedIds
224
+ .filter((id) => typeof id === 'string')
225
+ .map(id => id.trim())
226
+ .filter(id => id.length > 0);
227
+ }
228
+ function normalizeTrackedDevices(series) {
229
+ if (!Array.isArray(series.trackedDevices)) {
230
+ return [];
231
+ }
232
+ const trackedByKey = new Map();
233
+ series.trackedDevices.forEach(item => {
234
+ if (!item || typeof item !== 'object') {
235
+ return;
236
+ }
237
+ const id = typeof item.id === 'string' ? item.id.trim() : '';
238
+ const normalizedSource = typeof item.source === 'string' ? item.source.trim() : '';
239
+ const source = normalizedSource.length > 0 ? normalizedSource : 'default';
240
+ if (!id) {
241
+ return;
242
+ }
243
+ trackedByKey.set(`${id}||${source}`, { id, source });
244
+ });
245
+ return Array.from(trackedByKey.values()).sort((left, right) => {
246
+ const idCompare = left.id.localeCompare(right.id);
247
+ return idCompare !== 0 ? idCompare : left.source.localeCompare(right.source);
248
+ });
249
+ }
250
+ function resolveElectricalIdsFromSelfPath(rootPath) {
251
+ const node = server.getSelfPath(rootPath);
252
+ const readCandidate = (entry) => {
253
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
162
254
  return null;
163
255
  }
164
- const root = node;
256
+ const root = entry;
165
257
  if (Object.prototype.hasOwnProperty.call(root, 'value')) {
166
258
  const value = root.value;
167
259
  if (value && typeof value === 'object' && !Array.isArray(value)) {
@@ -171,7 +263,7 @@ const start = (server) => {
171
263
  }
172
264
  return root;
173
265
  };
174
- const candidates = readCandidate(batteriesPath);
266
+ const candidates = readCandidate(node);
175
267
  if (!candidates) {
176
268
  return [];
177
269
  }
@@ -179,11 +271,13 @@ const start = (server) => {
179
271
  .filter(id => /^[a-z0-9_-]+$/i.test(id))
180
272
  .sort((left, right) => left.localeCompare(right));
181
273
  }
182
- function getExistingConcreteBmsSeries(templateSeries, existingSeries) {
274
+ function getExistingConcreteElectricalSeries(templateSeries, familyKey, existingSeries) {
275
+ const rootPrefix = `${ELECTRICAL_SELF_ROOT_BY_FAMILY[familyKey]}.`;
183
276
  return existingSeries
184
277
  .filter(series => series.ownerWidgetUuid === templateSeries.ownerWidgetUuid)
185
278
  .filter(history_series_service_1.isKipConcreteSeriesDefinition)
186
279
  .filter(series => series.seriesId !== templateSeries.seriesId)
280
+ .filter(series => series.path.startsWith(rootPrefix))
187
281
  .map(series => ({ ...series }));
188
282
  }
189
283
  function mergeSeriesDefinitions(series) {
@@ -194,44 +288,66 @@ const start = (server) => {
194
288
  return Array.from(mergedById.values());
195
289
  }
196
290
  function expandTemplateSeriesDefinitions(payload, existingSeries = []) {
197
- const bmsMetrics = ['capacity.stateOfCharge', 'current'];
198
291
  const expandedById = new Map();
199
- const discoveredBatteryIds = resolveBmsBatteryIdsFromSelfPath();
292
+ const discoveredIdsByFamily = new Map();
293
+ const ensureDiscoveredIds = (familyKey) => {
294
+ if (!discoveredIdsByFamily.has(familyKey)) {
295
+ const discovered = resolveElectricalIdsFromSelfPath(ELECTRICAL_SELF_ROOT_BY_FAMILY[familyKey]);
296
+ discoveredIdsByFamily.set(familyKey, discovered);
297
+ }
298
+ return discoveredIdsByFamily.get(familyKey) ?? [];
299
+ };
200
300
  payload.forEach(series => {
201
301
  if (!(0, history_series_service_1.isKipTemplateSeriesDefinition)(series)) {
202
302
  expandedById.set(series.seriesId, series);
203
303
  return;
204
304
  }
205
- if (discoveredBatteryIds.length === 0) {
206
- getExistingConcreteBmsSeries(series, existingSeries).forEach(existing => {
207
- expandedById.set(existing.seriesId, existing);
208
- });
305
+ const familyKey = resolveFamilyKeyFromTemplateSeries(series);
306
+ if (!familyKey) {
307
+ expandedById.set(series.seriesId, series);
209
308
  return;
210
309
  }
211
- const allowedBatteryIds = Array.isArray(series.allowedBatteryIds)
212
- ? series.allowedBatteryIds
213
- .filter((id) => typeof id === 'string')
214
- .map(id => id.trim())
215
- .filter(id => id.length > 0)
216
- : [];
217
- const allowedSet = allowedBatteryIds.length > 0 ? new Set(allowedBatteryIds) : null;
218
- const batteryIds = discoveredBatteryIds.filter(id => !allowedSet || allowedSet.has(id));
219
- if (batteryIds.length === 0) {
220
- return;
310
+ const seriesPrefix = ELECTRICAL_SERIES_PREFIX_BY_FAMILY[familyKey];
311
+ const metricList = ELECTRICAL_METRICS_BY_FAMILY[familyKey];
312
+ const selfRoot = `self.${ELECTRICAL_SELF_ROOT_BY_FAMILY[familyKey]}`;
313
+ const trackedDevices = normalizeTrackedDevices(series);
314
+ let concreteDevices = trackedDevices;
315
+ if (concreteDevices.length === 0) {
316
+ const discoveredIds = ensureDiscoveredIds(familyKey);
317
+ if (discoveredIds.length === 0) {
318
+ getExistingConcreteElectricalSeries(series, familyKey, existingSeries).forEach(existing => {
319
+ expandedById.set(existing.seriesId, existing);
320
+ });
321
+ return;
322
+ }
323
+ const allowedIds = normalizeAllowedIds(series);
324
+ const allowedSet = allowedIds.length > 0 ? new Set(allowedIds) : null;
325
+ const filteredIds = discoveredIds.filter(id => !allowedSet || allowedSet.has(id));
326
+ if (filteredIds.length === 0) {
327
+ return;
328
+ }
329
+ const fallbackSource = series.source ?? 'default';
330
+ concreteDevices = filteredIds.map(deviceId => ({
331
+ id: deviceId,
332
+ source: fallbackSource
333
+ }));
221
334
  }
222
- const source = series.source ?? 'default';
223
- const sourceKey = slugify(source || 'default') || 'default';
224
- batteryIds.forEach(batteryId => {
225
- bmsMetrics.forEach(metric => {
226
- const path = `self.electrical.batteries.${batteryId}.${metric}`;
227
- const seriesId = `${series.ownerWidgetUuid}:bms:${batteryId}:${metric}:${sourceKey}`;
335
+ concreteDevices.forEach(device => {
336
+ const sourceKey = slugify(device.source || 'default') || 'default';
337
+ metricList.forEach(metric => {
338
+ const path = `${selfRoot}.${device.id}.${metric}`;
339
+ const seriesId = `${series.ownerWidgetUuid}:${seriesPrefix}:${device.id}:${metric}:${sourceKey}`;
228
340
  expandedById.set(seriesId, {
229
341
  ...series,
230
342
  seriesId,
231
- datasetUuid: `${series.ownerWidgetUuid}:bms:${batteryId}:${metric}:${sourceKey}`,
343
+ datasetUuid: `${series.ownerWidgetUuid}:${seriesPrefix}:${device.id}:${metric}:${sourceKey}`,
232
344
  path,
345
+ source: device.source,
233
346
  retentionDurationMs: Number.isFinite(series.retentionDurationMs) ? series.retentionDurationMs : 24 * 60 * 60 * 1000,
234
- expansionMode: null
347
+ expansionMode: null,
348
+ familyKey: null,
349
+ allowedIds: null,
350
+ trackedDevices: null
235
351
  });
236
352
  });
237
353
  });
@@ -282,16 +398,15 @@ const start = (server) => {
282
398
  }
283
399
  }
284
400
  function getRouteError(error, fallbackMessage) {
285
- const message = String(error?.message || fallbackMessage);
401
+ const message = error?.message?.trim() || fallbackMessage;
286
402
  const normalized = message.toLowerCase();
287
403
  if (normalized.includes('invalid ')
288
404
  || normalized.includes('missing ')
289
- || normalized.includes('body must')
290
- || normalized.includes('required')
291
- || normalized.includes('expected an iso')) {
405
+ || normalized.includes('required')) {
292
406
  return { statusCode: 400, message };
293
407
  }
294
408
  if (normalized.includes('sqlite')
409
+ || normalized.includes('database')
295
410
  || normalized.includes('storage unavailable')
296
411
  || normalized.includes('not initialized')
297
412
  || isSqliteUnavailable()) {
@@ -1152,21 +1267,32 @@ const start = (server) => {
1152
1267
  const scopedPayload = payload.map(series => ({
1153
1268
  ...series
1154
1269
  }));
1155
- const isBatteryDiscoveryUnavailable = resolveBmsBatteryIdsFromSelfPath().length === 0;
1156
- const preservedBmsSeries = isBatteryDiscoveryUnavailable
1157
- ? scopedPayload
1158
- .filter(history_series_service_1.isKipTemplateSeriesDefinition)
1159
- .flatMap(series => currentSeries.filter(current => current.ownerWidgetUuid === series.ownerWidgetUuid && (0, history_series_service_1.isKipConcreteSeriesDefinition)(current) && current.seriesId !== series.seriesId))
1160
- : [];
1270
+ const preservedTemplateConcreteSeries = scopedPayload
1271
+ .filter(history_series_service_1.isKipTemplateSeriesDefinition)
1272
+ .flatMap(series => {
1273
+ const familyKey = resolveFamilyKeyFromTemplateSeries(series);
1274
+ if (!familyKey) {
1275
+ return [];
1276
+ }
1277
+ if (normalizeTrackedDevices(series).length > 0) {
1278
+ return [];
1279
+ }
1280
+ const isDiscoveryUnavailable = resolveElectricalIdsFromSelfPath(ELECTRICAL_SELF_ROOT_BY_FAMILY[familyKey]).length === 0;
1281
+ if (isDiscoveryUnavailable) {
1282
+ return getExistingConcreteElectricalSeries(series, familyKey, currentSeries);
1283
+ }
1284
+ return [];
1285
+ });
1161
1286
  const expandedPayload = mergeSeriesDefinitions([
1162
1287
  ...expandTemplateSeriesDefinitions(scopedPayload, currentSeries),
1163
- ...preservedBmsSeries
1288
+ ...preservedTemplateConcreteSeries
1164
1289
  ]);
1165
1290
  const result = simulated.reconcileSeries(expandedPayload);
1166
1291
  const nextSeries = simulated.listSeries();
1167
1292
  await storageService.replaceSeriesDefinitions(nextSeries);
1168
- const seriesOutsideScope = historySeries.listSeries();
1169
- historySeries.reconcileSeries([...seriesOutsideScope, ...nextSeries]);
1293
+ // /series/reconcile expects the full desired set for KIP-managed series.
1294
+ // Keep in-memory state aligned with persisted state to avoid reintroducing stale series.
1295
+ historySeries.reconcileSeries(nextSeries);
1170
1296
  server.debug(`[KIP][SERIES_RECONCILE] created=${result.created} updated=${result.updated} deleted=${result.deleted} total=${result.total}`);
1171
1297
  rebuildSeriesCaptureSubscriptions();
1172
1298
  return sendOk(res, result);
@@ -1,10 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isKipTemplateSeriesDefinition = isKipTemplateSeriesDefinition;
4
+ exports.isKipElectricalTemplateSeriesDefinition = isKipElectricalTemplateSeriesDefinition;
5
+ exports.isKipBmsTemplateSeriesDefinition = isKipBmsTemplateSeriesDefinition;
6
+ exports.isKipSolarTemplateSeriesDefinition = isKipSolarTemplateSeriesDefinition;
4
7
  exports.isKipConcreteSeriesDefinition = isKipConcreteSeriesDefinition;
5
8
  exports.isKipSeriesEnabled = isKipSeriesEnabled;
6
9
  function isKipTemplateSeriesDefinition(series) {
7
- return series.expansionMode === 'bms-battery-tree';
10
+ return series.expansionMode === 'bms-battery-tree'
11
+ || series.expansionMode === 'solar-tree'
12
+ || series.expansionMode === 'charger-tree'
13
+ || series.expansionMode === 'inverter-tree'
14
+ || series.expansionMode === 'alternator-tree'
15
+ || series.expansionMode === 'ac-tree';
16
+ }
17
+ function isKipElectricalTemplateSeriesDefinition(series) {
18
+ return isKipTemplateSeriesDefinition(series);
19
+ }
20
+ function isKipBmsTemplateSeriesDefinition(series) {
21
+ return series.expansionMode === 'bms-battery-tree' && (series.familyKey == null || series.familyKey === 'batteries');
22
+ }
23
+ function isKipSolarTemplateSeriesDefinition(series) {
24
+ return series.expansionMode === 'solar-tree' && (series.familyKey == null || series.familyKey === 'solar');
8
25
  }
9
26
  function isKipConcreteSeriesDefinition(series) {
10
27
  return series.expansionMode == null;
@@ -217,13 +217,38 @@
217
217
  "nullable": true,
218
218
  "description": "Null for concrete series definitions."
219
219
  },
220
- "allowedBatteryIds": {
220
+ "familyKey": {
221
+ "type": "string",
222
+ "nullable": true,
223
+ "description": "Concrete series do not use family key and should leave this null."
224
+ },
225
+ "allowedIds": {
221
226
  "type": "array",
222
227
  "nullable": true,
223
228
  "items": {
224
229
  "type": "string"
225
230
  },
226
- "description": "Concrete series do not use battery filters and should leave this null."
231
+ "description": "Concrete series do not use template filters and should leave this null."
232
+ },
233
+ "trackedDevices": {
234
+ "type": "array",
235
+ "nullable": true,
236
+ "items": {
237
+ "type": "object",
238
+ "properties": {
239
+ "id": {
240
+ "type": "string"
241
+ },
242
+ "source": {
243
+ "type": "string"
244
+ }
245
+ },
246
+ "required": [
247
+ "id",
248
+ "source"
249
+ ]
250
+ },
251
+ "description": "Concrete series do not use tracked device template filters and should leave this null."
227
252
  }
228
253
  }
229
254
  }
@@ -240,26 +265,69 @@
240
265
  "ownerWidgetSelector": {
241
266
  "type": "string",
242
267
  "enum": [
243
- "widget-bms"
268
+ "widget-bms",
269
+ "widget-solar-charger",
270
+ "widget-charger",
271
+ "widget-inverter",
272
+ "widget-alternator",
273
+ "widget-ac"
244
274
  ]
245
275
  },
246
276
  "expansionMode": {
247
277
  "type": "string",
248
278
  "enum": [
249
- "bms-battery-tree"
279
+ "bms-battery-tree",
280
+ "solar-tree",
281
+ "charger-tree",
282
+ "inverter-tree",
283
+ "alternator-tree",
284
+ "ac-tree"
250
285
  ]
251
286
  },
252
- "allowedBatteryIds": {
287
+ "familyKey": {
288
+ "type": "string",
289
+ "enum": [
290
+ "batteries",
291
+ "solar",
292
+ "chargers",
293
+ "inverters",
294
+ "alternators",
295
+ "ac"
296
+ ]
297
+ },
298
+ "allowedIds": {
253
299
  "type": "array",
254
300
  "nullable": true,
255
301
  "items": {
256
302
  "type": "string"
257
- }
303
+ },
304
+ "description": "Template device IDs to include. Null or empty includes all discovered devices."
305
+ },
306
+ "trackedDevices": {
307
+ "type": "array",
308
+ "nullable": true,
309
+ "items": {
310
+ "type": "object",
311
+ "properties": {
312
+ "id": {
313
+ "type": "string"
314
+ },
315
+ "source": {
316
+ "type": "string"
317
+ }
318
+ },
319
+ "required": [
320
+ "id",
321
+ "source"
322
+ ]
323
+ },
324
+ "description": "Explicit tracked device id/source pairs to expand. When provided, template expansion skips self-tree discovery and emits one concrete series per pair and metric."
258
325
  }
259
326
  },
260
327
  "required": [
261
328
  "ownerWidgetSelector",
262
- "expansionMode"
329
+ "expansionMode",
330
+ "familyKey"
263
331
  ]
264
332
  }
265
333
  ]
@@ -1061,32 +1061,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1061
1061
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1062
1062
  THE SOFTWARE.
1063
1063
 
1064
- --------------------------------------------------------------------------------
1065
- Package: zone.js
1066
- License: "MIT"
1067
-
1068
- The MIT License
1069
-
1070
- Copyright (c) 2010-2025 Google LLC. https://angular.dev/license
1071
-
1072
- Permission is hereby granted, free of charge, to any person obtaining a copy
1073
- of this software and associated documentation files (the "Software"), to deal
1074
- in the Software without restriction, including without limitation the rights
1075
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1076
- copies of the Software, and to permit persons to whom the Software is
1077
- furnished to do so, subject to the following conditions:
1078
-
1079
- The above copyright notice and this permission notice shall be included in
1080
- all copies or substantial portions of the Software.
1081
-
1082
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1083
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1084
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1085
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1086
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1087
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1088
- THE SOFTWARE.
1089
-
1090
1064
  --------------------------------------------------------------------------------
1091
1065
  Package: prismjs
1092
1066
  License: "MIT"
@@ -0,0 +1,40 @@
1
+ ## Contributing Widgets
2
+
3
+ KIP widgets are community-powered. If you need a widget that does not exist yet, you can request one from the community or build and contribute it.
4
+
5
+ ## For End Users
6
+
7
+ - Check [Dashboards and Layout](#/help/dashboards.md) to confirm the current widget catalog.
8
+ - Ask in the community first before building from scratch; someone may already have a branch or reusable approach.
9
+ - Use [The Embed Page Viewer](#/help/embedwidget.md) when your requirement is best served by an external web dashboard.
10
+
11
+ ## For Developers
12
+
13
+ Widget creation follows a schematic-first workflow for Host2 widgets.
14
+
15
+ 1. Scaffold with `npm run generate:widget`.
16
+ 2. Follow schematic flags and examples in `docs/widget-schematic.md`.
17
+ 3. Apply Host2 implementation patterns and stream wiring guardrails.
18
+ 4. Add domain assertions to the generated spec before submitting a PR.
19
+
20
+ Developer references:
21
+ - `docs/widget-schematic.md`
22
+ - `.github/instructions/project.instructions.md`
23
+ - `.agents/skills/kip-widget-creation/SKILL.md`
24
+ - `.agents/skills/kip-host2-widget/SKILL.md`
25
+
26
+ ## Contribution Flow
27
+
28
+ 1. Create a feature branch.
29
+ 2. Generate and implement the widget.
30
+ 3. Generate and implement the widget test when necessary.
31
+ 4. Add an svg icon
32
+ 5. Update README.md and KIP documentation with your plugin information.
33
+ 6. Validate with lint/tests (ng lint/ng test).
34
+ 7. Submit a pull request with screenshots and a short behavior summary.
35
+
36
+ ## Notes
37
+
38
+ - Keep new widget behavior deterministic and path-safe.
39
+ - Prefer theme roles and shared formatting helpers.
40
+ - Keep docs concise and link to canonical references rather than duplicating option tables.
@@ -81,6 +81,9 @@ KIP widgets turn Signal K data into readable visuals and controls. Available wid
81
81
  - **Classic Steel** – Traditional steel-look linear & radial gauges with range sizes and zone highlights.
82
82
  - **Windsteer** – Combines wind, wind sectors, heading, COG, and waypoint info for wind steering.
83
83
  - **Wind Trends** – Real-time True Wind trends with dual axes for direction and speed, live values, and averages.
84
+ - **Battery Monitor** - Display batteries or whole banks state State of Charge, remaining capacity, remaining time, voltage, current, power flow, and temperature.
85
+ - **Solar Charger**- Track solar generation and charging performance at a glance with live panel output, battery-side metrics, and clear charger and relay status indicators.
86
+ - **AC/DC Charger**- Monitor charging performance at a glance with a compact AC/DC Charger Widget. View single or multiple chargers with charge mode, voltage, current, power and temperature. Chargers are discovered automatically.
84
87
  - **Freeboard-SK** – Adds the Freeboard-SK chart plotter as a widget with automatic sign-in.
85
88
  - **Autopilot Head** – Typical autopilot controls for compatible Signal K Autopilot devices.
86
89
  - **Realtime Data Chart** – Visualizes data on a real-time chart with actuals, averages, and min/max.
@@ -91,6 +94,11 @@ KIP widgets turn Signal K data into readable visuals and controls. Available wid
91
94
  - **Racer - Start Timer** – Advanced racing countdown timer with OCS status and auto dashboard switching.
92
95
  - **Countdown Timer** – Simple race start countdown timer with start, pause, sync, and reset options.
93
96
 
97
+ ### Need a Widget Not Listed Here?
98
+
99
+ - Check the community first: someone may already have a reusable setup or branch.
100
+ - If you are a developer, see [Contributing Widgets](#/help/contributing-widgets.md) for the contribution path.
101
+ - If you only need external visuals, consider [The Embed Page Viewer](#/help/embedwidget.md) for web dashboards.
94
102
 
95
103
  ## Performance & Layout Tips
96
104
  - Favor clarity over cramming: leave space around high‑priority values
@@ -14,13 +14,15 @@
14
14
  { "title": "The Embed Page Viewer", "file": "embedwidget.md" },
15
15
  { "title": "External History-API Provider", "file": "history-api.md" },
16
16
  { "title": "Grafana Integration", "file": "grafana.md" },
17
- { "title": "InfluxDB and Signal K", "file": "influxdb.md" }
17
+ { "title": "InfluxDB and Signal K", "file": "influxdb.md" },
18
+ { "title": "Contributing Widgets", "file": "contributing-widgets.md" }
18
19
  ]
19
20
  },
20
21
  {
21
22
  "title": "Advanced Features",
22
23
  "items": [
23
- { "title": "Kiosk Mode", "file": "kiosk.md" }
24
+ { "title": "Kiosk Mode", "file": "kiosk.md" },
25
+ { "title": "Contributing Widgets", "file": "contributing-widgets.md" }
24
26
  ]
25
27
  },
26
28
  { "title": "Online Community Content", "file": "community.md" },
@@ -337,7 +337,7 @@
337
337
  <path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="3" d="M44.999 20.501v6.9987"></path>
338
338
  </svg>
339
339
  <svg id="battery_charging" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16">
340
- <path fill="var(--mat-sys-primary)" d="M9.585 2.568a0.5 0.5 0 0 1 0.226 0.58L8.677 6.832h1.99a0.5 0.5 0 0 1 0.364 0.843l-5.334 5.667a0.5 0.5 0 0 1 -0.842 -0.49L5.99 9.167H4a0.5 0.5 0 0 1 -0.364 -0.843l5.333 -5.667a0.5 0.5 0 0 1 0.616 -0.09z" stroke-width="1"></path>
340
+ <path fill="var(--mat-sys-primary)" stroke-width="1" d="M9.585 2.568a0.5 0.5 0 0 1 0.226 0.58L8.677 6.832h1.99a0.5 0.5 0 0 1 0.364 0.843l-5.334 5.667a0.5 0.5 0 0 1 -0.842 -0.49L5.99 9.167H4a0.5 0.5 0 0 1 -0.364 -0.843l5.333 -5.667a0.5 0.5 0 0 1 0.616 -0.09z" />
341
341
  <path d="M2 4h4.332l-0.94 1H2a1 1 0 0 0 -1 1v4a1 1 0 0 0 1 1h2.38l-0.308 1H2a2 2 0 0 1 -2 -2V6a2 2 0 0 1 2 -2" stroke-width="1"></path>
342
342
  <path d="M2 6h2.45L2.908 7.639A1.5 1.5 0 0 0 3.313 10H2zm8.595 -2 -0.308 1H12a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1H9.276l-0.942 1H12a2 2 0 0 0 2 -2V6a2 2 0 0 0 -2 -2z" stroke-width="1"></path>
343
343
  <path d="M12 10h-1.783l1.542 -1.639q0.146 -0.156 0.241 -0.34zm0 -3.354V6h-0.646a1.5 1.5 0 0 1 0.646 0.646M16 8a1.5 1.5 0 0 1 -1.5 1.5v-3A1.5 1.5 0 0 1 16 8" stroke-width="1"></path>
@@ -348,6 +348,53 @@
348
348
  <svg id="power_renewal" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
349
349
  <path fill="currentColor" d="M4.06445 13C4.55672 16.9461 7.92051 20 12 20c2.3364 -0.0002 4.4372 -1.0031 5.8994 -2.6006L15.5 15h6v6l-2.1855 -2.1855C17.4894 20.7734 14.8887 21.9998 12 22c-5.18532 0 -9.44843 -3.9467 -9.9502 -9zM12.5 10H16l-4.5 9v-5H8l4.5 -9zM12 2c5.1851 0.00028 9.4485 3.94685 9.9502 9h-2.0147C19.4434 7.05399 16.0793 4.00027 12 4 9.66354 4 7.56264 5.00292 6.10059 6.60059L8.5 9h-6V3l2.18457 2.18457C6.50965 3.22563 9.11128 2 12 2" stroke-width="1"></path>
350
350
  </svg>
351
+ <svg id="charger" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
352
+ <path fill="currentColor" fill-opacity="0.7" stroke="currentColor" stroke-opacity="0.5" stroke-width="0.2" d="m 18.526522,2.992101 c 0.580439,-0.01321 0.578699,0.00149 0.596108,-0.2465077 0.02012,-0.2470691 0.02494,-1.91517073 0.01988,-1.97978103 0.12148,-0.43521163 1.030866,-0.40307568 1.104701,-0.006281 -0.0061,0.0964339 0.0157,1.83374753 0.01125,1.99193083 0.01054,0.1942281 0.01127,0.1915155 0.236692,0.2282672 0.123095,0.0048 0.663877,0.046959 0.870775,0.071236 0.629908,0.078435 0.546705,0.7198795 0.184123,1.0560261 -0.280622,0.2614474 -0.478291,0.795547 -0.593006,1.6022987 -0.118811,0.8291615 -0.664733,1.3978094 -1.637762,1.7059437 -0.108951,0.033365 -0.182889,0.1266077 -0.184364,0.2325013 l -0.0092,1.3249778 c -6.99e-4,0.058661 -0.01761,0.1164398 -0.04916,0.1680732 -0.08194,0.1419286 -0.272448,0.211959 -0.571527,0.2100915 -0.301128,0 -0.491636,-0.070965 -0.571527,-0.2128928 -0.03157,-0.051631 -0.04849,-0.1094094 -0.0492,-0.1680731 V 7.6449295 C 17.884596,7.5387391 17.811826,7.4442875 17.703031,7.4096269 16.732051,7.0977576 16.189203,6.5263085 16.074488,5.6952795 15.965918,4.8885278 15.772337,4.3534946 15.493743,4.0901798 c -0.362582,-0.3389478 -0.427109,-0.949614 0.2028,-1.0224459 0.206897,-0.024278 0.524028,-0.032992 0.851764,-0.039526 0.236092,-0.013163 0.243529,-0.028409 0.250893,-0.2966214 -0.0019,-0.067609 0.02171,-1.91973584 0.01888,-1.9804634 0.130447,-0.40934157 1.03846,-0.42750873 1.115399,0.0164377 0.0056,0.0751203 -0.01932,1.7128202 -0.0031,1.9752302 0.02251,0.2158291 0.03941,0.2199654 0.596108,0.2493092 z" />
353
+ <path fill="currentColor" fill-opacity="0.7" stroke="currentColor" stroke-opacity="0.5" stroke-width="0.5" d="m 11.506042,7.8057483 c 0.107183,-0.0032 0.191857,-0.086257 0.190184,-0.1865545 C 11.687026,5.8381807 10.641016,5.6953499 8.8986916,5.68952 6.4569849,5.68174 4.6614917,5.709924 3.5122123,5.774052 2.0306237,5.858584 1.837373,6.8846346 1.8343055,8.1759419 c -0.00204,2.6001051 -6.194e-4,5.6507651 0.00427,9.8228741 -0.01077,0.569851 0.2418172,1.058016 0.5448099,1.265464 0.481593,0.32647 0.9904809,0.518667 1.0607401,0.771427 0.1216783,0.387457 0.1123871,0.577471 -0.1448346,0.903967 C 2.9574225,21.008744 2.5101741,20.797525 2.2238768,20.65178 0.61345188,19.829773 0.59403898,18.908625 0.60863288,16.952184 0.58951088,13.116111 0.58583788,10.506564 0.59449588,8.9828056 0.46872958,6.3214882 1.1379898,4.511894 4.183989,4.4914895 c 1.9999912,-0.013603 4.0050948,0.00292 6.015311,0.049553 1.83128,0.043724 2.558271,1.1397318 2.70551,2.7633394 0.02019,0.2208227 0.09035,0.4337311 0.205521,0.6237919 0.147238,0.2487394 0.24642,0.5120534 0.297544,0.7899419 0.03654,0.1905362 -0.118279,0.3656754 -0.322085,0.364365 L 9.1747636,9.0708215 C 8.8570456,9.0696334 8.6200346,8.7923263 8.6839686,8.4965832 l 0.0061,-0.023319 c 0.08615,-0.3642987 0.423096,-0.625504 0.815951,-0.632537 z" />
354
+ <path fill="currentColor" fill-opacity="1" stroke="var(--mat-sys-background)" stroke-opacity="0.5" stroke-width="1.25" d="m 23.341383,21.855011 a 2.148899,1.5884815 0 0 1 -2.148899,1.588482 H 6.8688978 A 2.148899,1.5884815 0 0 1 4.7199987,21.855011 V 12.345479 A 2.148899,1.5884815 0 0 1 6.8688978,10.756996 H 21.192484 a 2.148899,1.5884815 0 0 1 2.148899,1.588483 z" />
355
+ <path fill="var(--mat-sys-primary)" stroke="var(--mat-sys-background)" stroke-opacity="0.8" stroke-width="1.55" stroke-linejoin="round" stroke-linecap="butt" d="m 16.664469,10.746745 a 0.58489405,0.58489405 0 0 1 0.264373,0.678475 l -1.326539,4.309501 h 2.327878 a 0.58489405,0.58489405 0 0 1 0.425802,0.986131 l -6.239649,6.629189 a 0.58489405,0.58489405 0 0 1 -0.984963,-0.573196 l 1.32771,-4.310669 H 10.131203 A 0.58489405,0.58489405 0 0 1 9.7053994,17.480044 L 15.94388,10.850855 a 0.58489405,0.58489405 0 0 1 0.720589,-0.105283 z" />
356
+ </svg>
357
+ <svg id="inverter" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
358
+ <g transform="translate(-0.53766295,-0.11764124)">
359
+ <path fill="currentColor" stroke="none" stroke-width="1" d="M 5.2587761,0.79493449 A 1.6175304,1.6175304 0 0 0 3.6412457,2.4124751 H 1.2149499 V 3.22123 a 0.8087703,0.8087703 0 0 1 0,1.6175406 V 5.6475257 H 3.6412457 V 18.587757 H 1.2149499 v 0.808785 a 0.80876527,0.80876527 0 0 1 0,1.61751 v 0.808787 h 2.4262958 a 1.6175304,1.6175304 0 0 0 1.6175304,1.617509 H 19.81655 a 1.6175304,1.6175304 0 0 0 1.61753,-1.617509 h 2.426296 v -0.808787 a 0.80876527,0.80876527 0 0 1 0,-1.61751 V 18.587757 H 21.43408 V 5.6475257 h 2.426296 V 4.8387706 a 0.8087703,0.8087703 0 0 1 0,-1.6175406 V 2.4124751 H 21.43408 A 1.6175304,1.6175304 0 0 0 19.81655,0.79493449 Z" />
360
+ <rect fill="var(--mat-sys-background)" fill-opacity="0.7" width="14.700929" height="4.7658048" x="5.1283541" y="2.9362586" rx="0.22678168" ry="0.14382635" />
361
+ <path fill="var(--mat-sys-background)" fill-opacity="0.5" stroke="none" d="m 21.43408,19.396542 h 1.025175 a 1.6175304,1.6175304 0 0 0 0,1.61751 H 21.43408 Z" />
362
+ <path fill="var(--mat-sys-background)" fill-opacity="0.5" stroke="none" d="m 2.6160708,19.396542 h 1.0251749 v 1.61751 H 2.6160708 a 1.6175304,1.6175304 0 0 0 0,-1.61751 z" />
363
+ <path fill="var(--mat-sys-background)" fill-opacity="0.5" stroke="none" d="m 21.43408,3.22123 h 1.025175 a 1.6175304,1.6175304 0 0 0 0,1.6175406 H 21.43408 Z" />
364
+ <path fill="var(--mat-sys-background)" fill-opacity="0.5" stroke="none" d="M 2.6160708,3.22123 H 3.6412457 V 4.8387706 H 2.6160708 a 1.6175304,1.6175304 0 0 0 0,-1.6175406 z" />
365
+ </g>
366
+ <path fill="var(--mat-sys-primary)" stroke="var(--mat-sys-background)" stroke-opacity="0.8" stroke-width="0.5" stroke-linejoin="round" stroke-linecap="butt" d="m 16.222725,13.289851 c 1.434346,2.625425 0.01816,5.899677 -2.877292,6.652372 l 0.671063,2.291434 -3.478775,-2.821079 c -0.443589,-1.025432 0.349519,-1.629526 1.002288,-1.487952 1.980739,0.62225 4.065616,-1.282791 2.927928,-3.642059 z" />
367
+ <path fill="var(--mat-sys-primary)" stroke="var(--mat-sys-background)" stroke-opacity="0.8" stroke-width="0.5" stroke-linejoin="round" stroke-linecap="butt" d="m 8.1473717,17.739509 c -1.4343461,-2.625425 -0.01816,-5.899677 2.8772923,-6.652372 l -0.671063,-2.2914343 3.478775,2.8210793 c 0.443589,1.025432 -0.349519,1.629526 -1.002288,1.487952 -1.980739,-0.62225 -4.0656163,1.282791 -2.9279283,3.642059 z" />
368
+ </svg>
369
+ <svg id="alternator" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
370
+ <path fill="currentColor" d="m 4.1744266,0.22283685 c 0.8577487,0 1.6494163,0.28025712 2.2953397,0.74961596 0.017761,0.01097 0.036306,0.0195893 0.053283,0.0321264 L 21.148396,11.973263 c 0.01071,0.0081 0.01881,0.01802 0.02899,0.02664 1.570015,1.193901 2.586306,3.080217 2.586306,5.200298 0,3.600507 -2.929249,6.529756 -6.529756,6.529756 -2.122693,0 -4.011359,-1.018642 -5.204738,-2.592313 -0.0068,-0.0084 -0.01541,-0.01437 -0.02194,-0.02298 L 1.0385767,6.489574 C 1.0257784,6.4725967 1.0174203,6.4540522 1.0064503,6.4362912 0.53683027,5.7903678 0.25657315,4.998439 0.25657315,4.1406903 c 0,-2.1603044 1.75754905,-3.91785345 3.91785345,-3.91785345 z M 17.233938,21.771031 c 2.520225,0 4.570829,-2.050605 4.570829,-4.570829 0,-2.520225 -2.050604,-4.570829 -4.570829,-4.570829 -2.520224,0 -4.570829,2.050604 -4.570829,4.570829 0,2.520224 2.050605,4.570829 4.570829,4.570829 z m -6.529756,-4.570307 c 0,0 0,-5.22e-4 0,-5.22e-4 0,-3.600508 2.929249,-6.529756 6.529756,-6.529756 2.61e-4,0 5.22e-4,0 5.22e-4,0 L 8.0753027,3.801143 c 0.0094,0.1120506 0.016977,0.2248848 0.016977,0.3395473 0,2.1603044 -1.757549,3.9178535 -3.9178534,3.9178535 -0.1146625,0 -0.2274967,-0.00757 -0.3395473,-0.017239 z M 4.1744266,6.099617 c 1.0802828,0 1.9589267,-0.8786439 1.9589267,-1.9589267 0,-1.0802828 -0.8786439,-1.9589267 -1.9589267,-1.9589267 -1.0802828,0 -1.9589267,0.8786439 -1.9589267,1.9589267 0,1.0802828 0.8786439,1.9589267 1.9589267,1.9589267 z" />
371
+ <path fill="currentColor" d="m 17.233938,14.5883 c 1.440203,0 2.611902,1.171699 2.611902,2.611902 0,1.440203 -1.171699,2.611902 -2.611902,2.611902 -1.440203,0 -2.611902,-1.171699 -2.611902,-2.611902 0,-1.440203 1.171699,-2.611902 2.611902,-2.611902 z m 0,3.917853 c 0.720102,0 1.305951,-0.58585 1.305951,-1.305951 0,-0.720102 -0.585849,-1.305951 -1.305951,-1.305951 -0.720101,0 -1.305951,0.585849 -1.305951,1.305951 0,0.720101 0.58585,1.305951 1.305951,1.305951 z" />
372
+ <circle fill="currentColor" opacity="0.8" cx="-19.808893" cy="14.623878" r="0.51125419" transform="scale(-1,1)" />
373
+ <circle fill="currentColor" opacity="0.8" cx="-17.148212" cy="13.600105" r="0.51125419" transform="scale(-1,1)" />
374
+ <circle fill="currentColor" opacity="0.8" cx="-13.631804" cy="17.14979" r="0.51125419" transform="scale(-1,1)" />
375
+ <circle fill="currentColor" opacity="0.8" cx="-14.605977" cy="19.607031" r="0.51125419" transform="scale(-1,1)" />
376
+ <circle fill="currentColor" opacity="0.8" cx="-17.162188" cy="20.772921" r="0.51125419" transform="scale(-1,1)" />
377
+ <circle fill="currentColor" opacity="0.8" cx="-19.747438" cy="19.743822" r="0.51125419" transform="scale(-1,1)" />
378
+ <circle fill="currentColor" opacity="0.8" id="path3-02" cx="-20.833885" cy="17.123131" r="0.51125419" transform="scale(-1,1)" />
379
+ <path fill="var(--mat-sys-primary)" stroke="var(--mat-sys-background)" stroke-opacity="0.8" stroke-width="1.55" stroke-linejoin="round" stroke-linecap="butt" d="m 15.9823,2.3965923 a 0.88436594,0.88436594 0 0 1 0.399735,1.0258611 l -2.005742,6.5160113 h 3.519777 a 0.88436594,0.88436594 0 0 1 0.643817,1.4910403 L 9.1054724,21.452908 A 0.88436594,0.88436594 0 0 1 7.6161975,20.58623 L 9.6237097,14.068454 H 6.103933 A 0.88436594,0.88436594 0 0 1 5.4601144,12.577413 L 14.892762,2.5540075 A 0.88436594,0.88436594 0 0 1 15.9823,2.3948186 Z" />
380
+ <circle fill="currentColor" fill-opacity="0.5" stroke="var(--mat-sys-background)" stroke-width="1.5" cx="4.1754789" cy="4.0798988" r="2.6573768" />
381
+ </svg>
382
+ <svg id="acMonitor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
383
+ <path fill="currentColor" stroke="currentColor" stroke-width="0.8" d="m 12.254583,10.347123 3.128436,0.134257 h 0.09626 a 0.82580589,0.82580589 0 0 1 0.595289,0.430635 0.81314015,0.81314015 0 0 1 0,0.734612 l -3.921311,7.523447 a 0.60542211,0.60542211 0 0 1 -1.137383,-0.372373 l 0.732079,-4.450739 -3.1335025,-0.144389 h -0.09626 a 0.82580589,0.82580589 0 0 1 -0.5902232,-0.428102 0.8156733,0.8156733 0 0 1 0,-0.734613 L 11.84168,5.5265444 a 0.6104884,0.6104884 0 0 1 1.142449,0.3749058 z M 1.5140397,13.207046 a 1.2057779,1.2057779 0 0 0 1.2083111,-1.205778 9.2890497,9.2890497 0 0 1 9.2789172,-9.2789172 1.2070445,1.2070445 0 0 0 0,-2.41408899 A 11.705672,11.705672 0 0 0 0.30826182,12.001268 1.2057779,1.2057779 0 0 0 1.5140397,13.207046 Z m 2.4571525,3.450146 A 1.2083111,1.2083111 0 1 0 3.535491,18.308804 1.2057779,1.2057779 0 0 0 3.9737254,16.659725 Z M 21.0826,7.9608986 a 1.2083111,1.2083111 0 1 0 -1.04619,-0.602889 1.2007116,1.2007116 0 0 0 1.04619,0.602889 z M 6.17503,22.133855 A 1.2057779,1.2057779 0 1 0 5.7291962,20.484777 1.2133774,1.2133774 0 0 0 6.17503,22.133855 Z M 21.280185,12.001268 a 1.2057779,1.2057779 0 1 0 1.203245,-1.208311 1.2057779,1.2057779 0 0 0 -1.203245,1.208311 z m -0.795408,6.279671 a 1.2057779,1.2057779 0 1 0 -0.443301,-1.646545 v 0 a 1.2032448,1.2032448 0 0 0 0.443301,1.646545 z m -3.832651,1.752938 a 1.2083111,1.2083111 0 1 0 1.649078,0.435701 1.2057779,1.2057779 0 0 0 -1.649078,-0.435701 z m -5.838904,2.454619 a 1.2057779,1.2057779 0 1 0 1.203245,-1.208311 1.2057779,1.2057779 0 0 0 -1.203245,1.208311 z M 16.63946,3.9610596 A 1.2057779,1.2057779 0 1 0 16.196159,2.3145142 1.2032448,1.2032448 0 0 0 16.63946,3.9610596 Z" />
384
+ <path fill="var(--mat-sys-primary)" stroke="var(--mat-sys-background)" stroke-opacity="0.8" stroke-width="1.55" stroke-linejoin="round" stroke-linecap="butt" d="m 16.273919,2.4799121 a 0.88436594,0.88436594 0 0 1 0.399735,1.0258611 l -2.005742,6.5160108 h 3.519777 a 0.88436594,0.88436594 0 0 1 0.643817,1.491041 L 9.3970913,21.536228 A 0.88436594,0.88436594 0 0 1 7.9078168,20.66955 L 9.9153293,14.151774 H 6.3955523 A 0.88436594,0.88436594 0 0 1 5.7517337,12.660733 L 15.184381,2.6373273 a 0.88436594,0.88436594 0 0 1 1.089538,-0.1591889 z" />
385
+ </svg>
386
+ <svg id="solar_charger" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24">
387
+ <path fill="currentColor" stroke="currentColor" stroke-width="1" d="M7.666666666666667 2a3.8333333333333335 3.8333333333333335 0 0 0 7.666666666666667 0z"></path>
388
+ <path stroke="currentColor" d="M3.8333333333333335 2.875h0.9583333333333334" stroke-width="1.5"></path>
389
+ <path stroke="currentColor" d="M18.208333333333336 2.875h0.9583333333333334" stroke-width="1.5"></path>
390
+ <path stroke="currentColor" d="M11.5 8.625v0.9583333333333334" stroke-width="1.5"></path>
391
+ <path stroke="currentColor" d="m16.483333333333334 6.9 0.6775416666666667 0.6775416666666667" stroke-width="1.5"></path>
392
+ <path stroke="currentColor" d="m6.516666666666667 6.9 -0.6708333333333333 0.6708333333333333" stroke-width="1.5"></path>
393
+ <path fill="var(--mat-sys-primary)" fill-opacity="0.5" stroke="var(--mat-sys-primary)" stroke-opacity="1" d="M4.1016666666666675 20.125h14.796666666666667a0.9583333333333334 0.9583333333333334 0 0 0 0.9295833333333333 -1.1912083333333334l-1.4375 -5.75a0.9583333333333334 0.9583333333333334 0 0 0 -0.9295833333333333 -0.7254583333333333H5.5391666666666675a0.9583333333333334 0.9583333333333334 0 0 0 -0.9295833333333333 0.7254583333333333l-1.4375 5.75A0.9583333333333334 0.9583333333333334 0 0 0 4.1016666666666675 20.125z" stroke-width="1"></path>
394
+ <path stroke="var(--mat-sys-primary)" stroke-width="1" d="M3.8333333333333335 16.291666666666668h15.333333333333334"></path>
395
+ <path stroke="var(--mat-sys-primary)" stroke-width="1" d="m9.583333333333334 12.458333333333334 -0.9583333333333334 7.666666666666667"></path>
396
+ <path stroke="var(--mat-sys-primary)" stroke-width="1" d="m13.416666666666668 12.458333333333334 0.9583333333333334 7.666666666666667"></path>
397
+ </svg>
351
398
  <svg id="dashboard-beating-starboard" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
352
399
  <g id="g9" transform="matrix(-0.70710678,0.70710678,0.70710678,0.70710678,8.8211956,-6.2237242)">
353
400
  <path d="M 12,5.9999999 C 16,9.777778 17.777778,15.444445 16,23 H 8.0000001 C 6.2222223,15.444445 8.0000001,9.777778 12,5.9999999 Z" fill="var(--mat-sys-tertiary)" id="path1" style="stroke-width:0.229061" />