signalk-endurance 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -37,7 +37,7 @@ npm install github:SCarns/signalk-endurance
37
37
  Or install a downloaded package with:
38
38
 
39
39
  ```sh
40
- npm install /absolute/path/signalk-endurance-1.0.0.tgz
40
+ npm install /absolute/path/signalk-endurance-1.0.1.tgz
41
41
  ```
42
42
 
43
43
  Restart Signal K, enable **Endurance** in **Server → Plugin Config**, then open its
@@ -178,6 +178,13 @@ The server's preferred-source model values are used.
178
178
  `electrical.energy.prediction.status`, or enter a custom path. Healthy measured SOC plus
179
179
  `deficit` gives informational WATCH; forecast `critical` gives WARNING. Measured
180
180
  low SOC takes priority. A missing forecast does not prevent SOC assessment.
181
+ Forecasts use `resources.energy.predictionStaleSeconds` (default 7200 seconds),
182
+ independently of measured SOC and battery duration (`staleSeconds`, default 300).
183
+ Energy Predictor 0.12.3 recalculates every 60 minutes by default (configurable
184
+ 5–60 minutes) and republishes status on each successful cycle, including unchanged
185
+ values. Two hours allows one delayed cycle while still expiring stopped forecasts.
186
+ Configure a shorter forecast window if desired. Missing, expired, or invalidly
187
+ timestamped forecasts leave measured-SOC assessment in control.
181
188
  - **Battery duration:** leave `timePath` blank to discover `capacity.timeRemaining`
182
189
  for the selected SOC battery bank. For example, selecting
183
190
  `electrical.batteries.House.capacity.stateOfCharge` discovers
package/docs/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.0.1
2
+
3
+ - Fix forecasts disappearing after five minutes: use an independent configurable
4
+ `resources.energy.predictionStaleSeconds` window, default two hours, matching
5
+ Energy Predictor's default hourly refresh with allowance for one delayed cycle.
6
+ - Keep measured SOC and battery duration freshness unchanged, including standalone
7
+ SOC fallback when the optional forecast is missing or expired.
8
+
1
9
  # Release notes
2
10
 
3
11
  ## 1.0.0
package/lib/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  const {unitSchema,toM3,migrateOptions}=require('./input-units');
3
3
  const defaults = {
4
- energy: { warning: 0.70, critical: 0.50, staleSeconds: 300 },
4
+ energy: { warning: 0.70, critical: 0.50, staleSeconds: 300, predictionStaleSeconds: 7200 },
5
5
  freshwater: { warning: 0.35, critical: 0.20, staleSeconds: 1200 },
6
6
  fuel: { warning: 0.30, critical: 0.20, staleSeconds: 1200 },
7
7
  propane: { warning: 0.25, critical: 0.10, staleSeconds: 86400 },
@@ -37,7 +37,7 @@ const schema = {
37
37
  warning: ratio('Warning threshold (ratio)', d.warning),
38
38
  critical: ratio('Critical threshold (ratio)', d.critical),
39
39
  staleSeconds: { type: 'integer', default: d.staleSeconds, minimum: 1, maximum: 604800 },
40
- ...(name === 'energy' ? { predictionPath: path('Energy Predictor status path (blank: discover electrical.energy.prediction.status)'), timePath: path('Measured time remaining path in seconds (blank: discover for selected battery bank)') } : {
40
+ ...(name === 'energy' ? { predictionStaleSeconds: { type: 'integer', title: 'Forecast freshness window (seconds)', description: 'Separate from measured SOC and battery time. Energy Predictor defaults to hourly forecasts; two hours allows one delayed cycle.', default: 7200, minimum: 1, maximum: 604800 }, predictionPath: path('Energy Predictor status path (blank: discover electrical.energy.prediction.status)'), timePath: path('Measured time remaining path in seconds (blank: discover for selected battery bank)') } : {
41
41
  tanks: { type: 'array', title: 'Tanks', default: [], maxItems: 64, items: tankSchema },
42
42
  ...(name !== 'holding' ? { assessmentMode: { type: 'string', enum: ['combined', 'individual'], default: name === 'freshwater' ? 'combined' : 'individual', title: 'Assess combined supply or each tank individually' } } : {})
43
43
  }),
@@ -72,6 +72,7 @@ function normalize(options = {}) {
72
72
  for (const k of ['warning', 'critical']) if (typeof r[k] !== 'number' || !Number.isFinite(r[k]) || r[k] < 0 || r[k] > 1) throw Error(`${name}: invalid ${k} ratio`);
73
73
  if (name === 'holding' ? r.critical <= r.warning : r.critical >= r.warning) throw Error(`${name}: thresholds are reversed or equal`);
74
74
  if (!Number.isInteger(r.staleSeconds) || r.staleSeconds < 1 || r.staleSeconds > 604800) throw Error(`${name}: invalid freshness window`);
75
+ if (name === 'energy' && (!Number.isInteger(r.predictionStaleSeconds) || r.predictionStaleSeconds < 1 || r.predictionStaleSeconds > 604800)) throw Error('energy: invalid forecast freshness window');
75
76
  for (const k of ['levelPath', 'predictionPath', 'timePath', 'volumePath', 'consumptionPath']) if (r[k] !== undefined && !validPath(r[k])) throw Error(`${name}: invalid input path`);
76
77
  if (name !== 'energy') {
77
78
  r.assessmentMode ??= name === 'freshwater' ? 'combined' : 'individual';
@@ -4,7 +4,7 @@ const { read, resolveEnergyInputs } = require('../discovery');
4
4
  module.exports = (config, app, selection, now) => {
5
5
  const r = assessLevel('energy', config, app, selection, now);
6
6
  const paths=resolveEnergyInputs(app,config,selection.path);
7
- r.prediction = {...read(app, paths.prediction.path, config.staleSeconds, now),selection:paths.prediction.selection};
7
+ r.prediction = {...read(app, paths.prediction.path, config.predictionStaleSeconds, now),selection:paths.prediction.selection};
8
8
  r.duration = {...read(app, paths.duration.path, config.staleSeconds, now),selection:paths.duration.selection};
9
9
  if (r.status === 'UNKNOWN') return r;
10
10
  if (r.duration.valid && typeof r.duration.value === 'number' && Number.isFinite(r.duration.value) && r.duration.value >= 0) r.time = r.duration.value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalk-endurance",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Standalone vessel resource assessment for Signal K",
5
5
  "main": "index.js",
6
6
  "license": "MIT",