<file_sep>export const THEMES_NAMES = {
  light: "light",
  dark_grey: "dark_grey",
  green: "green",
  purpule: "purpule",
  rainbow: "rainbow",
};

export const REFRESH_RATE_MS = 500;

export const SETTINGS_FILE_NAME = "settings.json";
export const MUSIC_FOLDER = "/Music/";
<file_sep>// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

// eslint-disable-next-line import/no-extraneous-dependencies
const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("electronAPI", {
  getMusicFiles: (folderPath) =>
    ipcRenderer.invoke("getMusicFiles", folderPath),
  networkInterfaces: () => ipcRenderer.invoke("networkInterfaces"),
  isLinux: () => ipcRenderer.invoke("isLinux"),
  startParameters: () => ipcRenderer.invoke("startParameters"),
  minimize: () => ipcRenderer.invoke("minimize"),
  close: () => ipcRenderer.invoke("close"),
  appVersion: () => ipcRenderer.invoke("appVersion"),
  getUserDataPath: () => ipcRenderer.invoke("getUserDataPath"),
  profileStartTime: () => ipcRenderer.invoke("profileStartTime"),
  bufferFrom: () => ipcRenderer.invoke("bufferFrom"),
  listSerialPorts: () => ipcRenderer.invoke("listSerialPorts"),
  pathJoin: (args = []) => ipcRenderer.invoke("pathJoin", args),
  pathDirname: (filePath) => ipcRenderer.invoke("pathDirname", filePath),
  existsSync: (stringPath = "") => ipcRenderer.invoke("existsSync", stringPath),
  readFileSync: (stringPath = "") =>
    ipcRenderer.invoke("readFileSync", stringPath),
  writeFileSync: (args) => ipcRenderer.invoke("writeFileSync", args),
  writeFile: (args) => ipcRenderer.invoke("writeFile", args),
  CPUtemp: () => ipcRenderer.invoke("CPUtemp"),
  getSong: (filePath) => ipcRenderer.invoke("getSong", filePath),
  getWifiSSID: () => ipcRenderer.invoke("getWifiSSID"),
  getListOfAvailableNetworks: () =>
    ipcRenderer.invoke("getListOfAvailableNetworks"),
  disconnectFromWifi: () => ipcRenderer.invoke("disconnectFromWifi"),
  connectToWifiNetwork: () => ipcRenderer.invoke("connectToWifiNetwork"),
  onCAN: (callback) => ipcRenderer.on("fromCAN", callback),
  sendMsgToCAN: (data) => ipcRenderer.invoke("sendMsgToCAN", data),
});
<file_sep>import { BrowserWindow } from "electron";

import { wrtieIBUSdataToFile } from "./utils";

const { IbusDevices } = require("ibus");

// config

export function sendAllData(data) {
  // id, from, to, message, message hex, analyzing, description
  return `${data.id} | ${IbusDevices.getDeviceName(
    data.src
  )} |  ${IbusDevices.getDeviceName(data.dst)} | 
    ${data.msg} | 
    ${data}`;
}

export default function initIBUSinMain(ibusInterface) {
  // disables logs from ibus (lots of colorfull spam, "ibus" is using "debug" package)
  // const debug = require("debug");
  // debug.disable();

  // Configure your device location

  function init() {
    try {
      ibusInterface.startup();
    } catch (error) {
      console.log(error);
    }
  }

  // main start
  // if (window.appData.isLinux) {
  init();
  // }

  // Message structure:
  // 1. Transmitter address (8 bit Source ID)
  // 2. Length of data (number of following message bytes)
  // 3. Receiver address (8 bit Destination ID)
  // 4. Detailed description of message (maximum 32 bytes of data)
  // 5. Summary of transmitted information (check sum)

  // | Source ID | Length | Dest ID | Message Data | Checksum |
  // |-----------|--------|---------|--------------|----------|
  // |           |        |           Length                  |

  // Add an event listener on the ibus interface output stream.

  function onIbusDataHandler(data) {
    console.log(data)
    const msgDescryption = "";
    const buffMsg = Buffer.from(data.msg);
    const buffMsgHex = buffMsg.toString("hex").toLowerCase();
    const output = {
      name: null,
      data: null,
    };

    function sendToRenderer(dataTosend) {
      BrowserWindow.getFocusedWindow().webContents.send("fromCAN", dataTosend);
    }

    // remove empty data, like: from BodyModule - 0 to BodyModule - 0
    // tre for less data from CAN, for les noise in debuging (only knowns comands?)
    if (true) {
      if ((data.src === "0" && data.dst === "0") || data.src === "a4") {
        // end this round of data
      }
      if (data.src === "3b" && data.dst === "80" && buffMsgHex === "410701") {
        // filters this: id: 1588784831880 | GraphicsNavigationDriver - 3b | InstrumentClusterElectronics - 80 Message: A |  410701 | [object Object] |
      }
    }

    // from: MultiFunctionSteeringWheel && to: Radio or Telephone ('c8')
    if (data.src === "50" && (data.dst === "68" || data.dst === "c8")) {
      // volume down steering wheel
      if (data.msg === Buffer.from([50, 16]).toString("ascii")) {
        // Volume down button steering wheel
        output.name = "volDown";
        sendToRenderer(output);
      }
      // volume up steering wheel
      if (data.msg === Buffer.from([50, 17]).toString("ascii")) {
        // Volume up button steering wheel
        output.name = "volUp";
        sendToRenderer(output);
      }
      // previous track steering wheel
      if (data.msg === Buffer.from([59, 8]).toString("ascii")) {
        // Arrow up? button steering wheel
        output.name = "steeringWheelArrowDown";
        sendToRenderer(output);
      }
      // next song stearing wheel
      if (data.msg === Buffer.from([59, 1]).toString("ascii")) {
        // Arrow up button steering wheel
        output.name = "steeringWheelArrowUp";
        sendToRenderer(output);
      }
    }

    // from: MultiFunctionSteeringWheel
    if (data.src === "50") {
      // face button steering wheel
      if (buffMsgHex === "3b40") {
        // Face button steering wheel
        output.name = "pauseAudio";
        sendToRenderer(output);
      }
      // RT button steering wheel
      if (buffMsgHex === "3b80") {
        // RT button steering wheel
        output.name = "muteAudio";
        sendToRenderer(output);
      }
    }
    // egz WHOLE message: 80 0a ff 24 03 00 2b 31 32 2e 35 61 80 0f ff 24 02 00 31 30 2e 31 31 2e 32 30 31 39 5d 80 05 bf 18 00 06 24
    // or WHOLE message: Buffer(36) [128, 10, 255, 36, 3, 0, 43, 49, 50, 46, 53, 97, 128, 15, 255, 36, 2, 0, 49, 48, 46, 49, 49, 46, 50, 48, 49, 57, 93, 128, 5, 191, 24, 0, 6, 36]
    // 80 source, 0a length, ff destenation, 2b 31 32 2e 35 actual temperature, there is allso time later in string
    if (
      data.src === "80" &&
      data.dst === "ff" &&
      buffMsgHex.substring(0, 4) === "2403"
    ) {
      // outside temperature
      output.name = "tempOutside";
      output.data = buffMsgHex.substring(6, 16);
      sendToRenderer(output);
    }

    // time from instrument cluster not GPS
    // data.msg -> first 3 parts in buffer describe is it time ([36, 1, 0]) in hex: 24010031373a30392020
    if (
      data.src === "80" &&
      data.dst === "ff" &&
      buffMsgHex.substring(0, 6) === "240100"
    ) {
      // remove 3 first parts from buffer and remove 2 parts from end -> this give time in format HH:MM egz 12:45 (12h or 24h?)
      output.name = "timeFromInstrumentCluster";
      output.data = buffMsg.slice(3, 9).toString("utf-8");
      sendToRenderer(output);
    }

    // temperature outside and coolant temperature
    // from: InstrumentClusterElectronics - 80 to: GlobalBroadcastAddress - bf msg: 19154b00
    if (
      data.src === "80" &&
      data.dst === "bf" &&
      buffMsgHex.substring(0, 2) === "19"
    ) {
      // first hex in message indicates temperature outside and coolant temperatur message

      // to decimal conversion
      // in Celsius
      // CHECK IF ITS OK
      output.data.tempOutside = parseInt(buffMsgHex.substring(2, 4), 16);
      output.data.coolantTemp = parseInt(buffMsgHex.substring(4, 6), 16);

      output.name = "tempOutsideAndCoolantTemp";
      sendToRenderer(output);
    }

    if (data.src === "80" && data.dst === "ff") {
      // if (buffMsg.slice(1, 1).toString('hex') === '04') {
      if (data.msg[1] === "04") {
        // fuel consumption 1
        output.name = "fuleConsumption1";
        output.data = data.msg
          .slice(3, 14)
          .toString("utf-8")
          .replace(".", ",")
          .trim();
        sendToRenderer(output);
      }
      if (buffMsgHex.slice(0, 4) === "2405") {
        // if (data.msg[1] === '05') {
        // fuel consumption 2
        // src: "80";
        // len: "9";
        // dst: "ff";
        // msg: {"type":"Buffer","data":[36,5,0,32,56,46,52]};
        // 24050020382e34
        // crc: "55";
        output.name = "fuleConsumption2";
        output.data = buffMsgHex
          .slice(6, 14)
          .toString("utf-8")
          .replace(".", ",")
          .trim();
        sendToRenderer(output);
      }
      if (buffMsgHex.slice(0, 4) === "2406") {
        output.name = "range";
        output.data = buffMsgHex.slice(6, 14).toString("utf-8").trim();

        sendToRenderer(output);
      }
      if (data.msg[1] === "07") {
        // window.CANdata.distance
        output.name = "distance";
        output.data = data.msg.slice(3, 14).toString("hex");

        sendToRenderer(output);
      }
      if (data.msg[1] === "09") {
        output.name = "speedLimit";
        output.data = data.msg.slice(3, 14).toString("hex");

        sendToRenderer(output);
      }
      if (buffMsgHex.slice(0, 6) === "240a00") {
        output.data = buffMsgHex.slice(6, 14);
        output.name = "avgSpeed";

        sendToRenderer(output);
      }
    }
    // if from NavigationEurope 7f
    if (data.src === "7f") {
      // Town from NavModule GPS
      // data.msg -> third part ("1") in buffer msg describe is it town ([164, 0, 1])
      if (buffMsgHex.slice(0, 6) === "a40001") {
        output.data = buffMsg
          .slice(3, buffMsg.indexOf("0", 5))
          .toString("utf-8");
        output.name = "navTownName";
      }
      // Street from NavModule GPS
      // data.msg -> third part ("2") in buffer msg describe is it street ([164, 0, 2])
      if (buffMsgHex.slice(0, 6) === "a40002") {
        output.data = buffMsg
          .slice(3, buffMsg.indexOf("0", 5))
          .toString("utf-8");
        output.name = "navStreetName";
      }
      // GPS cords, time, altitude
      // data.msg -> first 3 parts in buffer describe is it GPS position ([162, 1, 0]) -> 'a20100' -- maybe only one part?? [162] - > 'a2', it can be ([162, 0, 0])
      if (buffMsgHex.slice(0, 6) === "a20100") {
        // buffer egxample: [162, 1, 0, 81, 64, 82, 80, 0, 32, 87, 65, 144, 1, 16, 0, 16, 71, 73] -> hex -> a2 01 00 51 40 52 50 00 20 57 41 90 01 10 00 10 47 49
        // first 3 can be  erased (a20100) -> 514052500020574190011000104749
        // from nav encoder: 2019-10-13 10:25:49.105||NAV  |TEL |Current position|GPS fix; 51°40'52.5"N  20°57'41.9"E; Alt 111m;  UTC 08:25:49
        // 51 (stopnie) 40 (minuty) 52 (sekundy) 50 (połowki sekundy N?) 00 (separator?) 20 (stopnie) 57 (minuty) 41 (sekundy) 90 (połowki sekundy E?) 01 10 00 (wysokość 111m, traktować jak stringi i połączyć? wtedy 01 + 10 + 00 daje 011000 -> 110m?) 10 (UTC godzina)47 (UTC minuty) 49 (UTC sekundy)
        output.name = "navLatLngTimeLatitiude";
        output.data.lat = buffMsg.slice(3, 7).toString("hex");
        output.data.lng = buffMsg.slice(8, 12).toString("hex");
        output.data.altitude = parseInt(buffMsgHex.slice(24, 28), 10);
        output.data.utcTime = buffMsg.slice(15, 18).toString("hex");

        sendToRenderer(output);
      }
    }

    // light dimmer
    // data.msg -> hex -> 5C FF FC FF 00 -> lights on?
    // data.msg -> hex -> 5c ff c9 ff 00 -> lights on?
    if (data.src === "d0" && data.dst === "bf" && buffMsgHex === "5cff3fff00") {
      output.data = "day";
      output.name = "lightSensor";

      sendToRenderer(output);
    }

    if (data.src === "d0" && data.dst === "bf" && buffMsgHex === "5cfe3fff00") {
      output.data = "night";
      output.name = "lightSensor";

      sendToRenderer(output);
    }

    // debugLog('[app] Analyzing: ', Buffer.from(data), '\n');

    const msgDate = new Date();

    wrtieIBUSdataToFile(
      msgDate.getHours(),
      ":",
      msgDate.getMinutes(),
      ":",
      msgDate.getSeconds(),
      ":",
      msgDate.getMilliseconds(),
      " | id: ",
      data.id,
      " | src: ",
      IbusDevices.getDeviceName(data.src),
      "| dst: ",
      IbusDevices.getDeviceName(data.dst),
      "| msg:",
      data.msg,
      "| buffMsgHex:",
      buffMsgHex,
      "| data: ",
      JSON.stringify(data),
      "| msgDescryption: ",
      msgDescryption
    );
    sendToRenderer(output); // null
  }

  // Cleanup and close the device when exiting.
  function onSignalInt() {
    ibusInterface.shutdown(() => {
      process.exit();
    });
  }
  // events
  ibusInterface.on("data", onIbusDataHandler);

  // implementation
  // process.on('SIGINT', onSignalInt);
  process.on("exit", onSignalInt);
}
<file_sep>// const { fs } = require("fs");

