Work on NMEA2000 device list

This commit is contained in:
2026-01-12 20:03:02 +01:00
parent c953340362
commit eb73c573b4
7 changed files with 485 additions and 22 deletions

32
README
View File

@@ -1,9 +1,9 @@
OBP Keypad OBP Keypad 6/1
========== ==============
- Stromversorgung über M12-Anschluß über NMEA2000 - Stromversorgung über M12-Anschluß über NMEA2000
- Eingangsbereich 6~21V - Eingangsbereich 6~21V
- Konfigurationsmodus durch langen Tastendruck auf DST - Konfigurationsmodus durch langen Tastendruck (>3s) auf DST
- Tiefschlaf und Reset aus Konfigmodus heraus auswählbar - Tiefschlaf und Reset aus Konfigmodus heraus auswählbar
- Konfiguration über Web-Interface - Konfiguration über Web-Interface
- Buzzer für Tastendruck-Feedback - Buzzer für Tastendruck-Feedback
@@ -12,7 +12,7 @@ OBP Keypad
Optionen für später Optionen für später
- Helligkeitssensor z.B. zum automatischen LED dimmen - Helligkeitssensor z.B. zum automatischen LED dimmen
Pins umbelegen: Pins umbelegen?:
I²C -> D0, D1 (GPIO 44, 43) I²C -> D0, D1 (GPIO 44, 43)
LEDs umsortieren: A0 bis A5 für die 6 LEDs LEDs umsortieren: A0 bis A5 für die 6 LEDs
A6 als analoger Input für Sensor A6 als analoger Input für Sensor
@@ -72,11 +72,14 @@ Anschlußmöglichkeiten
Bemerkungen Bemerkungen
----------- -----------
Bei den aktuell verwendeten Tasten sind die Anschlußdrähte extrem Bei den aktuell verwendeten vorverkabelten Tasten sind die Anschlußdrähte
filigran. Leichtes Brechen und schlechte Verarbeitung. extrem filigran. Leichtes Brechen und schlechte Verarbeitung.
Besser Taster ohne Kabel verwenden. Schaltdraht mit 0,25mm² scheint am
besten geeignet zu sein, sowohl auf Tastenseite als auch zum Einführen
in den Terminalblock.
Es gibt verschiedene Varianten mit unterschiedlicher Federkraft. Es gibt verschiedene Varianten mit unterschiedlicher Federkraft.
Auswahl muß noch erfolgen Finale Tasten-Auswahl muß noch erfolgen.
Beschaltung MCU Nano Beschaltung MCU Nano
@@ -106,8 +109,8 @@ ggf. ein E-Paper angeschlossen werden.
3 B D4 GPIO7 3 B D4 GPIO7
4 B D5 GPIO8 4 B D5 GPIO8
5 B D6 GPIO9 5 B D6 GPIO9
6 Y D7 GPIO10 Illumination 6 Y D7 GPIO10
DST Y D8 GPIO17 Destination, On/Off DST Y D8 GPIO17 Destination, Konfiguration
LED Pin Remarks LED Pin Remarks
------ ---------- ---------------------- ------ ---------- ----------------------
@@ -124,7 +127,7 @@ ggf. ein E-Paper angeschlossen werden.
RX D10 GPIO21 RX D10 GPIO21
BUZZER BUZZER
TBD GPIO43 temporär, piept allerdings beim flashen über USB und beim Reset
Bauteilliste (WIP) Bauteilliste (WIP)
------------ ------------
@@ -142,7 +145,9 @@ Bauteilliste (WIP)
1x Buzzer 12V, passiv 1x Buzzer 12V, passiv
1x MOSFET 2N7000 1x MOSFET 2N7000
1x Widerstand 150 Ohm 1x Widerstand 150 Ohm
1x Kabelsatz für Tasten, 0,25 bis 0,5 mm² 1x Kabelsatz für Tasten, 0,25 mm², je 15cm lang
8x schwarz (GND)
7x farbig (Signal)
1x Terminalblock 2pol. 2,54mm schraubbar 1x Terminalblock 2pol. 2,54mm schraubbar
1x 3D-Gehäuse bestehend auf Front- und Rückseite 1x 3D-Gehäuse bestehend auf Front- und Rückseite
1x Mutternwerkzeug 3D-Druck 1x Mutternwerkzeug 3D-Druck
@@ -155,6 +160,7 @@ Bauteilliste (WIP)
Stiftleiste 2,54mm Stiftleiste 2,54mm
2x Jumper 2x Jumper
1x Polyfuse 1x Polyfuse
Schrumpfschlauch
Konfiguration Konfiguration
------------- -------------
@@ -168,6 +174,10 @@ Konfiguration
NMEA2000 NMEA2000
-------- --------
Für verbesserten Zugriff auf die Geräteliste der NMEA2000-Bibliothek
wird eine erweiterte Funktion GetDeviceByIndex benötigt.
Aus diesem Grund wird mit einem Fork der Bibliothek gearbeitet.
Es werden keine eingehenden Pakete verarbeitet bis auf die ISO-Pflichtpakete Es werden keine eingehenden Pakete verarbeitet bis auf die ISO-Pflichtpakete
Es wird eine Geräteliste geführt Es wird eine Geräteliste geführt

