Device list and selection integrated into configuration interface
This commit is contained in:
@@ -60,9 +60,9 @@ static const ConfigDef configdefs[] = {
|
|||||||
{"key5long", ConfigType::BYTE, uint8_t(15)},
|
{"key5long", ConfigType::BYTE, uint8_t(15)},
|
||||||
{"key6", ConfigType::BYTE, uint8_t(6)},
|
{"key6", ConfigType::BYTE, uint8_t(6)},
|
||||||
{"key6long", ConfigType::BYTE, uint8_t(16)},
|
{"key6long", ConfigType::BYTE, uint8_t(16)},
|
||||||
{"n2kDestA", ConfigType::STRING, String("")},
|
{"n2kDestA", ConfigType::STRING, String("none")},
|
||||||
{"n2kDestB", ConfigType::STRING, String("")},
|
{"n2kDestB", ConfigType::STRING, String("none")},
|
||||||
{"n2kDestC", ConfigType::STRING, String("")},
|
{"n2kDestC", ConfigType::STRING, String("none")},
|
||||||
{"envInterval", ConfigType::SHORT, int16_t(5)},
|
{"envInterval", ConfigType::SHORT, int16_t(5)},
|
||||||
|
|
||||||
// no user access
|
// no user access
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ void webserver_init() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request) {
|
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
StaticJsonDocument<512> doc;
|
StaticJsonDocument<1024> doc;
|
||||||
doc["systemName"] = config.getString("systemName");
|
doc["systemName"] = config.getString("systemName");
|
||||||
doc["systemMode"] = String(config.getChar("systemMode"));
|
doc["systemMode"] = String(config.getChar("systemMode"));
|
||||||
doc["logLevel"] = loglevel;
|
doc["logLevel"] = loglevel;
|
||||||
@@ -130,6 +130,9 @@ void webserver_init() {
|
|||||||
doc["key4long"] = longcode[BUTTON_4];
|
doc["key4long"] = longcode[BUTTON_4];
|
||||||
doc["key5long"] = longcode[BUTTON_5];
|
doc["key5long"] = longcode[BUTTON_5];
|
||||||
doc["key6long"] = longcode[BUTTON_6];
|
doc["key6long"] = longcode[BUTTON_6];
|
||||||
|
doc["n2kDestA"] = config.getString("n2kDestA");
|
||||||
|
doc["n2kDestB"] = config.getString("n2kDestB");
|
||||||
|
doc["n2kDestC"] = config.getString("n2kDestC");
|
||||||
doc["envInterval"] = config.getShort("envInterval");
|
doc["envInterval"] = config.getShort("envInterval");
|
||||||
String out;
|
String out;
|
||||||
serializeJson(doc, out);
|
serializeJson(doc, out);
|
||||||
@@ -230,7 +233,7 @@ void webserver_init() {
|
|||||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||||
response->print("[");
|
response->print("[");
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (int i = 0; i <= 252; i++) {
|
for (uint8_t i = 0; i <= 252; i++) {
|
||||||
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
|
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
|
||||||
if (d == nullptr) {
|
if (d == nullptr) {
|
||||||
continue;
|
continue;
|
||||||
@@ -244,8 +247,36 @@ void webserver_init() {
|
|||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
// TODO last seen?
|
// TODO last seen?
|
||||||
response->printf("{\"source\":%d,\"name\":\"%s\",\"manuf\":\"%d\",\"model\":\"%s\"}",
|
uint16_t mfcode = d->GetManufacturerCode();
|
||||||
i, hex_name, d->GetManufacturerCode(), d->GetModelID());
|
// TODO RAW-String!
|
||||||
|
response->printf("{\"source\":%d,\"name\":\"%s\",\"manufcode\":\"%d\",\"model\":\"%s\",\"manufname\":\"%s\"}",
|
||||||
|
i, hex_name, mfcode, d->GetModelID(), NMEA2000.GetManufacturerName(mfcode));
|
||||||
|
}
|
||||||
|
response->print("]");
|
||||||
|
request->send(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/api/dyndevlist", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||||
|
// NMEA2000 dynmic config list: devices
|
||||||
|
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||||
|
response->print("[");
|
||||||
|
bool first = true;
|
||||||
|
for (uint8_t i = 0; i <= 252; i++) {
|
||||||
|
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
|
||||||
|
if (d == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
uint64_t NAME = d->GetName();
|
||||||
|
char hex_name[17];
|
||||||
|
snprintf(hex_name, sizeof(hex_name), "%08X%08X", (uint32_t)(NAME >> 32), (uint32_t)(NAME & 0xFFFFFFFF));
|
||||||
|
if (!first) {
|
||||||
|
response->print(",");
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
uint16_t mfcode = d->GetManufacturerCode();
|
||||||
|
response->printf(R"DELIM({"v":"%s","l":"%s - %s (%d)"})DELIM",
|
||||||
|
hex_name, d->GetModelID(), NMEA2000.GetManufacturerName(mfcode), mfcode);
|
||||||
}
|
}
|
||||||
response->print("]");
|
response->print("]");
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|||||||
@@ -54,6 +54,20 @@
|
|||||||
"description": "A password for configuration modifications is required.",
|
"description": "A password for configuration modifications is required.",
|
||||||
"category": "System"
|
"category": "System"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "instDesc1",
|
||||||
|
"label": "Description 1",
|
||||||
|
"type": "string",
|
||||||
|
"description": "NMEA2000 installation description 1",
|
||||||
|
"category": "System"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "instDesc2",
|
||||||
|
"label": "Description 2",
|
||||||
|
"type": "string",
|
||||||
|
"description": "NMEA2000 installation description 2",
|
||||||
|
"category": "System"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "apEnable",
|
"name": "apEnable",
|
||||||
"label": "Enable AP",
|
"label": "Enable AP",
|
||||||
@@ -120,8 +134,8 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 64,
|
"default": 64,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 255,
|
"max": 4095,
|
||||||
"description": "The brightness of the destination leds (0..255).",
|
"description": "The brightness of the destination leds (0..4095).",
|
||||||
"category": "Hardware"
|
"category": "Hardware"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -130,8 +144,8 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 64,
|
"default": 64,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 255,
|
"max": 4095,
|
||||||
"description": "The brightness of the rgb status led (0..255).",
|
"description": "The brightness of the rgb status led (0..4095).",
|
||||||
"category": "Hardware"
|
"category": "Hardware"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -281,8 +295,8 @@
|
|||||||
"name": "n2kDestA",
|
"name": "n2kDestA",
|
||||||
"label": "Destination A",
|
"label": "Destination A",
|
||||||
"type": "dynlist",
|
"type": "dynlist",
|
||||||
"source": "devicelist",
|
"source": "dyndevlist",
|
||||||
"interval": "5000",
|
"interval": "30000",
|
||||||
"description": "Destination device A",
|
"description": "Destination device A",
|
||||||
"category": "NMEA2000"
|
"category": "NMEA2000"
|
||||||
},
|
},
|
||||||
@@ -290,8 +304,8 @@
|
|||||||
"name": "n2kDestB",
|
"name": "n2kDestB",
|
||||||
"label": "Destination B",
|
"label": "Destination B",
|
||||||
"type": "dynlist",
|
"type": "dynlist",
|
||||||
"source": "devicelist",
|
"source": "dyndevlist",
|
||||||
"interval": "5000",
|
"interval": "30000",
|
||||||
"description": "Destination device B",
|
"description": "Destination device B",
|
||||||
"category": "NMEA2000"
|
"category": "NMEA2000"
|
||||||
},
|
},
|
||||||
@@ -299,8 +313,8 @@
|
|||||||
"name": "n2kDestC",
|
"name": "n2kDestC",
|
||||||
"label": "Destination C",
|
"label": "Destination C",
|
||||||
"type": "dynlist",
|
"type": "dynlist",
|
||||||
"source": "devicelist",
|
"source": "dyndevlist",
|
||||||
"interval": "5000",
|
"interval": "30000",
|
||||||
"description": "Destination device C",
|
"description": "Destination device C",
|
||||||
"category": "NMEA2000"
|
"category": "NMEA2000"
|
||||||
},
|
},
|
||||||
@@ -312,7 +326,7 @@
|
|||||||
"check": "checkMinMax",
|
"check": "checkMinMax",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"max": 300,
|
"max": 300,
|
||||||
"description": "interval seconds to send environment data [1..300]",
|
"description": "interval in seconds to send environment data [1..300]",
|
||||||
"category": "NMEA2000"
|
"category": "NMEA2000"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -104,11 +104,11 @@ if (!window.isSecureContext) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="deviceForm tabPage hidden" id="devicePage">
|
<div class="tabPage hidden" id="devicePage">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button id="reloadDevices">Reload</button>
|
<button id="reloadDevices">Reload</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="deviceFormRows">
|
<div class="deviceListRows" id="deviceList">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
104
web/index.js
104
web/index.js
@@ -10,6 +10,7 @@
|
|||||||
let buttonHandlers = {};
|
let buttonHandlers = {};
|
||||||
let checkers = {};
|
let checkers = {};
|
||||||
let userFormatters = {};
|
let userFormatters = {};
|
||||||
|
let dynLists = {}; // dynamic selection lists
|
||||||
let apiPrefix = "";
|
let apiPrefix = "";
|
||||||
function addEl(type, clazz, parent, text) {
|
function addEl(type, clazz, parent, text) {
|
||||||
let el = document.createElement(type);
|
let el = document.createElement(type);
|
||||||
@@ -131,6 +132,12 @@
|
|||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
// check if any dynamic list needs update
|
||||||
|
for (let l in dynLists) {
|
||||||
|
if (loadDynList(l)) {
|
||||||
|
updateDynLists(l, configDefinitions)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function resetForm(ev) {
|
function resetForm(ev) {
|
||||||
getJson(apiPrefix + "/api/config")
|
getJson(apiPrefix + "/api/config")
|
||||||
@@ -614,6 +621,12 @@
|
|||||||
})
|
})
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
if (configItem.type === 'dynlist') {
|
||||||
|
el = addEl('select', clazz, frame);
|
||||||
|
if (configItem.readOnly) el.setAttribute('disabled', true);
|
||||||
|
el.setAttribute('name', configItem.name);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
if (configItem.type === 'filter') {
|
if (configItem.type === 'filter') {
|
||||||
return createFilterInput(configItem, frame, clazz);
|
return createFilterInput(configItem, frame, clazz);
|
||||||
}
|
}
|
||||||
@@ -834,6 +847,50 @@
|
|||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadDynList(apiname) {
|
||||||
|
// returns true if list was loaded
|
||||||
|
// TODO
|
||||||
|
// - implement generic all/none or static entries from config.json
|
||||||
|
// - check if current list is updated, so later dom refresh is not needed
|
||||||
|
let now = (new Date()).getTime();
|
||||||
|
if (now - dynLists[apiname].timestamp < dynLists[apiname].interval) {
|
||||||
|
// console.log("loading dynamic list: update interval not reached")
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// console.log("loading dynamic list: " + apiname)
|
||||||
|
getJson("api/"+apiname)
|
||||||
|
.then(function(list) {
|
||||||
|
dynLists[apiname].timestamp = (new Date()).getTime();
|
||||||
|
dynLists[apiname].updated = true
|
||||||
|
dynLists[apiname].data = []
|
||||||
|
dynLists[apiname].data.push({"l": "all devices (broadcast)", "v": "all"})
|
||||||
|
dynLists[apiname].data.push({"l": "disabled (none)", "v": "none"})
|
||||||
|
for (let i in list) {
|
||||||
|
// let l = list[i].model + " - " + list[i].manufname + " (" + list[i].manufcode + ")"
|
||||||
|
// dynLists[apiname].data.push({"l": l, "v": list[i].name})
|
||||||
|
dynLists[apiname].data.push({"l": list[i].l, "v": list[i].v})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
alert("unable to load dynamic list: " + err)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDynLists(listname, defs) {
|
||||||
|
for (i in defs) {
|
||||||
|
if ((defs[i].type === "dynlist") && (defs[i].source === listname)) {
|
||||||
|
let el = document.querySelector("[name='" + defs[i].name + "']");
|
||||||
|
// TODO append here static list from configdefinition
|
||||||
|
// if defs[i] has key "list" then prepend it
|
||||||
|
// let options =
|
||||||
|
// options += dynLists[listname].data
|
||||||
|
updateSelectList(el, dynLists[listname].data, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createConfigDefinitions(parent, capabilities, defs) {
|
function createConfigDefinitions(parent, capabilities, defs) {
|
||||||
let categories = {};
|
let categories = {};
|
||||||
let frame = parent.querySelector('.configFormRows');
|
let frame = parent.querySelector('.configFormRows');
|
||||||
@@ -843,6 +900,22 @@
|
|||||||
let currentCategoryPopulated = true;
|
let currentCategoryPopulated = true;
|
||||||
defs.forEach(function (item) {
|
defs.forEach(function (item) {
|
||||||
if (!item.type || item.category === undefined) return;
|
if (!item.type || item.category === undefined) return;
|
||||||
|
// look for dynamic lists
|
||||||
|
if (item.type === 'dynlist') {
|
||||||
|
if (item.source in dynLists) {
|
||||||
|
// check if interval is smaller as already defined
|
||||||
|
if (item.interval < dynLists[item.source].interval) {
|
||||||
|
dynLists[item.source].interval = item.interval;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// define new dynamic list
|
||||||
|
dynLists[item.source] = {
|
||||||
|
interval: item.interval,
|
||||||
|
updated: true
|
||||||
|
};
|
||||||
|
loadDynList(item.source);
|
||||||
|
}
|
||||||
|
}
|
||||||
let catEntry;
|
let catEntry;
|
||||||
if (categories[item.category] === undefined) {
|
if (categories[item.category] === undefined) {
|
||||||
catEntry = {
|
catEntry = {
|
||||||
@@ -974,10 +1047,32 @@
|
|||||||
.catch(function (err) { alert("unable to load config: " + err) })
|
.catch(function (err) { alert("unable to load config: " + err) })
|
||||||
}
|
}
|
||||||
function loadDeviceList() {
|
function loadDeviceList() {
|
||||||
getJson("api/devices")
|
getJson("api/devicelist")
|
||||||
.then(function(devices) {
|
.then(function(devices) {
|
||||||
let deviceForm = document.getElementById('devicePage');
|
let deviceList = document.getElementById('deviceList');
|
||||||
console.log("Device form called");
|
let row;
|
||||||
|
let el;
|
||||||
|
let even = true;
|
||||||
|
let n = 0
|
||||||
|
deviceList.replaceChildren();
|
||||||
|
for (let d in devices) {
|
||||||
|
row = addEl('div', even ? 'row even' : 'row', deviceList);
|
||||||
|
el = addEl('span', 'label', row);
|
||||||
|
el.innerHTML = devices[d].name;
|
||||||
|
el = addEl('span', 'value', row);
|
||||||
|
el.innerHTML = devices[d].model;
|
||||||
|
el.innerHTML += " - " + devices[d].manufname;
|
||||||
|
el.innerHTML += " (" + devices[d].manufcode+ "): "
|
||||||
|
el.innerHTML += devices[d].source
|
||||||
|
even = ! even;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
addEl('hr', '', deviceList);
|
||||||
|
row = addEl('div', even ? 'row even' : 'row', deviceList);
|
||||||
|
el = addEl('span', 'label', row);
|
||||||
|
el.innerHTML = "total devices";
|
||||||
|
el = addEl('span', 'value', row);
|
||||||
|
el.innerHTML = n;
|
||||||
})
|
})
|
||||||
.catch(function (err) { alert("unable to load devicelist: " + err) })
|
.catch(function (err) { alert("unable to load devicelist: " + err) })
|
||||||
}
|
}
|
||||||
@@ -1097,7 +1192,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
buttonHandlers.reloadDevices=function() {
|
buttonHandlers.reloadDevices=function() {
|
||||||
console.log("Button reload devices");
|
loadDeviceList()
|
||||||
}
|
}
|
||||||
function handleTab(el) {
|
function handleTab(el) {
|
||||||
let activeName = el.getAttribute('data-page');
|
let activeName = el.getAttribute('data-page');
|
||||||
@@ -1664,6 +1759,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
loadDeviceList();
|
||||||
});
|
});
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user