frugal-iot-client 2.0.5 → 2.0.6
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/admin.js +97 -59
- package/base/boards.json +6 -6
- package/base/partitions/{frugal_16mb.bin → default_16MB.bin} +0 -0
- package/base/partitions/default_16MB.csv +7 -0
- package/base/partitions/{frugal_8mb.bin → default_8MB.bin} +0 -0
- package/base/partitions/default_8MB.csv +7 -0
- package/cards.js +9 -0
- package/core.js +28 -12
- package/docs/FLASH_PLAN.md +53 -48
- package/flash.js +28 -8
- package/frugaliot.css +5 -1
- package/package.json +1 -1
- package/test/cardui.test.js +10 -0
- package/test/fixtures/config.json +189 -2
- package/test/flash.test.js +86 -1
- package/test/mock.js +13 -0
- package/test/page.test.js +30 -0
- package/base/partitions/frugal_16mb.csv +0 -8
- package/base/partitions/frugal_8mb.csv +0 -8
package/admin.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {EL, GET, HTMLElementExtended} from '/node_modules/html-element-extended/htmlelementextended.js';
|
|
9
9
|
import mqtt from '/node_modules/mqtt/dist/mqtt.esm.js'; // https://www.npmjs.com/package/mqtt
|
|
10
|
-
import { CssUrl, DELETE, POST, XXX, brokerHost, configSet, el, getString, getStringWith, hasCapability, mqttTempConnect, mqtt_subscribe, retainedPattern, locationParameterChange, mqtt_client, preferedLanguageSet, preferedLanguages, redirectToLogin, server_config } from './core.js';
|
|
10
|
+
import { CssUrl, DELETE, POST, XXX, brokerHost, configSet, el, getString, getStringWith, hasCapability, mqttTempConnect, mqtt_subscribe, mqtt_unsubscribe, retainedPattern, locationParameterChange, mqtt_client, preferedLanguageSet, preferedLanguages, redirectToLogin, server_config } from './core.js';
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
// The OTA upload is multipart, which POST() (JSON) cannot carry. Callback-style, like the rest.
|
|
@@ -173,8 +173,9 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
173
173
|
]);
|
|
174
174
|
}
|
|
175
175
|
// Fetch ota files and display
|
|
176
|
-
getOrDeleteOtaFiles(url) {
|
|
176
|
+
getOrDeleteOtaFiles(url, cb = () => {}) {
|
|
177
177
|
GET(url, {}, (err, json) => {
|
|
178
|
+
cb();
|
|
178
179
|
if (err) {
|
|
179
180
|
this.message(err);
|
|
180
181
|
return;
|
|
@@ -184,11 +185,11 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
184
185
|
}
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
|
-
getOtaFiles() {
|
|
188
|
-
if (this.state.org) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
getOtaFiles(cb = () => {}) {
|
|
189
|
+
if (this.state.org && this.otaOrgs.map(o => o[0]).includes(this.state.org)) {
|
|
190
|
+
this.getOrDeleteOtaFiles(`/ota_list/${this.state.org}`, cb);
|
|
191
|
+
} else {
|
|
192
|
+
cb();
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
onOtaDelete(val, ev) {
|
|
@@ -241,8 +242,9 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
241
242
|
);
|
|
242
243
|
}
|
|
243
244
|
// Fetch people and display
|
|
244
|
-
getOrChangeAdminPeople(url) {
|
|
245
|
+
getOrChangeAdminPeople(url, cb = () => {}) {
|
|
245
246
|
GET(url, {}, (err, json) => {
|
|
247
|
+
cb();
|
|
246
248
|
if (err) {
|
|
247
249
|
this.message(err.message);
|
|
248
250
|
return;
|
|
@@ -328,9 +330,10 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
328
330
|
onclick: this.onEnrolmentSecretAdd.bind(this)}),
|
|
329
331
|
]);
|
|
330
332
|
}
|
|
331
|
-
getEnrolledNodes() {
|
|
332
|
-
if (!this.state.org) return;
|
|
333
|
+
getEnrolledNodes(cb = () => {}) {
|
|
334
|
+
if (!this.state.org) { cb(); return; }
|
|
333
335
|
GET(`/nodes_list/${this.state.org}`, {}, (err, json) => {
|
|
336
|
+
cb();
|
|
334
337
|
if (err) { this.message(err.message); return; }
|
|
335
338
|
this.state.enrolled_nodes = json; // [{ project, nodeid, lora, enrolled_at }]
|
|
336
339
|
this.replaceElement("enrolled_nodes", this.enrolledNodesList());
|
|
@@ -345,9 +348,10 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
345
348
|
* itself is up. A Pi with a broken bridge still checks in, and that difference is exactly what
|
|
346
349
|
* is hard to see from production any other way.
|
|
347
350
|
*/
|
|
348
|
-
getBridges() {
|
|
349
|
-
if (!this.state.org) return;
|
|
351
|
+
getBridges(cb = () => {}) {
|
|
352
|
+
if (!this.state.org) { cb(); return; }
|
|
350
353
|
GET(`/bridges_list/${this.state.org}`, {}, (err, json) => {
|
|
354
|
+
cb();
|
|
351
355
|
if (err) { this.message(err.message); return; }
|
|
352
356
|
this.state.bridges = json; // [{ site, account, created_at, last_pull }]
|
|
353
357
|
this.subscribeBridgeState();
|
|
@@ -554,9 +558,7 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
554
558
|
const nodes = this.state.enrolled_nodes;
|
|
555
559
|
if (!nodes || !nodes.length) {
|
|
556
560
|
return el('p', {textContent:
|
|
557
|
-
"No nodes have enrolled yet, and none
|
|
558
|
-
"connects; one that asks and is refused is listed here too, so an empty list means nothing " +
|
|
559
|
-
"is reaching this server at all - check that the node's org and enrol URL are this one."});
|
|
561
|
+
"No nodes have enrolled yet, and none have asked and been rejected."});
|
|
560
562
|
}
|
|
561
563
|
const states = this.nodeStates();
|
|
562
564
|
return el('table', {class: 'nodestates'}, [
|
|
@@ -594,14 +596,17 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
594
596
|
}),
|
|
595
597
|
]);
|
|
596
598
|
}
|
|
597
|
-
getPeopleList() {
|
|
599
|
+
getPeopleList(cb = () => {}) {
|
|
598
600
|
if (this.state.org) {
|
|
599
|
-
this.getOrChangeAdminPeople(`/people_list/${this.state.org}
|
|
601
|
+
this.getOrChangeAdminPeople(`/people_list/${this.state.org}`, cb);
|
|
602
|
+
} else {
|
|
603
|
+
cb();
|
|
600
604
|
}
|
|
601
605
|
}
|
|
602
606
|
// Fetch projects and display
|
|
603
|
-
getOrChangeAdminProjects(url) {
|
|
607
|
+
getOrChangeAdminProjects(url, cb = () => {}) {
|
|
604
608
|
GET(url, {}, (err, json) => {
|
|
609
|
+
cb();
|
|
605
610
|
if (err) {
|
|
606
611
|
this.message(err.message);
|
|
607
612
|
return;
|
|
@@ -611,9 +616,11 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
611
616
|
}
|
|
612
617
|
});
|
|
613
618
|
}
|
|
614
|
-
getProjectsList() {
|
|
619
|
+
getProjectsList(cb = () => {}) {
|
|
615
620
|
if (this.state.org) {
|
|
616
|
-
this.getOrChangeAdminProjects(`/projects_list/${this.state.org}
|
|
621
|
+
this.getOrChangeAdminProjects(`/projects_list/${this.state.org}`, cb);
|
|
622
|
+
} else {
|
|
623
|
+
cb();
|
|
617
624
|
}
|
|
618
625
|
}
|
|
619
626
|
onPermissionsDelete(val, ev) {
|
|
@@ -717,9 +724,10 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
717
724
|
}
|
|
718
725
|
// Fetch all registered Farm-Platforms, and display. Not org-scoped - a platform is a many-to-many
|
|
719
726
|
// relationship with orgs/projects, recorded per-mapping in api_farms (see getFarmsList).
|
|
720
|
-
getPlatformsList() {
|
|
727
|
+
getPlatformsList(cb = () => {}) {
|
|
721
728
|
if (this.state.org) {
|
|
722
729
|
GET(`/api/platforms/list`, {}, (err, json) => {
|
|
730
|
+
cb();
|
|
723
731
|
if (err) {
|
|
724
732
|
this.message(err.message);
|
|
725
733
|
return;
|
|
@@ -733,6 +741,8 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
733
741
|
this.replaceElement("platforms_list_display", this.platformsListDisplay());
|
|
734
742
|
this.replaceElement("add_farm", this.collapsibleArea('add_farm', "Add Farm", this.farmRegisterForm));
|
|
735
743
|
});
|
|
744
|
+
} else {
|
|
745
|
+
cb();
|
|
736
746
|
}
|
|
737
747
|
}
|
|
738
748
|
onPlatformSelect(ev) {
|
|
@@ -802,9 +812,10 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
802
812
|
return el('div', {}, [el('h3', {textContent: "Register Platform"}), EL]);
|
|
803
813
|
}
|
|
804
814
|
// Fetch farm-to-project mappings registered for the selected organization, and display
|
|
805
|
-
getFarmsList() {
|
|
815
|
+
getFarmsList(cb = () => {}) {
|
|
806
816
|
if (this.state.org) {
|
|
807
817
|
GET(`/api/farms/list?org=${encodeURIComponent(this.state.org)}`, {}, (err, json) => {
|
|
818
|
+
cb();
|
|
808
819
|
if (err) {
|
|
809
820
|
this.message(err.message);
|
|
810
821
|
return;
|
|
@@ -819,6 +830,8 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
819
830
|
this.replaceElement("farm_node_actions", this.farmNodeActions());
|
|
820
831
|
this.getDeviceActionSchema();
|
|
821
832
|
});
|
|
833
|
+
} else {
|
|
834
|
+
cb();
|
|
822
835
|
}
|
|
823
836
|
}
|
|
824
837
|
onFarmSelect(ev) {
|
|
@@ -907,13 +920,13 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
907
920
|
// server_config locally - this is the API tab, so it should exercise the same API a real
|
|
908
921
|
// Farm-Platform would use. A farm can map to more than one project, so one request per project id,
|
|
909
922
|
// joined into a single row set once all have replied.
|
|
910
|
-
getFarmNodesList() {
|
|
923
|
+
getFarmNodesList(cb = () => {}) {
|
|
911
924
|
const org = this.state.org;
|
|
912
925
|
const projectIds = this.selectedFarmProjectIds();
|
|
913
926
|
if (!org || projectIds.length === 0) {
|
|
914
927
|
this.state.farm_nodes_list = [];
|
|
915
|
-
this.state.enrolled_nodes = null;
|
|
916
928
|
this.replaceElement("farm_nodes_table", this.farmNodesTable());
|
|
929
|
+
cb();
|
|
917
930
|
return;
|
|
918
931
|
}
|
|
919
932
|
let remaining = projectIds.length;
|
|
@@ -936,6 +949,7 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
936
949
|
});
|
|
937
950
|
}
|
|
938
951
|
if (--remaining === 0) {
|
|
952
|
+
cb();
|
|
939
953
|
this.state.farm_nodes_list = allNodes;
|
|
940
954
|
this.replaceElement("farm_nodes_table", this.farmNodesTable());
|
|
941
955
|
}
|
|
@@ -999,15 +1013,17 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
999
1013
|
}
|
|
1000
1014
|
// Fetch the selected node's Device Schema so the Action section can list its actions - triggered
|
|
1001
1015
|
// whenever the selected node changes.
|
|
1002
|
-
getDeviceActionSchema() {
|
|
1016
|
+
getDeviceActionSchema(cb = () => {}) {
|
|
1003
1017
|
const device = this.selectedFarmNodeDevice();
|
|
1004
1018
|
this.state.device_schema = null;
|
|
1005
1019
|
this.state.selected_action = null;
|
|
1006
1020
|
if (!device) {
|
|
1007
1021
|
this.replaceElement("action_section", this.actionSection());
|
|
1022
|
+
cb();
|
|
1008
1023
|
return;
|
|
1009
1024
|
}
|
|
1010
1025
|
GET(`/api/devices/schema?device=${encodeURIComponent(device)}`, {}, (err, json) => {
|
|
1026
|
+
cb();
|
|
1011
1027
|
if (err) {
|
|
1012
1028
|
this.message(err.message);
|
|
1013
1029
|
return;
|
|
@@ -1169,17 +1185,21 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
1169
1185
|
}
|
|
1170
1186
|
// Each of these lists is the result of one fetch made when the card opened - nothing pushes a
|
|
1171
1187
|
// change, so the heading carries the way to ask again.
|
|
1188
|
+
// refreshFn is given a callback for when its reply has arrived, which is what stops the spin.
|
|
1172
1189
|
refreshableHeading(title, refreshFn) {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1190
|
+
const iconEl = el('span', {class: 'pseudolink refresh', i8n: false, title: getString("Refresh"),
|
|
1191
|
+
textContent: '\u27f3', onclick: () => {
|
|
1192
|
+
if (iconEl.classList.contains('busy')) return;
|
|
1193
|
+
iconEl.classList.add('busy');
|
|
1194
|
+
refreshFn.call(this, () => iconEl.classList.remove('busy'));
|
|
1195
|
+
}});
|
|
1196
|
+
return el('h3', {}, [el('span', {textContent: title}), iconEl]);
|
|
1178
1197
|
}
|
|
1179
1198
|
// The organization's nodes are read out of server_config, so refreshing this table means fetching
|
|
1180
1199
|
// /config.json again rather than re-rendering what is already here.
|
|
1181
|
-
refreshNodesTable() {
|
|
1200
|
+
refreshNodesTable(cb = () => {}) {
|
|
1182
1201
|
GET("/config.json", {}, (err, json) => {
|
|
1202
|
+
cb();
|
|
1183
1203
|
if (err) { this.message(err.message); return; }
|
|
1184
1204
|
configSet(json);
|
|
1185
1205
|
this.replaceElement("nodes_table", this.nodesTable());
|
|
@@ -1204,9 +1224,7 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
1204
1224
|
this.adminSections().forEach((section) => this.replaceElement(section.rest, this.gatedContent(section)));
|
|
1205
1225
|
// Which tabs do their own network fetch, and so need one again for a new organization. Only
|
|
1206
1226
|
// Dashboard does not (mqtt-wrapper handles it). "Nodes" is in the list because it now fetches
|
|
1207
|
-
// the enrolled-node list as well as reading server_config
|
|
1208
|
-
// always said "No nodes have enrolled yet": loadSectionIfNeeded returns early for a tab that is not
|
|
1209
|
-
// in this set, so the fetch never happened.
|
|
1227
|
+
// the enrolled-node list as well as reading server_config:
|
|
1210
1228
|
this.state.sectionsNeedingLoad = new Set(['OTA', 'Admin', 'Projects', 'Nodes', 'Bridges', 'API']);
|
|
1211
1229
|
this.loadSectionIfNeeded(this.state.activeSectionTitle);
|
|
1212
1230
|
// Only a card created without an organization has a dropdown of its own; replaceElement does
|
|
@@ -1393,32 +1411,55 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
1393
1411
|
}
|
|
1394
1412
|
|
|
1395
1413
|
onNodeProjectChange(nodeId, oldProjectId, selectElement, onChanged) {
|
|
1396
|
-
// Handle project change for a node
|
|
1397
1414
|
const newProjectId = selectElement.value;
|
|
1398
1415
|
if (newProjectId === oldProjectId) {
|
|
1399
1416
|
return; // No change
|
|
1400
1417
|
}
|
|
1418
|
+
const org = this.state.org;
|
|
1419
|
+
const topic = `${org}/${oldProjectId}/${nodeId}/set/frugal_iot/project`;
|
|
1420
|
+
console.log(`Publishing ${topic} = ${newProjectId}`);
|
|
1421
|
+
// Not retained: a sleeping node still gets it, from its persistent session, when it wakes.
|
|
1422
|
+
mqtt_client.publish(topic, newProjectId, {retain: false, qos: 1});
|
|
1423
|
+
this.message(getStringWith("Asked %s to move", nodeId));
|
|
1424
|
+
this.watchNodeMove(org, nodeId, oldProjectId, newProjectId, onChanged);
|
|
1425
|
+
onChanged();
|
|
1426
|
+
}
|
|
1401
1427
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1428
|
+
// The move is done when the node is heard at its new address - nothing else says so, because the
|
|
1429
|
+
// node restarts and enrols afresh there, and the server tidies the old address away only then.
|
|
1430
|
+
watchNodeMove(org, nodeId, fromProjectId, toProjectId, onChanged) {
|
|
1431
|
+
const moves = this.state.nodeMoves || (this.state.nodeMoves = {});
|
|
1432
|
+
if (moves[nodeId]) { mqtt_unsubscribe(moves[nodeId].watch); }
|
|
1433
|
+
const watch = `${org}/${toProjectId}/${nodeId}/#`;
|
|
1434
|
+
moves[nodeId] = {from: fromProjectId, to: toProjectId, watch};
|
|
1435
|
+
mqtt_subscribe(watch, (topic, message, retained) => {
|
|
1436
|
+
// A retained message is the broker's memory of an earlier stay in that project, not this node
|
|
1437
|
+
if (retained || moves[nodeId]?.watch !== watch) { return; }
|
|
1438
|
+
delete moves[nodeId];
|
|
1439
|
+
mqtt_unsubscribe(watch);
|
|
1440
|
+
this.moveNodeInConfig(org, nodeId, fromProjectId, toProjectId);
|
|
1441
|
+
onChanged();
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1409
1444
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1445
|
+
// server_config is only fetched at startup, so it is told here what the server now believes.
|
|
1446
|
+
moveNodeInConfig(org, nodeId, fromProjectId, toProjectId) {
|
|
1447
|
+
const projects = this.projectsOf(org);
|
|
1448
|
+
const from = projects[fromProjectId]?.nodes;
|
|
1449
|
+
const to = projects[toProjectId] && (projects[toProjectId].nodes || (projects[toProjectId].nodes = {}));
|
|
1450
|
+
if (!to) { return; }
|
|
1451
|
+
to[nodeId] = {...(from?.[nodeId] || {}), lastseen: new Date().toISOString()};
|
|
1452
|
+
if (from) { delete from[nodeId]; }
|
|
1412
1453
|
}
|
|
1413
1454
|
|
|
1414
1455
|
renderProjectIdCell(node, onChanged) {
|
|
1415
|
-
|
|
1456
|
+
const move = this.state.nodeMoves?.[node.nodeId];
|
|
1457
|
+
const pending = move && move.from === node.projectId;
|
|
1416
1458
|
return el('td', {
|
|
1417
1459
|
style: 'cursor: pointer; position: relative;',
|
|
1418
|
-
title:
|
|
1460
|
+
title: pending ? getStringWith("Waiting for %s to report", node.nodeId) : getString("Click to change project"),
|
|
1419
1461
|
onclick: (e) => {
|
|
1420
|
-
|
|
1421
|
-
const dropdown = this.projectsDropdownForNode(this.state.org, node.projectId);
|
|
1462
|
+
const dropdown = this.projectsDropdownForNode(this.state.org, pending ? move.to : node.projectId);
|
|
1422
1463
|
dropdown.addEventListener('change', (changeEvent) => {
|
|
1423
1464
|
this.onNodeProjectChange(node.nodeId, node.projectId, changeEvent.target, onChanged);
|
|
1424
1465
|
});
|
|
@@ -1427,7 +1468,7 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
1427
1468
|
// Close dropdown if focus lost
|
|
1428
1469
|
dropdown.addEventListener('blur', onChanged);
|
|
1429
1470
|
}
|
|
1430
|
-
}, [node.projectId]);
|
|
1471
|
+
}, [pending ? `${node.projectId} → ${move.to} …` : node.projectId]);
|
|
1431
1472
|
}
|
|
1432
1473
|
|
|
1433
1474
|
// Content of the OTA tab below the organization dropdown - only rendered once an org is selected
|
|
@@ -1627,20 +1668,17 @@ class MqttAdmin extends HTMLElementExtended { // TODO-89 may depend on organizat
|
|
|
1627
1668
|
el('section', {}, [
|
|
1628
1669
|
this.refreshableHeading("Enrolled nodes", this.getEnrolledNodes),
|
|
1629
1670
|
el('p', {textContent:
|
|
1630
|
-
"
|
|
1631
|
-
"
|
|
1632
|
-
"
|
|
1633
|
-
"node that asked and was refused is listed here as well, with why: click Approve on it " +
|
|
1634
|
-
"to accept its next request whatever secret it presents. It retries every half hour."}),
|
|
1671
|
+
"Nodes that have obtained broker credentials on first connection. " +
|
|
1672
|
+
"Forget reissues a new credential at next connection and is needed if memory has been erased. " +
|
|
1673
|
+
"Refused nodes are listed - click Approve to allow (on next retry, typically 30 minutes)."}),
|
|
1635
1674
|
this.state.elements.enrolled_nodes = this.enrolledNodesList(),
|
|
1636
1675
|
]),
|
|
1637
1676
|
el('section', {}, [
|
|
1638
1677
|
el('h3', {textContent: "Enrolment secret"}),
|
|
1639
|
-
el('p', {textContent:
|
|
1640
|
-
"Compiled into
|
|
1641
|
-
"
|
|
1642
|
-
"
|
|
1643
|
-
"withdraw the old one."}),
|
|
1678
|
+
el('p', {i8n: false, textContent: getString(
|
|
1679
|
+
"Compiled into firmware and used to request broker credential. " +
|
|
1680
|
+
"Add a second one to change it: both are accepted, so nodes already flashed keep working until you " +
|
|
1681
|
+
"withdraw the old one.")}),
|
|
1644
1682
|
this.state.elements.enrolment_secrets = this.enrolmentSecretsList(),
|
|
1645
1683
|
]),
|
|
1646
1684
|
]);
|
package/base/boards.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version":
|
|
2
|
+
"version": 8,
|
|
3
3
|
"coreVersion": "pioarduino framework-arduinoespressif32 3.3.9",
|
|
4
|
-
"comment": "Base bundles for USB flashing - see FLASH_PLAN.md. Bootloaders MUST come from the same framework the firmware is built against: generated from pioarduino framework-arduinoespressif32-libs/<chip>/bin/bootloader_qio_80m.elf via 'esptool elf2image --flash-mode dio --flash-freq 80m --flash-size 4MB'. The C3 one is byte-identical to a real pioarduino build; S2/S3 use the same recipe but are unverified against a reference build. The Arduino IDE core's dio_80m ELF produces a DIFFERENT bootloader that did not work. Flash-size independent (esptool patches the header at write time). Partition tables are chip-independent. boot_app0.bin is universal.",
|
|
4
|
+
"comment": "Base bundles for USB flashing - see FLASH_PLAN.md. Bootloaders MUST come from the same framework the firmware is built against: generated from pioarduino framework-arduinoespressif32-libs/<chip>/bin/bootloader_qio_80m.elf via 'esptool elf2image --flash-mode dio --flash-freq 80m --flash-size 4MB'. The C3 one is byte-identical to a real pioarduino build; S2/S3 use the same recipe but are unverified against a reference build. The Arduino IDE core's dio_80m ELF produces a DIFFERENT bootloader that did not work. Flash-size independent (esptool patches the header at write time). Partition tables are chip-independent, and are the core's stock CSVs, so they match what a PlatformIO env using the same scheme writes. boot_app0.bin is universal.",
|
|
5
5
|
"otadata": "boot_app0.bin",
|
|
6
6
|
"partitionTableOffset": 32768,
|
|
7
7
|
"bootloaderByChip": {
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"ESP32-C3-4MB": "min_spiffs",
|
|
14
14
|
"ESP32-S2-4MB": "min_spiffs",
|
|
15
15
|
"ESP32-S3-4MB": "min_spiffs",
|
|
16
|
-
"ESP32-S3-8MB": "
|
|
17
|
-
"ESP32-S3-16MB": "
|
|
16
|
+
"ESP32-S3-8MB": "default_8MB",
|
|
17
|
+
"ESP32-S3-16MB": "default_16MB"
|
|
18
18
|
},
|
|
19
19
|
"partitionsByScheme": {
|
|
20
20
|
"min_spiffs": "partitions/min_spiffs.bin",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
21
|
+
"default_8MB": "partitions/default_8MB.bin",
|
|
22
|
+
"default_16MB": "partitions/default_16MB.bin"
|
|
23
23
|
},
|
|
24
24
|
"wholeImageChips": [
|
|
25
25
|
"ESP8266"
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Name, Type, SubType, Offset, Size, Flags
|
|
2
|
+
nvs, data, nvs, 0x9000, 0x5000,
|
|
3
|
+
otadata, data, ota, 0xe000, 0x2000,
|
|
4
|
+
app0, app, ota_0, 0x10000, 0x640000,
|
|
5
|
+
app1, app, ota_1, 0x650000,0x640000,
|
|
6
|
+
spiffs, data, spiffs, 0xc90000,0x360000,
|
|
7
|
+
coredump, data, coredump,0xFF0000,0x10000,
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Name, Type, SubType, Offset, Size, Flags
|
|
2
|
+
nvs, data, nvs, 0x9000, 0x5000,
|
|
3
|
+
otadata, data, ota, 0xe000, 0x2000,
|
|
4
|
+
app0, app, ota_0, 0x10000, 0x330000,
|
|
5
|
+
app1, app, ota_1, 0x340000,0x330000,
|
|
6
|
+
spiffs, data, spiffs, 0x670000,0x180000,
|
|
7
|
+
coredump, data, coredump,0x7F0000,0x10000,
|
package/cards.js
CHANGED
|
@@ -384,11 +384,20 @@ class MqttDeviceCard extends HTMLElementExtendedMinimum {
|
|
|
384
384
|
this.compact(hyst, {}),
|
|
385
385
|
]) : null,
|
|
386
386
|
].filter(Boolean)) : null;
|
|
387
|
+
// Any other control - a display's inputs, Blinken's timings - gets a plain row per topic, or its
|
|
388
|
+
// section came out empty and was dropped, leaving nothing to wire (control_lcd_ht did exactly that)
|
|
389
|
+
const laidOut = ['now', 'greater', 'limit', 'hysteresis', 'hysterisis', 'out', 'manual'];
|
|
390
|
+
const others = Object.entries(t).filter(([leaf]) => !laidOut.includes(leaf)).map(([, mt]) => {
|
|
391
|
+
const widget = this.widgetForTopic(mt, '', mt.wireable ? { wiring: 'open' } : {});
|
|
392
|
+
this.state.elements.widgets.push({ row: { mt }, widget });
|
|
393
|
+
return this.field(mt.name, widget);
|
|
394
|
+
});
|
|
387
395
|
return this.section(groupMt.state.name || groupId, [
|
|
388
396
|
t.now ? this.field(getString('Input'), this.widgetForTopic(t.now, '', { wiring: 'open' })) : null,
|
|
389
397
|
when ? this.field(getString('When'), when) : null,
|
|
390
398
|
t.out ? this.field(getString('Output'), this.widgetForTopic(t.out, '', { wiring: 'open' })) : null,
|
|
391
399
|
t.manual ? this.field(getString('Manual'), this.widgetForTopic(t.manual, '')) : null,
|
|
400
|
+
...others,
|
|
392
401
|
]);
|
|
393
402
|
}
|
|
394
403
|
// A bare input with no label of its own, for the inline "when" row
|
package/core.js
CHANGED
|
@@ -208,6 +208,13 @@ function mqtt_deliver(topic, msg, retained) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
|
+
// Drop one subscription, made with mqtt_subscribe, that was only wanted for a while.
|
|
212
|
+
function mqtt_unsubscribe(topic) {
|
|
213
|
+
mqtt_subscriptions = mqtt_subscriptions.filter((s) => s.topic !== topic);
|
|
214
|
+
if (mqtt_client && mqtt_client.connected) {
|
|
215
|
+
mqtt_client.unsubscribe(topic, (err) => { if (err) console.error(err); });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
211
218
|
// Drop every subscription belonging to an organization - all its topics start with the organization id.
|
|
212
219
|
// Called when switching organization, both to tell the broker (if still connected) and so that the
|
|
213
220
|
// connection made for the new organization does not resubscribe to the old organization's topics.
|
|
@@ -423,7 +430,7 @@ EN:
|
|
|
423
430
|
close: close
|
|
424
431
|
Collapse: Collapse
|
|
425
432
|
Color: Color
|
|
426
|
-
"Compiled into
|
|
433
|
+
"Compiled into firmware and used to request broker credential. Add a second one to change it: both are accepted, so nodes already flashed keep working until you withdraw the old one.": "Compiled into firmware and used to request broker credential. Add a second one to change it: both are accepted, so nodes already flashed keep working until you withdraw the old one."
|
|
427
434
|
Connect board: Connect board
|
|
428
435
|
connected: connected
|
|
429
436
|
connecting: connecting
|
|
@@ -446,7 +453,6 @@ EN:
|
|
|
446
453
|
Done: Done
|
|
447
454
|
ds18b20: ds18b20
|
|
448
455
|
e.g. LiteFarm: e.g. LiteFarm
|
|
449
|
-
Each node has its own broker credential, which it collects from this server the first time it connects. Forget one to have it issued a new credential - needed if its filesystem has been erased, because it can then no longer prove which node it is.: Each node has its own broker credential, which it collects from this server the first time it connects. Forget one to have it issued a new credential - needed if its filesystem has been erased, because it can then no longer prove which node it is.
|
|
450
456
|
eCO2: eCO2
|
|
451
457
|
Email: Email
|
|
452
458
|
Enrol URL: Enrol URL
|
|
@@ -533,7 +539,7 @@ EN:
|
|
|
533
539
|
No farms registered for this organization yet.: No farms registered for this organization yet.
|
|
534
540
|
No nodes found for this farm's project(s).: No nodes found for this farm's project(s).
|
|
535
541
|
No nodes found for this organization: No nodes found for this organization
|
|
536
|
-
No nodes have enrolled yet
|
|
542
|
+
No nodes have enrolled yet, and none have asked and been rejected.: No nodes have enrolled yet, and none have asked and been rejected.
|
|
537
543
|
No organization: No organization
|
|
538
544
|
No organization selected: No organization selected
|
|
539
545
|
No OTA files uploaded yet.: No OTA files uploaded yet.
|
|
@@ -550,6 +556,7 @@ EN:
|
|
|
550
556
|
Nodes: Nodes
|
|
551
557
|
Nodes in Farm: Nodes in Farm
|
|
552
558
|
Nodes in Organization: Nodes in Organization
|
|
559
|
+
Nodes that have obtained broker credentials on first connection. Forget reissues a new credential at next connection and is needed if memory has been erased. Refused nodes are listed - click Approve to allow (on next retry, typically 30 minutes).: Nodes that have obtained broker credentials on first connection. Forget reissues a new credential at next connection and is needed if memory has been erased. Refused nodes are listed - click Approve to allow (on next retry, typically 30 minutes).
|
|
553
560
|
Not in the schema: Not in the schema
|
|
554
561
|
Not selected: Not selected
|
|
555
562
|
Note this is your organization - not the organizations whose devices you want to access.: Note this is your organization - not the organizations whose devices you want to access.
|
|
@@ -581,6 +588,8 @@ EN:
|
|
|
581
588
|
Pressure: Pressure
|
|
582
589
|
Project: Project
|
|
583
590
|
Project changed to: Project changed to
|
|
591
|
+
Asked %s to move: Asked %s to move
|
|
592
|
+
Waiting for %s to report: Waiting for %s to report
|
|
584
593
|
Project ID: Project ID
|
|
585
594
|
Project ID *: Project ID *
|
|
586
595
|
Project Name: Project Name
|
|
@@ -733,7 +742,7 @@ FR:
|
|
|
733
742
|
close: fermer
|
|
734
743
|
Collapse: Réduire
|
|
735
744
|
Color: Couleur
|
|
736
|
-
"Compiled into
|
|
745
|
+
"Compiled into firmware and used to request broker credential. Add a second one to change it: both are accepted, so nodes already flashed keep working until you withdraw the old one.": "Compilé dans le micrologiciel et utilisé pour demander l'identifiant de broker. Ajoutez-en un second pour le changer : les deux sont acceptés, donc les nœuds déjà flashés continuent de fonctionner jusqu'à ce que vous retiriez l'ancien."
|
|
737
746
|
Connect board: Connecter la carte
|
|
738
747
|
connected: connecté
|
|
739
748
|
connecting: connexion
|
|
@@ -756,7 +765,6 @@ FR:
|
|
|
756
765
|
Done: Fait
|
|
757
766
|
ds18b20: ds18b20
|
|
758
767
|
e.g. LiteFarm: par ex. LiteFarm
|
|
759
|
-
Each node has its own broker credential, which it collects from this server the first time it connects. Forget one to have it issued a new credential - needed if its filesystem has been erased, because it can then no longer prove which node it is.: Chaque nœud a son propre identifiant de broker, qu'il récupère sur ce serveur lors de sa première connexion. Oubliez-en un pour qu'un nouvel identifiant lui soit délivré - nécessaire si son système de fichiers a été effacé, car il ne peut alors plus prouver quel nœud il est.
|
|
760
768
|
eCO2: eCO2
|
|
761
769
|
Email: Email
|
|
762
770
|
Enrol URL: URL d'enrôlement
|
|
@@ -843,7 +851,7 @@ FR:
|
|
|
843
851
|
No farms registered for this organization yet.: Aucune ferme enregistrée pour cette organisation pour l'instant.
|
|
844
852
|
No nodes found for this farm's project(s).: Aucun nœud trouvé pour le(s) projet(s) de cette ferme.
|
|
845
853
|
No nodes found for this organization: Aucun nœud trouvé pour cette organisation
|
|
846
|
-
No nodes have enrolled yet
|
|
854
|
+
No nodes have enrolled yet, and none have asked and been rejected.: Aucun nœud ne s'est encore enrôlé, et aucun n'a été refusé.
|
|
847
855
|
No organization: Aucune organisation
|
|
848
856
|
No organization selected: Aucune organisation sélectionnée
|
|
849
857
|
No OTA files uploaded yet.: Aucun fichier OTA téléversé pour l'instant.
|
|
@@ -860,6 +868,7 @@ FR:
|
|
|
860
868
|
Nodes: Nœuds
|
|
861
869
|
Nodes in Farm: Nœuds dans la ferme
|
|
862
870
|
Nodes in Organization: Nœuds dans l'organisation
|
|
871
|
+
Nodes that have obtained broker credentials on first connection. Forget reissues a new credential at next connection and is needed if memory has been erased. Refused nodes are listed - click Approve to allow (on next retry, typically 30 minutes).: Nœuds ayant obtenu leur identifiant de broker à la première connexion. Oublier en délivre un nouveau à la prochaine connexion, nécessaire si la mémoire a été effacée. Les nœuds refusés sont listés - cliquez sur Approuver pour autoriser (à la prochaine tentative, généralement 30 minutes).
|
|
863
872
|
Not in the schema: Absent du schéma
|
|
864
873
|
Not selected: Non sélectionné
|
|
865
874
|
Note this is your organization - not the organizations whose devices you want to access.: Ceci est votre organisation - pas les organisations dont vous voulez accéder aux appareils.
|
|
@@ -891,6 +900,8 @@ FR:
|
|
|
891
900
|
Pressure: Pression
|
|
892
901
|
Project: Projet
|
|
893
902
|
Project changed to: Projet changé en
|
|
903
|
+
Asked %s to move: Déplacement demandé à %s
|
|
904
|
+
Waiting for %s to report: En attente du rapport de %s
|
|
894
905
|
Project ID: ID du projet
|
|
895
906
|
Project ID *: ID du projet *
|
|
896
907
|
Project Name: Nom du projet
|
|
@@ -1043,7 +1054,7 @@ HI:
|
|
|
1043
1054
|
close: बंद करें
|
|
1044
1055
|
Collapse: छोटा करें
|
|
1045
1056
|
Color: रंग
|
|
1046
|
-
"Compiled into
|
|
1057
|
+
"Compiled into firmware and used to request broker credential. Add a second one to change it: both are accepted, so nodes already flashed keep working until you withdraw the old one.": "फ़र्मवेयर में संकलित और ब्रोकर क्रेडेंशियल माँगने के लिए उपयोग किया जाता है। इसे बदलने के लिए दूसरा जोड़ें: दोनों स्वीकार किए जाते हैं, इसलिए पहले फ़्लैश किए गए नोड तब तक काम करते रहते हैं जब तक आप पुराना वापस नहीं लेते।"
|
|
1047
1058
|
Connect board: बोर्ड कनेक्ट करें
|
|
1048
1059
|
connected: जुड़े हुए
|
|
1049
1060
|
connecting: कनेक्ट हो रहा है
|
|
@@ -1066,7 +1077,6 @@ HI:
|
|
|
1066
1077
|
Done: पूर्ण
|
|
1067
1078
|
ds18b20: ds18b20
|
|
1068
1079
|
e.g. LiteFarm: उदाहरण के लिए LiteFarm
|
|
1069
|
-
Each node has its own broker credential, which it collects from this server the first time it connects. Forget one to have it issued a new credential - needed if its filesystem has been erased, because it can then no longer prove which node it is.: हर नोड का अपना ब्रोकर क्रेडेंशियल होता है, जो वह पहली बार जुड़ते समय इस सर्वर से लेता है। किसी को भुला दें ताकि उसे नया क्रेडेंशियल मिले - यह ज़रूरी है यदि उसका फ़ाइल-सिस्टम मिटा दिया गया हो, क्योंकि तब वह यह सिद्ध नहीं कर सकता कि वह कौन सा नोड है।
|
|
1070
1080
|
eCO2: ईसीओ2
|
|
1071
1081
|
Email: ईमेल
|
|
1072
1082
|
Enrol URL: पंजीकरण URL
|
|
@@ -1153,7 +1163,7 @@ HI:
|
|
|
1153
1163
|
No farms registered for this organization yet.: इस संगठन के लिए अभी तक कोई फार्म पंजीकृत नहीं है।
|
|
1154
1164
|
No nodes found for this farm's project(s).: इस फार्म के प्रोजेक्ट (प्रोजेक्ट्स) के लिए कोई नोड नहीं मिला।
|
|
1155
1165
|
No nodes found for this organization: इस संगठन के लिए कोई नोड नहीं मिला
|
|
1156
|
-
No nodes have enrolled yet
|
|
1166
|
+
No nodes have enrolled yet, and none have asked and been rejected.: अभी तक कोई नोड पंजीकृत नहीं हुआ है, और किसी को अस्वीकार नहीं किया गया है।
|
|
1157
1167
|
No organization: कोई संगठन नहीं
|
|
1158
1168
|
No organization selected: कोई संगठन चयनित नहीं
|
|
1159
1169
|
No OTA files uploaded yet.: अभी तक कोई OTA फ़ाइल अपलोड नहीं की गई है।
|
|
@@ -1170,6 +1180,7 @@ HI:
|
|
|
1170
1180
|
Nodes: नोड्स
|
|
1171
1181
|
Nodes in Farm: फार्म में नोड्स
|
|
1172
1182
|
Nodes in Organization: संगठन में नोड्स
|
|
1183
|
+
Nodes that have obtained broker credentials on first connection. Forget reissues a new credential at next connection and is needed if memory has been erased. Refused nodes are listed - click Approve to allow (on next retry, typically 30 minutes).: वे नोड जिन्होंने पहली बार जुड़ने पर ब्रोकर क्रेडेंशियल प्राप्त किए हैं। भुलाएँ अगली बार जुड़ने पर नया क्रेडेंशियल जारी करता है, और यह ज़रूरी है यदि मेमोरी मिटा दी गई हो। अस्वीकृत नोड भी सूचीबद्ध हैं - अनुमति देने के लिए स्वीकृत करें पर क्लिक करें (अगले प्रयास पर, आमतौर पर 30 मिनट)।
|
|
1173
1184
|
Not in the schema: स्कीमा में नहीं
|
|
1174
1185
|
Not selected: चयनित नहीं
|
|
1175
1186
|
Note this is your organization - not the organizations whose devices you want to access.: ध्यान दें यह आपका संगठन है - वे संगठन नहीं जिनके उपकरणों तक आप पहुंचना चाहते हैं।
|
|
@@ -1201,6 +1212,8 @@ HI:
|
|
|
1201
1212
|
Pressure: दाब
|
|
1202
1213
|
Project: परियोजना
|
|
1203
1214
|
Project changed to: प्रोजेक्ट बदलकर किया गया
|
|
1215
|
+
Asked %s to move: "%s को स्थानांतरित करने को कहा"
|
|
1216
|
+
Waiting for %s to report: "%s की रिपोर्ट की प्रतीक्षा"
|
|
1204
1217
|
Project ID: प्रोजेक्ट आईडी
|
|
1205
1218
|
Project ID *: प्रोजेक्ट आईडी *
|
|
1206
1219
|
Project Name: प्रोजेक्ट का नाम
|
|
@@ -1353,7 +1366,7 @@ ID:
|
|
|
1353
1366
|
close: tutup
|
|
1354
1367
|
Collapse: Ciutkan
|
|
1355
1368
|
Color: Warna
|
|
1356
|
-
"Compiled into
|
|
1369
|
+
"Compiled into firmware and used to request broker credential. Add a second one to change it: both are accepted, so nodes already flashed keep working until you withdraw the old one.": "Dikompilasi ke dalam firmware dan digunakan untuk meminta kredensial broker. Tambahkan yang kedua untuk menggantinya: keduanya diterima, sehingga node yang sudah di-flash tetap bekerja sampai Anda mencabut yang lama."
|
|
1357
1370
|
Connect board: Hubungkan papan
|
|
1358
1371
|
connected: terhubung
|
|
1359
1372
|
connecting: menghubungkan
|
|
@@ -1376,7 +1389,6 @@ ID:
|
|
|
1376
1389
|
Done: Selesai
|
|
1377
1390
|
ds18b20: ds18b20
|
|
1378
1391
|
e.g. LiteFarm: misalnya LiteFarm
|
|
1379
|
-
Each node has its own broker credential, which it collects from this server the first time it connects. Forget one to have it issued a new credential - needed if its filesystem has been erased, because it can then no longer prove which node it is.: Setiap node memiliki kredensial broker sendiri, yang diambilnya dari server ini saat pertama kali tersambung. Lupakan salah satu agar diberi kredensial baru - diperlukan jika sistem berkasnya telah dihapus, karena node itu tidak lagi dapat membuktikan node yang mana dirinya.
|
|
1380
1392
|
eCO2: eCO2
|
|
1381
1393
|
Email: Email
|
|
1382
1394
|
Enrol URL: URL pendaftaran
|
|
@@ -1463,7 +1475,7 @@ ID:
|
|
|
1463
1475
|
No farms registered for this organization yet.: Belum ada farm yang terdaftar untuk organisasi ini.
|
|
1464
1476
|
No nodes found for this farm's project(s).: Tidak ada node ditemukan untuk proyek farm ini.
|
|
1465
1477
|
No nodes found for this organization: Tidak ada node ditemukan untuk organisasi ini
|
|
1466
|
-
No nodes have enrolled yet
|
|
1478
|
+
No nodes have enrolled yet, and none have asked and been rejected.: Belum ada node yang terdaftar, dan tidak ada yang ditolak.
|
|
1467
1479
|
No organization: Tidak ada organisasi
|
|
1468
1480
|
No organization selected: Tidak ada organisasi yang dipilih
|
|
1469
1481
|
No OTA files uploaded yet.: Belum ada berkas OTA yang diunggah.
|
|
@@ -1480,6 +1492,7 @@ ID:
|
|
|
1480
1492
|
Nodes: Node
|
|
1481
1493
|
Nodes in Farm: Node dalam Farm
|
|
1482
1494
|
Nodes in Organization: Node dalam Organisasi
|
|
1495
|
+
Nodes that have obtained broker credentials on first connection. Forget reissues a new credential at next connection and is needed if memory has been erased. Refused nodes are listed - click Approve to allow (on next retry, typically 30 minutes).: Node yang telah memperoleh kredensial broker saat pertama kali tersambung. Lupakan menerbitkan kredensial baru pada sambungan berikutnya, dan diperlukan jika memori telah dihapus. Node yang ditolak juga dicantumkan - klik Setujui untuk mengizinkan (pada percobaan berikutnya, biasanya 30 menit).
|
|
1483
1496
|
Not in the schema: Tidak ada di skema
|
|
1484
1497
|
Not selected: Tidak dipilih
|
|
1485
1498
|
Note this is your organization - not the organizations whose devices you want to access.: Ini adalah organisasi Anda - bukan organisasi yang perangkatnya ingin Anda akses.
|
|
@@ -1511,6 +1524,8 @@ ID:
|
|
|
1511
1524
|
Pressure: Tekanan
|
|
1512
1525
|
Project: Proyek
|
|
1513
1526
|
Project changed to: Proyek diubah menjadi
|
|
1527
|
+
Asked %s to move: Meminta %s pindah
|
|
1528
|
+
Waiting for %s to report: Menunggu laporan %s
|
|
1514
1529
|
Project ID: ID Proyek
|
|
1515
1530
|
Project ID *: ID Proyek *
|
|
1516
1531
|
Project Name: Nama Proyek
|
|
@@ -3332,6 +3347,7 @@ export {
|
|
|
3332
3347
|
mqttTempConnect,
|
|
3333
3348
|
mqtt_deliver,
|
|
3334
3349
|
mqtt_subscribe,
|
|
3350
|
+
mqtt_unsubscribe,
|
|
3335
3351
|
mqtt_unsubscribe_organization,
|
|
3336
3352
|
nextUniqueId,
|
|
3337
3353
|
nowMs,
|