further components and bootstrap initial

This commit is contained in:
2024-12-20 03:29:29 +01:00
parent 5fedbec433
commit 58b63fc8ee
22 changed files with 3347 additions and 436 deletions

View File

@@ -52,9 +52,12 @@ interface SetTime {
time: string
}
interface GetData {
interface GetTime {
rtc: string,
native: string,
native: string
}
interface Moistures {
moisture_a: [number],
moisture_b: [number],
}

View File

@@ -1,127 +0,0 @@
<html>
<head>
</head>
<body>
<input type="button" id="test" value="Test">
<h2>Current Firmware</h2>
<div>
<div id="firmware_buildtime">Buildtime loading</div>
<div id="firmware_githash">Build githash loading</div>
</div>
<h2>Time</h2>
<div id="timeview">
<div id="timeview_esp_time">Esp time</div>
<div id="timeview_rtc_time">Rtc time</div>
<div id="timeview_browser_time">Rtc time</div>
<div>Store Browser time into esp and rtc</div>
<input type="button" id="timeview_time_upload" value="write">
</div>
<h2>firmeware OTA v3</h2>
<form id="upload_form" method="post">
<input type="file" name="file1" id="file1"><br>
<progress id="progressBar" value="0" max="100" style="width:300px;"></progress>
<h3 id="status"></h3>
<h3 id="answer"></h3>
<p id="loaded_n_total"></p>
</form>
<div>
<div id="remote_ip">remote ip</div>
<h2>WIFI</h2>
<input type="button" id="scan" value="Scan">
<br>
<label for="ap_ssid">AP SSID:</label>
<input type="text" id="ap_ssid" list="ssidlist">
<label for="ssid">SSID:</label>
<input type="text" id="ssid" list="ssidlist">
<datalist id="ssidlist">
<option value="Not scanned yet">
</datalist>
<label for="ssid">Password:</label>
<input type="text" id="password">
</div>
<h2>config</h2>
<div id="configform">
<h3>Mqtt:</h3>
<div>
MQTT Url
<input type="text" id="mqtt_url" placeholder="mqtt://192.168.1.1:1883">
</div>
<div>
Base Topic
<input type="text" id="base_topic" placeholder="plants/one">
</div>
<h3>Tank:</h3>
<div>
<input type="checkbox" id="tank_sensor_enabled">
Enable Tank Sensor
</div>
<div>
<input type="checkbox" id="tank_allow_pumping_if_sensor_error">
Allow Pumping if Sensor Error
</div>
<div>
<input type="number" min="2" max="500000" id="tank_useable_ml">
Tank Size mL
</div>
<div>
<input type="number" min="1" max="500000" id="tank_warn_percent">
Tank Warn Percent (mapped in relation to empty and full)
</div>
<div>
<input type="number" min="0" max="100" id="tank_empty_percent">
Tank Empty Percent (% max move)
</div>
<div>
<input type="number" min="0" max="100" id="tank_full_percent">
Tank Full Percent (% max move)
</div>
<h3>Light:</h3>
<div>
Start
<select type="time" id="night_lamp_time_start">
</select>
Stop
<select type="time" id="night_lamp_time_end">
</select>
</div>
<div>
<input type="checkbox" id="night_lamp_only_when_dark">
Light only when dark
</div>
<h2>Battery Firmeware (bq34z100 may be R2)</h2>
<form id="upload_form" method="post">
<input type="file" name="battery_flash_file" id="battery_flash_file"><br>
<progress id="battery_flash_progressBar" value="0" max="100" style="width:300px;"></progress>
<input type="button" name="battery_flash_button" id="battery_flash_button"><br>
<h3 id="battery_flash_status"></h3>
<p id="battery_flash_loaded_n_total"></p>
<div style="height: 100px; display: block; overflow-y: auto;" id="battery_flash_message"></div>
</form>
<h3>Plants:</h3>
<div id="plants"></div>
</div>
<button id="submit">Submit</button>
<div id="submit_status"></div>
<br>
<textarea id="json" cols=50 rows=10></textarea>
<script src="bundle.js"></script>
</body>
</html>

View File

@@ -1,249 +0,0 @@
declare var PUBLIC_URL: string;
console.log("Url is " + PUBLIC_URL);
import { TimeView } from "./timeview";
import { PlantView, PlantViews } from "./plant";
import { NetworkConfigView } from "./network";
import { NightLampView } from "./nightmode";
import { TankConfigView } from "./tanks";
export class SubmitView{
json: HTMLInputElement;
submitFormBtn: HTMLButtonElement;
submit_status: HTMLElement;
constructor(controller: Controller){
this.json = document.getElementById('json') as HTMLInputElement
this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement
this.submit_status = document.getElementById("submit_status") as HTMLElement
this.submitFormBtn.onclick = () => {
controller.uploadConfig(this.json.value, (status:string) => {
this.submit_status.innerHTML = status;
});
}
}
setJson(pretty: string) {
this.json.value = pretty
}
}
export class Controller {
downloadConfig() {
fetch(PUBLIC_URL + "/get_config")
.then(response => response.json())
.then(loaded => {
var currentConfig = loaded as PlantControllerConfig;
this.setConfig(currentConfig);
//sync json view initially
this.configChanged();
})
}
uploadConfig(json: string, statusCallback: (status: string) => void) {
fetch(PUBLIC_URL + "/set_config", {
method: "POST",
body: json,
})
.then(response => response.text())
.then(text => statusCallback(text))
}
readonly timeView: TimeView;
readonly plantViews: PlantViews;
readonly networkView: NetworkConfigView;
readonly tankView: TankConfigView;
readonly nightLampView: NightLampView;
readonly submitView: SubmitView;
constructor() {
this.timeView = new TimeView(this)
this.plantViews = new PlantViews(this)
this.networkView = new NetworkConfigView(this, PUBLIC_URL)
this.tankView = new TankConfigView(this)
this.nightLampView = new NightLampView(this)
this.submitView = new SubmitView(this)
}
syncRTCFromBrowser(){
var value: SetTime = {
time: new Date().toISOString()
}
var pretty = JSON.stringify(value, undefined, 1);
fetch(PUBLIC_URL + "/time", {
method: "POST",
body: pretty
})
}
configChanged() {
const current = controller.getConfig();
var pretty = JSON.stringify(current, undefined, 1);
console.log(pretty)
controller.submitView.setJson(pretty);
}
testPlant(plantId: number) {
var body: TestPump = {
pump: plantId
}
var pretty = JSON.stringify(body, undefined, 1);
fetch(PUBLIC_URL + "/pumptest", {
method: "POST",
body: pretty
})
.then(response => response.text())
.then(text => console.log(text))
}
updateRealTimeData() {
fetch(PUBLIC_URL + "/data")
.then(response => response.json())
.then(json => json as GetData)
.then(time => {
controller.timeView.update(time.native, time.rtc)
controller.plantViews.update(time.moisture_a, time.moisture_b)
setTimeout(controller.updateRealTimeData, 1000);
})
.catch(error => {
console.log(error);
setTimeout(controller.updateRealTimeData, 10000);
});
}
getConfig(): PlantControllerConfig{
return {
network: controller.networkView.getConfig(),
tank: controller.tankView.getConfig(),
night_lamp: controller.nightLampView.getConfig(),
plants: controller.plantViews.getConfig()
}
}
scanWifi() {
var ajax = new XMLHttpRequest();
ajax.responseType = 'json';
ajax.onreadystatechange = () => {
if (ajax.readyState === 4) {
this.networkView.setScanResult(ajax.response as SSIDList)
}
};
ajax.onerror = (evt) => {
alert("Failed to start see console")
}
ajax.open("POST", PUBLIC_URL + "/wifiscan");
ajax.send();
}
setConfig(current: PlantControllerConfig) {
this.tankView.setConfig(current.tank);
this.networkView.setConfig(current.network);
this.nightLampView.setConfig(current.night_lamp);
this.plantViews.setConfig(current.plants);
}
}
const controller = new Controller();
setTimeout(controller.updateRealTimeData, 1000);
controller.downloadConfig();
export function uploadFile() {
var file1 = document.getElementById("file1") as HTMLInputElement;
var loaded_n_total = document.getElementById("loaded_n_total") as HTMLElement;
var progressBar = document.getElementById("progressBar") as HTMLProgressElement;
var status = document.getElementById("status") as HTMLElement;
var answer = document.getElementById("answer") as HTMLElement;
if (file1.files == null) {
//TODO error dialog here
return
}
var file = file1.files[0];
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", event => {
loaded_n_total.innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total;
var percent = (event.loaded / event.total) * 100;
progressBar.value = Math.round(percent);
status.innerHTML = Math.round(percent) + "%";
answer.innerHTML = "in progress";
}, false);
ajax.addEventListener("load", () => {
status.innerHTML = ajax.responseText;
answer.innerHTML = "finished";
progressBar.value = 0;
}, false);
ajax.addEventListener("error", () => {
status.innerHTML = ajax.responseText;
answer.innerHTML = "failed";
}, false);
ajax.addEventListener("abort", () => {
status.innerHTML = ajax.responseText;
answer.innerHTML = "aborted";
}, false);
ajax.open("POST", PUBLIC_URL + "/ota");
ajax.send(file);
}
let file1Upload = document.getElementById("file1") as HTMLInputElement;
file1Upload.onchange = uploadFile;
let firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement;
let firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement;
document.addEventListener('DOMContentLoaded', function () {
fetch(PUBLIC_URL + "/version")
.then(response => response.json())
.then(json => json as VersionInfo)
.then(versionInfo => {
firmware_buildtime.innerText = versionInfo.build_time;
firmware_githash.innerText = versionInfo.git_hash;
})
}, false);
let battery_flash_button = document.getElementById("battery_flash_button") as HTMLButtonElement;
let battery_flash_file = document.getElementById("battery_flash_file") as HTMLInputElement;
let battery_flash_message = document.getElementById("battery_flash_message") as HTMLElement;
let battery_flash_progressBar = document.getElementById("battery_flash_progressBar") as HTMLProgressElement;
let battery_flash_loaded_n_total = document.getElementById("battery_flash_loaded_n_total") as HTMLElement;
let battery_flash_status = document.getElementById("battery_flash_status") as HTMLElement;
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", event => {
battery_flash_loaded_n_total.innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total;
var percent = (event.loaded / event.total) * 100;
battery_flash_progressBar.value = Math.round(percent);
battery_flash_status.innerHTML = Math.round(percent) + "%";
battery_flash_message.innerHTML = "in progress";
}, false);
ajax.addEventListener("load", () => {
battery_flash_status.innerHTML = ajax.responseText;
battery_flash_message.innerHTML = "finished";
battery_flash_progressBar.value = 0;
}, false);
ajax.addEventListener("error", () => {
battery_flash_status.innerHTML = ajax.responseText;
battery_flash_message.innerHTML = "failed";
}, false);
ajax.addEventListener("abort", () => {
battery_flash_status.innerHTML = ajax.responseText;
battery_flash_message.innerHTML = "aborted";
}, false);
battery_flash_button.onclick = async function () {
//ajax.open("POST", "/flashbattery");
//ajax.send(battery_flash_file.files[0]);
ajax.open("POST", PUBLIC_URL + "/flashbattery?flash=true");
ajax.send(battery_flash_file.files![0]);
};