// export default function debugLog(...args) {
//   // let args = arguments;
//   let output = "";

//   if (window.appData.debugMode === true) {
//     args.forEach((arg) => {
//       if (typeof arg === "object") {
//         // console.log(JSON.stringify(arg, null, 4));
//         Object.keys(arg).forEach((property) => {
//           output += `${property}: ${JSON.stringify(arg[property])};\n`;
//         });

//         console.log(output);
//       } else if (args.lastIndexOf("colorGREEN") >= 0) {
//         args.pop();
//         console.log("%c%s", "background: green;", arg);
//       } else if (args.lastIndexOf("colorOwindow.CANdata.range") >= 0) {
//         args.pop();
//         console.log(
//           "%c%s",
//           "background: owindow.CANdata.range; color: black;",
//           arg
//         );
//       } else {
//         console.log(arg);
//       }
//     });
//   }
// }
export default function debugLog(...args) {
  if (window.appData.debugMode) {
    // eslint-disable-next-line prefer-spread
    console.log.apply(console, args);
  }
}

export function hexToAscii(str) {
  if (str === " " || str.length < 7) {
    return null;
  }
  const hex = str.toString();
  let output = "";
  for (let n = 0; n < hex.length; n += 2) {
    output += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
  }
  return output;
}

export function isJsonString(str) {
  try {
    JSON.parse(str);
  } catch (e) {
    return false;
  }
  return true;
}

export function clampNumbers(value, min, max) {
  return Math.min(Math.max(value, min), max);
}
<file_sep>// SERIAL PORT MOCKING
//
const { MockBinding } = require("@serialport/binding-mock");
const { SerialPortStream } = require("@serialport/stream");
const { ReadlineParser } = require("@serialport/parser-readline");

// Create a port and enable the echo and recording.
MockBinding.createPort("/dev/ROBOT", { echo: true, record: true });
const port = new SerialPortStream({
  binding: MockBinding,
  path: "/dev/ROBOT",
  baudRate: 9600,
});

/* Add some action for incoming data. For example,
 ** print each incoming line in uppercase */
const parser = new ReadlineParser({ length: 8 });
port.pipe(parser).on("data", (line) => {
  console.log(line);
  console.log(line.toString());
});

// wait for port to open...
port.on("open", () => {
  // ...then test by simulating incoming data
  setInterval(() => {
    console.log("emitingData");
    port.port.emitData("\x50\x04\x68\x3B\x21\xA6");
  }, 1000);
});

/* Expected output:
HELLO, WORLD!
*/
<file_sep>/**
 * This file will automatically be loaded by webpack and run in the "renderer" context.
 * To learn more about the differences between the "main" and the "renderer" context in
 * Electron, visit:
 *
 * https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
 *
 * By default, Node.js integration in this file is disabled. When enabling Node.js integration
 * in a renderer process, please be aware of potential security implications. You can read
 * more about security risks here:
 *
 * https://electronjs.org/docs/tutorial/security
 *
 * To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
 * flag:
 *
 * ```
 *  // Create the browser window.
 *  mainWindow = new BrowserWindow({
 *    width: 800,
 *    height: 600,
 *    webPreferences: {
 *      nodeIntegration: true
 *    }
 *  });
 * ```
 */
import "./css/index.css";
import { settingsInit } from "./js/settings";

import Keyboard from "./js/keyboard";
import "./js/bluetooth";
import { getWifiInfo } from "./js/wifi";
import "./js/translation";
import dialogCloseHandler, {
  dialogShowHandler,
  modalClasses,
} from "./js/modal";
import playAudio, {
  pauseAudio,
  muteAudio,
  volUp,
  volDown,
  shuffl,
  nextSong,
  previousSong,
  musicEventRegister,
} from "./js/music-player";
import {
  guiUpdateData,
  dateAndTimeSetInHTML,
  getIPs,
} from "./js/htmlRefreshers";
import updateTimeCANfromInput, { asksCANforNewData } from "./js/canUpdaters";
import { switchTheme } from "./js/themes";
import { menuHideToggle } from "./js/menu";

console.log(
  '👋 This message is being logged by "renderer.js", included via webpack'
);

function timeSetModal() {
  const elemModal = window.appData.modalTemplateEl.modalTwoInputs;

  const modalTime = document.createElement("div");
  modalTime.id = "timeModalSet";
  modalTime.classList = `${modalClasses[0]} ${modalClasses[1]}`;

  modalTime.innerHTML = elemModal;

  document.body.appendChild(modalTime);

  // add attribute "onclick" to template file
  // fire chnage time CAN fucntion
  document.querySelector("#timeModalSet dialog .confirm").onclick =
    updateTimeCANfromInput();

  // second init, for inputs in modal that was created
  Keyboard.reinitForInputs();

  // set focus on first input to show keyboard
  document.querySelector("#timeModalSet dialog input").focus();
}

function fuelConsResetModal() {
  // show dialog
  const dialogEl = document.getElementById("modalConfirm");
  dialogShowHandler(dialogEl);
  // after confirm send reset
  dialogEl.querySelector(".confirm").addEventListener("click", () => {
    // TODO: add reset fuel consumption function
  });
}

window.electronAPI.listSerialPorts();

