Compare commits

...

5 Commits

8 changed files with 247 additions and 147 deletions

View File

@ -28,6 +28,9 @@
</div>
</div>
<!-- Connection-Overlay -->
<!-- Notification-Container -->
<div id="notification-container" class="notification-container"></div>
<!-- Notification-Container -->
<nav class="navbar fixed-top navbar-dark bg-primary" id="navbar1">
<a class="navbar-brand" href="#">
<img src="static/img/logo.png" width="30" height="30" class="d-inline-block align-top mr-1" alt="">
@ -122,7 +125,7 @@
</div>
<div class="form-group row">
<div class="col text-center">
<button id="resettank" class="btn-wsevent btn btn-outline-primary ml-2">Tank zurücksetzen</button>
<button id="resettank" class="btn-wsevent confirm btn btn-outline-primary ml-2">Tank zurücksetzen</button>
</div>
</div>
</p>
@ -195,7 +198,7 @@
<h4>Ger&auml;t neustarten</h4>
<div class="form-group row">
<div class="col text-center">
<button id="reboot" class="btn-wsevent btn btn-outline-primary">Reboot</button>
<button id="reboot" class="btn-wsevent confirm btn btn-outline-primary">Reboot</button>
</div>
</div>
</p>
@ -230,7 +233,7 @@
</div>
<div class="form-group row">
<div class="col text-center">
<button id="sourcesave" class="btn-wsevent btn btn-outline-primary">&Uuml;bernehmen</button>
<button id="sourcesave" class="btn-wsevent confirm btn btn-outline-primary">&Uuml;bernehmen</button>
</div>
</div>
</p>

View File