View File

@@ -0,0 +1,157 @@
<div class="container-xxl">
<link rel="stylesheet" href="bootstrap-grid.css">
<style>
.progressPane{
display: block;
position: fixed;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
top: 0;
left: 0;
background-color: lightgrey;
opacity: 0.8;
}
.progressPaneCenter{
display: inline-block;
margin-top: 48%;
position: absolute;
height: 4%;
width: 50%;
margin-left: 25%;
margin-right: 25%;
}
.progress {
height: 1.5em;
width: 100%;
background-color: #c9c9c9;
position: relative;
}
.progress:after {
content: attr(data-label);
font-size: 0.8em;
position: absolute;
text-align: center;
top: 5px;
left: 0;
right: 0;
}
.progress .value {
background-color: #7cc4ff;
display: inline-block;
height: 100%;
}
.progress .valueIndeterminate {
background-color: #7cc4ff;
display: inline-block;
height: 100%;
animation: indeterminateAnimation 1s infinite linear;
transform-origin: 0% 50%;
}
@keyframes indeterminateAnimation {
0% {
transform: translateX(0%) scaleX(0.5);
}
50% {
transform: translateX(50%) scaleX(0.5);
}
100% {
transform: translateX(0%) scaleX(0.5);
}
}
</style>
<div id="progressPane" class="progressPane">
<div class="progressPaneCenter">
<div id="progressPaneBar" class="progress" data-label="50% Complete">
<span id="progressPaneSpan" class="value" style="width:100%;"></span>
</div>
</div>
</div>
<input type="button" id="test" value="Test">
<h2>Current Firmware</h2>
<div>
<div id="firmware_buildtime">Buildtime loading</div>
<div id="firmware_githash">Build githash loading</div>
</div>
<h2>firmeware OTA v3</h2>
<form id="upload_form" method="post">
<input type="file" name="file1" id="firmware_file"><br>
<progress id="firmware_progressBar" value="0" max="100" style="width:300px;"></progress>
<h3 id="firmware_status"></h3>
<h3 id="firmware_answer"></h3>
<p id="firmware_loaded_n_total"></p>
</form>
<div id="timeview">
</div>
<div id="network_view">
</div>
<h2>config</h2>
<div id="configform">
<h3>Tank:</h3>
<div>
<input type="checkbox" id="tank_sensor_enabled">
Enable Tank Sensor
</div>
<div>
<input type="checkbox" id="tank_allow_pumping_if_sensor_error">
Allow Pumping if Sensor Error
</div>
<div>
<input type="number" min="2" max="500000" id="tank_useable_ml">
Tank Size mL
</div>
<div>
<input type="number" min="1" max="500000" id="tank_warn_percent">
Tank Warn Percent (mapped in relation to empty and full)
</div>
<div>
<input type="number" min="0" max="100" id="tank_empty_percent">
Tank Empty Percent (% max move)
</div>
<div>
<input type="number" min="0" max="100" id="tank_full_percent">
Tank Full Percent (% max move)
</div>
<h3>Light:</h3>
<input type="checkbox" id="night_lamp_enabled" checked="false"> Enable Nightlight
<div>
Start
<select type="time" id="night_lamp_time_start">
</select>
Stop
<select type="time" id="night_lamp_time_end">
</select>
</div>
<div>
<input type="checkbox" id="night_lamp_only_when_dark">
Light only when dark
</div>
<h3>Plants:</h3>
<button id="measure_moisture">Measure Moisture</button>
<div id="plants" class="row"></div>
</div>
<button id="submit">Submit</button>
<div id="submit_status"></div>
<br>
<textarea id="json" cols=50 rows=10></textarea>
<script src="bundle.js"></script>
</div>