window.addEventListener("DOMContentLoaded", () => {
  window.CANdata = {};

  window.appData = {
    ...window.appData,
    switchTheme,
    timeSetModal,
    fuelConsResetModal,
  };
  // const path = require("path");

  window.electronAPI.isLinux().then((data) => {
    window.appData.isLinux = data;
  });
  window.appData.debugMode = true;

  // const settings = require('./js/settings.js');
  window.electronAPI.startParameters().then((startParameters) => {
    if (
      startParameters.includes("debug") ||
      startParameters.includes("remote-debugging-port=8315")
    ) {
      window.appData.debugMode = true;
    }
  });

  const isTouchDevice = () =>
    "ontouchstart" in window ||
    navigator.maxTouchPoints > 0 ||
    navigator.msMaxTouchPoints > 0;

  if (!isTouchDevice()) {
    document.body.style.cursor = "auto";
  }
  document.getElementById("loader-wrapper").style.display = "none";
  document.getElementById("app").classList.remove("hidden");

  window.appData.audio = {
    ...window.appData.audio,
    audio: {},
    methods: {
      playAudio,
      pauseAudio,
      muteAudio,
      volUp,
      volDown,
      shuffl,
      nextSong,
      previousSong,
    },
  };

  window.appData.dialogs = {
    dialogCloseHandler,
  };

  window.menuHideToggle = menuHideToggle;

  document.getElementById("settings-main-nav").addEventListener("click", () => {
    getIPs();
    getWifiInfo();
  });

  // minimizing and closing
  document.getElementById("minimize").addEventListener("click", () => {
    window.electronAPI.minimize();
  });
  document.getElementById("closeApp").addEventListener("click", () => {
    window.electronAPI.close();
  });
  //
  // bluetooth
  document.getElementById("bluetooth").addEventListener("click", () => {
    // show/hide bluetooth settings
    document
      .querySelector("#bluetooth-settings-modal")
      .classList.toggle("hide");
  });
  document.getElementById("btClose").addEventListener("click", () => {
    document
      .querySelector("#.bluetooth-settings-modal")
      .classList.toggle("hide");
  });
  // display app version from package.json
  window.electronAPI.appVersion().then((data) => {
    document.getElementById("app_version").innerText = `Version: ${data}`;
  });

  // do settings file stuff
  settingsInit().then(() => {
    // initiall start
    window.electronAPI
      .getMusicFiles(window.appData.settingsFile.musicFolder)
      .then((data) => {
        window.appData.songsObj = data;
        // create audio object
        window.appData.audio.audio = new Audio();
        musicEventRegister();
        playAudio(data[0]);
      });
    setTimeout(getIPs, 4000);
    setTimeout(getWifiInfo, 4000);

    asksCANforNewData();
    // and repeate checking and asking if necessery every x miliseconds
    setInterval(asksCANforNewData, 500);

    dateAndTimeSetInHTML();
    setInterval(dateAndTimeSetInHTML, 1000);

    guiUpdateData();
    setInterval(guiUpdateData, 1000);

    window.electronAPI.networkInterfaces();
  });
});
<file_sep>// https://www.youtube.com/watch?v=N3cq0BHDMOY

const Keyboard = {
  elements: {
    main: null,
    keysContainer: null,
    keys: [],
  },

  eventHandlers: {
    oninput: null,
    onclose: null,
  },

  properties: {
    value: "",
    capsLock: false,
    keyboard_type: "",
  },

  init() {
    // Create main elements
    this.elements.main = document.createElement("div");
    this.elements.keysContainer = document.createElement("div");

    // Setup main elements
    this.elements.main.classList.add("keyboard", "keyboard--hidden");
    this.elements.keysContainer.classList.add("keyboard__keys");
    this.elements.keysContainer.appendChild(this.createKeys());

    this.elements.keys =
      this.elements.keysContainer.querySelectorAll(".keyboard__key");

    // Add to DOM
    this.elements.main.appendChild(this.elements.keysContainer);
    document.body.appendChild(this.elements.main);

    // Automatically use keyboard for elements with input
    document.querySelectorAll("input:not(.nokeyboard)").forEach((element) => {
      this.properties.keyboard_type = element.type;

      element.addEventListener("focus", () => {
        this.show(element.value);
      });
    });

    document.getElementById("yt-view").addEventListener("click", (e) => {
      if (e.path[0].localName !== "input" && e.currentTarget !== "#keyboard") {
        Keyboard.close();
        Keyboard.triggerEvent("onclose");
      }
    });

    // hide keyboard after focus is lost on input element - for modal
    document.querySelectorAll(".backgrounddialog").forEach((element) => {
      element.addEventListener("click", (e) => {
        if (
          e.path[0].localName !== "input" &&
          e.currentTarget !== "div#keyboard"
        ) {
          Keyboard.close();
          Keyboard.triggerEvent("onclose");
        }
      });
    });
  },

  // when new input element is created 'Keyboard' dont see them, 'Keyboard' needs to be reinitialized
  reinitForInputs() {
    // Automatically use keyboard for elements with input
    document.querySelectorAll("input:not(.nokeyboard)").forEach((element) => {
      this.properties.keyboard_type = element.type;

      element.addEventListener("focus", () => {
        this.show(element.value);
      });
    });

    // hide in youtube section
    // not working
    document.getElementById("yt-view").addEventListener("click", (e) => {
      if (e.path[0].localName !== "input" && e.currentTarget !== "#keyboard") {
        Keyboard.close();
        Keyboard.triggerEvent("onclose");
      }
    });
    // hide keyboard after focus is lost on input element - for modal
    document.querySelectorAll(".backgrounddialog").forEach((element) => {
      element.addEventListener("click", (e) => {
        if (
          e.path[0].localName !== "input" &&
          e.currentTarget !== "div#keyboard"
        ) {
          Keyboard.close();
          Keyboard.triggerEvent("onclose");
        }
      });
    });
  },

  createKeys() {
    const fragmentText = document.createDocumentFragment();
    const fragmentNumeric = document.createDocumentFragment();
    let keyLayoutText = [];
    let keyLayoutNumeric = [];

    keyLayoutNumeric = [
      "1",
      "2",
      "3",
      "4",
      "5",
      "6",
      "7",
      "8",
      "9",
      "0",
      "backspace",
      "space",
      "enter",
      "TEXT",
    ];

    keyLayoutText = [
      "q",
      "w",
      "e",
      "r",
      "t",
      "y",
      "u",
      "i",
      "o",
      "p",
      "caps",
      "a",
      "s",
      "d",
      "f",
      "g",
      "h",
      "j",
      "k",
      "l",
      "enter",
      "done",
      "z",
      "x",
      "c",
      "v",
      "b",
      "n",
      "m",
      ",",
      ".",
      "?",
      "space",
      "backspace",
      "NUM",
    ];

    // Creates HTML for an icon
    const createIconHTML = (iconName) => `<i class="fas fa-${iconName}"></i>`;

    keyLayoutText.forEach((key) => {
      const keyElement = document.createElement("button");

      // Add attributes/classes
      keyElement.setAttribute("type", "button");
      keyElement.classList.add("keyboard__key");

      switch (key) {
        case "backspace":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = createIconHTML("backspace");

          keyElement.addEventListener("click", () => {
            this.properties.value = this.properties.value.substring(
              0,
              this.properties.value.length - 1
            );
            this.triggerEvent("oninput");
          });

          break;

        case "caps":
          keyElement.classList.add(
            "keyboard__key--wide",
            "keyboard__key--activatable"
          );
          keyElement.innerHTML = createIconHTML("arrow-up");

          keyElement.addEventListener("click", () => {
            this.toggleCapsLock();
            keyElement.classList.toggle(
              "keyboard__key--active",
              this.properties.capsLock
            );
          });

          break;

        case "enter":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = "enter";

          keyElement.addEventListener("click", () => {
            this.properties.value += "\n";
            this.triggerEvent("oninput");

            // pig hack for youtube
            // update webview adress with values from transprent input
            document.querySelector(
              "#yt-view"
            ).src = `https://www.youtube.com/results?search_query=${document
              .getElementById("input-for-yt")
              .value.replace(" ", "+")}`;
            // cleare input
            document.getElementById("input-for-yt").value = "";
            // end of pig hack
          });

          break;

        case "space":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = "___";

          keyElement.addEventListener("click", () => {
            this.properties.value += " ";
            this.triggerEvent("oninput");
          });

          break;

        case "done":
          keyElement.classList.add(
            "keyboard__key--wide",
            "keyboard__key--dark"
          );
          keyElement.innerHTML = "hide";

          keyElement.addEventListener("click", () => {
            this.close();
            this.triggerEvent("onclose");
          });

          break;

        case "NUM":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = key;

          keyElement.addEventListener("click", () => {
            this.toggleNumericAndTextKeyborad("number");
          });

          break;

        default:
          keyElement.textContent = key.toLowerCase();

          keyElement.addEventListener("click", () => {
            this.properties.value += this.properties.capsLock
              ? key.toUpperCase()
              : key.toLowerCase();
            this.triggerEvent("oninput");
          });

          break;
      }

      fragmentText.appendChild(keyElement);
    });

    keyLayoutNumeric.forEach((key) => {
      const keyElement = document.createElement("button");

      // Add attributes/classes
      keyElement.setAttribute("type", "button");
      keyElement.classList.add("keyboard__key");

      switch (key) {
        case "backspace":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = createIconHTML("backspace");

          keyElement.addEventListener("click", () => {
            this.properties.value = this.properties.value.substring(
              0,
              this.properties.value.length - 1
            );
            this.triggerEvent("oninput");
          });

          break;

        case "caps":
          keyElement.classList.add(
            "keyboard__key--wide",
            "keyboard__key--activatable"
          );
          keyElement.innerHTML = createIconHTML("arrow-up");

          keyElement.addEventListener("click", () => {
            this.toggleCapsLock();
            keyElement.classList.toggle(
              "keyboard__key--active",
              this.properties.capsLock
            );
          });

          break;

        case "enter":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = "enter";

          keyElement.addEventListener("click", () => {
            this.properties.value += "\n";
            this.triggerEvent("oninput");

            // pig hack for youtube
            // update webview adress with values from transprent input
            document.querySelector(
              "#yt-view"
            ).src = `https://www.youtube.com/results?search_query=${document
              .getElementById("input-for-yt")
              .value.replace(" ", "+")}`;
            // cleare input
            document.getElementById("input-for-yt").value = "";
            // end of pig hack
          });

          break;

        case "space":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = "___";

          keyElement.addEventListener("click", () => {
            this.properties.value += " ";
            this.triggerEvent("oninput");
          });

          break;

        case "done":
          keyElement.classList.add(
            "keyboard__key--wide",
            "keyboard__key--dark"
          );
          keyElement.innerHTML = "hide";

          keyElement.addEventListener("click", () => {
            this.close();
            this.triggerEvent("onclose");
          });

          break;

        case "TEXT":
          keyElement.classList.add("keyboard__key--wide");
          keyElement.innerHTML = key;

          keyElement.addEventListener("click", () => {
            this.toggleNumericAndTextKeyborad("password");
          });

          break;

        default:
          keyElement.textContent = key.toLowerCase();

          keyElement.addEventListener("click", () => {
            this.properties.value += this.properties.capsLock
              ? key.toUpperCase()
              : key.toLowerCase();
            this.triggerEvent("oninput");
          });

          break;
      }

      fragmentNumeric.appendChild(keyElement);
    });

    const fragmentResult = document.createDocumentFragment();
    const textWrapper = document.createElement("div");
    textWrapper.appendChild(fragmentText);

    fragmentResult.appendChild(textWrapper);

    const numericWrapper = document.createElement("div");
    numericWrapper.appendChild(fragmentNumeric);
    fragmentResult.appendChild(numericWrapper);

    return fragmentResult;
  },

  triggerEvent(handlerName) {
    if (typeof this.eventHandlers[handlerName] === "function") {
      this.eventHandlers[handlerName](this.properties.value);
    }
  },

  toggleCapsLock() {
    this.properties.capsLock = !this.properties.capsLock;

    this.elements.keys.forEach((key, index) => {
      if (key.childElementCount === 0) {
        this.elements.keys[index].textContent = this.properties.capsLock
          ? key.textContent.toUpperCase()
          : key.textContent.toLowerCase();
      }
    });
  },

  show(initialValue) {
    this.properties.value = initialValue || "";
    this.elements.main.classList.remove("keyboard--hidden");

    this.toggleNumericAndTextKeyborad();
  },

  close() {
    this.properties.value = "";
    this.elements.main.classList.add("keyboard--hidden");
  },
  toggleNumericAndTextKeyborad(manual) {
    const firstkeyboard = this.elements.main.querySelector(
      ".keyboard__keys > div:first-child"
    );

    if (this.properties.keyboard_type === "number" || manual === "number") {
      firstkeyboard.style.left = "-100%";
      this.elements.main.querySelector(
        ".keyboard__keys > div:last-child"
      ).style.left = "0";
    }
    if (this.properties.keyboard_type === "password" || manual === "password") {
      firstkeyboard.style.left = "0%";
      this.elements.main.querySelector(
        ".keyboard__keys > div:last-child"
      ).style.left = "100%";
    }
  },
};