View File

@@ -13,6 +13,7 @@ lib_deps =
ESP32Async/AsyncTCP@3.4.9 ESP32Async/AsyncTCP@3.4.9
ESP32Async/ESPAsyncWebServer@3.9.1 ESP32Async/ESPAsyncWebServer@3.9.1
ttlappalainen/NMEA2000-library@4.24 ttlappalainen/NMEA2000-library@4.24
# https://github.com/thooge/NMEA2000.git
robtillaart/SHT31@^0.5.2 robtillaart/SHT31@^0.5.2
# only these files will be emedded into firmware # only these files will be emedded into firmware
@@ -20,6 +21,7 @@ board_build.embed_files =
lib/generated/index.html.gz lib/generated/index.html.gz
lib/generated/index.js.gz lib/generated/index.js.gz
lib/generated/sha256.js.gz lib/generated/sha256.js.gz
lib/generated/md5.js.gz
lib/generated/index.css.gz lib/generated/index.css.gz
lib/generated/config.json.gz lib/generated/config.json.gz

View File

@@ -4,6 +4,7 @@
#include <WiFi.h> #include <WiFi.h>
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <Update.h>
#include <Wire.h> #include <Wire.h>
#include <SHT31.h> // temp. sensor #include <SHT31.h> // temp. sensor
#include <NMEA2000.h> #include <NMEA2000.h>
@@ -11,6 +12,7 @@
#include <N2kMessages.h> #include <N2kMessages.h>
#include "main.h" #include "main.h"
#include "Nmea2kTwai.h" #include "Nmea2kTwai.h"
#include "N2kDeviceList.h"
#include <map> #include <map>
#include "mbedtls/md.h" // for SHA256 #include "mbedtls/md.h" // for SHA256
@@ -112,6 +114,7 @@ uint8_t rgb_brightness = 64;
uint buzzerpower = 50; // TBD make use of this uint buzzerpower = 50; // TBD make use of this
uint8_t keycode[6]; // configurable keycodes uint8_t keycode[6]; // configurable keycodes
uint8_t longcode[6]; // configurable keycodes for long pressed keys
SHT31 sht(SHT31_ADDRESS); SHT31 sht(SHT31_ADDRESS);
bool sht_available = false; bool sht_available = false;
@@ -120,7 +123,7 @@ float hum = 0.0;
int nodeid; // NMEA2000 id on bus int nodeid; // NMEA2000 id on bus
Nmea2kTwai &NMEA2000=*(new Nmea2kTwai(CAN_TX, CAN_RX, CAN_RECOVERY_PERIOD)); Nmea2kTwai &NMEA2000=*(new Nmea2kTwai(CAN_TX, CAN_RX, CAN_RECOVERY_PERIOD));
tN2kDeviceList *pN2kDeviceList;
String processor(const String& var) { String processor(const String& var) {
// dummy for now // dummy for now
@@ -300,6 +303,12 @@ void setup() {
keycode[3] = preferences.getInt("key4", 4); keycode[3] = preferences.getInt("key4", 4);
keycode[4] = preferences.getInt("key5", 5); keycode[4] = preferences.getInt("key5", 5);
keycode[5] = preferences.getInt("key6", 6); keycode[5] = preferences.getInt("key6", 6);
longcode[0] = preferences.getInt("key1long", 11);
longcode[1] = preferences.getInt("key2long", 12);
longcode[2] = preferences.getInt("key3long", 13);
longcode[3] = preferences.getInt("key4long", 14);
longcode[4] = preferences.getInt("key5long", 15);
longcode[5] = preferences.getInt("key6long", 16);
preferences.end(); preferences.end();
// Configure I/O pins // Configure I/O pins
@@ -404,6 +413,31 @@ void setup() {
serializeJson(doc, out); serializeJson(doc, out);
request->send(200, "application/json", out); request->send(200, "application/json", out);
}); });
server.on("/api/devicelist", HTTP_GET, [](AsyncWebServerRequest *request){
// NMEA2000 device list
AsyncResponseStream *response = request->beginResponseStream("application/json");
response->print("[");
bool first = true;
for (int 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;
}
// TODO last seen?
response->printf("{\"source\":%d,\"name\":\"%s\",\"manuf\":\"%d\",\"model\":\"%s\"}",
i, hex_name, d->GetManufacturerCode(), d->GetModelID());
}
response->print("]");
request->send(response);
});
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){ server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<512> doc; StaticJsonDocument<512> doc;
doc["systemName"] = "Keypad1"; doc["systemName"] = "Keypad1";
@@ -428,6 +462,13 @@ void setup() {
doc["key4"] = keycode[BUTTON_4]; doc["key4"] = keycode[BUTTON_4];
doc["key5"] = keycode[BUTTON_5]; doc["key5"] = keycode[BUTTON_5];
doc["key6"] = keycode[BUTTON_6]; doc["key6"] = keycode[BUTTON_6];
doc["key1long"] = longcode[BUTTON_1];
doc["key2long"] = longcode[BUTTON_2];
doc["key3long"] = longcode[BUTTON_3];
doc["key4long"] = longcode[BUTTON_4];
doc["key5long"] = longcode[BUTTON_5];
doc["key6long"] = longcode[BUTTON_6];
doc["envInterval"] = 5;
String out; String out;
serializeJson(doc, out); serializeJson(doc, out);
request->send(200, "application/json", out); request->send(200, "application/json", out);
@@ -466,11 +507,33 @@ void setup() {
request->send(200, "application/json", out); request->send(200, "application/json", out);
}); });
server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request){ server.on("/api/update", HTTP_POST, [](AsyncWebServerRequest *request){
// the request handler is triggered after the upload has finished...
// create the response, add header, and send response
StaticJsonDocument<100> doc; StaticJsonDocument<100> doc;
doc["status"] = "FAILED"; doc["status"] = "FAILED";
String out; String out;
serializeJson(doc, out); serializeJson(doc, out);
request->send(200, "application/json", out); request->send(200, "application/json", out);
}, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
// this is the new image upload part
Serial.print("Retrieving firmware image named: ");
Serial.println(filename);
if (index == 0) {
if (! Update.begin(UPDATE_SIZE_UNKNOWN)) {
Update.printError(Serial);
}
}
if (Update.write(data, len) != len) {
Update.printError(Serial);
}
if (final) {
if (!Update.end(true)) {
Update.printError(Serial);
}
}
}); });
// TODO POST vom Client entgegennehmen // TODO POST vom Client entgegennehmen
@@ -501,21 +564,31 @@ void setup() {
4 // Industrygoup=Marine 4 // Industrygoup=Marine
); );
NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// Debug // Debug Start
// NMEA2000.SetForwardStream(&Serial); // NMEA2000.SetForwardStream(&Serial);
// NMEA2000.SetForwardType(tNMEA2000::fwdt_Text);
// NMEA2000.SetForwardOwnMessages(true);
// NMEA2000.SetDebugMode(tNMEA2000::dm_2); // NMEA2000.SetDebugMode(tNMEA2000::dm_2);
// Debug End
//TODO: N2km_NodeOnly N2km_ListenAndNode?
NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid); NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, nodeid);
NMEA2000.SetForwardOwnMessages(false); NMEA2000.SetForwardOwnMessages(false);
NMEA2000.SetHeartbeatIntervalAndOffset(NMEA2000_HEARTBEAT_INTERVAL); NMEA2000.SetHeartbeatIntervalAndOffset(NMEA2000_HEARTBEAT_INTERVAL);
const unsigned long TransmitPGNs[] = { // Features?
127502UL, // 130311 environment
// 130316 temperature extended range
const unsigned long TransmitPGNs[] PROGMEM= {
127502UL, // switch bank control
130312UL, // temperature
130313UL, // humidity
0 0
}; };
NMEA2000.ExtendTransmitMessages(TransmitPGNs); NMEA2000.ExtendTransmitMessages(TransmitPGNs);
pN2kDeviceList = new tN2kDeviceList(&NMEA2000);
// Debug: NMEA2000.EnableForward(true);
NMEA2000.Open(); NMEA2000.Open();
// internal user led (red) // internal user led (red)
@@ -618,6 +691,41 @@ void atariKeyclick() {
ledcWriteTone(LEDC_CHANNEL, 0); ledcWriteTone(LEDC_CHANNEL, 0);
} }
void print_n2k_devicelist() {
if (!pN2kDeviceList) {
Serial.println("Devicelist not initialized");
}
if (!pN2kDeviceList->ReadResetIsListUpdated()) {
// no changes, nothing to do
Serial.println("Devicelist empty or unchanged");
return;
}
Serial.println("---- NMEA2000 Device List ----");
for (int i = 0; i <= 252; i++) {
const tNMEA2000::tDevice *d = pN2kDeviceList->FindDeviceBySource(i);
if (d == nullptr) {
continue;
}
// age in milliseconds
unsigned long lastseen = pN2kDeviceList->GetDeviceLastMessageTime(i);
Serial.printf("Device %d age %d ms\n", i, lastseen);
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));
Serial.printf("Src:%d Man:%d Model:%s (0x%s)\n",
d->GetSource(),
d->GetManufacturerCode(),
d->GetModelID(),
hex_name
);
}
Serial.println("------------------------------");
}
// rename to: void sendSwitchBank()
void SendToN2K(uint8_t keycode) { void SendToN2K(uint8_t keycode) {
tN2kMsg N2kMsg; tN2kMsg N2kMsg;
tN2kBinaryStatus bankstatus; tN2kBinaryStatus bankstatus;
@@ -636,6 +744,20 @@ void SendToN2K(uint8_t keycode) {
Serial.println(" Action=On"); Serial.println(" Action=On");
} }
void send_sensor_temphum(float temp_k, float hum_perc) {
tN2kMsg N2kMsg;
static unsigned char SID = 0;
unsigned char instance = 0;
tN2kTempSource temp_src = N2kts_OutsideTemperature; // 1=outside, 2=inside
tN2kHumiditySource hum_src = N2khs_OutsideHumidity; // 0=inside, 1=outside
Serial.printf("Sending temp=%f K, hum=%f %%\n", temp_k, hum_perc);
SetN2kPGN130312(N2kMsg, SID, instance, temp_src, temp_k);
NMEA2000.SendMsg(N2kMsg);
SetN2kPGN130313(N2kMsg, SID, instance, hum_src, hum_perc);
NMEA2000.SendMsg(N2kMsg);
SID = (SID + 1) % 256;
}
void loop() { void loop() {
ButtonEvent event; ButtonEvent event;
@@ -684,17 +806,29 @@ void loop() {
if (mode == 'N') { if (mode == 'N') {
mode = 'C'; mode = 'C';
analogWrite(RGBLED_B, rgb_brightness); // blue status indicator analogWrite(RGBLED_B, rgb_brightness); // blue status indicator
Serial.println("Entering config mode");
} else { } else {
mode = 'N'; mode = 'N';
analogWrite(RGBLED_B, 0); analogWrite(RGBLED_B, 0);
Serial.println("Leaving config mode");
} }
} }
} else { } else {
// normal button // normal button
if (mode == 'N') { if (mode == 'N') {
// TODO send key code to destination // Send key code to destination
atariKeyclick(); atariKeyclick();
if ((event.pressType == ButtonPressType::SHORT)) {
SendToN2K(keycode[event.buttonId]); SendToN2K(keycode[event.buttonId]);
} else if ((event.pressType == ButtonPressType::MEDIUM)) {
SendToN2K(longcode[event.buttonId]);
}
// Debug:
if ((event.buttonId == BUTTON_1)
and (event.pressType == ButtonPressType::MEDIUM))
{
print_n2k_devicelist();
}
} else { } else {
// online config mode // online config mode
switch (event.buttonId) { switch (event.buttonId) {
@@ -777,8 +911,11 @@ void loop() {
if (millis() - lastSensor >= 5000) { if (millis() - lastSensor >= 5000) {
lastSensor = millis(); lastSensor = millis();
sht.read(); sht.read();
temp = sht.getTemperature(); temp = sht.getTemperature(); // °C
hum = sht.getHumidity(); hum = sht.getHumidity(); // Percent
// Send environment data to NMEA2000
send_sensor_temphum(temp + 273.15, hum);
} }
// development heartbeat // development heartbeat

View File

@@ -136,6 +136,16 @@
"description": "The keycode to send for key 1 (leftmost) [1 .. 28]", "description": "The keycode to send for key 1 (leftmost) [1 .. 28]",
"category": "Keys" "category": "Keys"
}, },
{
"name": "key1long",
"label": "Key 1 long code",
"type": "number",
"default": 12,
"min": 1,
"max": 28,
"description": "The keycode to send for key 1 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{ {
"name": "key2", "name": "key2",
"label": "Key 2 code", "label": "Key 2 code",
@@ -146,6 +156,16 @@
"description": "The keycode to send for key 2 (second from left) [1 .. 28]", "description": "The keycode to send for key 2 (second from left) [1 .. 28]",
"category": "Keys" "category": "Keys"
}, },
{
"name": "key2long",
"label": "Key 2 long code",
"type": "number",
"default": 12,
"min": 1,
"max": 28,
"description": "The keycode to send for key 2 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{ {
"name": "key3", "name": "key3",
"label": "Key 3 code", "label": "Key 3 code",
@@ -156,6 +176,16 @@
"description": "The keycode to send for key 3 (third from left) [1 .. 28]", "description": "The keycode to send for key 3 (third from left) [1 .. 28]",
"category": "Keys" "category": "Keys"
}, },
{
"name": "key3long",
"label": "Key 3 long code",
"type": "number",
"default": 13,
"min": 1,
"max": 28,
"description": "The keycode to send for key 3 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{ {
"name": "key4", "name": "key4",
"label": "Key 4 code", "label": "Key 4 code",
@@ -166,6 +196,16 @@
"description": "The keycode to send for key 4 (fourth from left) [1 .. 28]", "description": "The keycode to send for key 4 (fourth from left) [1 .. 28]",
"category": "Keys" "category": "Keys"
}, },
{
"name": "key4long",
"label": "Key 4 long code",
"type": "number",
"default": 14,
"min": 1,
"max": 28,
"description": "The keycode to send for key 4 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{ {
"name": "key5", "name": "key5",
"label": "Key 5 code", "label": "Key 5 code",
@@ -176,6 +216,16 @@
"description": "The keycode to send for key 5 (fifth from left) [1 .. 28]", "description": "The keycode to send for key 5 (fifth from left) [1 .. 28]",
"category": "Keys" "category": "Keys"
}, },
{
"name": "key5long",
"label": "Key 5 long code",
"type": "number",
"default": 15,
"min": 1,
"max": 28,
"description": "The keycode to send for key 5 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{ {
"name": "key6", "name": "key6",
"label": "Key 6 code", "label": "Key 6 code",
@@ -185,5 +235,47 @@
"max": 28, "max": 28,
"description": "The keycode to send for key 6 (rightmost) [1 .. 28]", "description": "The keycode to send for key 6 (rightmost) [1 .. 28]",
"category": "Keys" "category": "Keys"
},
{
"name": "key6long",
"label": "Key 6 long code",
"type": "number",
"default": 16,
"min": 1,
"max": 28,
"description": "The keycode to send for key 6 long pressed (leftmost) [1 .. 28]",
"category": "Keys"
},
{
"name": "n2kDestA",
"label": "Destination A",
"type": "list",
"description": "Destination device A",
"category": "NMEA2000"
},
{
"name": "n2kDestB",
"label": "Destination B",
"type": "list",
"description": "Destination device B",
"category": "NMEA2000"
},
{
"name": "n2kDestC",
"label": "Destination C",
"type": "list",
"description": "Destination device C",
"category": "NMEA2000"
},
{
"name": "envInterval",
"label":"Environment Data Interval",
"type": "number",
"default": "5",
"check": "checkMinMax",
"min": 1,
"max": 300,
"description": "interval seconds to send environment data [1..300]",
"category": "NMEA2000"
} }
] ]