View File

@@ -0,0 +1,244 @@
declare var PUBLIC_URL: string;
console.log("Url is " + PUBLIC_URL);
document.body.innerHTML = require('./main.html') as string;
import { TimeView } from "./timeview";
import { PlantView, PlantViews } from "./plant";
import { NetworkConfigView } from "./network";
import { NightLampView } from "./nightmode";
import { TankConfigView } from "./tanks";
import { SubmitView } from "./submitView";
import { ProgressView } from "./progress";
import { OTAView } from "./ota";
export class Controller {
updateRTCData() {
fetch(PUBLIC_URL + "/time")
.then(response => response.json())
.then(json => json as GetTime)
.then(time => {
controller.timeView.update(time.native, time.rtc)
})
.catch(error => {
controller.timeView.update("n/a","n/a")
console.log(error);
});
}
uploadNewFirmware(file: File) {
var current = 0;
var max = 100;
controller.progressview.addProgress("ota_upload", (current/max) *100 , "Uploading firmeware ("+current+"/" + max+")")
var ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", event => {
current = event.loaded/1000;
max = event.total/1000;
controller.progressview.addProgress("ota_upload", (current/max) *100 , "Uploading firmeware ("+current+"/" + max+")")
}, false);
ajax.addEventListener("load", () => {
//TODO wait for reboot here!
controller.progressview.removeProgress("ota_upload")
}, false);
ajax.addEventListener("error", () => {
alert("Error ota")
controller.progressview.removeProgress("ota_upload")
}, false);
ajax.addEventListener("abort", () => {
alert("abort ota")
controller.progressview.removeProgress("ota_upload")
}, false);
ajax.open("POST", PUBLIC_URL + "/ota");
ajax.send(file);
}
version() {
controller.progressview.addIndeterminate("version", "Getting buildVersion")
fetch(PUBLIC_URL + "/version")
.then(response => response.json())
.then(json => json as VersionInfo)
.then(versionInfo => {
controller.progressview.removeProgress("version")
controller.firmWareView.setVersion(versionInfo);
})
}
downloadConfig() {
controller.progressview.addIndeterminate("get_config", "Downloading Config")
fetch(PUBLIC_URL + "/get_config")
.then(response => response.json())
.then(loaded => {
var currentConfig = loaded as PlantControllerConfig;
this.setConfig(currentConfig);
//sync json view initially
this.configChanged();
controller.progressview.removeProgress("get_config")
})
}
uploadConfig(json: string, statusCallback: (status: string) => void) {
controller.progressview.addIndeterminate("set_config", "Uploading Config")
fetch(PUBLIC_URL + "/set_config", {
method: "POST",
body: json,
})
.then(response => response.text())
.then(text => statusCallback(text))
controller.progressview.removeProgress("set_config")
}
syncRTCFromBrowser(){
controller.progressview.addIndeterminate("write_rtc", "Writing RTC")
var value: SetTime = {
time: new Date().toISOString()
}
var pretty = JSON.stringify(value, undefined, 1);
fetch(PUBLIC_URL + "/time", {
method: "POST",
body: pretty
}).then(
_ => controller.progressview.removeProgress("write_rtc")
)
}
configChanged() {
const current = controller.getConfig();
var pretty = JSON.stringify(current, undefined, 1);
console.log(pretty)
controller.submitView.setJson(pretty);
}
testPlant(plantId: number) {
let counter = 0
let limit = 30
controller.progressview.addProgress("test_pump", counter/limit*100, "Testing pump " + (plantId+1) + " for " + (limit-counter)+"s")
let timerId: string | number | NodeJS.Timeout | undefined
function updateProgress(){
counter++;
controller.progressview.addProgress("test_pump", counter/limit*100, "Testing pump " + (plantId+1) + " for " + (limit-counter)+"s")
timerId = setTimeout(updateProgress, 1000);
}
timerId = setTimeout(updateProgress, 1000);
var body: TestPump = {
pump: plantId
}
var pretty = JSON.stringify(body, undefined, 1);
fetch(PUBLIC_URL + "/pumptest", {
method: "POST",
body: pretty
})
.then(response => response.text())
.then(
text => {
clearTimeout(timerId);
controller.progressview.removeProgress("test_pump");
}
)
}
getConfig(): PlantControllerConfig{
return {
network: controller.networkView.getConfig(),
tank: controller.tankView.getConfig(),
night_lamp: controller.nightLampView.getConfig(),
plants: controller.plantViews.getConfig()
}
}
scanWifi() {
let counter = 0
let limit = 5
controller.progressview.addProgress("scan_ssid", counter/limit*100, "Scanning for SSIDs for " + (limit-counter)+"s")
let timerId: string | number | NodeJS.Timeout | undefined
function updateProgress(){
counter++;
controller.progressview.addProgress("scan_ssid", counter/limit*100, "Scanning for SSIDs for " + (limit-counter)+"s")
timerId = setTimeout(updateProgress, 1000);
}
timerId = setTimeout(updateProgress, 1000);
var ajax = new XMLHttpRequest();
ajax.responseType = 'json';
ajax.onreadystatechange = () => {
if (ajax.readyState === 4) {
clearTimeout(timerId);
controller.progressview.removeProgress("scan_ssid");
this.networkView.setScanResult(ajax.response as SSIDList)
}
};
ajax.onerror = (evt) => {
clearTimeout(timerId);
controller.progressview.removeProgress("scan_ssid");
alert("Failed to start see console")
}
ajax.open("POST", PUBLIC_URL + "/wifiscan");
ajax.send();
}
setConfig(current: PlantControllerConfig) {
this.tankView.setConfig(current.tank);
this.networkView.setConfig(current.network);
this.nightLampView.setConfig(current.night_lamp);
this.plantViews.setConfig(current.plants);
}
measure_moisture (){
let counter = 0
let limit = 2
controller.progressview.addProgress("measure_moisture", counter/limit*100, "Measure Moisture " + (limit-counter)+"s")
let timerId: string | number | NodeJS.Timeout | undefined
function updateProgress(){
counter++;
controller.progressview.addProgress("measure_moisture", counter/limit*100, "Measure Moisture " + (limit-counter)+"s")
timerId = setTimeout(updateProgress, 1000);
}
timerId = setTimeout(updateProgress, 1000);
fetch(PUBLIC_URL + "/moisture")
.then(response => response.json())
.then(json => json as Moistures)
.then(time => {
controller.plantViews.update(time.moisture_a, time.moisture_b)
clearTimeout(timerId);
controller.progressview.removeProgress("measure_moisture");
})
.catch(error => {
clearTimeout(timerId);
controller.progressview.removeProgress("measure_moisture");
console.log(error);
});
}
readonly timeView: TimeView;
readonly plantViews: PlantViews;
readonly networkView: NetworkConfigView;
readonly tankView: TankConfigView;
readonly nightLampView: NightLampView;
readonly submitView: SubmitView;
readonly firmWareView : OTAView;
readonly progressview: ProgressView;
constructor() {
this.timeView = new TimeView(this)
this.plantViews = new PlantViews(this)
this.networkView = new NetworkConfigView(this, PUBLIC_URL)
this.tankView = new TankConfigView(this)
this.nightLampView = new NightLampView(this)
this.submitView = new SubmitView(this)
this.firmWareView = new OTAView(this)
this.progressview = new ProgressView(this)
}
}
const controller = new Controller();
controller.updateRTCData();
controller.downloadConfig();
controller.measure_moisture();
controller.version();