window.addEventListener("DOMContentLoaded", () => {
  Keyboard.init();
});

export default Keyboard;
<file_sep>import debugLog, { clampNumbers } from "./utils";

export default async function playAudio(songObj = {}) {
  console.log("playAudio");
  const { audio } = window.appData.audio;
  const { path } = songObj;

  if (!path) return;

  window.electronAPI.getSong(path).then((data) => {
    // window.appData.audio.audio.currentTime = 0;
    // window.appData.audio.audio.pause();
    audio.src = data;
    audio.load();
    window.appData.currentSongId = songObj.id;
  });
}

export function pauseAudio() {
  if (!window.appData.audio.audio.paused) {
    window.appData.audio.audio.pause();
  }
}

export function muteAudio() {
  // works only for "music"

  const muteButton = document.querySelector("#music-player .mute.button");

  if (window.appData.audio.audio.muted === true) {
    window.appData.audio.audio.muted = false;

    debugLog("audio unmuted");
    // remove class
    muteButton.classList.toggle("active");

    // hide mute icon on tom of nav element
    document.querySelector(
      "#notifications-persistant .mute-icon"
    ).style.opacity = 0;
  } else {
    window.appData.audio.audio.muted = true;
    debugLog("audio muted");

    // remove class
    muteButton.classList.toggle("active");

    // show mute icon on tom of nav element
    document.querySelector(
      "#notifications-persistant .mute-icon"
    ).style.opacity = 1;
  }
}

export function volUp() {
  window.appData.audio.audio.volume = clampNumbers(
    (window.appData.audio.audio.volume + 0.1).toFixed(2),
    0,
    1
  );
  debugLog("volUp");
}

export function volDown() {
  window.appData.audio.audio.volume = clampNumbers(
    (window.appData.audio.audio.volume - 0.1).toFixed(2),
    0,
    1
  );
  debugLog("volDown");
}

export function shuffl() {
  console.log("TODO");
}

export function currentVolume() {
  console.log("currentVolume");
  const sliVmValue = window.appData.sliderVolumeMusicEl.valueAsNumber;

  // update slider value
  // set window.appData.audio.audio volume "globaly", value from slider value
  window.appData.audio.audio.volume = sliVmValue;

  // debugLog(window.appData.audio.audio.volume.toFixed(2));
  // debugLog("sliderVolMusicValue: " + sliderVolMusicValue);
}

export function updateThreeSongsInHTML() {
  const previousSongHTMLObject = document.getElementById("previousSong");
  const currentSongHTMLObject = document.getElementById("currentSong");
  const nextSongHTMLObject = document.getElementById("nextSong");

  const currentSongID =
    window.appData.currentSongId || window.appData.songsObj[0].id;

  if (currentSongID < 0) return;

  const currentSongIndex = window.appData.songsObj.findIndex(
    (obj) => obj.id === currentSongID
  );
  let nextSongIndex = currentSongIndex + 1;
  // if this is last song then go to first
  if (nextSongIndex === window.appData.songsObj.length) {
    nextSongIndex = 0;
  }
  let prevSongIndex = currentSongIndex - 1;
  // if this is first song then go to last song
  if (prevSongIndex < 0) {
    prevSongIndex = window.appData.songsObj.length - 1;
  }

  // update HTML with songs
  nextSongHTMLObject.innerText = window.appData.songsObj[nextSongIndex].name
    ? window.appData.songsObj[nextSongIndex].name
    : "";

  currentSongHTMLObject.innerText = window.appData.songsObj[currentSongIndex]
    .name
    ? window.appData.songsObj[currentSongIndex].name
    : "";

  previousSongHTMLObject.innerText = window.appData.songsObj[prevSongIndex].name
    ? window.appData.songsObj[prevSongIndex].name
    : "";
}

export function nextSong() {
  const currentSongID = window.appData.currentSongId;
  const currentSongIndex = window.window.appData.songsObj.findIndex(
    (obj) => obj.id === currentSongID
  );
  let nextSongIndex = currentSongIndex + 1;

  if (window.appData.songsObj[0]) {
    // if this is last song then go to first
    if (nextSongIndex >= window.window.appData.songsObj.length) {
      nextSongIndex = 0;
    }
    window.appData.currentSongId = window.appData.songsObj[nextSongIndex].id;
    window.appData.audio.audio.pause();
    playAudio(window.appData.songsObj[nextSongIndex]);
    debugLog("next song");
  }
}

export function previousSong() {
  const currentSongID = window.appData.currentSongId;
  const currentSongIndex = window.window.appData.songsObj.findIndex(
    (obj) => obj.id === currentSongID
  );
  let prevSongIndex = currentSongIndex - 1;

  if (window.appData.songsObj[0]) {
    // if this is first song then go to last song
    if (prevSongIndex < 0) {
      prevSongIndex = window.appData.songsObj.length - 1;
    }

    debugLog(
      `previous song - prevSongIndex: ${prevSongIndex}, currentSongIndex: ${currentSongIndex}`
    );

    playAudio(window.appData.songsObj[prevSongIndex]);
  }
}

export function musicEventRegister() {
  const plyButon = document.querySelector("#music-player .play.button");
  const pauseButon = document.querySelector("#music-player .pause.button");

  // setting sliders values to default window.appData.audio.audio value (at start they are different)
  window.appData.sliderVolumeMusicEl =
    document.getElementById("volume-music-bar");
  window.appData.sliderVolumeMusicEl.value = window.appData.audio.audio.volume;
  window.appData.sliderVolumeMusicEl.setAttribute(
    "value",
    window.appData.sliderVolumeMusicEl.value
  );

  // second time as above
  // on every volume change update slider as well
  window.appData.audio.audio.onvolumechange = function () {
    window.appData.sliderVolumeMusicEl.value =
      window.appData.audio.audio.volume;
    window.appData.sliderVolumeMusicEl.setAttribute(
      "value",
      window.appData.sliderVolumeMusicEl.value
    );
  };

  // hook to play event and manage DOM classes
  window.appData.audio.audio.addEventListener("play", () => {
    if (!window.appData.hasFirstPlayStarted) {
      window.appData.hasFirstPlayStarted = true;
      // start time messure
      window.electronAPI.profileStartTime().then((startTime) => {
        console.log(
          "app start time (to play event): ",
          (Date.now() - startTime) / 1000,
          " s"
        );
      });
    }

    //
    debugLog("event PLAY occured");

    // add classes for Play button
    plyButon.classList.add("playing");
    plyButon.classList.add("active");

    // remove class for pause button
    pauseButon.classList.remove("active");
  });

  window.appData.audio.audio.addEventListener("canplaythrough", () => {
    window.appData.audio.audio
      .play()
      .then(() => {
        updateThreeSongsInHTML();
        console.log("playing currentSongId: ", window.appData.currentSongId);
      })
      .catch((err) => {
        console.error(err);
        window.appData.currentSongId = 0;
      });
  });

  // hook to pause event and manage DOM classes
  window.appData.audio.audio.onpause = function () {
    debugLog("event PAUSE occured");

    // remove classes for play button
    plyButon.classList.remove("playing");
    plyButon.classList.remove("active");

    // add class for pause button
    pauseButon.classList.add("active");
  };

  window.appData.audio.audio.onended = function () {
    // start new song when "current" song ends
    debugLog("audio event ENDED occured");
    nextSong();
  };

  window.appData.sliderVolumeMusicEl.addEventListener("input", () =>
    currentVolume()
  );
}
<file_sep>// {
//   // var exec = require('child_process').exec;
//   const bt = require("bluetoothctl");
//   const btConnectedTo = (btAvailableDevices = "");

//   const selectedMAC = "";

//   window.addEventListener("DOMContentLoaded", () => {
//     document.getElementById("bluetooth").addEventListener("click", () => {
//       // bluetoothctl init
//       try {
//         bt.Bluetooth();

//         const btConnectedDevicesDOM = document.querySelector(
//           ".settings .bluetooth .pairedDevices .devices"
//         );

//         bt.on(bt.bluetoothEvents.ScaningEnded, () => {
//           debugLog("ScaningEnded");

