More work on webserver
This commit is contained in:
48
README
48
README
@@ -6,22 +6,16 @@ OBP Keypad 6/1
|
||||
- Configuration mode via long key press (>3 s) on DST
|
||||
- Deep sleep and reset can be activated from configuration mode
|
||||
- Configuration via web interface
|
||||
- Firmware update via web interface
|
||||
- Buzzer for key press feedback
|
||||
passive, allowing tones to be programmed via PWM
|
||||
- I²C temperature/humidity sensor SHT31
|
||||
- Light sensor, e.g. for automatic LED dimming
|
||||
|
||||
Later options
|
||||
- Brightness sensor, e.g. for automatic LED dimming
|
||||
Reassign pins?:
|
||||
I²C -> D0, D1 (GPIO 44, 43)
|
||||
Reorder LEDs: A0 to A5 for the 6 LEDs
|
||||
A6 as analog input for sensor
|
||||
A7 reserved
|
||||
- Version 2
|
||||
- Seatalk 1 connector for Raymarine tiller pilot remote control
|
||||
- Version X, always optional
|
||||
- 2.9" ePaper display to show key assignments
|
||||
also means: much more complex enclosure
|
||||
|
||||
To prevent the LEDs from being distracting, switching is possible between
|
||||
permanent illumination and only brief flashing on actuation:
|
||||
@@ -63,6 +57,9 @@ Connections
|
||||
To connect LED PCB
|
||||
JST 2.54 XH 7-pin connector -> LEDs + GND
|
||||
|
||||
To connect light sensor to LED PCB
|
||||
2 pins for +3.3V and GPIO
|
||||
|
||||
For I²C modules
|
||||
2x 4pin-pin female headers
|
||||
qwiic-connector (JST_SH_BM04B-SRSS-TB_04x1.00mm)
|
||||
@@ -83,10 +80,11 @@ Connections
|
||||
Notes
|
||||
-----
|
||||
|
||||
With the currently used pre-wired buttons, the connection wires are
|
||||
extremely delicate. Easy to break and poor workmanship.
|
||||
Do not use used pre-wired buttons, the connection wires are extremely
|
||||
delicate. Easy to break and poor workmanship.
|
||||
Better to use buttons without cables. Solid wire with 0.25 mm² seems
|
||||
best suited, both on the button side and for insertion into the terminal block.
|
||||
best suited, both on the button side and for insertion into the terminal
|
||||
block.
|
||||
|
||||
There are various variants with different spring forces.
|
||||
Final button selection still pending.
|
||||
@@ -105,33 +103,7 @@ Do not use the 3.3 V pin. It is intended as an output!
|
||||
The mapping from Nano pins to GPIOs still needs to be verified.
|
||||
The Nano can be operated in two different mapping modes!
|
||||
|
||||
KEY Color Pin Remarks
|
||||
----- ------- -------- --------------------
|
||||
1 B D2 GPIO5
|
||||
2 B D3 GPIO6
|
||||
3 B D4 GPIO7
|
||||
4 B D5 GPIO8
|
||||
5 B D6 GPIO9
|
||||
6 Y D7 GPIO10
|
||||
DST Y D8 GPIO17 Destination, configuration
|
||||
|
||||
LED Pin Remarks
|
||||
------ ---------- ----------------------
|
||||
A A0 GPIO1
|
||||
B A1 GPIO2
|
||||
C A2 GPIO3
|
||||
RGB-R A3 GPIO4
|
||||
RGB-G A6 GPIO13
|
||||
RGB-B A7 GPIO14
|
||||
|
||||
CAN Pin Remarks
|
||||
------ ---------- ----------------------
|
||||
TX D9 GPIO18
|
||||
RX D10 GPIO21
|
||||
|
||||
BUZZ Pin Remarks
|
||||
------ ---------- ----------------------
|
||||
TBD
|
||||
The pin assignments are defined in main.h.
|
||||
|
||||
Bill of materials (WIP)
|
||||
-----------------
|
||||
|
||||
@@ -37,12 +37,12 @@ static const ConfigDef configdefs[] = {
|
||||
{"systemMode", ConfigType::CHAR, 'K'},
|
||||
{"nightMode", ConfigType::BOOL, false},
|
||||
{"logLevel", ConfigType::BYTE, uint8_t(4)},
|
||||
{"adminPassword", ConfigType::STRING, String("obpkp61")},
|
||||
{"adminPassword", ConfigType::STRING, String(ADMIN_PASS)},
|
||||
{"useAdminPass", ConfigType::BOOL, true},
|
||||
{"instDesc1", ConfigType::STRING, String("")},
|
||||
{"instDesc2", ConfigType::STRING, String("")},
|
||||
{"apEnable", ConfigType::BOOL, true},
|
||||
{"apPassword", ConfigType::STRING, String("obpkp61")},
|
||||
{"apPassword", ConfigType::STRING, String(WIFI_PASS)},
|
||||
{"apIp", ConfigType::STRING, String("192.168.15.1")},
|
||||
{"apMask", ConfigType::STRING, String("255.255.255.0")},
|
||||
{"stopApTime", ConfigType::SHORT, int16_t(0)},
|
||||
@@ -94,6 +94,7 @@ public:
|
||||
void loadValue(const char* key);
|
||||
void save();
|
||||
void dump();
|
||||
void clear();
|
||||
|
||||
bool hasKey(const char* key);
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
|
||||
#define PREF_NAME "nvs"
|
||||
|
||||
// Generic
|
||||
#define ADMIN_PASS "obpkp61"
|
||||
|
||||
// WIFI AP
|
||||
#define WIFI_CHANNEL 9
|
||||
#define WIFI_MAX_STA 2
|
||||
|
||||
@@ -220,6 +220,13 @@ void Config::dump() {
|
||||
LOGI(TAG, "====================================");
|
||||
}
|
||||
|
||||
void Config::clear() {
|
||||
LOGI(TAG, "Clearing NVS volume: %s", PREF_NAME);
|
||||
prefs.begin(PREF_NAME, false);
|
||||
prefs.clear();
|
||||
prefs.end();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T Config::get(const char* key) const {
|
||||
return std::get<T>(values.at(key));
|
||||
|
||||
@@ -623,16 +623,18 @@ void send_sensor_brightness(uint16_t value) {
|
||||
// device instance 8bits
|
||||
// brightness 0-100%, resolution 0.1% 16bits
|
||||
// 3 bytes reserved
|
||||
uint16_t n2kvalue = value * 1000UL / 4095; // 0..100%, resolution 0.1
|
||||
tN2kMsg N2kMsg;
|
||||
N2kMsg.SetPGN(65280); // proprietary PGN
|
||||
N2kMsg.Priority = 6;
|
||||
// 11bits manuf.-code, 2bits reserved (1), 3bits industry group
|
||||
N2kMsg.Add2ByteUInt((N2K_MANUFACTURERCODE & 0x7FF) | (0x03 << 11) | ((N2K_INDUSTRYGROUP & 0x7) << 13));
|
||||
N2kMsg.AddByte(0); // instance not yet used now
|
||||
N2kMsg.Add2ByteUInt(value * 1000UL / 4095); // resolution 0.1
|
||||
N2kMsg.Add2ByteUInt(n2kvalue);
|
||||
N2kMsg.AddByte(0xFF); //reserved bytes
|
||||
N2kMsg.AddByte(0xFF);
|
||||
N2kMsg.AddByte(0xFF);
|
||||
LOGI(TAG, "Sending LDR value=%d (%d)", n2kvalue, value);
|
||||
NMEA2000.SendMsg(N2kMsg);
|
||||
}
|
||||
|
||||
@@ -772,10 +774,9 @@ void loop() {
|
||||
send_sensor_temphum(temp + 273.15, hum);
|
||||
|
||||
#ifdef HARDWARE_V2
|
||||
// Send brightness to NMEA2000 (proprietary)
|
||||
int ldrval = analogRead(LDR);
|
||||
LOGI(TAG, "LDR value =%d", ldrval);
|
||||
// TODO send brightness to NMEA2000
|
||||
//send_sensor_brightness(ldrval);
|
||||
send_sensor_brightness(ldrval);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -76,6 +76,18 @@ String uptime_with_unit() {
|
||||
return String(uptime) + " " + uptime_unit;
|
||||
}
|
||||
|
||||
bool check_pass(String hash) {
|
||||
if (! config.getBool("useAdminPass")) {
|
||||
return true;
|
||||
}
|
||||
char salt[9]; // use to easy get upper case hex
|
||||
sprintf(salt, "%08X", apiToken + (millis()/1000UL & ~0x7UL));
|
||||
String passhash = get_sha256(String(salt) + config.getString("adminPassword"));
|
||||
LOGD(TAG, "check hash: %s", hash.c_str());
|
||||
LOGD(TAG, "check against: %s", passhash.c_str());
|
||||
return hash == passhash;
|
||||
}
|
||||
|
||||
void webserver_init() {
|
||||
|
||||
// Route for root / web page
|
||||
@@ -103,16 +115,23 @@ void webserver_init() {
|
||||
|
||||
server.on("/api/checkpass", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
// hash has to be in sha256 format
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
LOGD(TAG, "checkpass called");
|
||||
String hash = request->arg("hash");
|
||||
StaticJsonDocument<100> doc;
|
||||
String passhash = get_sha256(config.getString("adminPassword"));
|
||||
LOGD(TAG, "check hash: %s", hash.c_str());
|
||||
LOGD(TAG, "check against: %s", passhash.c_str());
|
||||
doc["status"] = hash == passhash ? "OK" : "FAILED";
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
request->send(200, "application/json", out);
|
||||
//StaticJsonDocument<100> doc;
|
||||
//char salt[9];
|
||||
//sprintf(salt, "%08X", apiToken + (millis()/1000UL & ~0x7UL));
|
||||
//String passhash = get_sha256(String(salt) + config.getString("adminPassword"));
|
||||
//LOGD(TAG, "check hash: %s", hash.c_str());
|
||||
//LOGD(TAG, "check against: %s", passhash.c_str());
|
||||
//doc["status"] = check_pass(hash) ? "OK" : "FAILED";
|
||||
//String out;
|
||||
//serializeJson(doc, out);
|
||||
//request->send(200, "application/json", out);
|
||||
response->print(R"({"status":)");
|
||||
response->print(check_pass(hash) ? R"("OK")" : R"("FAILED")");
|
||||
response->print("}");
|
||||
request->send(response);
|
||||
});
|
||||
|
||||
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
@@ -125,12 +144,10 @@ void webserver_init() {
|
||||
doc["instDesc2"] = config.getString("instDesc2");
|
||||
doc["logLevel"] = loglevel;
|
||||
doc["version"] = VERSION;
|
||||
doc["AdminPassword"] = "********";
|
||||
doc["useAdminPass"] = config.getBool("useAdminPass") ? "true" : "false";
|
||||
doc["apEnable"] = config.getBool("apEnable") ? "true" : "false";
|
||||
doc["apIp"] = config.getString("apIp");
|
||||
doc["apMask"] = config.getString("apMask");
|
||||
doc["apPassword"] = "********";
|
||||
doc["stopApTime"] = config.getShort("stopApTime");
|
||||
doc["apHidden"] = config.getBool("apHidden") ? "true" : "false";
|
||||
doc["cpuSpeed"] = config.getShort("cpuSpeed");
|
||||
@@ -160,31 +177,55 @@ void webserver_init() {
|
||||
doc["n2kDestC"] = config.getString("n2kDestC");
|
||||
doc["switchBankC"] = config.getByte("switchBankC");
|
||||
doc["envInterval"] = config.getShort("envInterval");
|
||||
|
||||
// TODO needed? Perhaps because entry fields are created by this list
|
||||
doc["AdminPassword"] = "********";
|
||||
doc["apPassword"] = "********";
|
||||
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
request->send(200, "application/json", out);
|
||||
});
|
||||
|
||||
server.on("/api/reset", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
String hash = request->arg("hash");
|
||||
response->print(R"([{"status":)");
|
||||
if (check_pass(hash)) {
|
||||
LOGD(TAG, "reset called");
|
||||
StaticJsonDocument<100> doc;
|
||||
doc["status"] = "OK";
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
request->send(200, "application/json", out);
|
||||
response->print(R"("OK")");
|
||||
response->print("}]");
|
||||
ledcWrite(LEDC_RGBLED_B, 0); // blue config light off
|
||||
led_blink(LEDC_RGBLED_G, 3, 4095, 500);
|
||||
esp_rom_uart_tx_wait_idle(0);
|
||||
ESP.restart();
|
||||
|
||||
}
|
||||
LOGD(TAG, "reset failed: wrong password");
|
||||
response->print(R"("FAILED")");
|
||||
response->print("}]");
|
||||
|
||||
/*StaticJsonDocument<100> doc;
|
||||
doc["status"] = "OK";
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
request->send(200, "application/json", out); */
|
||||
});
|
||||
|
||||
server.on("/api/resetconfig", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *response = request->beginResponseStream("application/json");
|
||||
String hash = request->arg("hash");
|
||||
if (check_pass(hash)) {
|
||||
LOGD(TAG, "resetconfig: checkpass successful");
|
||||
} else {
|
||||
LOGD(TAG, "resetconfig: checkpass failed");
|
||||
}
|
||||
LOGD(TAG, "resetconfig called");
|
||||
StaticJsonDocument<100> doc;
|
||||
doc["status"] = "FAILED";
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
request->send(200, "application/json", out);
|
||||
// config.clear();
|
||||
response->print("{");
|
||||
response->print(R"DELIM({"status": "FAILED"})DELIM");
|
||||
response->print("}");
|
||||
request->send(response);
|
||||
});
|
||||
|
||||
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
@@ -229,6 +270,10 @@ void webserver_init() {
|
||||
sprintf(salt, "%08X", apiToken + (millis()/1000UL & ~0x7UL));
|
||||
doc["salt"] = salt;
|
||||
|
||||
// security warnings
|
||||
doc["warnAdminPass"] = config.getString("AdminPassword") == ADMIN_PASS ? "true" : "false";
|
||||
doc["warnApPass"] = config.getString("apPassword") == WIFI_PASS ? "true" : "false";
|
||||
|
||||
doc["status"] = "OK";
|
||||
String out;
|
||||
serializeJson(doc, out);
|
||||
|
||||
@@ -44,15 +44,6 @@
|
||||
"description": "Log level at the USB port.\nHigher level means more output.",
|
||||
"category": "System"
|
||||
},
|
||||
{
|
||||
"name": "adminPassword",
|
||||
"label": "Admin Password",
|
||||
"type": "password",
|
||||
"default": "esp32admin",
|
||||
"check": "checkAdminPass",
|
||||
"description": "Set the password for configuration modifications and firmware upload",
|
||||
"category": "System"
|
||||
},
|
||||
{
|
||||
"name": "useAdminPass",
|
||||
"label": "Use Admin-Pass",
|
||||
@@ -61,6 +52,18 @@
|
||||
"description": "A password for configuration modifications is required.",
|
||||
"category": "System"
|
||||
},
|
||||
{
|
||||
"name": "adminPassword",
|
||||
"label": "Admin Password",
|
||||
"type": "password",
|
||||
"default": "esp32admin",
|
||||
"check": "checkAdminPass",
|
||||
"description": "Set the password for configuration modifications and firmware upload",
|
||||
"category": "System",
|
||||
"condition": {
|
||||
"useAdminPass": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "instDesc1",
|
||||
"label": "Description 1",
|
||||
|
||||
@@ -33,6 +33,9 @@ if (!window.isSecureContext) {
|
||||
|
||||
<div id="statusPage" class="tabPage">
|
||||
<div id="statusPageContent">
|
||||
<div class="row hidden">
|
||||
<span class="value" id="warnAdminPass">OK</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="label">Firmware</span>
|
||||
<span class="value" id="version">---</span>
|
||||
|
||||
@@ -68,10 +68,11 @@
|
||||
}
|
||||
function update() {
|
||||
let now = (new Date()).getTime();
|
||||
let is_connected = (lastUpdate + 3 * updateInterval) > now;
|
||||
let ce = document.getElementById('connected');
|
||||
let cl = document.getElementById('conn_label');
|
||||
if (ce) {
|
||||
if ((lastUpdate + 3 * updateInterval) > now) {
|
||||
if (is_connected) {
|
||||
ce.classList.add('ok');
|
||||
cl.textContent = 'connected';
|
||||
}
|
||||
@@ -108,6 +109,7 @@
|
||||
resetForm();
|
||||
}
|
||||
})
|
||||
if (is_connected) {
|
||||
// check if any dynamic list needs update
|
||||
for (let l in dynLists) {
|
||||
if (loadDynList(l)) {
|
||||
@@ -115,6 +117,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function resetForm(ev) {
|
||||
getJson(apiPrefix + "/api/config")
|
||||
.then(function (jsonData) {
|
||||
@@ -942,6 +945,7 @@
|
||||
forEl('#adminPassInput', function (el) {
|
||||
el.value = '';
|
||||
});
|
||||
alert("Admin password not cached anymore.");
|
||||
}
|
||||
function ensurePass() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
Reference in New Issue
Block a user