View File

@@ -12,11 +12,12 @@ if (!window.isSecureContext) {
} }
</script> </script>
<script type="text/javascript" src="index.js"></script> <script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="md5.js"></script>
<link rel="stylesheet" href="index.css"> <link rel="stylesheet" href="index.css">
</head> </head>
<body> <body>
<div class="main"> <div class="main">
<h1 id="headline">OBPkb</h1> <h1 id="headline">OBPkb61</h1>
<div class="row"> <div class="row">
<span class="label" id="conn_label">disconnected</span> <span class="label" id="conn_label">disconnected</span>
<span class="value" id="connected"></span> <span class="value" id="connected"></span>
@@ -24,6 +25,7 @@ if (!window.isSecureContext) {
<div id="tabs"> <div id="tabs">
<div class="tab active" data-page="statusPage">Status</div> <div class="tab active" data-page="statusPage">Status</div>
<div class="tab" data-page="configPage">Config</div> <div class="tab" data-page="configPage">Config</div>
<div class="tab" data-page="devicePage">Devices</div>
<div class="tab" data-page="updatePage">Update</div> <div class="tab" data-page="updatePage">Update</div>
<div class="tab" data-url="https://computerclub.hoogi.de/obpkeypad" data-window="help" id="helpButton">Help</div> <div class="tab" data-url="https://computerclub.hoogi.de/obpkeypad" data-window="help" id="helpButton">Help</div>
</div> </div>
@@ -98,6 +100,15 @@ if (!window.isSecureContext) {
</div> </div>
</div> </div>
<div class="deviceForm tabPage hidden" id="devicePage">
<div class="buttons">
<button id="reloadDevices">Reload</button>
</div>
<div class="deviceFormRows">
</div>
</div>
<div class="tabPage hidden" id="updatePage"> <div class="tabPage hidden" id="updatePage">
<div class="row"> <div class="row">
<span class="label">Firmware type</span> <span class="label">Firmware type</span>

View File

@@ -973,6 +973,14 @@
}) })
.catch(function (err) { alert("unable to load config: " + err) }) .catch(function (err) { alert("unable to load config: " + err) })
} }
function loadDeviceList() {
getJson("api/devices")
.then(function(devices) {
let deviceForm = document.getElementById('devicePage');
console.log("Device form called");
})
.catch(function (err) { alert("unable to load devicelist: " + err) })
}
// New hash function by SubtleCrypto Browser API // New hash function by SubtleCrypto Browser API
async function hashString(str) { async function hashString(str) {
const enc = new TextEncoder(); const enc = new TextEncoder();
@@ -1089,6 +1097,9 @@
showOverlay(text, true); showOverlay(text, true);
}); });
} }
buttonHandlers.reloadDevices=function() {
console.log("Button reload devices");
}
function handleTab(el) { function handleTab(el) {
let activeName = el.getAttribute('data-page'); let activeName = el.getAttribute('data-page');
if (!activeName) { if (!activeName) {
@@ -1394,7 +1405,23 @@
} }
} }
} }
// calculate MD5 hash of firmware to upload
let md5hash;
const fwreader = new FileReader();
fwreader.addEventListener('load', function (e) {
const content = new Uint8Array(e.target.result);
let binary = "";
const chunkSize = 0x8000;
for (let i = 0; i < content.length; i += chunkSize) {
binary += String.fromCharCode.apply(null, content.subarray(i, i + chunkSize));
}
md5hash = MD5(binary).toLowerCase();
});
fwreader.readAsArrayBuffer(file);
console.log("MD5:", md5hash);
let formData = new FormData(); let formData = new FormData();
formData.append("md5", md5hash)
formData.append("file1", el.files[0]); formData.append("file1", el.files[0]);
req.open("POST", apiPrefix + '/api/update?_hash=' + encodeURIComponent(hash)); req.open("POST", apiPrefix + '/api/update?_hash=' + encodeURIComponent(hash));
req.send(formData); req.send(formData);