//           btAvailableDevices = bt.devices;
//           const btScanedDevicesDOM = document.querySelector(
//             ".settings .bluetooth .scanedDevices .devices"
//           );

//           // clear DOM element for new data after scanning
//           btScanedDevicesDOM.innerHTML = "";

//           for (let i = 0, l = btAvailableDevices.length; i < l; i++) {
//             btScanedDevicesDOM.innerHTML +=
//               `<div class="device" onclick="markDeviceToPair(this)"><div class="name">${ 
//               btAvailableDevices[i].name 
//               }</div><div class="mac">${ 
//               btAvailableDevices[i].mac 
//               }</div></div>`;
//           }
//           debugLog("btAvailableDevices: ", btAvailableDevices);
//         });

//         bt.on(bt.bluetoothEvents.PassKey, (passkey) => {
//           debugLog(`Confirm passkey:${  passkey}`);
//           bt.confirmPassKey(true);
//         });

//         bt.on(bt.bluetoothEvents.Connected, () => {
//           debugLog("Connected");

//           // clear DOM element for new data after scanning
//           btConnectedDevicesDOM.innerHTML = "";

//           btConnectedTo = bt.devices.filter(
//             (element) => element.connected === "yes"
//           );
//           btConnectedTo.forEach((element) => {
//             btConnectedDevicesDOM.innerHTML +=
//               `<div class="device"><div class="name">${ 
//               element.name 
//               }</div><div class="mac">${ 
//               element.mac 
//               }</div></div>`;
//           });
//         });

//         bt.on(bt.bluetoothEvents.Device, () => {
//           debugLog("Event Device");
//           // clear DOM element for new data after scanning
//           btConnectedDevicesDOM.innerHTML = "";

//           btConnectedTo = bt.devices.filter(
//             (element) => element.connected === "yes"
//           );
//           btConnectedTo.forEach((element) => {
//             btConnectedDevicesDOM.innerHTML +=
//               `<div class="device"><div class="name">${ 
//               element.name 
//               }</div><div class="mac">${ 
//               element.mac 
//               }</div></div>`;
//           });
//         });
//       } catch (error) {
//         console.log("Bluetooth error:", error);
//       }
//     });

//     document.getElementById("btScan").addEventListener("click", () => {
//       btScan();
//     });

//     document.getElementById("btClose").addEventListener("click", () => {
//       bt.close;
//     });

//     document.getElementById("btPair").addEventListener("click", () => {
//       const macAdresSelectedDevice = document.querySelector(
//         ".settings .bluetooth .scanedDevices .devices .device.selected .mac"
//       );

//       bt.pair(macAdresSelectedDevice.innerText);
//       debugLog(
//         `macAdresSelectedDevice.innerText: ${  macAdresSelectedDevice.innerText}`
//       );
//     });

//     document
//       .getElementById("btDisconnect")
//       .addEventListener("click", () => {
//         bt.disconnect(
//           document.querySelector(
//             ".settings .bluetooth .pairedDevices .devices .device .mac"
//           ).innerText
//         );
//       });
//   });

//   function btScan() {
//     // check if bluetooth is ready, exist and it's not scaning curently
//     if (
//       bt.isBluetoothReady === true &&
//       bt.isBluetoothControlExists === true &&
//       bt.isScanning === false
//     ) {
//       // start scaning
//       bt.scan(true);
//       debugLog("bluetoooth scaning...");
//       // stop scaning after 7sec
//       setTimeout(() => {
//         debugLog("stopping scan");
//         bt.scan(false);
//       }, 5000);
//     }
//   }

//   function btConnect(MAC) {
//     bt.pair(MAC);
//   }

//   function markDeviceToPair(element) {
//     // remove class from already selected element
//     const alreadySelected = document.querySelector(
//       ".settings .bluetooth .scanedDevices .devices .device.selected"
//     );
//     if (alreadySelected) {
//       alreadySelected.classList.toggle("selected");
//     }

//     element.classList.toggle("selected");
//   }
// }
<file_sep>const { readdirSync, statSync } = require("fs");
const path = require("path");

module.exports.findMusicFilesRecursivly = (folderPath) => {
  // get files recursivly (with sub folders)
  // https://stackoverflow.com/questions/2727167/how-do-you-get-a-list-of-the-names-of-all-files-present-in-a-directory-in-node-j
  // add record in songsObj for each audio file in musicFolder
  const songs = [];
  try {
    // read files and directoryes in current directory
    const files = readdirSync(folderPath);

    files.forEach((file, index) => {
      // for each file or directory
      // check if it's file or directory
      // file = path.join(initailPath, file).toLowerCase();
      const fileFullPath = path.join(folderPath, file);
      const stat = statSync(fileFullPath); // file info

      if (stat && stat.isDirectory()) {
        // it is directory
        // Recurse into a subdirectory for curent path/directory
        this.findMusicFilesRecursivly(fileFullPath);
      }
      // Is a file
      // get only .mp3 audio files
      if (
        fileFullPath.split(".").pop() === "mp3" ||
        fileFullPath.split(".").pop() === "MP3"
      ) {
        songs.push({
          id: index,
          name: fileFullPath.split("\\").pop().split("/").pop(),
          length: "",
          path: fileFullPath,
        });
      }
    });
  } catch (error) {
    // eslint-disable-next-line no-console
    console.log(error);
  }
  return songs;
};
<file_sep>import dialogCloseHandler from "./modal";

export default function updateTimeCANfromInput(event) {
  const el = document.getElementById("modalTwoInputs");
  const hour = el.querySelector("input.hour.time").value;
  const minutes = el.querySelector("input.minutes.time").value;

  if (
    hour.match(/^[0-9][0-9]$/) != null &&
    minutes.match(/^[0-9][0-9]$/) != null
  ) {
    window.electronAPI.sendMsgToCAN("update-time", hour, minutes);
    dialogCloseHandler(event.target);
  }
}

export function asksCANforNewData() {
  // checks if variable is empty, if soo then ask CAN for new data

  if (!window.CANdata.fuelConsumption1) {
    window.electronAPI.sendMsgToCAN("fuelConsumption1");
  }
  if (!window.CANdata.fuelConsumption2) {
    window.electronAPI.sendMsgToCAN("fuelConsumption2");
  }
  if (!window.CANdata.tempOutside) {
    window.electronAPI.sendMsgToCAN("tempOutside");
  }
  if (!window.CANdata.distance) {
    window.electronAPI.sendMsgToCAN("distance");
  }
  if (!window.CANdata.range) {
    window.electronAPI.sendMsgToCAN("range");
  }
  if (!window.CANdata.avgSpeed) {
    window.electronAPI.sendMsgToCAN("avgSpeed");
  }
  if (!window.CANdata.timeFromInstrumentCluster) {
    window.electronAPI.sendMsgToCAN("time");
  }
}
<file_sep>import Keyboard from "./keyboard";
import dialogCloseHandler from "./modal";
import debugLog from "./utils";

// var exec = require("child_process").exec;
const currentWifi = "";

// executing OS commands
function executeCommand(command) {
  debugLog(`executeCommand: ${command}`);
  // exec(command, function (error, stdout, stderr) {
  //   callback(stdout);
  // });
}

export function getWifiInfo() {
  debugLog("Getting wifi info");

  window.electronAPI.getWifiSSID().then((data) => {
    const SSID = data.replace(/SSID:\s+|\n|\s+/g, "");
    console.log(SSID);
    document.querySelector(
      "#wifi-modal-with-settings .currentNetwork"
    ).innerHTML = `<p>Connected to: ${currentWifi}</p>`;
  });

  window.electronAPI
    .getListOfAvailableNetworks()
    .then((data) => {
      const wifiNetworkList = data
        .trimRight()
        .replace(/\s+ESSID:"/g, "")
        .split('"');

      // clear
      document.querySelector(
        "#wifi-modal-with-settings .availableNetworks .data"
      ).innerHTML = "";
      // populate
      wifiNetworkList.forEach((element) => {
        document.querySelector(
          "#wifi-modal-with-settings .availableNetworks .data"
        ).innerHTML += `<div class="ssid" onclick="selectedWiFitoConnect(this);">${element}</div>`;
      });
    })
    .catch((err) => {
      document.querySelector(
        "#wifi-modal-with-settings .availableNetworks .data"
      ).innerHTML = err;
    });
}

function wifiDisconnect() {
  if (window.appData.isLinux) {
    // Linux
    // nop
  } else {
    // Windows
    window.electronAPI
      .disconnectFromWifi()
      .then((data) => debugLog("disconnected from wifi", data));
  }
}

export default function wifiConnect() {
  const selectedWiFitoConnectEl = document.querySelector(
    "#settings .wifi .data .ssid.selectedWiFi"
  );
  let wifiPasswordDialog;

  if (window.appData.isLinux) {
    // Linux
    try {
      // connect to WiFi network
      // https://unix.stackexchange.com/questions/92799/connecting-to-wifi-network-through-command-line
      // using wpa_cli
      // first get network id, wpa_cli add_network
      executeCommand("wpa_cli add_network", (outputNetworkId) => {
        const networkId = outputNetworkId.match(/^\d/m)[0];
        debugLog(networkId);
        // connect to selected WiFi
        //
        executeCommand(
          `wpa_cli set_network ${networkId} ssid '"${selectedWiFitoConnectEl.innerText}"' psk '"${wifiPasswordDialog}"'`,
          (outputWpaCli) => {
            debugLog(`output_wpa_cli: ${outputWpaCli}`);
            document.querySelector(
              "#settings .wifi .currentNetwork"
            ).innerHTML = `<p>Connected to: ${currentWifi}</p>`;
            debugLog(
              `conected to Wi-Fi network: ${selectedWiFitoConnectEl.innerText}`
            );
          }
        );
      });
    } catch (error) {
      debugLog(error);
    }
  } else {
    // Windows
    try {
      // connect to WiFi network
      executeCommand(
        `netsh wlan connect name="${selectedWiFitoConnectEl.innerText}"`,
        (output) => {
          document.querySelector(
            "#settings .wifi .currentNetwork"
          ).innerHTML = `<p>Connected to: ${currentWifi}</p>`;
          debugLog(
            `conected to Wi-Fi network: ${selectedWiFitoConnectEl.innerText}`
          );

          const stringHelper = `There is no profile "${selectedWiFitoConnectEl.innerText}" assigned to the specified interface.`;
          const outputTrimed = output.trimRight();

          debugLog(outputTrimed);

          // no password, show password input
          if (outputTrimed === stringHelper && !wifiPasswordDialog) {
            showWifiDialog();
            // document.getElementById('wifiPasswordDialog').classList.toggle('hide');
            document.querySelector("#wifiPasswordDialog input").focus();
            debugLog("Enter wifi password");
          }
        }
      );
    } catch (error) {
      debugLog(error);
    }
  }
}

// for marking purposes
function selectedWiFitoConnect(element) {
  document
    .querySelectorAll("#settings .wifi .availableNetworks .data .selectedWiFi")
    .forEach((ele) => {
      ele.classList.remove("selectedWiFi");
    });
  element.classList.toggle("selectedWiFi");
}

export function showWifiDialog() {
  document.getElementById("wifiPasswordDialog").showModal();

  // for modal popup, after entering password
  document.querySelector("#wifiPasswordDialog .wifipassconnect").onclick =
    function () {
      wifiConnect();
    };

  // second init, for inputs in modal that was created
  Keyboard.reinitForInputs();
}

function showWifiNetworkListModal() {
  document.getElementById("wifi-modal-with-settings").showModal();
}

window.addEventListener("DOMContentLoaded", () => {
  document
    .getElementById("show-wifi-settings-dialog")
    .addEventListener("click", showWifiNetworkListModal);
  document.getElementById("wifiClose").addEventListener("click", (e) => {
    dialogCloseHandler(e.target);
  });
  document.getElementById("wifiDisconnect").addEventListener("click", () => {
    wifiDisconnect();
  });
  document.getElementById("wifiConnect").addEventListener("click", () => {
    wifiConnect();
  });
  document.getElementById("wifiRefreshList").addEventListener("click", () => {
    getWifiInfo();
  });
});
<file_sep>import path from "path";

const fs = require("fs");

export function wrtieIBUSdataToFile(...args) {
  return new Promise((resolve, reject) => {
    const pipFilePath = path.join(
      __dirname,
      `DATA_FROM_IBUS_${new Date().toISOString().split("T")[0]}.txt`
    );

    const outputFile = fs.createWriteStream(pipFilePath, { flags: "a" });

    outputFile.on("error", (error) => {
      console.error(error);
    });
    outputFile.write(`${args.join(", ")}\n`);
    outputFile.end();

    outputFile.on("finish", () => {
      outputFile.close(() => {
        resolve(`IBUS data saved to: ${pipFilePath}`);
      });
    });
    outputFile.on("error", (err) => {
      fs.unlink(pipFilePath);
      reject(err);
    });
  });
}

export const some = true;
<file_sep>This aplication and hardware was designed to replace default/stock navigation/radio provided by BMW in my x3 e83 (2004).<br>
Not ready yet :)