View File

@@ -0,0 +1,32 @@
<h2>Basic network</h2>
Api Redirection to:
<span id="remote_ip">remote ip</span>
<br>
AccessPoint Mode (or fallback)
<br>
<label for="ap_ssid">AP SSID:</label>
<input type="text" id="ap_ssid" list="ssidlist">
<br>
<br>
Station Mode:
<br>
<label for="ssid">SSID:</label>
<input type="text" id="ssid" list="ssidlist"> <input type="button" id="scan" value="Scan">
<datalist id="ssidlist">
<option value="Not scanned yet">
</datalist>
<br>
<label for="ssid">Password:</label>
<input type="text" id="password">
<br>
<br>
Mqtt Reporting
<br>
<div>
MQTT Url
<input type="text" id="mqtt_url" placeholder="mqtt://192.168.1.1:1883">
</div>
<div>
Base Topic
<input type="text" id="base_topic" placeholder="plants/one">
</div>

View File

@@ -1,4 +1,4 @@
import { Controller } from ".";
import { Controller } from "./main";
export class NetworkConfigView {
setScanResult(ssidList: SSIDList) {
@@ -17,6 +17,8 @@ export class NetworkConfigView {
private readonly ssidlist: HTMLElement;
constructor(controller: Controller, publicIp: string) {
(document.getElementById("network_view") as HTMLElement).innerHTML = require('./network.html') as string;
(document.getElementById("remote_ip") as HTMLElement).innerText = publicIp;
this.ap_ssid = (document.getElementById("ap_ssid") as HTMLInputElement);

View File

@@ -1,4 +1,4 @@
import { Controller } from ".";
import { Controller } from "./main";
export class NightLampView {
private readonly night_lamp_only_when_dark: HTMLInputElement;

View File

@@ -0,0 +1,28 @@
import { Controller } from "./main";
export class OTAView {
file1Upload: HTMLInputElement;
firmware_buildtime: HTMLDivElement;
firmware_githash: HTMLDivElement;
constructor(controller: Controller) {
this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement;
this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement;
const file = document.getElementById("firmware_file") as HTMLInputElement;
this.file1Upload = file
this.file1Upload.onchange = () => {
var selectedFile = file.files?.[0];
if (selectedFile == null) {
//TODO error dialog here
return
}
controller.uploadNewFirmware(selectedFile);
};
}
setVersion(versionInfo: VersionInfo) {
this.firmware_buildtime.innerText = versionInfo.build_time;
this.firmware_githash.innerText = versionInfo.git_hash;
}
}

View File

@@ -2,9 +2,22 @@
const PLANT_COUNT = 8;
import { Controller } from ".";
import { Controller } from "./main";
export class PlantViews {
private readonly measure_moisture: HTMLButtonElement;
private readonly plants: PlantView[] = []
private readonly plantsDiv: HTMLDivElement
constructor(syncConfig:Controller) {
this.measure_moisture = document.getElementById("measure_moisture") as HTMLButtonElement
this.measure_moisture.onclick = syncConfig.measure_moisture
this.plantsDiv = document.getElementById("plants") as HTMLDivElement;
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig);
}
}
getConfig(): PlantConfig[] {
const rv: PlantConfig[] = [];
for (let i = 0; i < PLANT_COUNT; i++) {
@@ -27,15 +40,6 @@ export class PlantViews {
plantView.setConfig(plantConfig)
}
}
private readonly plants: PlantView[] = []
private readonly plantsDiv: HTMLDivElement
constructor(syncConfig:Controller) {
this.plantsDiv = document.getElementById("plants") as HTMLDivElement;
for (let plantId = 0; plantId < PLANT_COUNT; plantId++) {
this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig);
}
}
}
export class PlantView {
@@ -62,6 +66,7 @@ export class PlantView {
const template = require('./plant.html') as string;
const plantRaw = template.replaceAll("${plantId}", String(plantId));
this.plantDiv.innerHTML = plantRaw
this.plantDiv.classList.add("col-auto" )
parent.appendChild(this.plantDiv)
this.header = document.getElementById("plant_"+plantId+"_header")!

View File

@@ -0,0 +1,62 @@
import { Controller } from "./main";
class ProgressInfo{
displayText:string;
percentValue:number;
indeterminate:boolean;
constructor(displayText:string, percentValue: number, indeterminate:boolean ){
this.displayText = displayText
this.percentValue = percentValue <0 ? 0 : percentValue > 100? 100: percentValue
this.indeterminate = indeterminate
}
}
export class ProgressView{
progressPane: HTMLElement;
progress: HTMLElement;
progressPaneSpan: HTMLSpanElement;
progresses: Map<string,ProgressInfo> = new Map;
progressPaneBar: HTMLDivElement;
constructor(controller:Controller){
this.progressPane = document.getElementById("progressPane") as HTMLElement;
this.progress = document.getElementById("progress") as HTMLElement;
this.progressPaneSpan = document.getElementById("progressPaneSpan") as HTMLSpanElement;
this.progressPaneBar = document.getElementById("progressPaneBar") as HTMLDivElement;
}
updateView() {
if (this.progresses.size == 0){
this.progressPane.style.display = "none"
} else{
const first = this.progresses.entries().next().value![1]
this.progressPaneBar.setAttribute("data-label", first.displayText)
if (first.indeterminate){
this.progressPaneSpan.className = "valueIndeterminate"
this.progressPaneSpan.style.width = "100%"
} else {
this.progressPaneSpan.className = "value"
this.progressPaneSpan.style.width = first.percentValue+"%"
}
}
}
addIndeterminate(id:string, displayText:string){
this.progresses.set(id, new ProgressInfo(displayText,0,true))
this.progressPane.style.display = "block"
this.updateView();
}
addProgress(id:string, value:number, displayText:string) {
this.progresses.set(id, new ProgressInfo(displayText,value, false))
this.progressPane.style.display = "block"
this.updateView();
}
removeProgress(id:string){
this.progresses.delete(id)
this.updateView();
}
}

View File

@@ -0,0 +1,22 @@
import { Controller } from "./main";
export class SubmitView{
json: HTMLInputElement;
submitFormBtn: HTMLButtonElement;
submit_status: HTMLElement;
constructor(controller: Controller){
this.json = document.getElementById('json') as HTMLInputElement
this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement
this.submit_status = document.getElementById("submit_status") as HTMLElement
this.submitFormBtn.onclick = () => {
controller.uploadConfig(this.json.value, (status:string) => {
this.submit_status.innerHTML = status;
});
}
}
setJson(pretty: string) {
this.json.value = pretty
}
}

View File

@@ -1,4 +1,4 @@
import { Controller } from ".";
import { Controller } from "./main";
export class TankConfigView {
private readonly tank_useable_ml: HTMLInputElement;

View File

@@ -0,0 +1,7 @@
<h2>Time</h2>
AutoRefresh:<input id="timeview_auto_refresh" type="checkbox">
<div id="timeview_esp_time">Esp time</div>
<div id="timeview_rtc_time">Rtc time</div>
<div id="timeview_browser_time">Rtc time</div>
<div></div>
<button id="timeview_time_upload">Store Browser time into esp and rtc</button>

View File

@@ -1,17 +1,33 @@
import { Controller } from ".";
import { Controller } from "./main";
export class TimeView {
esp_time: HTMLDivElement
rtc_time: HTMLDivElement
browser_time: HTMLDivElement
sync: HTMLButtonElement
auto_refresh: HTMLInputElement;
controller: Controller;
timer: NodeJS.Timeout | undefined;
constructor(controller:Controller) {
(document.getElementById("timeview") as HTMLElement).innerHTML = require("./timeview.html")
this.auto_refresh = document.getElementById("timeview_auto_refresh") as HTMLInputElement;
this.esp_time = document.getElementById("timeview_esp_time") as HTMLDivElement;
this.rtc_time = document.getElementById("timeview_rtc_time") as HTMLDivElement;
this.browser_time = document.getElementById("timeview_browser_time") as HTMLDivElement;
this.sync = document.getElementById("timeview_time_upload") as HTMLButtonElement;
this.sync.onclick = controller.syncRTCFromBrowser;
this.controller = controller;
this.auto_refresh.onchange = () => {
if(this.timer){
clearTimeout(this.timer)
}
if(this.auto_refresh.checked){
controller.updateRTCData()
}
}
}
update(native: string, rtc: string) {
@@ -19,5 +35,13 @@ export class TimeView {
this.rtc_time.innerText = rtc;
var date = new Date();
this.browser_time.innerText = date.toISOString();
if(this.auto_refresh.checked){
this.timer = setTimeout(this.controller.updateRTCData, 1000);
} else {
if(this.timer){
clearTimeout(this.timer)
}
}
}
}