initial working status (overview for each device)

This commit is contained in:
Aaska Black Wolf
2026-05-24 20:04:22 +02:00
parent cf77016f81
commit 10a03b645c
12 changed files with 628 additions and 1 deletions
+2
View File
@@ -0,0 +1,2 @@
public/images/*.jpg
public/images/*.png
+33 -1
View File
@@ -1,3 +1,35 @@
# VE.Direct-webui
Webbased User Interface to visualize the data collected by VE.Direct-Text-Reader
Webbased User Interface to visualize the data collected by VE.Direct-Text-Reader
## Prerequisites
This program is meant to run on a linux based computer. Something like a Raspberry Pi or similar system is more than sufficient.
### VE.Direct-Text-Reader (database)
This is the front end to the database containing the records of any device information collected by [VE.Direct-Text-Reader](https://git.schauaus.at/VE-Tools/VE.Direct-Text-Reader).
Therefore the PHP process needs read access to that database.
### PHP 8.5
This project was developed for PHP 8.5 older and newer versions may work but are not tested.
### Webserver
You need a webserver running PHP. This may be something lightweight like caddy. The program does not offer a web server service on its own.
## Installation
Option 1: move this directory to your webserver (it may be any sub-folder inside your webserver's directory structure)
Option 2: configure your webserver to serve the files from this directory
## Configuration
### Path to database
For the frontend to work you need to configure the path to the database where VE.Direct-Text-Reader records all the data.
To do so, open `config.json` and edit the entry `database`according to your setup. You may use absolute or relative file paths. If you are using relative paths, ensure the path is relative to the main directory, not the `public` directory.
### Show product images
We didn't as Victron Energy for the permission to use any of their product fotos. Therefore, product images are not part of this software and product pictures are disabled in the UI by default.
You may take your own pictures to show in your installation. To do so, you need to take 2 steps:
1. save your pictures to the folder `images` and rename it to {PID}.jpg or {PID}.png. Where {PID} needs to be replaced with the product ID of your product. (you can find that in the user interface)
2. open the file `config.json` and change the setting `showProductImages` to `true`
## Access the user interface
To access the user interface open it on your favourite web browser.
+4
View File
@@ -0,0 +1,4 @@
{
"database": "../VE.Direct-Text-Reader/VEDirect.db",
"showProductImages": false
}
Binary file not shown.
+72
View File
@@ -0,0 +1,72 @@
<?php
/*
* dbQuery.php
*
* This file is part of the VE.Direct web UI
* https://git.schauaus.at/VE-Tools/VE.Direct-webui
* It is licensed under the GPL3.0 or later.
* You should have received a copy of the license with this software
*/
$config = json_decode( file_get_contents('../config.json') );
$dbPath = (str_starts_with($config->database, '/')) ? $config->database : dirname(__FILE__) . '/../' . $config->database;
try {
$db = new SQLite3($dbPath, SQLITE3_OPEN_READONLY);
}
catch (Exception $e) {
returnJson(['error'=>'Cannot conect to database! ' . $e->getMessage()], 400);
}
switch ($_REQUEST['q']) {
case 'deviceData':
getDeviceData();
break;
case 'devices':
getDevices();
break;
case 'metadata':
getMetaData();
break;
default:
returnJson(['error'=>'No request provided!'], 400);
}
function getDeviceData() {
if(!isset($_REQUEST['device'])) returnJson(['error'=>'No device provided'], 400);
$sql = "SELECT date, time, data FROM '" . $_REQUEST['device'] . "' ORDER BY date ASC, time ASC;";
$data = queryDb($sql);
if(!$data) returnJson(['error'=>'No data found'], 400);
returnJson($data);
}
function getDevices () {
$sql = "SELECT name FROM sqlite_schema WHERE name NOT LIKE 'sqlite_%' AND name <> 'meta' ORDER BY name ASC;";
$data = queryDb($sql);
if(!$data) returnJson(['error'=>'No devices found'], 400);
returnJson($data);
}
function getMetaData() {
$sql = "SELECT * FROM meta;";
$data = queryDb($sql);
if(!$data) returnJson(['error'=>'No metadata found in the database!'], 400);
returnJson($data);
}
function queryDb($sql) {
global $db;
$res = $db->query($sql);
if(!$res) return false;
$data = [];
while ( $resLine = $res->fetchArray(SQLITE3_NUM) )
array_push($data, $resLine);
if(count($data) == 0) return false;
return $data;
}
function returnJson($data, $status = 200) {
http_response_code($status); //HTTP status code
header('Content-Type: application/json; charset=utf-8'); //HTTP headers
echo json_encode($data, JSON_UNESCAPED_UNICODE); //send data
exit();
}
?>
+155
View File
@@ -0,0 +1,155 @@
{
"0x203":"BMV-700",
"0x204":"BMV-702",
"0x205":"BMV-700H",
"0x0300":"BlueSolar MPPT 70|15",
"0xA040":"BlueSolar MPPT 75|50",
"0xA041":"BlueSolar MPPT 150|35",
"0xA042":"BlueSolar MPPT 75|15",
"0xA043":"BlueSolar MPPT 100|15",
"0xA044":"BlueSolar MPPT 100|30",
"0xA045":"BlueSolar MPPT 100|50",
"0xA046":"BlueSolar MPPT 150|70",
"0xA047":"BlueSolar MPPT 150|100",
"0xA049":"BlueSolar MPPT 100|50 rev2",
"0xA04A":"BlueSolar MPPT 100|30 rev2",
"0xA04B":"BlueSolar MPPT 150|35 rev2",
"0xA04C":"BlueSolar MPPT 75|10",
"0xA04D":"BlueSolar MPPT 150|45",
"0xA04E":"BlueSolar MPPT 150|60",
"0xA04F":"BlueSolar MPPT 150|85",
"0xA050":"SmartSolar MPPT 250|100",
"0xA051":"SmartSolar MPPT 150|100",
"0xA052":"SmartSolar MPPT 150|85",
"0xA053":"SmartSolar MPPT 75|15",
"0xA054":"SmartSolar MPPT 75|10",
"0xA055":"SmartSolar MPPT 100|15",
"0xA056":"SmartSolar MPPT 100|30",
"0xA057":"SmartSolar MPPT 100|50",
"0xA058":"SmartSolar MPPT 150|35",
"0xA059":"SmartSolar MPPT 150|100 rev2",
"0xA05A":"SmartSolar MPPT 150|85 rev2",
"0xA05B":"SmartSolar MPPT 250|70",
"0xA05C":"SmartSolar MPPT 250|85",
"0xA05D":"SmartSolar MPPT 250|60",
"0xA05E":"SmartSolar MPPT 250|45",
"0xA05F":"SmartSolar MPPT 100|20",
"0xA060":"SmartSolar MPPT 100|20 48V",
"0xA061":"SmartSolar MPPT 150|45",
"0xA062":"SmartSolar MPPT 150|60",
"0xA063":"SmartSolar MPPT 150|70",
"0xA064":"SmartSolar MPPT 250|85 rev2",
"0xA065":"SmartSolar MPPT 250|100 rev2",
"0xA066":"BlueSolar MPPT 100|20",
"0xA067":"BlueSolar MPPT 100|20 48V",
"0xA068":"SmartSolar MPPT 250|60 rev2",
"0xA069":"SmartSolar MPPT 250|70 rev2",
"0xA06A":"SmartSolar MPPT 150|45 rev2",
"0xA06B":"SmartSolar MPPT 150|60 rev2",
"0xA06C":"SmartSolar MPPT 150|70 rev2",
"0xA06D":"SmartSolar MPPT 150|85 rev3",
"0xA06E":"SmartSolar MPPT 150|100 rev3",
"0xA06F":"BlueSolar MPPT 150|45 rev2",
"0xA070":"BlueSolar MPPT 150|60 rev2",
"0xA071":"BlueSolar MPPT 150|70 rev2",
"0xA072":"BlueSolar MPPT 150/45 rev3",
"0xA073":"SmartSolar MPPT 150/45 rev3",
"0xA074":"SmartSolar MPPT 75 /10 rev2",
"0xA075":"SmartSolar MPPT 75 /15 rev2",
"0xA076":"BlueSolar MPPT 1 00/30 rev3",
"0xA077":"BlueSolar MPPT 1 00/50 rev3",
"0xA078":"BlueSolar MPPT 1 50/35 rev3",
"0xA079":"BlueSolar MPPT 75 /10 rev2",
"0xA07A":"BlueSolar MPPT 75 /15 rev2",
"0xA07B":"BlueSolar MPPT 100 /15 rev2",
"0xA07C":"BlueSolar MPPT 75 /10 rev3",
"0xA07D":"BlueSolar MPPT 75 /15 rev3",
"0xA07E":"SmartSolar MPPT 1 00/30 12V",
"0xA07F":"All -In -1 SmartSolar MPPT 75/15 12V",
"0xA080":"SmartSolar MPPT 250|60 rev3",
"0xA081":"SmartSolar MPPT 250|70 rev3",
"0xA102":"SmartSolar MPPT VE.Can 150/70",
"0xA103":"SmartSolar MPPT VE.Can 150/45",
"0xA104":"SmartSolar MPPT VE.Can 150/60",
"0xA105":"SmartSolar MPPT VE.Can 150/85",
"0xA106":"SmartSolar MPPT VE.Can 150/100",
"0xA107":"SmartSolar MPPT VE.Can 250/45",
"0xA108":"SmartSolar MPPT VE.Can 250/60",
"0xA109":"SmartSolar MPPT VE.Can 250/70",
"0xA10A":"SmartSolar MPPT VE.Can 250/85",
"0xA10B":"SmartSolar MPPT VE.Can 250/100",
"0xA10C":"SmartSolar MPPT VE.Can 150/70 rev2",
"0xA10D":"SmartSolar MPPT VE.Can 150/85 rev2",
"0xA10E":"SmartSolar MPPT VE.Can 150/100 rev2",
"0xA10F":"BlueSolar MPPT VE.Can 150/100",
"0xA112":"BlueSolar MPPT VE.Can 250/70",
"0xA113":"BlueSolar MPPT VE.Can 250/100",
"0xA114":"SmartSolar MPPT VE.Can 250/70 rev2",
"0xA115":"SmartSolar MPPT VE.Can 250/100 rev2",
"0xA116":"SmartSolar MPPT VE.Can 250/85 rev2",
"0xA117":"BlueSolar MPPT VE.Can 150/100 rev2",
"0xA201":"Phoenix Inverter 12V 250VA 230V",
"0xA202":"Phoenix Inverter 24V 250VA 230V",
"0xA204":"Phoenix Inverter 48V 250VA 230V",
"0xA211":"Phoenix Inverter 12V 375VA 230V",
"0xA212":"Phoenix Inverter 24V 375VA 230V",
"0xA214":"Phoenix Inverter 48V 375VA 230V",
"0xA221":"Phoenix Inverter 12V 500VA 230V",
"0xA222":"Phoenix Inverter 24V 500VA 230V",
"0xA224":"Phoenix Inverter 48V 500VA 230V",
"0xA231":"Phoenix Inverter 12V 250VA 230V",
"0xA232":"Phoenix Inverter 24V 250VA 230V",
"0xA234":"Phoenix Inverter 48V 250VA 230V",
"0xA239":"Phoenix Inverter 12V 250VA 120V",
"0xA23A":"Phoenix Inverter 24V 250VA 120V",
"0xA23C":"Phoenix Inverter 48V 250VA 120V",
"0xA241":"Phoenix Inverter 12V 375VA 230V",
"0xA242":"Phoenix Inverter 24V 375VA 230V",
"0xA244":"Phoenix Inverter 48V 375VA 230V",
"0xA249":"Phoenix Inverter 12V 375VA 120V",
"0xA24A":"Phoenix Inverter 24V 375VA 120V",
"0xA24C":"Phoenix Inverter 48V 375VA 120V",
"0xA251":"Phoenix Inverter 12V 500VA 230V",
"0xA252":"Phoenix Inverter 24V 500VA 230V",
"0xA254":"Phoenix Inverter 48V 500VA 230V",
"0xA259":"Phoenix Inverter 12V 500VA 120V",
"0xA25A":"Phoenix Inverter 24V 500VA 120V",
"0xA25C":"Phoenix Inverter 48V 500VA 120V",
"0xA261":"Phoenix Inverter 12V 800VA 230V",
"0xA262":"Phoenix Inverter 24V 800VA 230V",
"0xA264":"Phoenix Inverter 48V 800VA 230V",
"0xA269":"Phoenix Inverter 12V 800VA 120V",
"0xA26A":"Phoenix Inverter 24V 800VA 120V",
"0xA26C":"Phoenix Inverter 48V 800VA 120V",
"0xA271":"Phoenix Inverter 12V 1200VA 230V",
"0xA272":"Phoenix Inverter 24V 1200VA 230V",
"0xA274":"Phoenix Inverter 48V 1200VA 230V",
"0xA279":"Phoenix Inverter 12V 1200VA 120V",
"0xA27A":"Phoenix Inverter 24V 1200VA 120V",
"0xA27C":"Phoenix Inverter 48V 1200VA 120V",
"0xA281":"Phoenix Inverter 12V 1600VA 230V",
"0xA282":"Phoenix Inverter 24V 1600VA 230V",
"0xA284":"Phoenix Inverter 48V 1600VA 230V",
"0xA291":"Phoenix Inverter 12V 2000VA 230V",
"0xA292":"Phoenix Inverter 24V 2000VA 230V",
"0xA294":"Phoenix Inverter 48V 2000VA 230V",
"0xA2A1":"Phoenix Inverter 12V 3000VA 230V",
"0xA2A2":"Phoenix Inverter 24V 3000VA 230V",
"0xA2A4":"Phoenix Inverter 48V 3000VA 230V",
"0xA340":"Phoenix Smart IP43 Charger 12|50 (1+1)",
"0xA341":"Phoenix Smart IP43 Charger 12|50 (3)",
"0xA342":"Phoenix Smart IP43 Charger 24|25 (1+1)",
"0xA343":"Phoenix Smart IP43 Charger 24|25 (3)",
"0xA344":"Phoenix Smart IP43 Charger 12|30 (1+1)",
"0xA345":"Phoenix Smart IP43 Charger 12|30 (3)",
"0xA346":"Phoenix Smart IP43 Charger 24|16 (1+1)",
"0xA347":"Phoenix Smart IP43 Charger 24|16 (3)",
"0xA381":"BMV-712 Smart",
"0xA382":"BMV-710H Smart",
"0xA383":"BMV-712 Smart Rev2",
"0xA389":"SmartShunt 500A/50mV",
"0xA38A":"SmartShunt 1000A/50mV",
"0xA38B":"SmartShunt 2000A/50mV",
"0xA3F0":"Orion XS 12V/12V-50A",
"0xA3F1":"Orion XS 1400"
}
+22
View File
@@ -0,0 +1,22 @@
{
"datafield_CS": "Systemstatus",
"datafield_ERR": "Fehler",
"datafield_FW": "Firmwareversion",
"datafield_FWE": "Firmwareversion",
"datafield_H19": "Gesamtertrag seit letztem Reset",
"datafield_H20": "Ertrag aktuellster Tag",
"datafield_H21": "max. Leistung aktuellster Tag",
"datafield_H22": "Ertrag Vortag",
"datafield_H23": "max. Leistung Vortag",
"datafield_I": "Stromst&auml;rke Kanal 1",
"datafield_LOAD": "Status Lastausgang",
"datafield_MPPT": "Arbeitsmodus Tracker",
"datafield_OR": "Abschaltgrund",
"datafield_P": "Systemleistung",
"datafield_PPV": "Panelleistung",
"datafield_Relay": "Status Relais",
"datafield_V": "Spannung Kanal 1",
"datafield_VPV": "Panelspannung",
"latestDataPoint": "letzter Datenpunkt",
"serialNumber": "Seriennummer"
}
+22
View File
@@ -0,0 +1,22 @@
{
"datafield_CS": "state of operation",
"datafield_ERR": "error",
"datafield_FW": "firmware version",
"datafield_FWE": "firmware version",
"datafield_H19": "total yield since last reset",
"datafield_H20": "yield latest day",
"datafield_H21": "max. power latest day",
"datafield_H22": "yield yesterday",
"datafield_H23": "max. power yesterday",
"datafield_I": "current channel 1",
"datafield_LOAD": "status load output",
"datafield_MPPT": "tracker operation mode",
"datafield_OR": "off reason",
"datafield_P": "system power",
"datafield_PPV": "panel power",
"datafield_Relay": "relay status",
"datafield_V": "voltage channel 1",
"datafield_VPV": "panel voltage",
"latestDataPoint": "last data point",
"serialNumber": "serial number"
}
+10
View File
@@ -0,0 +1,10 @@
# Product images
Put any picture of your own products in this folder. It might be in JPEG or PNG format.
## Naming convention
Every picture needs to be named in a certain way to be automatically identified by the frontend.
The naming shall follow this convention:
ProductID.jpg or ProductID.png
Let's say you have a SmartSolar MPPT VE.CAN150/70 rev. 2 (like the developer of this tool). The product ID would be 0xA10C. So any picture shall be named 0xA10C.jpg or 0xA10C.png
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>VE.Direct web-UI</title>
<link rel="stylesheet" href="main.css">
<script type="text/javascript" src="main.js"></script>
</head>
<body onload="initialize()">
<div class="container">
<div class="productList" id="productList">
<div class="padding bright" id="liDashboard">&#127968; Dashboard</div>
</div>
<div class="mainView dashboard" id="dashboard">
<h1>Dashboard</h1>
</div>
<div class="mainView deviceDetails" id="deviceDetails">
<img id="deviceDetailsProductImage">
<h1>Details <span id="deviceDetailsName"></span></h1>
<div id="deviceDetailsSN"></div>
</div>
</div><!-- end of container -->
</bod>
<footer>
<div>This is the <a href="https://git.schauaus.at/VE-Tools/VE.Direct-webui" target="_blank">VE.Direct web-UI</a> by SCHAUAUS GmbH.</div>
</footer>
</html>
+36
View File
@@ -0,0 +1,36 @@
/*
* main.css
*
* This file is part of the VE.Direct web UI
* https://git.schauaus.at/VE-Tools/VE.Direct-webui
* It is licensed under the GPL3.0 or later.
* You should have received a copy of the license with this software
*/
a, a.visited { color: #aa0; font-weight: bold; }
body { font: normal 16px sans-serif; color: #ccc; background-color: #222; }
.bright { background-color: #ddd; color: #444; }
.container { display: flex; width: 100%; }
.dashboard { display: inline-block; }
.deviceDetails { display: none; }
#deviceDetailsProductImage { max-width: 200px; max-height: 200px; }
.dark { background-color: #222; }
footer { font-size: 12px; text-align: center; clear: both; }
h1 { display: inline-block; }
img { float: left; padding: 0px; margin: 0px; margin-right: 5px; }
.mainView { min-width: 600px; background-color: #660; margin: 5px; flex-grow: 1; }
.productImageSmall { height: 50px; }
.productList { width: 300px; overflow: auto; background-color: #00a; margin: 5px; }
.padding { padding: 5px; }
table { border-collapse: collapse; border-width: 0px; }
[id^="li"] { cursor: pointer; }
+243
View File
@@ -0,0 +1,243 @@
/*
* main.js
*
* This file is part of the VE.Direct web UI
* https://git.schauaus.at/VE-Tools/VE.Direct-webui
* It is licensed under the GPL3.0 or later.
* You should have received a copy of the license with this software
*/
var deviceDataParsed = false
var deviceDataSet = {}
var deviceDataTimer;
var deviceIds = {}
var devices = [];
var deviceTimer;
var i18n = {}
var lang = 'de'
function checkFile(url) {
let xhr = new XMLHttpRequest()
xhr.open('HEAD', url, false);
xhr.send()
return xhr.status !==404
}
function fileReadJson(ReturnFunction, url, params = "") {
let xhr = new XMLHttpRequest()
xhr.onload = function () {
if(xhr.status == 200)
ReturnFunction(xhr.response)
else return false
}
xhr.onerror = function() { return false }
xhr.open('POST', url, true)
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.responseType = 'json'
xhr.send(params)
}
function getDeviceData(device) {
if(!device.startsWith('0x')) //check if device ID is valid
return false
fileReadJson(parseDeviceData, './dbQuery.php', 'q=deviceData&device=' + device)
}
function getDeviceIds() {
let xhr = new XMLHttpRequest()
xhr.onload = function() {
if(xhr.status === 200)
deviceIds = xhr.response
}
xhr.open('GET', './deviceIds.json', true)
xhr.responseType = 'json'
xhr.send()
}
function getDevices() {
if(Object.keys(deviceIds).length < 1) return
if(Object.keys(i18n).lenght < 1) return
clearInterval(deviceTimer)
let xhr = new XMLHttpRequest()
xhr.onload = function() {
if(xhr.status != 200) { // error handling
devices = false
}
else { //process data
devices = []
for(let i=0; i<xhr.response.length; i++) {
devices.push(xhr.response[i][0])
}
renderDeviceList()
}
}
xhr.onerror = function() {
devices = false
}
xhr.open("POST", "dbQuery.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.responseType = 'json'
xhr.send("q=devices");
}
function getLang(i18n_data) {
i18n[lang] = i18n_data
}
function initialize () {
getDeviceIds()
fileReadJson(getLang, './i18n/' + lang + '.json')
deviceTimer = setInterval( getDevices, 100)
document.getElementById('liDashboard').addEventListener("click", showDevice)
}
function parseDeviceData(data) {
deviceDataParsed = false
deviceDataSet = {}
for(let i=0; i < data.length; i++) {
let date = data[i][0]
let time = data[i][1]
let dataPoint = data[i][2]
let year = date.split('-')[0]
let month = date.split('-')[1]
let day = date.split('-')[2]
if(!deviceDataSet[year])
deviceDataSet[year] = {}
if(!deviceDataSet[year][month])
deviceDataSet[year][month] = {}
if(!deviceDataSet[year][month][day])
deviceDataSet[year][month][day] = {}
deviceDataSet[year][month][day][time] = JSON.parse(dataPoint)
}
deviceDataParsed = true
}
function renderDeviceDetails(device) {
let deviceName = device.split('-')[0]
let deviceSN = device.split('-')[1]
document.getElementById('deviceDetailsName').innerHTML = ""
document.getElementById('deviceDetailsSN').innerHTML = ""
document.getElementById('deviceDetailsProductImage').src = ""
//load product images
let deviceImagePath = './images/' + deviceName
if( checkFile(deviceImagePath + '.jpg') )
deviceImagePath += '.jpg'
else if( checkFile(deviceImagePath + '.png') )
deviceImagePath += '.png'
if( deviceImagePath.endsWith('.jpg') || deviceImagePath.endsWith('.png') )
document.getElementById('deviceDetailsProductImage').src = deviceImagePath
//set basic information about the device
document.getElementById('deviceDetailsName').innerHTML = deviceIds[deviceName]
document.getElementById('deviceDetailsSN').innerHTML = i18n[lang]['serialNumber'] + ": " + deviceSN
//query device data from database and store it in global variable
deviceDataParsed = false
deviceDataSet = {}
getDeviceData(device)
//view data on frontend
deviceDataTimer = setInterval(showDeviceData, 100)
}
function renderDeviceList() {
for(let i=0; i < devices.length; i++) {
let device = devices[i].split('-')[0]
let sn = devices[i].split('-')[1]
let classStyle = (i % 2) ? 'bright' : 'dark'
let block = document.createElement('div')
block.classList.add('padding')
block.classList.add(classStyle)
block.id = 'li' + devices[i]
let productImage = document.createElement('img')
let productImagePath = './images/' + device
if( checkFile(productImagePath + '.jpg') )
productImage.src = productImagePath + '.jpg'
else if( checkFile(productImagePath + '.png') )
productImage.src = productImagePath + '.png'
if(productImage.src) {
productImage.classList.add('productImageSmall')
block.appendChild(productImage)
}
document.getElementById('productList').appendChild(block)
block.append(deviceIds[device])
block.innerHTML += "<br>\r\n<small>" + i18n[lang]['serialNumber'] + ': ' + sn + '<small>'
block.addEventListener("click", showDevice)
}
}
function showDevice(event) {
let deviceName = event.currentTarget.id.substr(2)
if(deviceName == "Dashboard") {
document.getElementById('deviceDetails').style.display = "none" //hide device details
document.getElementById('dashboard').style.display = "inline-block"
}
else {
document.getElementById('dashboard').style.display = "none" //hide dashboard
//cleanup device details
let deviceDetailNode = document.getElementById('deviceDetails')
while(deviceDetailNode.children.length > 3)
deviceDetailNode.removeChild( deviceDetailNode.children[ deviceDetailNode.children.length - 1 ] )
//show device data block and fill in the details
document.getElementById('deviceDetails').style.display = "inline-block"
renderDeviceDetails(deviceName)
}
}
function showDeviceData() {
if(!deviceDataParsed) return
clearInterval(deviceDataTimer)
//parse years, months, days and timestamps
let years = Object.keys(deviceDataSet)
years.sort()
let latestYear = years[years.length - 1]
let months = Object.keys(deviceDataSet[latestYear])
months.sort()
let latestMonth = months[months.length - 1]
let days = Object.keys(deviceDataSet[latestYear][latestMonth])
days.sort()
let latestDay = days[days.length - 1]
let timestamps = Object.keys( deviceDataSet[latestYear][latestMonth][latestDay] )
timestamps.sort()
let latestTimestamp = timestamps[timestamps.length - 1]
let latestDataPoint = document.createElement('div')
latestDataPoint.innerHTML = i18n[lang]['latestDataPoint'] + ': ' + latestYear + '-' + latestMonth + '-' + latestDay + ' ' + latestTimestamp
document.getElementById('deviceDetails').appendChild(latestDataPoint)
//parse latest datapoint regarding system power, system voltage, solar power, solar voltage
let latestDataSet = deviceDataSet[latestYear][latestMonth][latestDay][latestTimestamp]
let latestPowerData = document.createElement('table')
if(latestDataSet['PPV'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_PPV'] + ': ' + latestDataSet['PPV'] + 'W' + '</td></tr>'
if(latestDataSet['VPV'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_VPV'] + ': ' + parseInt(latestDataSet['VPV']) / 1000 + 'V' + '</td></tr>'
if(latestDataSet['V'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_V'] + ': ' + parseInt(latestDataSet['V']) / 1000 + 'V' + '</td></tr>'
if(latestDataSet['I'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_I'] + ': ' + parseInt(latestDataSet['I']) / 1000 + 'A' + '</td></tr>'
if(latestDataSet['P'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_P'] + ': ' + latestDataSet['P'] + 'W' + '</td></tr>'
if(latestDataSet['H20'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_H20'] + ': ' + parseInt(latestDataSet['H20']) * 0.01 + 'kWh' + '</td></tr>'
if(latestDataSet['H22'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_H22'] + ': ' + parseInt(latestDataSet['H22']) / 100 + 'kWh' + '</td></tr>'
if(latestDataSet['H19'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_H19'] + ': ' + parseInt(latestDataSet['H19']) / 100 + 'kWh' + '</td></tr>'
if(latestDataSet['H21'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_H21'] + ': ' + parseInt(latestDataSet['H21']) + 'W' + '</td></tr>'
if(latestDataSet['H23'])
latestPowerData.innerHTML += '<tr><td>' + i18n[lang]['datafield_H23'] + ': ' + parseInt(latestDataSet['H23']) + 'W' + '</td></tr>'
document.getElementById('deviceDetails').appendChild(latestPowerData)
}