for serialport: 
rm -f node_modules/@serialport
npm install serialport --build-from-source
### How it looks

#### Dashboard GUI

<img src="src/for_readme/img/stearing_fox_dashboard_GUI_1.jpg" width="300">
<img src="src/for_readme/img/stearing_fox_dashboard_GUI_2.jpg" width="300">

#### Music GUI

<img src="src/for_readme/img/stearing_fox_music_GUI.png" width="300">

`more to come`

<h3>Hardware:</h3>
<ol>
  <li><del>Raspbery pi 3B+</del> - heart of hardware, in short Raspberry sucks, a better alternative is Rock Pi 4 Model B 1GB (cost about this same, and have a lot more features, for example can run on 12V (this will eliminate need steepdown DC converter usage, and eMMC disk can be installed))</li>
<li>Resler USB IBUS interface (I encourage you to try other solutions, cheaper ones @aliexpres :) )</li>
<li>DAC plus soundcard (raspberry pi hat), DYKB PCM5122 Raspberry pi B+ 2/3B HIFI DAC + Sound Card Digital Audio Module</li>
<li>12v step down DC converter with output set around 5,7V</li>
<li>Bluetooth dongle</li>
<li>Cables (for speakers, HDMI, USB extender 5m+, comon USB extender)</li>
<li>Few realeys (10A will do)</li>
<li>6,5 inch touchscreen with control board PCB800099-V.9.</li>
<li>Revers camera</li>
<li>GPIO Extension Expansion Board Module for Raspberry pi 3B+</li>
</ol>

#### Wiring diargam/schematics

<img src="src/for_readme/img/Instalation_schema_raspberry_pi_BMW_x3_e83.png" width="300">

<h3>Software:</h3>
<ol>
<li>Raspbian</li>
<li>Node.JS</li>
<li>Electron</li>
<li>more in package.json ;)</li>
</ol>

<h3>Few facts:</h3>
<ol>
<li>GUI was designed for 6,5 inch screen (800 x 480 px)</li>
<li>Image from revers camera is handled by PCB control board (when revers gear is on then revers camera is on and signal send to control board)</li>
</ol>

<h3>Aplication can:</h3>
<ol>
  <li>Displays diffrent data from car CAN network like (temperature outside, fuel consumption, avarage speed, altitude, time and more</li>
  <li>Play mp3 from disk (USB to do?)</li>
  <li>Play YouTube</li>
  <li>Displays google map (navigation on to do list)</li>
  <li>Manage Wi-Fi connections</li>
  <li>Manage Bluetooth conections/devices</li>
  <li>Play music via ADSP (egz. music from phone)</li>
  <li>Be controlled from steering wheel controls</li>
  <li>Allows to do phone calls (to do)</li>
</ol>
  
### Usage
First install all dependencies:
```
npm install
```

Start application without debug logs, logs can be later turn on by setting: `debugMode = true`

```
npm start
```

Start application with debug logs

```
npm run debug
```

Rebuild application

```
npm run prepare
```

Build application for Raspberry pi 3B+ (linux)

```
npm run build-linux-arm
```

### Settings

More logs can be show by setting:

```
lessInfoFromCAN=false
```

### OS settings

#### Auto shutdown script

This Python script is monitoring PIN 33 status when it's changes it will run `sudo shutdown -h now` command and close Linux OS
Code:

```
from time import sleep
import RPi.GPIO as GPIO
import os

GPIO.setmode(GPIO.BOARD)
pin=33

GPIO.setup(pin,GPIO.IN,pull_up_down=GPIO.PUD_UP)

while(1):
        if GPIO.input(pin)==1:
                print "Shuting down - ACC power (switched) off"
                os.system("sudo shutdown -h now")
                sleep(.1)
```

To auto start script: `sudo nano /etc/rc.local`
and at the end add:
`sudo python /path_to_your_script_file/script.py &`

### Deployment

Github actions need tag to be present in order to compile release
First update version in package.json:

```
git commit -am 1.0.0
```

Tag your commit:

```
git tag 1.0.0
```

Push your changes to GitHub:

```
git push && git push --tags
```
<file_sep>const rules = require("./webpack.rules");

rules.push({
  test: /\.css$/,
  use: [
    { loader: "style-loader" },
    {
      loader: "css-loader",
      options: {
        importLoaders: 1,
      },
    },
    {
      loader: "postcss-loader",
    },
  ],
});

module.exports = {
  // Put your normal webpack config below here
  module: {
    rules,
  },
  experiments: {
    topLevelAwait: true,
  },
  // externals: {
  //   serialport: "serialport", // Ref: https://copyprogramming.com/howto/electron-and-serial-ports
  // },
};
<file_sep>const fs = require("fs");
const path = require("path");

const svg_folder = "./svg/";

let result = "";

fs.readdir(svg_folder, (error, files) => {
  if (error) {
    console.error(error);
    return;
  }

  files.forEach((file) => {
    try {
      let data = fs.readFileSync(path.join(svg_folder, file), "utf8");

      data = data.replace(
        /(style|stroke|stroke\-linecap|stroke\-linejoin|fill)\=\"(.*?)"/gm,
        ""
      );
      result += data
        .replace("<svg", `<svg id="${file.slice(0, -4)}_svg"`)
        .replace('<?xml version="1.0" encoding="UTF-8"?>', "");
    } catch (error) {
      console.log(error);
      return;
    }

    console.log(`${file} - done`);
  });

  fs.writeFileSync(path.join("./tools/", "output.txt"), result);
});
<file_sep>export default function sendMsgToCAN(
  requestName,
  ibusInterface,
  arg1,
  arg2,
  arg3
) {
  if (Object.keys(ibusInterface) === 0) {
    // ibus not initialised
    return;
  }

  switch (requestName) {
    // request for fuel consumption 1
    case "fuelConsumption1":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x04, 0x01]),
        // 3B 05 80 41 04 01
      });
      break;
    // request for fuel consumption 2
    case "fuelConsumption2":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x05, 0x01]),
        // 3B 05 80 41 05 01
      });
      break;
    // request for outside temperature
    case "tempOutside":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x03, 0x01]),
      });
      break;
    // request for total kilometers
    // Odometer request
    case "distance":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x07, 0x01]),
      });
      break;
    // request window.CANdata.range
    case "range":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x06, 0x01]),
      });
      break;
    // request avg speed
    case "avgSpeed":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x0a, 0x01]),
      });
      break;
    // request obc
    case "obc":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x19]),
      });
      break;

    // set DSP
    case "dsp-on-concert-hall":
      ibusInterface.sendMessage({
        src: 0x68,
        dst: 0x6a,
        msg: Buffer.from([0x34, 0x09]),
      });
      break;

    // request time
    case "time":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x41, 0x01, 0x01]),
      });
      break;

    // update time
    // arg1->Hours, arg2->minutes
    case "updateTime":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x40, 0x01, arg1, arg2]),
      });
      break;

    // update date
    // arg1->day, arg2->month, arg3->year
    case "update-date":
      ibusInterface.sendMessage({
        src: 0x3b,
        dst: 0x80,
        msg: Buffer.from([0x40, 0x02, arg1, arg2, arg3]),
      });
      break;

    default:
      break;
  }
}
<file_sep>import debugLog from "./utils";
import { SETTINGS_FILE_NAME } from "./var";

