further components and bootstrap initial
This commit is contained in:
@@ -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],
|
||||
}
|
||||
|
@@ -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>
|
@@ -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]);
|
||||
};
|
157
rust/src_webpack/src/main.html
Normal file
157
rust/src_webpack/src/main.html
Normal 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>
|
244
rust/src_webpack/src/main.ts
Normal file
244
rust/src_webpack/src/main.ts
Normal 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();
|
32
rust/src_webpack/src/network.html
Normal file
32
rust/src_webpack/src/network.html
Normal 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>
|
@@ -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);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Controller } from ".";
|
||||
import { Controller } from "./main";
|
||||
|
||||
export class NightLampView {
|
||||
private readonly night_lamp_only_when_dark: HTMLInputElement;
|
||||
|
28
rust/src_webpack/src/ota.ts
Normal file
28
rust/src_webpack/src/ota.ts
Normal 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;
|
||||
}
|
||||
}
|
@@ -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")!
|
||||
|
62
rust/src_webpack/src/progress.ts
Normal file
62
rust/src_webpack/src/progress.ts
Normal 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();
|
||||
|
||||
}
|
||||
}
|
22
rust/src_webpack/src/submitView.ts
Normal file
22
rust/src_webpack/src/submitView.ts
Normal 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
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { Controller } from ".";
|
||||
import { Controller } from "./main";
|
||||
|
||||
export class TankConfigView {
|
||||
private readonly tank_useable_ml: HTMLInputElement;
|
||||
|
7
rust/src_webpack/src/timeview.html
Normal file
7
rust/src_webpack/src/timeview.html
Normal 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>
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user