@ -4075,9 +4075,9 @@ input[type=submit].btn-block {
}
.alert-success {
color: #012d36;
background-color: #ccdde1;
border-color: #b8d0d5
color: #002200;
background-color: #99ff99;
border-color: #20bb20
}
.alert-success hr {
@ -4089,9 +4089,9 @@ input[type=submit].btn-block {
}
.alert-info {
color: #084367;
background-color: #cfe6f4;
border-color: #bcdcef
color: #000022;
background-color: #99ddff;
border-color: #2040FF
}
.alert-info hr {
@ -4103,9 +4103,9 @@ input[type=submit].btn-block {
}
.alert-warning {
color: #07767a;
background-color: #cff9fb;
border-color: #bbf7f9
color: #222200;
background-color: #FFFF99;
border-color: #FFFF00
}
.alert-warning hr {
@ -4117,9 +4117,9 @@ input[type=submit].btn-block {
}
.alert-danger {
color: #851929;
background-color: #ffd6dc;
border-color: #ffc5ce
color: #200000;
background-color: #ffcccc;
border-color: #AA2020
}
.alert-danger hr {

View File

@ -75,4 +75,16 @@ hr {
100% {
transform: rotate(360deg);
}
}
}
.notification-container {
position: fixed;
top: 30%;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
}
.notification {
margin-bottom: 20px; /* Fügen Sie bei Bedarf weitere Stile hinzu */
}

View File

@ -2,142 +2,111 @@ const jsonFilePath = "static/dtc_table.json";
var dtcState = {};
function processDTCNotifications(dtcArray) {
async function processDTCNotifications(dtcArray) {
if (dtcArray.length === 0 || dtcArray[0] == "0") {
dtcState = {};
return;
}
for (var i = 0; i < dtcArray.length; i++) {
var dtcInfo = dtcArray[i].split(",");
var errorCode = dtcInfo[1];
var errorCode = parseInt(dtcInfo[1]);
var activity = parseInt(dtcInfo[3]);
var severity = parseInt(dtcInfo[2]);
if (dtcState[errorCode]) {
// Überprüfen, ob sich der Zustand von "previous" auf "active" geändert hat
if (activity !== dtcState[errorCode]) {
dtcState[errorCode] = activity;
if (activity === 1) showDTCNotification(errorCode);
try {
var { title, description } = await getDescriptionForDTCNumber(errorCode);
switch (severity) {
case 1:
severity = "info";
break;
case 2:
severity = "warning";
break;
case 3:
severity = "danger";
break;
}
} else {
// DTC ist neu, Zustand speichern und Benachrichtigung anzeigen
dtcState[errorCode] = activity;
showDTCNotification(errorCode);
}
}
}
function showDTCNotification(dtctext, severity) {
// Überprüfen, ob der Browser die Notification API unterstützt
if ("Notification" in window) {
// Überprüfen, ob der Benutzer bereits Berechtigungen erteilt hat
if (Notification.permission === "granted") {
// Benachrichtigung anzeigen
showNotification(dtctext, severity);
} else if (Notification.permission !== "denied") {
// Aufforderung zur Erlaubnis einholen
Notification.requestPermission().then(function (permission) {
if (permission === "granted") {
// Benachrichtigung anzeigen
showNotification(dtctext, severity);
} else {
// Der Benutzer hat die Berechtigung abgelehnt oder das Dialogfeld geschlossen
console.log("Benachrichtigungsberechtigung wurde verweigert.");
if (dtcState[errorCode]) {
// Überprüfen, ob sich der Zustand von "previous" auf "active" geändert hat
if (activity !== dtcState[errorCode]) {
dtcState[errorCode] = activity;
if (activity === 1) showNotification(description, severity);
}
});
} else {
// Der Benutzer hat die Berechtigung bereits verweigert
console.log("Benachrichtigungsberechtigung wurde bereits verweigert.");
} else {
// DTC ist neu, Zustand speichern und Benachrichtigung anzeigen
dtcState[errorCode] = activity;
showNotification(description, severity);
}
} catch (error) {
console.error("Error processing DTC:", error);
}
}
}
// Funktion zum Anzeigen der Benachrichtigung
function showNotification(dtctext, severity) {
var severityIcon;
switch (severity) {
case 1:
severityIcon = "static/img/info.png";
break;
case 2:
severityIcon = "static/img/warn.png";
break;
case 3:
severityIcon = "static/img/critical.png";
break;
default:
severityIcon = "static/img/none.png";
}
function getDescriptionForDTCNumber(number) {
return new Promise((resolve, reject) => {
fetch(jsonFilePath)
.then((response) => response.json())
.then((data) => {
const dtcList = data.dtc_table_data;
const foundEntry = dtcList.find((entry) => entry.num === number);
var options = {
body: dtctext,
icon: severityIcon,
};
var notification = new Notification("KTM Chain Oiler DTC", options);
// Optional: Handle Click-Event
notification.onclick = function () {
console.log("Benachrichtigung wurde angeklickt.");
};
if (foundEntry) {
const description = foundEntry.description;
const title = foundEntry.title;
resolve({ title, description });
} else {
// Wenn die Nummer nicht gefunden wurde, geben Sie einen Fehler zurück
reject(`Beschreibung für Nummer ${number} nicht gefunden.`);
}
})
.catch((error) => {
// Im Fehlerfall geben Sie den Fehler zurück
reject(error);
});
});
}
function getDescriptionForDTCNumber(number, callback) {
fetch(jsonFilePath)
.then((response) => response.json())
.then((data) => {
const dtcList = data.dtc_table_data;
const foundEntry = dtcList.find((entry) => entry.num === number);
if (foundEntry) {
const description = foundEntry.description;
const title = foundEntry.title;
callback(null, title, description);
} else {
// Wenn die Nummer nicht gefunden wurde, geben Sie einen Fehler zurück
callback(
`Beschreibung für Nummer ${number} nicht gefunden.`,
null,
null
);
}
})
.catch((error) => {
// Im Fehlerfall geben Sie den Fehler zurück
callback(error, null, null);
});
}
function showDTCModal(event) {
async function showDTCModal(event) {
var dtc = parseInt(event.currentTarget.getAttribute("data-dtc"));
var debugval = event.currentTarget.getAttribute("data-debugval");
var modal = $("#dtcModal");
getDescriptionForDTCNumber(dtc, function (error, title, description) {
if (error) {
console.error("Fehler beim Abrufen der Beschreibung:", error);
modal.find(".modal-title").text("Fehler");
modal
.find(".dtc-desc")
.text("DTC-Beschreibung konnte nicht geladen werden");
try {
var { title, description } = await getDescriptionForDTCNumber(dtc);
modal.find(".modal-title").text(title);
modal.find(".dtc-desc").text(description);
if (debugval > 0) {
modal.find(".dtc-debugval").text("Debugvalue: " + debugval);
} else {
modal.find(".modal-title").text(title);
modal.find(".dtc-desc").text(description);
if (debugval > 0) {
modal.find(".dtc-debugval").text("Debugvalue: " + debugval);
} else {
modal.find(".dtc-debugval").remove();
}
modal.find(".dtc-debugval").remove();
}
});
} catch (error) {
console.error("Fehler beim Abrufen der Beschreibung:", error);
modal.find(".modal-title").text("Fehler");
modal.find(".dtc-desc").text("DTC-Beschreibung konnte nicht geladen werden");
}
// Modal anzeigen
modal.modal("show");
}
function fillDTCTable(dtcArray) {
// Referenz auf das Tabellen-Element
var table = document.getElementById("dtc_table");
var tablediv = document.getElementById("dtc_container");
// Prüfen, ob DTC vorhanden sind
if (dtcArray.length === 0) {
if (dtcArray.length === 0 || dtcArray[0] == "0") {
// Verstecke das Tabellen-Div, wenn keine DTC vorhanden sind
tablediv.hidden = true;
table.innerHTML = "";
return;
} else {
// Zeige das Tabellen-Div, wenn DTC vorhanden sind

View File

@ -25,9 +25,7 @@ function initButtons() {
if (elements.length > 0) {
for (var i = 0; i < elements.length; i++) {
let element = elements[i];
element.addEventListener("click", function () {
websocket_sendevent("btn-" + element.id, 0);
});
element.addEventListener("click", sendButton);
}
}
}
@ -55,57 +53,58 @@ function onClose(event) {
overlay.style.display = "flex";
}
function sendButton(event) {
var targetElement = event.target;
if (
targetElement.classList.contains("confirm") &&
window.confirm("Sicher?") == false
)
return;
websocket_sendevent("btn-" + targetElement.id, targetElement.value);
}
function onMessage(event) {
var data = event.data;
console.log("ws_msg:" + event.data + "\n");
if (data.startsWith("DEBUG:")) {
if (data.startsWith("NOTIFY:")) {
var notify_data = data.slice(7).split(";")[1];
var notify_type = data.slice(7).split(";")[0];
showNotification(notify_data, notify_type);
} else if (data.startsWith("DEBUG:")) {
var addtext = data.slice(6);
var livedebug_out = document.getElementById("livedebug-out");
livedebug_out.value += addtext;
livedebug_out.scrollTop = livedebug_out.scrollHeight;
do_resize(livedebug_out);
return;
}
if (data.startsWith("DTC:")) {
} else if (data.startsWith("DTC:")) {
const dtcs = data.slice(4);
const dtcArray = dtcs.trim() !== "" ? dtcs.split(";").filter(Boolean) : [];
if (dtcArray[0] != "0") {
processDTCNotifications(dtcArray);
fillDTCTable(dtcArray);
}
return;
}
processDTCNotifications(dtcArray);
fillDTCTable(dtcArray);
if (data.startsWith("MAPPING_STATUS:")) {
} else if (data.startsWith("MAPPING_STATUS:")) {
const data_sliced = data.slice(15);
statusMapping = createMapping(data_sliced);
}
if (data.startsWith("MAPPING_STATIC:")) {
} else if (data.startsWith("MAPPING_STATIC:")) {
const data_sliced = data.slice(15);
staticMapping = createMapping(data_sliced);
}
if (data.startsWith("STATUS:")) {
} else if (data.startsWith("STATUS:")) {
const data_sliced = data.slice(7);
const result = processDataString(data_sliced, statusMapping);
console.log("STATUS:");
console.log(JSON.stringify(result, null, 2));
fillValuesToHTML(result);
return;
}
if (data.startsWith("STATIC:")) {
} else if (data.startsWith("STATIC:")) {
const data_sliced = data.slice(7);
const result = processDataString(data_sliced, staticMapping);
console.log("STATIC:");
console.log(JSON.stringify(result, null, 2));
fillValuesToHTML(result);
overlay.style.display = "none";
return;
}
}
@ -211,3 +210,27 @@ function updateProgressBar(progressBar, value) {
progressBar.style.width = value + "%";
progressBar.textContent = value + "%";
}
function showNotification(message, type) {
// Erstellen Sie ein Bootstrap-Alert-Element
var alertElement = $(
'<div class="alert alert-' +
type +
' alert-dismissible fade show notification" role="alert">' +
"<strong>" +
message +
"</strong>" +
'<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
'<span aria-hidden="true">&times;</span>' +
"</button>" +
"</div>"
);
// Fügen Sie das Alert-Element dem Container hinzu
$("#notification-container").append(alertElement);
// Nach 5 Sekunden das Alert-Element ausblenden
setTimeout(function () {
alertElement.alert("close");
}, 5000);
}

View File

@ -31,10 +31,19 @@
#include "debugger.h"
#include "struct2json.h"
typedef enum
{
info,
success,
warning,
error
} NotificationType_t;
void initWebUI();
void Webserver_Process();
void Webserver_Shutdown();
void Websocket_PushLiveDebug(String Message);
void Websocket_PushNotification(String Message, NotificationType_t type);
#endif // _WEBUI_H_

View File

@ -73,6 +73,7 @@ void Debug_Process()
inputBuffer[inputCnt] = 0; // terminate the String
inputCnt = 0;
InputProcessed = CMD_COMPLETE;
Serial.write(inputChar);
break;
case 0x1B: // Esc
@ -84,6 +85,7 @@ void Debug_Process()
case 0x21 ... 0x7E: // it's a real letter or sign and not some control-chars
inputBuffer[inputCnt] = inputChar;
inputCnt++;
Serial.write(inputChar);
break;
default:
@ -117,6 +119,10 @@ void Debug_Process()
default:
break;
}
if (InputProcessed != IDLE)
Serial.print(">");
InputProcessed = IDLE;
}
/**
@ -152,7 +158,7 @@ void Debug_pushMessage(const char *format, ...)
if ((DebuggerStatus[dbg_Serial] == enabled) || (DebuggerStatus[dbg_Webui] == enabled))
{
char buff[128]; // Buffer to hold the formatted message
va_list arg; // Variable argument list for vsnprintf
va_list arg; // Variable argument list for vsnprintf
va_start(arg, format);
// Format the message and store it in the buffer
@ -257,6 +263,14 @@ void processCmdDebug(String command)
MaintainDTC(DTC_FAKE_DTC_WARN, true, millis());
else if (command == "dtc_info")
MaintainDTC(DTC_FAKE_DTC_INFO, true, millis());
else if (command == "notify_error")
Websocket_PushNotification("Debug Error Notification", error);
else if (command == "notify_warning")
Websocket_PushNotification("Debug Warning Notification", warning);
else if (command == "notify_success")
Websocket_PushNotification("Debug Success Notification", success);
else if (command == "notify_info")
Websocket_PushNotification("Debug Info Notification", info);
else
Debug_pushMessage("unknown Command\n");
}

View File

@ -492,6 +492,10 @@ void Websocket_HandleButtons(uint8_t *data)
{
globals.systemStatus = sysStat_Shutdown;
}
else if (strcmp(identifier, "resettank") == 0)
{
PersistenceData.tankRemain_microL = LubeConfig.tankCapacity_ml * 1000;
}
else
{
Debug_pushMessage("Got unknown Button-id '%s' from ws-client\n", identifier);
@ -532,6 +536,38 @@ void Websocket_HandleSettings(uint8_t *data)
int index = findIndexByString(value, GPSBaudRateString, GPSBaudRateString_Elements);
LubeConfig.GPSBaudRate = (GPSBaudRate_t)index;
}
else if (strcmp(identifier, "ledmaxbrightness") == 0)
{
LubeConfig.LED_Max_Brightness = atoi(value);
}
else if (strcmp(identifier, "ledminbrightness") == 0)
{
LubeConfig.LED_Min_Brightness = atoi(value);
}
else if (strcmp(identifier, "pumppulse") == 0)
{
LubeConfig.BleedingPulses = atoi(value);
}
else if (strcmp(identifier, "tankwarn") == 0)
{
LubeConfig.TankRemindAtPercentage = atoi(value);
}
else if (strcmp(identifier, "tankcap") == 0)
{
LubeConfig.tankCapacity_ml = atoi(value);
}
else if (strcmp(identifier, "lubedistancerain") == 0)
{
LubeConfig.DistancePerLube_Rain = atoi(value);
}
else if (strcmp(identifier, "lubedistancenormal") == 0)
{
LubeConfig.DistancePerLube_Default = atoi(value);
}
else if (strcmp(identifier, "ledmodeflash") == 0)
{
LubeConfig.LED_Mode_Flash = value[0] == '1' ? true : false;
}
else
{
Debug_pushMessage("Got unknown Settings-id and value '%s' from ws-client\n", identifier);
@ -794,4 +830,38 @@ int findIndexByString(const char *searchString, const char *const *array, int ar
}
// String nicht gefunden, gib -1 zurück
return -1;
}
/**
* @brief Pushes a notification to all WebSocket clients.
*
* This function sends a live debug message to all connected WebSocket clients.
*
* @param Message The debug message to be sent.
* @param type The type of notification (info, success, warning, error).
* - Use NotificationType_t::info for informational messages.
* - Use NotificationType_t::success for successful operation messages.
* - Use NotificationType_t::warning for warning messages.
* - Use NotificationType_t::error for error messages.
*/
void Websocket_PushNotification(String Message, NotificationType_t type)
{
String typeString = "";
switch (type)
{
case info:
typeString = "info";
break;
case success:
typeString = "success";
break;
case warning:
typeString = "warning";
break;
case error:
typeString = "danger";
break;
}
webSocket.textAll("NOTIFY:" + typeString + ";" + Message);
Debug_pushMessage("Sending Notification to WebUI: %s\n", typeString);
}