export function getUserPath() {
  // eslint-disable-next-line no-return-await
  return window.electronAPI
    .getUserDataPath()
    .then((userPath) =>
      window.electronAPI
        .pathJoin([userPath, SETTINGS_FILE_NAME])
        .then((path) => path)
    );
}

export function saveSettingsToFile() {
  let contentObject = {};
  let themeOption = "";

  try {
    debugLog("Saving settings to file");

    const musiFolderPathEl = document.getElementById("music-folder-path");
    const { musicFolder } = window.appData.settingsFile;

    window.electronAPI
      .pathDirname(musiFolderPathEl?.files[0]?.path || musicFolder)
      .then((response) => {
        console.log(`saveSettingsToFile response: `, response);
        window.appData.settingsFile.musicFolder = response;
        const themeElement = document.querySelector("#settings .themeOption");
        // check if egzist
        if (themeElement.length > 0) {
          themeOption = themeElement.selectedOptions[0].value;
        }

        const fuel_1_ele = document.querySelector("#main .fuel_cons1 .data");

        if (fuel_1_ele.innerText) {
          contentObject.lastFuel1Con = fuel_1_ele.innerText.replace(
            /^\s+|\n|-|\s+$/gm,
            ""
          );
        }
        // adding options to object
        contentObject.musicFolder = window.appData.settingsFile.musicFolder;
        contentObject.theme = themeOption;
        contentObject.audioVolume = window.appData.audio.audio.volume;

        // converting object to string
        contentObject = JSON.stringify(contentObject);

        debugLog(`musicFolder: ${window.appData.settingsFile.musicFolder}`);
        debugLog(`SETTINGS_FILE_NAME: ${SETTINGS_FILE_NAME}`);
        debugLog(`contentObject: ${contentObject}`);

        try {
          window.electronAPI
            .writeFile([window.appData.userPath, contentObject])
            .then(() => {
              console.log(
                `The file has been saved! ${window.appData.userPath}`
              );
              window.appData.settingsFile = JSON.parse(contentObject);
            });
        } catch (e) {
          debugLog(`FAILD to save setting file ${e}`);
        }
      });
  } catch (err) {
    console.error(err);
  }
}

export function updateGUIwithSettings(settingsObj) {
  // update GUI
  const themeElement = document.querySelector("#settings .themeOption");
  const musicFolderEl = document.querySelector("#settings .item .filepath");

  musicFolderEl.innerText = settingsObj.musicFolder;

  themeElement.value = settingsObj.theme;
  const event = new Event("change");
  themeElement.dispatchEvent(event);

  window.appData.audio.audio.volume = settingsObj.audioVolume;
}

export function settingsFileContentHandler(fileContentObject) {
  // if settings file contain folderPath (and it's not empty string) then use it in var musicFolder else use "/Music/"
  if (!fileContentObject) return;

  debugLog("Settings file, loading content...");

  try {
    if (!("musicFolder" in fileContentObject)) {
      console.log("invalid content - not JSON");
      return;
    }

    window.appData.settingsFile = fileContentObject;

    updateGUIwithSettings(window.appData.settingsFile);
    //
    // TODO
    // switchTheme();
  } catch (error) {
    console.error(error);
  }
}

export function checkForSettingsFileCreate() {
  // check if settings.json file dose not egzist in app folder
  // if it's not there then create it that file with default data
  // try {
  return window.electronAPI
    .existsSync(window.appData.userPath)
    .then((exist) => {
      if (exist) {
        return window.electronAPI
          .readFileSync(window.appData.userPath)
          .then((settingsFileContent) => {
            // file exist and no error
            const parsed = JSON.parse(settingsFileContent);
            window.appData.settingsFile = parsed;
            debugLog("Settings file exist");
            settingsFileContentHandler(parsed);
            return parsed;
          });
      }
      // file not exist or empty
      debugLog("Settings file not exist or empty, creating...");

      const defaultSettingsFileContent = {
        musicFolder: "",
        theme: "light",
        audioVolume: 0.5,
      };

      return window.electronAPI.writeFileSync([
        window.appData.userPath,
        JSON.stringify(defaultSettingsFileContent),
      ]);
    });
  // } catch (err) {
  //   console.error(`Setting file: ${err}`);
  // }
}

export function listenForUIchanges() {
  // add events listners responsible for saving when user interacts with UI
  const elToWatch = document.querySelectorAll(".settings-watcher");

  elToWatch.forEach((el) => {
    el.addEventListener(
      "change",
      () => {
        console.log(`listenForUIchanges`);
        saveSettingsToFile();
      },
      false
    );
  });
  window.appData.audio.audio.onvolumechange = () => {
    console.log(`volumechange`);
  };

  // theme and language chages and saves to settings file are handled in elements on click functions
}

export function settingsInit() {
  return getUserPath().then((userPath) => {
    window.appData.userPath = userPath;
    listenForUIchanges();
    return checkForSettingsFileCreate();
  });
}
<file_sep>import { THEMES_NAMES } from "./var";

export function setHTMLtheme(themeName = "") {
  document.querySelector("html").setAttribute("data-theme", themeName);
}

export function switchTheme(ele) {
  if (!ele) return;
  // update css in html
  // based on select drop down change OR from setting file

  const theme =
    ele.options[ele.options.selectedIndex].value ||
    window.appData.settingsFile.theme;
  // save settings to file - changed theme
  console.log(`switchTheme`);
  setHTMLtheme(theme);
}

export function setDarkTheme() {
  setHTMLtheme(THEMES_NAMES.dark_grey);
}
export function setPreviousTheme() {
  // TODO
  // switchTheme();
}
<file_sep>const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");
const os = require("os");
const {
  existsSync,
  readFileSync,
  writeFileSync,
  writeFile,
  lstatSync,
  readFile,
} = require("fs");
const { SerialPort } = require("serialport");
const { spawn, exec } = require("child_process");
const { findMusicFilesRecursivly } = require("../main-music-utils");
const { default: sendMsgToCAN } = require("./ibus-senders");

const convertSong = (filePath) => {
  const songPromise = new Promise((resolve, reject) => {
    if (typeof filePath !== "string") reject();
    readFile(filePath, "base64", (err, data) => {
      if (err) {
        reject(err);
      }
      resolve(`data:audio/mp3;base64,${data}`);
    });
  });
  return songPromise;
};

function currentWifiSSID() {
  const cmd =
    process.platform === "linux"
      ? "iw wlan0 link | grep SSID"
      : "netsh wlan show interfaces";

  return new Promise((resolve, reject) => {
    exec(cmd, (error, stdout, stderr) => {
      if (error) {
        console.log("Error on BE while getting WIFI SSID");
        console.warn(error);
        reject(error);
      }
      resolve(stdout || stderr);
    });
  });
}

function getListOfAvailableNetworks() {
  const cmd =
    process.platform === "linux"
      ? "iwlist wlan0 scan | grep ESSID"
      : "netsh wlan show networks mode=Bssid";

  return new Promise((resolve, reject) => {
    exec(cmd, (error, stdout, stderr) => {
      if (error) {
        console.log("Error on BE while getting WIFI list");
        console.warn(error);
        reject(error);
      }
      resolve(stdout || stderr);
    });
  });
}

function disconnectFromWifi() {
  const cmd = process.platform === "linux" ? "" : "netsh wlan disconnect";

  return new Promise((resolve, reject) => {
    exec(cmd, (error, stdout, stderr) => {
      if (error) {
        console.log("Error on BE while disconnecting from wifi");
        console.warn(error);
        reject(error);
      }
      resolve(stdout || stderr);
    });
  });
}

function connectToWifiNetwork() {
  const cmd = process.platform === "linux" ? "" : "";

  return new Promise((resolve, reject) => {
    exec(cmd, (error, stdout, stderr) => {
      if (error) {
        console.log("Error on BE while connecting to wifi");
        console.warn(error);
        reject(error);
      }
      resolve(stdout || stderr);
    });
  });
}

const mainIpcs = (profileStartTime, ibusInstance) => {
  ipcMain.handle("getMusicFiles", (event, folderPath) =>
    findMusicFilesRecursivly(folderPath)
  );
  ipcMain.handle("networkInterfaces", () => os.networkInterfaces());
  ipcMain.handle("isLinux", () => process.platform === "linux");
  ipcMain.handle("startParameters", () => process.argv.slice(2));
  ipcMain.handle("minimize", () => BrowserWindow.getFocusedWindow().minimize());
  ipcMain.handle("close", () => BrowserWindow.getFocusedWindow().close());
  ipcMain.handle("appVersion", () => app.getVersion());
  ipcMain.handle("getUserDataPath", () => app.getPath("userData"));
  ipcMain.handle("profileStartTime", () => profileStartTime);
  ipcMain.handle("bufferFrom", (event, arg) => Buffer.from(arg));
  ipcMain.handle("pathJoin", (event, argArray) => {
    if (argArray.length > 0) {
      return path.join(...argArray);
    }
    return null;
  });
  ipcMain.handle("pathDirname", (event, arg) => {
    if (!existsSync(arg)) return null;
    if (lstatSync(arg).isDirectory()) {
      return arg;
    }
    const parentFolder = path.dirname(arg);
    return parentFolder;
  });
  ipcMain.handle("listSerialPorts", async () => {
    await SerialPort.list()
      .then((ports, err) => {
        if (err) {
          // eslint-disable-next-line no-console
          console.log(`serialPort list error`, err);
          return null;
        }
        // eslint-disable-next-line no-console
        console.log("ports: ", ports);
        return ports;
      })
      // eslint-disable-next-line no-console
      .catch((err) => console.log("listSerialPorts error: ", err));
  });
  ipcMain.handle("existsSync", (event, arg) => existsSync(arg));
  ipcMain.handle(
    "readFileSync",
    (event, arg) => arg && readFileSync(arg, { encoding: "utf8" })
  );
  ipcMain.handle("writeFileSync", (event, args) => writeFileSync(...args));
  ipcMain.handle("writeFile", (event, args) => {
    writeFile(...args, (err) => {
      // eslint-disable-next-line no-console
      if (err) console.log(err);
      else {
        // eslint-disable-next-line no-console
        console.log("writeFile callback: File written successfully");
      }
    });
  });

  ipcMain.handle(
    "CPUtemp",
    () =>
      new Promise((resolve, reject) => {
        const temp = spawn("cat", ["/sys/class/thermal/thermal_zone0/temp"]);
        temp.stdout.on("data", async (data) => resolve(Number(data) / 1000));
        temp.stdout.on("error", async (err) => reject(err));
      })
  );

  ipcMain.handle("getSong", (event, filePath) => convertSong(filePath));
  ipcMain.handle("getWifiSSID", currentWifiSSID);
  ipcMain.handle("getListOfAvailableNetworks", getListOfAvailableNetworks);
  ipcMain.handle("disconnectFromWifi", disconnectFromWifi);
  ipcMain.handle("connectToWifiNetwork", connectToWifiNetwork);
  ipcMain.handle("sendMsgToCAN", (event, data) => {
    sendMsgToCAN(data, ibusInstance);
  });
};

