frugal-iot-client 2.0.6 → 2.0.7

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/CLAUDE.md CHANGED
@@ -259,7 +259,9 @@ else — no `mqtt-wrapper`, no discovery tree, no `/config.json`. `standaloneTop
259
259
  the `mqtt-client` element instead. `test/embedded.test.js` guards this — it has broken twice
260
260
  unnoticed.
261
261
 
262
- Still not available on such a page: graph history (`addDataFrom` needs `server_config.logger.url`).
262
+ Graph history has no `server_config.logger.url` to read on such a page, so `loggerUrl()` falls back
263
+ to `/data` on the server the modules were loaded from. `/data` needs a login, so a page on another
264
+ site gets dotted "no data" gaps instead of history.
263
265
 
264
266
  ### Looking up topics
265
267
 
package/core.js CHANGED
@@ -66,6 +66,11 @@ function nextUniqueId() { return ++unique_id; }
66
66
  const RECEIVER_ATTRIBUTES = ['value','color','type','label','topic','graphable','wired','wiring'];
67
67
  let server_config; // { user, organizations, logger, mqtt, server } - note loaded by MqttWrapper OR MqttAdmin
68
68
 
69
+ // An embedded page loads no /config.json, so assume the logger is on the server these modules came from
70
+ function loggerUrl() {
71
+ return server_config?.logger?.url ?? new URL('/data', import.meta.url).href;
72
+ }
73
+
69
74
  // This structure defines each of the common Input/Output types included within a sensor or acctuator
70
75
  // NOTE BELOW COPIED TO SERVER config.d/schema on 20 Feb - append any changes since then
71
76
 
@@ -2186,7 +2191,9 @@ class MqttTopic {
2186
2191
  } else {
2187
2192
  // Headless path: update the data tree directly (also reached by detached MqttGraphDataset topics).
2188
2193
  // topicSetPath throws if nodeMt is null, so guard the startsWith call with nodeMt.
2189
- const isParam = this.nodeMt && topic.startsWith(this.topicSetPath + '/');
2194
+ // The node publishes its own wired paths on the read path, the UX writes them on the set path.
2195
+ const isParam = this.nodeMt
2196
+ && (topic.startsWith(this.topicPath + '/') || topic.startsWith(this.topicSetPath + '/'));
2190
2197
  if (isParam) {
2191
2198
  // Parameter subtopic arriving (e.g. now/wired, temperature/max).
2192
2199
  if (topic.split('/').pop() === 'wired') this.setWired(message);
@@ -2329,7 +2336,7 @@ class MqttTopic {
2329
2336
  addDataFrom(filename, first, cb) {
2330
2337
  //TODO this location may change
2331
2338
  // noinspection JSUnresolvedReference
2332
- let filepath = `${server_config.logger.url}/${this.topicPath}/${filename}`;
2339
+ let filepath = `${loggerUrl()}/${this.topicPath}/${filename}`;
2333
2340
  console.log("Adding from", filepath);
2334
2341
  //let self = this; // if needed in Promise
2335
2342
  fetch(filepath)
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "dependencies": {
10
10
  "async": "^3.2.6",
11
11
  "chart.js": "^4.5.1",
12
- "csv-parse": "^5.6.0",
12
+ "csv-parse": "^7.0.3",
13
13
  "dial-gauge": "^0.1.5",
14
14
  "esptool-js": "^0.6.1",
15
15
  "html-element-extended": "^0.1.7",
@@ -41,5 +41,5 @@
41
41
  "unlink:hee": "rm -f node_modules/html-element-extended && npm install html-element-extended"
42
42
  },
43
43
  "type": "module",
44
- "version": "2.0.6"
44
+ "version": "2.0.7"
45
45
  }
@@ -260,6 +260,15 @@ describe('controls and wiring', () => {
260
260
  assert.equal(nowMt.wiredTopic, nodeMt.groups.sht.topics.temperature);
261
261
  });
262
262
 
263
+ test('a wired path the node publishes on its read path is a parameter, not a value', () => {
264
+ const { projectMt } = mock.runScenario('control-wired');
265
+ const groupMt = projectMt.nodes['esp8266-fb94bb'].groups.controlhysteresis;
266
+ mock.deliver('dev/lotus/esp8266-fb94bb/controlhysteresis/out/wired', 'dev/lotus/esp8266-fb94bb/set/relay/on');
267
+ assert.equal(groupMt.state.out_wired, 'dev/lotus/esp8266-fb94bb/set/relay/on');
268
+ assert.equal(groupMt.topics.out.wired, 'dev/lotus/esp8266-fb94bb/set/relay/on');
269
+ assert.equal(typeof groupMt.summaryShort(), 'string');
270
+ });
271
+
263
272
  test('an unwired control has no wiredTopic', () => {
264
273
  const { projectMt } = mock.runScenario('control-unwired');
265
274
  const nowMt = projectMt.nodes['esp8266-fb94bb'].groups.controlhysteresis?.topics?.now;
@@ -47,4 +47,19 @@ describe('embedded widgets', () => {
47
47
  // Not delivering a message here: dataChanged() calls chart.update(), and Chart.js cannot
48
48
  // measure text without a canvas, which jsdom does not have.
49
49
  });
50
+
51
+ test('a graphdataset fetches history from the logger with no server config', (t, done) => {
52
+ document.body.innerHTML =
53
+ '<mqtt-graph><mqtt-graphdataset topic="dev/lotus/esp32-784702/sht/temperature" color="red" label="Office Temperature" min=0 max=50 type="float"></mqtt-graphdataset></mqtt-graph>';
54
+ const ds = document.querySelector('mqtt-graphdataset');
55
+ const realFetch = globalThis.fetch;
56
+ let asked;
57
+ globalThis.fetch = (url) => { asked = url; return Promise.resolve({ ok: false, status: 404, statusText: 'Not Found' }); };
58
+ ds.mt.addDataFrom('2026-10-18.csv', true, (err) => {
59
+ globalThis.fetch = realFetch;
60
+ assert.equal(err, null);
61
+ assert.match(asked, /\/data\/dev\/lotus\/esp32-784702\/sht\/temperature\/2026-10-18\.csv$/);
62
+ done();
63
+ });
64
+ });
50
65
  });