184
web/md5.js Normal file
View File

@@ -0,0 +1,184 @@
/**
*
* MD5 (Message-Digest Algorithm)
* http://www.webtoolkit.info/
*
**/
var MD5 = function (string) {
function RotateLeft(lValue, iShiftBits) {
return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
}
function AddUnsigned(lX,lY) {
var lX4,lY4,lX8,lY8,lResult;
lX8 = (lX & 0x80000000);
lY8 = (lY & 0x80000000);
lX4 = (lX & 0x40000000);
lY4 = (lY & 0x40000000);
lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
if (lX4 & lY4) {
return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
}
if (lX4 | lY4) {
if (lResult & 0x40000000) {
return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
} else {
return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
}
} else {
return (lResult ^ lX8 ^ lY8);
}
}
function F(x,y,z) { return (x & y) | ((~x) & z); }
function G(x,y,z) { return (x & z) | (y & (~z)); }
function H(x,y,z) { return (x ^ y ^ z); }
function I(x,y,z) { return (y ^ (x | (~z))); }
function FF(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function GG(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function HH(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function II(a,b,c,d,x,s,ac) {
a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
return AddUnsigned(RotateLeft(a, s), b);
};
function ConvertToWordArray(string) {
var lWordCount;
var lMessageLength = string.length;
var lNumberOfWords_temp1=lMessageLength + 8;
var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
var lWordArray=Array(lNumberOfWords-1);
var lBytePosition = 0;
var lByteCount = 0;
while ( lByteCount < lMessageLength ) {
lWordCount = (lByteCount-(lByteCount % 4))/4;
lBytePosition = (lByteCount % 4)*8;
lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<<lBytePosition));
lByteCount++;
}
lWordCount = (lByteCount-(lByteCount % 4))/4;
lBytePosition = (lByteCount % 4)*8;
lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
lWordArray[lNumberOfWords-2] = lMessageLength<<3;
lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
return lWordArray;
};
function WordToHex(lValue) {
var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
for (lCount = 0;lCount<=3;lCount++) {
lByte = (lValue>>>(lCount*8)) & 255;
WordToHexValue_temp = "0" + lByte.toString(16);
WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
}
return WordToHexValue;
};
function Utf8Encode(string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
};
var x=Array();
var k,AA,BB,CC,DD,a,b,c,d;
var S11=7, S12=12, S13=17, S14=22;
var S21=5, S22=9 , S23=14, S24=20;
var S31=4, S32=11, S33=16, S34=23;
var S41=6, S42=10, S43=15, S44=21;
string = Utf8Encode(string);
x = ConvertToWordArray(string);
a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;
for (k=0;k<x.length;k+=16) {
AA=a; BB=b; CC=c; DD=d;
a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
d=GG(d,a,b,c,x[k+10],S22,0x2441453);
c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
a=II(a,b,c,d,x[k+0], S41,0xF4292244);
d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
c=II(c,d,a,b,x[k+6], S43,0xA3014314);
b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
a=AddUnsigned(a,AA);
b=AddUnsigned(b,BB);
c=AddUnsigned(c,CC);
d=AddUnsigned(d,DD);
}
var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);
return temp.toLowerCase();
}