module.exports = mainIpcs;
<file_sep>import debugLog, { hexToAscii } from "./utils";

export function dateAndTimeSetInHTML() {
  const dateObj = new Date();
  const date = dateObj.toLocaleDateString("pl-PL").replace(/\./g, "-");
  const time = dateObj.toLocaleTimeString("pl-PL").substring(0, 5);
  const timeDateElement = document.querySelector("#main #info .curent_time");
  // get time from instrument claster (CAN)

  if (window.CANdata.timeFromInstrumentCluster) {
    // get time from instrument claster (CAN)
    timeDateElement.innerHTML =
      `<span class="time">${window.CANdata.timeFromInstrumentCluster}</span>` +
      `<span class="date">${date}</span>`;
    // to do
    // set time and date (on OS) from CAN/car?
  } else {
    timeDateElement.innerHTML = `<span class="time">${time}</span><span class="date">${date}</span>`;
  }

  /// / if 'window.CANdata.gps_utc_time' have data then get time from gps
  // if (window.CANdata.gps_utc_time) {
  //     // current time from GPS
  //     window.CANdata.gps_utc_time = parseInt(window.CANdata.gps_utc_time.substring(0, 2)) + 1 + ':' + window.CANdata.gps_utc_time.substring(2, 4) + ':' + window.CANdata.gps_utc_time.substring(4, 6);

  // }
}

export function CPUtemp() {
  if (window.appData.isLinux) {
    try {
      window.electronAPI.CPUtemp().then((data) => {
        // return data/1000;
        document.querySelector(
          "#info .cpu_temp .data"
        ).innerHTML = `${Math.round(
          data / 1000
        )}<div class="small text">\xB0C</div>`;
      });
    } catch (error) {
      debugLog(error);
    }
  } else {
    document.querySelector(
      "#info .cpu_temp .data"
    ).innerHTML = `-<div class="small text">\xB0C</div>`;
  }
}

export function guiUpdateData() {
  // neerest town
  if (window.CANdata.townNav) {
    // document.querySelector('.grid-cell.nearest_town .data').innerText = window.CANdata.townNav;
  }

  // alltitude, with conversion
  if (window.CANdata.altitude) {
    document.querySelector(
      ".grid-cell.altitude .data"
    ).innerHTML = `${window.CANdata.altitude}<div class="small text">m</div>`;
  }

  // Temperature outside
  if (window.CANdata.tempOutside) {
    window.CANdata.tempOutside =
      window.CANdata.tempOutside.length > 7
        ? hexToAscii(window.CANdata.tempOutside)
        : window.CANdata.tempOutside;

    // to string conversion
    window.CANdata.tempOutside += "";

    if (window.CANdata.tempOutside.includes(" ")) {
      window.CANdata.tempOutside = window.CANdata.tempOutside.trim();
    } else if (
      window.CANdata.tempOutside.includes("+") ||
      window.CANdata.tempOutside.includes("-")
    ) {
      window.CANdata.tempOutside = window.CANdata.tempOutside.replace(
        "+",
        "+ "
      );
      window.CANdata.tempOutside = window.CANdata.tempOutside.replace(
        "-",
        "- "
      );
      window.CANdata.tempOutside = window.CANdata.tempOutside.replace(".", ",");

      document.querySelector(
        ".grid-cell.temp_outside_car .data"
      ).innerText = `${window.CANdata.tempOutside} \xB0C`;
    }
  }

  if (window.CANdata.coolantTemp) {
    document.querySelector(
      ".grid-cell.coolant_temp .data"
    ).innerText = `${window.CANdata.coolantTemp} \xB0C`;
  }

  // fuel consumption 1
  if (window.CANdata.fuelConsumption1) {
    document.querySelector(
      "#main .fuel_cons1 .data"
    ).innerHTML = `<div class="data1">${window.CANdata.fuelConsumption1}</div><div class="small text">l/100km</div>`;
  }
  // fuel consumption 2
  if (window.CANdata.fuelConsumption2) {
    document.querySelector(
      ".grid-cell.fuel_cons2 .data"
    ).innerHTML = `${window.CANdata.fuelConsumption2}<div class="small text">l/100km</div>`;
  }

  // light sensor in place of weather forecast
  if (window.CANdata.lightSensor) {
    document.querySelector(".grid-cell.weather_forecast").innerText =
      window.CANdata.lightSensor;
  }

  // window.CANdata.range
  if (window.CANdata.range) {
    document.querySelector(
      ".grid-cell.range .data"
    ).innerHTML = `${window.CANdata.range}<div class="small text">km</div>`;
  }

  // window.CANdata.avgSpeed
  if (window.CANdata.avgSpeed) {
    document.querySelector(
      ".grid-cell.avg_speed .data"
    ).innerHTML = `${window.CANdata.avgSpeed}<div class="small text">km/h</div>`;
  }

  CPUtemp();
}

export function getIPs() {
  // clear DOM
  document.getElementById("ipslist").innerHTML = "";

  window.electronAPI.networkInterfaces().then((data) => {
    Object.keys(data).forEach((ifname) => {
      let alias = 0;

      data[ifname].forEach((iface) => {
        if (iface.family !== "IPv4" || iface.internal !== false) {
          // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
          return;
        }

        if (alias >= 1) {
          // this single interface has multiple ipv4 addresses
          debugLog(`${ifname}:${alias}`, iface.address);
          document.getElementById(
            "ipslist"
          ).innerHTML += `<span class="ip">${ifname}: ${alias} ${iface.address}</span>`;
        } else {
          // this interface has only one ipv4 adress
          debugLog(`${ifname}: ${iface.address}`);
          document.getElementById(
            "ipslist"
          ).innerHTML += `<span class="ip">${ifname}: ${iface.address}</span>`;
        }
        alias += 1;
      });
    });
  });
}

<file_sep>export const modalClasses = ["background", "modal"];

export default function dialogCloseHandler(element) {
  element.closest("dialog").close();
}
export function dialogShowHandler(element) {
  element.closest("dialog").showModal();
}<file_sep>module.exports = {
  plugins: [
    [
      "postcss-preset-env",
      {
        stage: 2,
        features: {
          "nesting-rules": true,
          "custom-media-queries": true,
          "custom-properties": true,
          "custom-selectors": true,
          clamp: true,
          "cascade-layers": true,
          "nested-calc": true,
          "not-pseudo-class": true,
          "is-pseudo-class": true
        },
      },
    ],
  ],
};
<file_sep>// mesuure start time
const profileStartTime = Date.now();

require("log-node")();

// Modules to control application life and create native browser window
// eslint-disable-next-line import/no-extraneous-dependencies
const { app, BrowserWindow } = require("electron");
const IbusInterface = require("ibus/src/IbusInterface");
const { SerialPort } = require("serialport");
const mainIpcs = require("./js/mainProcess/ipcs");
const {
  default: handleSquirrelEvent,
} = require("./js/main-squirel-events-handler");

const { default: initUpdates } = require("./js/main-updates");
const { default: initIBUSinMain } = require("./js/mainProcess/ibus");

require("update-electron-app")({
  updateInterval: "1 hour",
  notifyUser: true,
});
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
// eslint-disable-next-line global-require
if (require("electron-squirrel-startup")) {
  app.quit();
}

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.

const createWindow = () => {
  // Create the browser window.
  let mainWindow = new BrowserWindow({
    width: 800,
    height: 480,
    frame: !app.isPackaged,
    icon: "./build/icons/icon.ico",
    webPreferences: {
      // eslint-disable-next-line no-undef
      preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
      // nodeIntegration: true,
    },
    fullscreen: app.isPackaged,
    backgroundColor: "#2c2c2c",
  });

  if (!app.isPackaged) {
    // dev mode - kind off, not packed
    // Open the DevTools.
    mainWindow.webContents.openDevTools();
  } else {
    // production only
    mainWindow.maximize();
  }

  // and load the index.html of the app.
  // eslint-disable-next-line no-undef
  mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
  // mainWindow.loadFile(
  //   "F:/Repozytoria/my-app/src/index.html"
  // );
  // Emitted when the window is closed.
  mainWindow.on("closed", () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null;
  });
};

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on("ready", () => {
  createWindow();

  // let ibusInterface = {};

  SerialPort.list().then((ports, err) => {
    if (err) {
      // eslint-disable-next-line no-console
      console.log(`serialPort list error`, err);
      return;
    }
    // eslint-disable-next-line no-console
    console.log("ports: ", ports);
    // setup interface
    // const device = "/dev/ttyUSB0"; // for linux
    const ibusInterface = new IbusInterface(
      app.isPackaged ? ports[0].path : "/dev/ROBOT"
    );

    try {
      initIBUSinMain(ibusInterface);
    } catch (error) {
      console.log(error);
    }

    // API
    mainIpcs(profileStartTime, ibusInterface);
  });

  if (handleSquirrelEvent() === "firstrun") {
    setTimeout(initUpdates, 30000);
  } else {
    initUpdates();
  }
});

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

// DOMException: play() failed because the user didn't interact with the document first.
// allow to play audio without intereaction with document
app.commandLine.appendSwitch("--autoplay-policy", "no-user-gesture-required");

// console.log(powerSaveBlocker.isStarted(id))
// app.whenReady().then(() => {});
