Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
03e6fb6b22 | |||
2532f50215 | |||
10163f3aa7 | |||
1adef20edd | |||
617e77a9d8 | |||
c8d48a3236 |
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.6.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Certificates:</div><div class="col-md-4 themed-grid-col bg-light" id="certs">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var t="/api/device";$("#spinner").show(),$.getJSON(t,function(t){console.log(t),$("#app-ver").text(t["app-ver"]+" (html 0.6.0)"),$("#mdns").text(t.mdns),$("#id").text(t.id),t.certs?$("#certs").text("CA Store installed."):$("#certs").text("CA Store NOT installed.")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
BIN
data/certs.ar
Normal file
BIN
data/certs.ar
Normal file
Binary file not shown.
@ -88,6 +88,10 @@
|
||||
<div class="col-md-8 themed-grid-col bg-light">Device ID:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Certificates:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="certs">Loading...</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
@ -103,6 +107,11 @@
|
||||
$("#app-ver").text(cfg["app-ver"] + " (html 0.6.0)");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
|
||||
if( cfg["certs"] )
|
||||
$("#certs").text("CA Store installed.");
|
||||
else
|
||||
$("#certs").text("CA Store NOT installed.");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
|
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var n="/api/device";$("#spinner").show(),$.getJSON(n,function(n){console.log(n),$("#app-ver").text(n["app-ver"]+" (html 0.6.0)"),$("#mdns").text(n.mdns),$("#id").text(n.id)}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></a></li><li class="nav-item active"><a class="nav-link" href="/device.htm">Device</a></li><li class="nav-item"><a class="nav-link" href="/config.htm">Configuration</a></li><li class="nav-item"><a class="nav-link" href="/calibration.htm">Calibration</a></li><li class="nav-item"><a class="nav-link" href="/about.htm">About</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Current version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3" id="h-app-ver-new" hidden><div class="col-md-8 themed-grid-col bg-light">New version:</div><div class="col-md-4 themed-grid-col bg-light" id="app-ver-new">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Host name:</div><div class="col-md-4 themed-grid-col bg-light" id="mdns">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Device ID:</div><div class="col-md-4 themed-grid-col bg-light" id="id">Loading...</div></div><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">Certificates:</div><div class="col-md-4 themed-grid-col bg-light" id="certs">Loading...</div></div><hr class="my-4"></div><script type="text/javascript">function getConfig(){var t="/api/device";$("#spinner").show(),$.getJSON(t,function(t){console.log(t),$("#app-ver").text(t["app-ver"]+" (html 0.6.0)"),$("#mdns").text(t.mdns),$("#id").text(t.id),t.certs?$("#certs").text("CA Store installed."):$("#certs").text("CA Store NOT installed.")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getConfig</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
@ -82,6 +82,10 @@
|
||||
<div class="col-md-2 themed-grid-col bg-light">about.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">certs.ar</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="certs">Checking...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||
@ -137,6 +141,10 @@ function getUpload() {
|
||||
else
|
||||
$("#about").text("File is missing.");
|
||||
|
||||
if( cfg["certs"] )
|
||||
$("#certs").text("Completed.");
|
||||
else
|
||||
$("#certs").text("File is missing (optional).");
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
|
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/upload.htm">Beer Gravity Monitor - Missing html files</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">The listed files below needs to be uploaded to the FileSystem in order for the GUI to work. You can also flash the LittleFS filesystem but in that case you will loose your device settings. An OTA upgrade will automatically download the files if they are found in the same location as the firmware.bin. This page is a fallback option.</div><div class="col-md-8 themed-grid-col bg-light"><br><strong>Once all the files are confirmed, please reboot the device for normal operation.</strong></div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">index.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">device.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">config.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">calibration.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="calibration">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">about.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div></div><div class="row mb-3"><form action="/api/upload" method="post" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" class="custom-file-input" name="name" id="name"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button></form></div><hr class="my-4"></div><script type="text/javascript">function getUpload(){var i="/api/upload";$("#spinner").show(),$.getJSON(i,function(i){console.log(i),i.index?$("#index").text("Completed."):$("#index").text("File is missing."),i.device?$("#device").text("Completed."):$("#device").text("File is missing."),i.config?$("#config").text("Completed."):$("#config").text("File is missing."),i.calibration?$("#calibration").text("Completed."):$("#calibration").text("File is missing."),i.about?$("#about").text("Completed."):$("#about").text("File is missing.")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getUpload,$(".custom-file-input").on("change",function(){var i=$(this).val().split("\\").pop();$(this).siblings(".custom-file-label").addClass("selected").html(i)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="description" content=""><title>Beer Gravity Monitor</title><link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" crossorigin="anonymous"></script></head><body class="py-4"><!-- START MENU --><nav class="navbar navbar-expand-sm navbar-dark bg-primary"><a class="navbar-brand" href="/upload.htm">Beer Gravity Monitor - Missing html files</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbar"><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="alert alert-success alert-dismissible fade hide show d-none" role="alert" id="alert"><div id="alert-msg">...</div><button type="button" id="alert-btn" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("d-none").addClass("show"),$("#alert-msg").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("d-none").removeClass("show")})</script><div class="row mb-3"><div class="col-md-8 themed-grid-col bg-light">The listed files below needs to be uploaded to the FileSystem in order for the GUI to work. You can also flash the LittleFS filesystem but in that case you will loose your device settings. An OTA upgrade will automatically download the files if they are found in the same location as the firmware.bin. This page is a fallback option.</div><div class="col-md-8 themed-grid-col bg-light"><br><strong>Once all the files are confirmed, please reboot the device for normal operation.</strong></div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">index.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="index">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">device.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="device">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">config.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="config">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">calibration.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="calibration">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">about.min.htm</div><div class="col-md-6 themed-grid-col bg-light" id="about">Checking...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">certs.ar</div><div class="col-md-6 themed-grid-col bg-light" id="certs">Checking...</div></div><div class="row mb-3"><form action="/api/upload" method="post" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" class="custom-file-input" name="name" id="name"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Upload</button></form></div><hr class="my-4"></div><script type="text/javascript">function getUpload(){var e="/api/upload";$("#spinner").show(),$.getJSON(e,function(e){console.log(e),e.index?$("#index").text("Completed."):$("#index").text("File is missing."),e.device?$("#device").text("Completed."):$("#device").text("File is missing."),e.config?$("#config").text("Completed."):$("#config").text("File is missing."),e.calibration?$("#calibration").text("Completed."):$("#calibration").text("File is missing."),e.about?$("#about").text("Completed."):$("#about").text("File is missing."),e.certs?$("#certs").text("Completed."):$("#certs").text("File is missing (optional).")}).fail(function(){showError("Unable to get data from the device.")}).always(function(){$("#spinner").hide()})}window.onload=getUpload,$(".custom-file-input").on("change",function(){var e=$(this).val().split("\\").pop();$(this).siblings(".custom-file-label").addClass("selected").html(e)})</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
85
script/create_cert.py
Normal file
85
script/create_cert.py
Normal file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This script pulls the list of Mozilla trusted certificate authorities
|
||||
# from the web at the "mozurl" below, parses the file to grab the PEM
|
||||
# for each cert, and then generates DER files in a new ./data directory
|
||||
# Upload these to an on-chip filesystem and use the CertManager to parse
|
||||
# and use them for your outgoing SSL connections.
|
||||
#
|
||||
# Script by Earle F. Philhower, III. Released to the public domain.
|
||||
from __future__ import print_function
|
||||
import csv
|
||||
import os
|
||||
import sys
|
||||
from shutil import which
|
||||
|
||||
# Change the path to the installed files.
|
||||
arCmd = "C:\\Users\\magnu\\.platformio\\packages\\toolchain-xtensa\\bin\\xtensa-lx106-elf-ar.exe"
|
||||
opensslCmd = "C:\\Program Files\\Git\\usr\\bin\\openssl.exe"
|
||||
|
||||
from subprocess import Popen, PIPE, call
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except Exception:
|
||||
from urllib2 import urlopen
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except Exception:
|
||||
from io import StringIO
|
||||
|
||||
# check if ar and openssl are available
|
||||
#if which('ar') is None and not os.path.isfile('./ar') and not os.path.isfile('./ar.exe'):
|
||||
# raise Exception("You need the program 'ar' from xtensa-lx106-elf found here: (esp8266-arduino-core)/hardware/esp8266com/esp8266/tools/xtensa-lx106-elf/xtensa-lx106-elf/bin/ar")
|
||||
#if which('openssl') is None and not os.path.isfile('./openssl') and not os.path.isfile('./openssl.exe'):
|
||||
# raise Exception("You need to have openssl in PATH, installable from https://www.openssl.org/")
|
||||
|
||||
# Mozilla's URL for the CSV file with included PEM certs
|
||||
mozurl = "https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReportPEMCSV"
|
||||
|
||||
# Load the names[] and pems[] array from the URL
|
||||
names = []
|
||||
pems = []
|
||||
response = urlopen(mozurl)
|
||||
csvData = response.read()
|
||||
if sys.version_info[0] > 2:
|
||||
csvData = csvData.decode('utf-8')
|
||||
csvFile = StringIO(csvData)
|
||||
csvReader = csv.reader(csvFile)
|
||||
for row in csvReader:
|
||||
names.append(row[0]+":"+row[1]+":"+row[2])
|
||||
for item in row:
|
||||
if item.startswith("'-----BEGIN CERTIFICATE-----"):
|
||||
pems.append(item)
|
||||
del names[0] # Remove headers
|
||||
del pems[0] # Remove headers
|
||||
|
||||
# Try and make ./data, skip if present
|
||||
try:
|
||||
os.mkdir("../data")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
derFiles = []
|
||||
idx = 0
|
||||
# Process the text PEM using openssl into DER files
|
||||
for i in range(0, len(pems)):
|
||||
certName = "../data/ca_%03d.der" % (idx);
|
||||
thisPem = pems[i].replace("'", "")
|
||||
print(names[i] + " -> " + certName)
|
||||
ssl = Popen([opensslCmd,'x509','-inform','PEM','-outform','DER','-out', certName], shell = False, stdin = PIPE)
|
||||
pipe = ssl.stdin
|
||||
pipe.write(thisPem.encode('utf-8'))
|
||||
pipe.close()
|
||||
ssl.wait()
|
||||
if os.path.exists(certName):
|
||||
derFiles.append(certName)
|
||||
idx = idx + 1
|
||||
|
||||
if os.path.exists("../data/certs.ar"):
|
||||
os.unlink("../data/certs.ar");
|
||||
|
||||
arCmd = [arCmd, 'q', '../data/certs.ar'] + derFiles;
|
||||
call( arCmd )
|
||||
|
||||
for der in derFiles:
|
||||
os.unlink(der)
|
635
src/certificates.h
Normal file
635
src/certificates.h
Normal file
File diff suppressed because one or more lines are too long
@ -89,6 +89,7 @@ SOFTWARE.
|
||||
#define CFG_PARAM_BATTERY "battery"
|
||||
#define CFG_PARAM_SLEEP_MODE "sleep-mode"
|
||||
#define CFG_PARAM_RSSI "rssi"
|
||||
#define CFG_PARAM_CERTS "certs"
|
||||
|
||||
// Used for holding sensordata or sensoroffsets
|
||||
struct RawGyroData {
|
||||
@ -179,6 +180,7 @@ class Config {
|
||||
saveNeeded = true;
|
||||
}
|
||||
bool isOtaActive() { return otaURL.length() ? true : false; }
|
||||
bool isOtaSecure() { return otaURL.startsWith("https://"); }
|
||||
|
||||
const char* getWifiSSID() { return wifiSSID.c_str(); }
|
||||
void setWifiSSID(String s) {
|
||||
@ -208,12 +210,14 @@ class Config {
|
||||
saveNeeded = true;
|
||||
}
|
||||
bool isHttpActive() { return httpPushUrl.length() ? true : false; }
|
||||
bool isHttpSecure() { return httpPushUrl.startsWith("https://"); }
|
||||
const char* getHttpPushUrl2() { return httpPushUrl2.c_str(); }
|
||||
void setHttpPushUrl2(String s) {
|
||||
httpPushUrl2 = s;
|
||||
saveNeeded = true;
|
||||
}
|
||||
bool isHttpActive2() { return httpPushUrl2.length() ? true : false; }
|
||||
bool isHttpSecure2() { return httpPushUrl2.startsWith("https://"); }
|
||||
|
||||
// InfluxDB2
|
||||
const char* getInfluxDb2PushUrl() { return influxDb2Url.c_str(); }
|
||||
@ -240,6 +244,7 @@ class Config {
|
||||
|
||||
// MQTT
|
||||
bool isMqttActive() { return mqttUrl.length() ? true : false; }
|
||||
bool isMqttSecure() { return mqttUrl.endsWith(":8883"); }
|
||||
const char* getMqttUrl() { return mqttUrl.c_str(); }
|
||||
void setMqttUrl(String s) {
|
||||
mqttUrl = s;
|
||||
|
10
src/main.cpp
10
src/main.cpp
@ -180,6 +180,16 @@ void setup() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if we need SSL for any of the push targets
|
||||
if (myConfig.isHttpSecure() || myConfig.isHttpSecure2() || myConfig.isMqttSecure()) {
|
||||
LOG_PERF_START("main-cert-store");
|
||||
myWifi.initCertstore();
|
||||
LOG_PERF_STOP("main-cert-store");
|
||||
LOG_PERF_START("main-cert-ntp");
|
||||
myWifi.initNTP();
|
||||
LOG_PERF_STOP("main-cert-ntp");
|
||||
}
|
||||
|
||||
LOG_PERF_STOP("main-setup");
|
||||
Log.notice(F("Main: Setup completed." CR));
|
||||
stableGyroMillis = millis(); // Dont include time for wifi connection
|
||||
|
@ -21,11 +21,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <pushtarget.hpp>
|
||||
#include <MQTT.h>
|
||||
|
||||
#include <config.hpp>
|
||||
#include <gyro.hpp>
|
||||
#include <pushtarget.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
PushTarget myPushTarget;
|
||||
|
||||
@ -243,10 +243,24 @@ void PushTarget::sendHttp(String serverPath, float angle, float gravity,
|
||||
createIspindleFormat(doc, angle, gravity, corrGravity, temp, runTime);
|
||||
|
||||
WiFiClient client;
|
||||
WiFiClientSecure clientSecure;
|
||||
HTTPClient http;
|
||||
|
||||
// Your Domain name with URL path or IP address with path
|
||||
if (serverPath.startsWith("https://")) {
|
||||
/*if (myWifi.getCertCount() > 0) {
|
||||
// Allow secure channel, with CA validation
|
||||
clientSecure.setCertStore(myWifi.getCertStore());
|
||||
Log.notice(F("PUSH: SSL enabled using certificate store." CR));
|
||||
} else*/ {
|
||||
// Allow secure channel, but without certificate validation
|
||||
clientSecure.setInsecure();
|
||||
Log.notice(F("PUSH: SSL enabled without validation." CR));
|
||||
}
|
||||
http.begin(clientSecure, serverPath);
|
||||
} else {
|
||||
http.begin(client, serverPath);
|
||||
}
|
||||
|
||||
String json;
|
||||
serializeJson(doc, json);
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
@ -283,9 +297,26 @@ void PushTarget::sendMqtt(float angle, float gravity, float corrGravity,
|
||||
createIspindleFormat(doc, angle, gravity, corrGravity, temp, runTime);
|
||||
|
||||
WiFiClient client;
|
||||
WiFiClientSecure clientSecure;
|
||||
MQTTClient mqtt(512); // Maximum message size
|
||||
|
||||
if (myConfig.isMqttSecure()) {
|
||||
if (myWifi.getCertCount() > 0) {
|
||||
// Allow secure channel, with CA validation
|
||||
clientSecure.setCertStore(myWifi.getCertStore());
|
||||
Log.notice(F("PUSH: SSL enabled using certificate store." CR));
|
||||
} else {
|
||||
// Allow secure channel, but without certificate validation
|
||||
clientSecure.setInsecure();
|
||||
Log.notice(F("PUSH: SSL enabled without validation." CR));
|
||||
}
|
||||
String url = myConfig.getMqttUrl();
|
||||
url.replace(":8883", "");
|
||||
mqtt.begin(url.c_str(), 8883, clientSecure);
|
||||
} else {
|
||||
mqtt.begin(myConfig.getMqttUrl(), client);
|
||||
}
|
||||
|
||||
mqtt.connect(myConfig.getMDNS(), myConfig.getMqttUser(),
|
||||
myConfig.getMqttPass());
|
||||
|
||||
|
@ -32,12 +32,9 @@ INCBIN(DeviceHtm, "data/device.min.htm");
|
||||
INCBIN(ConfigHtm, "data/config.min.htm");
|
||||
INCBIN(CalibrationHtm, "data/calibration.min.htm");
|
||||
INCBIN(AboutHtm, "data/about.min.htm");
|
||||
|
||||
#else
|
||||
|
||||
// Minium web interface for uploading htm files
|
||||
INCBIN(UploadHtm, "data/upload.min.htm");
|
||||
|
||||
#endif
|
||||
|
||||
// Minium web interface for uploading htm files, also used to upload certificate store.
|
||||
INCBIN(UploadHtm, "data/upload.min.htm");
|
||||
|
||||
// EOF
|
||||
|
@ -49,6 +49,7 @@ void WebServer::webHandleDevice() {
|
||||
doc[CFG_PARAM_APP_NAME] = CFG_APPNAME;
|
||||
doc[CFG_PARAM_APP_VER] = CFG_APPVER;
|
||||
doc[CFG_PARAM_MDNS] = myConfig.getMDNS();
|
||||
doc[CFG_PARAM_CERTS] = checkHtmlFile(CA_CERTS);
|
||||
#if LOG_LEVEL == 6
|
||||
serializeJson(doc, Serial);
|
||||
Serial.print(CR);
|
||||
@ -108,6 +109,7 @@ void WebServer::webHandleUpload() {
|
||||
doc["config"] = myWebServer.checkHtmlFile(WebServer::HTML_CONFIG);
|
||||
doc["calibration"] = myWebServer.checkHtmlFile(WebServer::HTML_CALIBRATION);
|
||||
doc["about"] = myWebServer.checkHtmlFile(WebServer::HTML_ABOUT);
|
||||
doc["certs"] = myWebServer.checkHtmlFile(WebServer::CA_CERTS);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
@ -134,7 +136,8 @@ void WebServer::webHandleUploadFile() {
|
||||
f.equalsIgnoreCase("device.min.htm") ||
|
||||
f.equalsIgnoreCase("calibration.min.htm") ||
|
||||
f.equalsIgnoreCase("config.min.htm") ||
|
||||
f.equalsIgnoreCase("about.min.htm")) {
|
||||
f.equalsIgnoreCase("about.min.htm") ||
|
||||
f.equalsIgnoreCase("certs.ar")) {
|
||||
validFilename = true;
|
||||
}
|
||||
|
||||
@ -587,6 +590,8 @@ const char* WebServer::getHtmlFileName(HtmlFile item) {
|
||||
return "calibration.min.htm";
|
||||
case HTML_ABOUT:
|
||||
return "about.min.htm";
|
||||
case CA_CERTS:
|
||||
return "certs.ar";
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -636,6 +641,7 @@ bool WebServer::setupWebServer() {
|
||||
server->on("/calibration.htm",
|
||||
std::bind(&WebServer::webReturnCalibrationHtm, this));
|
||||
server->on("/about.htm", std::bind(&WebServer::webReturnAboutHtm, this));
|
||||
server->on("/upload.htm", std::bind(&WebServer::webReturnUploadHtm, this));
|
||||
#else
|
||||
// Show files in the filessytem at startup
|
||||
|
||||
|
@ -37,9 +37,8 @@ INCBIN_EXTERN(DeviceHtm);
|
||||
INCBIN_EXTERN(ConfigHtm);
|
||||
INCBIN_EXTERN(CalibrationHtm);
|
||||
INCBIN_EXTERN(AboutHtm);
|
||||
#else
|
||||
INCBIN_EXTERN(UploadHtm);
|
||||
#endif
|
||||
INCBIN_EXTERN(UploadHtm);
|
||||
|
||||
// classes
|
||||
class WebServer {
|
||||
@ -86,12 +85,11 @@ class WebServer {
|
||||
void webReturnAboutHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gAboutHtmData, gAboutHtmSize);
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
void webReturnUploadHtm() {
|
||||
server->send_P(200, "text/html", (const char*)gUploadHtmData,
|
||||
gUploadHtmSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
enum HtmlFile {
|
||||
@ -99,7 +97,8 @@ class WebServer {
|
||||
HTML_DEVICE = 1,
|
||||
HTML_CONFIG = 2,
|
||||
HTML_ABOUT = 3,
|
||||
HTML_CALIBRATION = 4
|
||||
HTML_CALIBRATION = 4,
|
||||
CA_CERTS = 5
|
||||
};
|
||||
|
||||
bool setupWebServer();
|
||||
|
26
src/wifi.cpp
26
src/wifi.cpp
@ -33,6 +33,7 @@ SOFTWARE.
|
||||
#include <helper.hpp>
|
||||
#include <tempsensor.hpp>
|
||||
#include <wifi.hpp>
|
||||
#warning "Implement SSL for OTA"
|
||||
|
||||
// Settings for DRD
|
||||
#define ESP_DRD_USE_LITTLEFS true
|
||||
@ -70,6 +71,31 @@ const char *userPWD = USER_SSID_PWD;
|
||||
|
||||
const int PIN_LED = 2;
|
||||
|
||||
//
|
||||
// Initialize the certificate store
|
||||
//
|
||||
void WifiConnection::initCertstore() {
|
||||
_certCount = _certStore.initCertStore(LittleFS, "/certs.idx", "/certs.ar");
|
||||
Log.notice(F("WIFI: Number of CA certs read: %d." CR), _certCount);
|
||||
}
|
||||
|
||||
// Set time via NTP, as required for x.509 validation
|
||||
void WifiConnection::initNTP() {
|
||||
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
||||
|
||||
Log.notice(F("WIFI: Waiting for NTP time sync." CR));
|
||||
time_t now = time(nullptr);
|
||||
while (now < 8 * 3600 * 2) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
now = time(nullptr);
|
||||
}
|
||||
Serial.println();
|
||||
struct tm timeinfo;
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Log.notice(F("WIFI: Current time %s." CR), asctime(&timeinfo));
|
||||
}
|
||||
|
||||
//
|
||||
// Constructor
|
||||
//
|
||||
|
15
src/wifi.hpp
15
src/wifi.hpp
@ -26,18 +26,23 @@ SOFTWARE.
|
||||
|
||||
// Include
|
||||
#include <Arduino.h>
|
||||
#include <CertStoreBearSSL.h>
|
||||
|
||||
// classes
|
||||
class WifiConnection {
|
||||
private:
|
||||
// SSL
|
||||
BearSSL::CertStore _certStore;
|
||||
int _certCount = 0;
|
||||
|
||||
// WIFI
|
||||
void connectAsync();
|
||||
bool waitForConnection(int maxTime = 20);
|
||||
|
||||
// OTA
|
||||
bool newFirmware = false;
|
||||
bool parseFirmwareVersionString(int (&num)[3], const char *version);
|
||||
void downloadFile(const char *fname);
|
||||
void connectAsync();
|
||||
bool waitForConnection(int maxTime = 20);
|
||||
|
||||
public:
|
||||
// WIFI
|
||||
@ -53,6 +58,12 @@ class WifiConnection {
|
||||
void startPortal();
|
||||
void loop();
|
||||
|
||||
// SSL
|
||||
void initCertstore();
|
||||
BearSSL::CertStore* getCertStore() { return &_certStore; }
|
||||
int getCertCount() { return _certCount; }
|
||||
void initNTP();
|
||||
|
||||
// OTA
|
||||
bool updateFirmware();
|
||||
bool checkFirmwareVersion();
|
||||
|
@ -1,13 +1,18 @@
|
||||
Backlog of changes
|
||||
##################
|
||||
|
||||
This is a list of potential ideas to implemnt in the software.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
- Write contribution instructions
|
||||
- Example project for creating integrations and instructions
|
||||
|
||||
Code
|
||||
-------------
|
||||
----
|
||||
|
||||
- Support for plato
|
||||
- Use pre-commit for validating check-in
|
||||
- Show indicated battery life based on interval (check if its feasable)
|
||||
- Use brewflasher for flashing
|
||||
|
@ -99,19 +99,19 @@ Push Settings
|
||||
|
||||
* **HTTP URL 1:**
|
||||
|
||||
Endpoint to send data via http. Format used is standard iSpindle format (see format section).
|
||||
Endpoint to send data via http. Format used Format used :ref:`data-formats-ispindle`
|
||||
|
||||
* **HTTP URL 2:**
|
||||
|
||||
Endpoint to send data via http. Format used is standard iSpindle format (see format section).
|
||||
Endpoint to send data via http. Format used :ref:`data-formats-ispindle`
|
||||
|
||||
* **Brewfather URL:**
|
||||
|
||||
Endpoint to send data via http to brewfather. Format used is defined by brewfather (see format section).
|
||||
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
|
||||
|
||||
* **Influx DB v2 URL:**
|
||||
|
||||
Endpoint to send data via http to InfluxDB. For format (see format section).
|
||||
Endpoint to send data via http to InfluxDB. Format used :ref:`data-formats-influxdb2`
|
||||
|
||||
* **Influx DB v2 Organisation:**
|
||||
|
||||
@ -127,7 +127,7 @@ Push Settings
|
||||
|
||||
* **MQTT server:**
|
||||
|
||||
IP or name of server to send data to.
|
||||
IP or name of server to send data to. Format used :ref:`data-formats-ispindle`
|
||||
|
||||
* **MQTT Topic:**
|
||||
|
||||
@ -151,7 +151,8 @@ Gravity Settings
|
||||
|
||||
* **Gravity formula:**
|
||||
|
||||
Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. Is updated if the calibration function is used.
|
||||
Gravity formula is compatible with standard iSpindle formulas so any existing calculation option can be used. You can also use
|
||||
the feature to create the formula by supplying the raw data. See :ref:`create-formula`
|
||||
|
||||
* **Temperature correct gravity:**
|
||||
|
||||
@ -253,6 +254,10 @@ Other parameters are the same as in the configuration guide.
|
||||
"influxdb2-org": "Qwerty",
|
||||
"influxdb2-bucket": "Qwerty",
|
||||
"influxdb2-auth": "Qwerty",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-topic": "Qwerty",
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty",
|
||||
"sleep-interval": 30,
|
||||
"voltage-factor": 1.59,
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436",
|
||||
@ -373,6 +378,10 @@ Used to update push settings via an HTTP POST command. Payload is in JSON format
|
||||
"influxdb2-org": "Qwerty",
|
||||
"influxdb2-bucket": "Qwerty",
|
||||
"influxdb2-auth": "Qwerty"
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-topic": "Qwerty",
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty",
|
||||
}
|
||||
|
||||
|
||||
@ -480,7 +489,11 @@ present or the API call will fail.
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
"influxdb2-auth": ""
|
||||
"influxdb2-auth": "",
|
||||
"mqtt-push": "192.168.1.50",
|
||||
"mqtt-topic": "Qwerty",
|
||||
"mqtt-user": "Qwerty",
|
||||
"mqtt-pass": "Qwerty"
|
||||
}
|
||||
set_config( url, json )
|
||||
|
||||
@ -521,6 +534,8 @@ present or the API call will fail.
|
||||
Data Formats
|
||||
############
|
||||
|
||||
.. _data-formats-ispindle:
|
||||
|
||||
iSpindle format
|
||||
===============
|
||||
|
||||
@ -547,6 +562,8 @@ This is the format used for standard http posts.
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-brewfather:
|
||||
|
||||
Brewfather format
|
||||
=================
|
||||
|
||||
@ -564,6 +581,8 @@ This is the format for Brewfather
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-influxdb2:
|
||||
|
||||
Influx DB v2
|
||||
============
|
||||
|
||||
|
BIN
src_docs/source/images/brewflasher.png
Normal file
BIN
src_docs/source/images/brewflasher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 156 KiB |
BIN
src_docs/source/images/software_design.png
Normal file
BIN
src_docs/source/images/software_design.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 196 KiB |
@ -7,7 +7,7 @@ Welcome to GravityMon's documentation!
|
||||
######################################
|
||||
|
||||
.. note::
|
||||
This documentation reflects **v0.6**. Last updated 2022-01-13
|
||||
This documentation reflects **v0.6**. Last updated 2022-01-15
|
||||
|
||||
|
||||
GravityMon is a replacement firmare for the iSpindle firmware, it uses the same hardware configuration so
|
||||
@ -23,6 +23,9 @@ The hardware design comes from the fantastic iSpindle project so that is not cov
|
||||
My approach to this software is a little different from that the original ispindle firmware. The github repository can
|
||||
be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
|
||||
|
||||
|
||||
|
||||
|
||||
.. note::
|
||||
This software is in the early stages even though its more than one year old so if you find issues, please
|
||||
open a ticket on github.
|
||||
@ -30,29 +33,36 @@ be found here; `GravityMon on Github <https://github.com/mp-se/gravitymon>`_
|
||||
I dont take responsibility for any errors that can cause problems with the use. I have tested v0.4 on 5+ brews
|
||||
over the last 6 months without any issues.
|
||||
|
||||
|
||||
The main differences:
|
||||
---------------------
|
||||
|
||||
* Operates in two modes ``gravity monitoring`` and ``configuration mode``
|
||||
* Send data to multiple endpoints when pushing data.
|
||||
* Operates in two modes gravity monitoring and configuration mode (simplify calibration)
|
||||
* Modern web based UI for configuration (in config mode)
|
||||
* REST API
|
||||
* Send data to multiple endpoints when pushing data (2xhttp, brewfather, influxdb v2, mqtt supported)
|
||||
* Automatic temperature adjustment of gravity reading
|
||||
* OTA support from local webserver
|
||||
* Build in function to create gravity formulas
|
||||
|
||||
There are also a experimental features such as:
|
||||
|
||||
* Built in function to create gravity formulas, no need for additional software, just enter tilt/gravity.
|
||||
* Visual graph showing how formula will be interpreted
|
||||
* Using the temperature sensor in gyro instead of DS18B20 (faster)
|
||||
* Performance measurements (used to optimise code)
|
||||
* Built in performance measurements (used to optimise code)
|
||||
|
||||
For a complete breakdown see the :ref:`functionallity`
|
||||
|
||||
This is a simple overview of the different components that the software contains. The green ones are only active during `configuration mode` in
|
||||
order to save battery.
|
||||
|
||||
.. image:: images/software_design.png
|
||||
:width: 600
|
||||
:alt: Software design
|
||||
|
||||
|
||||
Credits to
|
||||
----------
|
||||
Ideas to some of these functions have been picked up from disucssions in the iSpindle forums. This software uses
|
||||
the following libraries and without these this would have been much more difficult to acheive:
|
||||
|
||||
* https://github.com/jrowberg/i2cdevlib.git
|
||||
* https://github.com/jrowberg/i2cdevlib
|
||||
|
||||
This library contains the basic code to interact with the gyro + many more chips.
|
||||
|
||||
|
@ -1,36 +1,16 @@
|
||||
Installation
|
||||
------------
|
||||
|
||||
Official esptool
|
||||
================
|
||||
Brewflasher
|
||||
===========
|
||||
|
||||
The prefered option for flashing esp8266 device is via the official esptool. Documentation can be found
|
||||
here; `esptool home page <https://docs.espressif.com/projects/esptool/en/latest/esp32/>`_
|
||||
The prefered option for flashing GravityMon is using BrewFlasher, its a tools that support many brewing related firmwares for ESP8266 and ESP32. This works
|
||||
on both Windows and Mac. You can download the latest version from here: `Brewflasher <https://www.brewflasher.com/>`_
|
||||
|
||||
Windows 10 should install a driver for the USB -> Serial automatically when you connect a esp8266.
|
||||
|
||||
Flashing on windows
|
||||
*******************
|
||||
|
||||
The basic command for flashing on Windows is;
|
||||
|
||||
``esptool.py --port COM4 write_flash 0x0 firmware.bin``
|
||||
|
||||
If there are issues you can try do erase the flash first using this command;
|
||||
|
||||
``esptool.py --port COM4 erase_flash``
|
||||
|
||||
Serial Monitoring
|
||||
*******************
|
||||
|
||||
To check output from the device (logs) there are several tools out there. I found this simple tool in the Windows Store called ``Serial Port Monitoring``.
|
||||
Just select a baud rate of 115200, 8N1.
|
||||
|
||||
.. image:: images/serial.png
|
||||
:width: 800
|
||||
.. image:: images/brewflasher.png
|
||||
:width: 600
|
||||
:alt: Serial output
|
||||
|
||||
|
||||
Binaries
|
||||
********
|
||||
|
||||
@ -44,7 +24,6 @@ In the /bin directory you will find 2 different firmware builds;
|
||||
|
||||
This version also submits performance data to an influx database with detailed execution times.
|
||||
|
||||
|
||||
In these versions all the html files are embedded in the binaries. The file system is currently only used for storing
|
||||
the configuration file.
|
||||
|
||||
@ -52,6 +31,31 @@ If the software becomes so large the html files can be moved to the file system,
|
||||
default (see compiling for details). This approach makes installation much easier and ensure that html files
|
||||
and code is in sync.
|
||||
|
||||
Esptool
|
||||
=======
|
||||
|
||||
The other option for flashing esp8266 device is via the official esptool. Documentation can be found
|
||||
here; `esptool home page <https://docs.espressif.com/projects/esptool/en/latest/esp32/>`_
|
||||
|
||||
Windows 10 should install a driver for the USB -> Serial automatically when you connect a esp8266.
|
||||
|
||||
The basic command for flashing on Windows is;
|
||||
|
||||
``esptool.py --port COM4 write_flash 0x0 firmware.bin``
|
||||
|
||||
If there are issues you can try do erase the flash first using this command;
|
||||
|
||||
``esptool.py --port COM4 erase_flash``
|
||||
|
||||
Serial Monitoring
|
||||
=================
|
||||
|
||||
To check output from the device (logs) there are several tools out there. I found this simple tool in the Windows Store called ``Serial Port Monitoring``.
|
||||
Just select a baud rate of 115200, 8N1.
|
||||
|
||||
.. image:: images/serial.png
|
||||
:width: 800
|
||||
:alt: Serial output
|
||||
|
||||
Configuring WIFI
|
||||
================
|
||||
|
@ -3,10 +3,10 @@
|
||||
Releases
|
||||
########
|
||||
|
||||
v0.6.0 (work in progress)
|
||||
-------------------------
|
||||
v0.6.0
|
||||
------
|
||||
|
||||
This is features for the next release.
|
||||
Latest stable version.
|
||||
|
||||
* Changed the wifi manager and refactored wifi.cpp
|
||||
* LED is now turned on when Wifi Portal is open
|
||||
@ -19,11 +19,11 @@ This is features for the next release.
|
||||
* Added support for MQTT
|
||||
* Bug: MPU init sometimes caused crash during startup.
|
||||
|
||||
`Release v0.6 on Github <https://github.com/mp-se/gravitymon/releases/tag/v0.6.0>`_
|
||||
|
||||
v0.5.0
|
||||
------
|
||||
|
||||
Latest stable version.
|
||||
|
||||
* Added feature to calcuate formula on device
|
||||
* Total rewrite of documentation
|
||||
* WIFI settings are now stored in config file
|
||||
|
1
src_docs/source/sofware_overview.drawio
Normal file
1
src_docs/source/sofware_overview.drawio
Normal file
@ -0,0 +1 @@
|
||||
<mxfile host="app.diagrams.net" modified="2022-01-14T15:32:37.218Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.55" etag="muU0EPGkWBAsdBhYYONY" version="14.7.3" type="device"><diagram id="H8u3kietkkmoQsI5FEME" name="Page-1">7VpRb6M4EP41kfYetgoYCHls0na7uq3U22TV7dPJgSHxLcGRMU3SX38GDAFMknZL6vR0LxEeG2O+b+absUMPjZebLwyvFnfUh7Bn9v1ND131THOILPGbGra5wUDWILfMGfGlbWeYkGeQxr60JsSHuDaQUxpysqobPRpF4PGaDTNG1/VhAQ3rT13hOSiGiYdD1fpAfL7Ira452NlvgcwXxZMNZ5j3LHExWL5JvMA+XVdM6LqHxoxSnl8tN2MIU/AKXPL7bvb0lgtjEPGX3BDebQa3z8nj3xMy/OFux7M/J78+IznNEw4T+cZytXxbQMBoEvmQztLvodF6QThMVthLe9eCdGFb8GUoWoa4xCGZR+I6hECsaiRnB8Zhs3fdRomGcCOgS+BsK4bIG2yJX+FBRXu9o2PgSNuiQoVduBCWLjAvp96hJC4kUK8AzXROC5qP40V2q9ENgoZRh7AMrgqEJVpVCK2TQai63X0SL7LgFj9TzObA4wOgGsdBDUgYjmlIWXYvCoLA9Dxhjzmjv6DS4zszx3a6gdoaNKBu8VbDbIHaOZmzHvdViPzLVCpFywtxHBOvjiRsCP+ZXl/0naL9mHr2hY2QbF9tpKtnjW2lcQ+MiFcBJm3548FXdLeBsVgiTZgHx7xI5aLq1m3CIG0MQszJU30ZbfjLJ9xTIhZYUo3cOtVOM1jy5cu7qsLcmMhxGj6DGhPxLByUiTJ3KF/79z0EKR5yO53eZ+sz+6LHCVNRmjFxNU+vRgzWgchuwNS+r1EQJpurUTpfy613f02nivuJaON1h6uHaEQjaMSzNDV0M41cIlL3pTQvie+nD2lVirpAdxD6TiP0jWFL6LeprHuq0LcUYr9sGX2bqhaQewKVNKT1yKxhn5vMDl4gs0LyJrJJGV/QOY1weL2zNnxyN+YbpSuJ/z/A+VaWyTgR2bLGTofaaufjcu05MM5pJ+rF4vom1F0dIAso2fZntSGTYdHc5cKsVSRDDeRYOskZaiWnwsdjte98yNkjce9DjqFFn/ax0z/CTlmB1qvPg7WnDkb7WilFeil9nRoWlO5ofKwQ3E5pLBjg6lYlM9+QUGMWNLQGc/k6/+fBPQBpDUxTb2B+AHpMnfTYSum+xCT69IdC2gfcKCGrvlFCSPdGST07ncJyBQzzhIHomEAUCyS6xb4DJJtbTv1IGq4C5QTYUwqievKCV6uQeCKaaKRDjDoUleII5XhO3rP7kYR+7l8YhmHUSDXfeDRYDKFBEMNJDuuKt6+Q/gCzjPeW47hPHo0CQubihiX14cSS5tvg+labpLnmDDknOmK3tJ/9FE5U4eT79WQqLJf3X/eR8p/mxLa0c+J8pJrrdzZDXUqq9VJJ1XtmMVTjDLCf5bsb8fvARKi00v4NzyA8Ejh7/z1gEJNnPMvmS4FfpWKfvZs96tlXhyJIfpUgb+6V+aVK0gH33Rtv/QsRv3a9Hnlb5iqSYn3SssZ4h8Sm/lshEluqodXKRdFSlkSxGJT29Wfp5x7ATq2oGNygtXB3PBdmQTeK6rjnluXMlsqDBKRjsDuArlmpWy2fO7wzdKYC3TirARLWXpF/wJ2mgvrQ1o26+p/cO2w1zwJ8/ZtTU92cnkAtzgJs/fqCVH35XsmLPjwRgaiSPCFeuaajFspv/yDjeFF1/JOMgEZcFtqiCumEObORVM22MHFbmDNfz5xo7r7tzIuk3Rey6Ppf</diagram></mxfile>
|
195
test/certs-from-mozilla.py
Normal file
195
test/certs-from-mozilla.py
Normal file
@ -0,0 +1,195 @@
|
||||
# Script to extract root CA.
|
||||
#
|
||||
# Credit to: https://maakbaas.com/esp8266-iot-framework/logs/https-requests/
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
import csv
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
from asn1crypto.x509 import Certificate
|
||||
import hashlib
|
||||
|
||||
from subprocess import Popen, PIPE, call, check_output
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except:
|
||||
from urllib2 import urlopen
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except:
|
||||
from io import StringIO
|
||||
|
||||
import inspect, os.path
|
||||
|
||||
import socket
|
||||
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
|
||||
from ssl import SSLContext # Modern SSL?
|
||||
from ssl import HAS_SNI # Has SNI?
|
||||
|
||||
def preBuildCertificatesFun(domains, openssl):
|
||||
|
||||
print('Start building certificate store', flush=True)
|
||||
|
||||
allDomains = True
|
||||
|
||||
if len(domains) > 0:
|
||||
print('Only for domains: ' + domains, flush=True)
|
||||
domains = domains.split(',')
|
||||
allDomains = False
|
||||
|
||||
if allDomains:
|
||||
domains = []
|
||||
print('For all domains', flush=True)
|
||||
|
||||
filename = inspect.getframeinfo(inspect.currentframe()).filename
|
||||
dir_path = os.path.dirname(os.path.abspath(filename))
|
||||
|
||||
#path to openssl
|
||||
if openssl is None:
|
||||
openssl = "openssl.exe"
|
||||
|
||||
# below script content is adapted from:
|
||||
# https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/BearSSL_CertStore/certs-from-mozilla.py
|
||||
|
||||
f = open(dir_path + "/../src/certificates.h", "w", encoding="utf8")
|
||||
|
||||
f.write("#ifndef CERT_H" + "\n")
|
||||
f.write("#define CERT_H" + "\n\n")
|
||||
f.write("#include <Arduino.h>" + "\n\n")
|
||||
|
||||
# Mozilla's URL for the CSV file with included PEM certs
|
||||
mozurl = "https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReportPEMCSV"
|
||||
|
||||
# Load the manes[] and pems[] array from the URL
|
||||
names = []
|
||||
pems = []
|
||||
dates = []
|
||||
response = urlopen(mozurl)
|
||||
csvData = response.read()
|
||||
if sys.version_info[0] > 2:
|
||||
csvData = csvData.decode('utf-8')
|
||||
csvFile = StringIO(csvData)
|
||||
csvReader = csv.reader(csvFile)
|
||||
for row in csvReader:
|
||||
names.append(row[0]+":"+row[1]+":"+row[2])
|
||||
pems.append(row[32])
|
||||
dates.append(row[8])
|
||||
del names[0] # Remove headers
|
||||
del pems[0] # Remove headers
|
||||
del dates[0] # Remove headers
|
||||
|
||||
certFiles = []
|
||||
totalbytes = 0
|
||||
idx = 0
|
||||
addflag = False
|
||||
|
||||
# Process the text PEM using openssl into DER files
|
||||
sizes=[]
|
||||
for i in range(0, len(pems)):
|
||||
certName = "ca_%03d" % (idx);
|
||||
thisPem = pems[i].replace("'", "")
|
||||
|
||||
pemfile = open(certName + '.pem', "w", encoding="utf8")
|
||||
pemfile.write(thisPem)
|
||||
pemfile.close()
|
||||
|
||||
if allDomains:
|
||||
print('Added: ' + re.sub(f'[^{re.escape(string.printable)}]', '', names[i]), flush=True)
|
||||
else:
|
||||
for j in range(0, len(domains)):
|
||||
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((domains[j], 443))
|
||||
|
||||
context = SSLContext(2)
|
||||
context.verify_mode = 2
|
||||
context.load_verify_locations(certName + '.pem')
|
||||
try:
|
||||
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
|
||||
context.wrap_socket(s, server_hostname=domains[j])
|
||||
else:
|
||||
context.wrap_socket(s)
|
||||
print('Added: ' + re.sub(f'[^{re.escape(string.printable)}]', '', names[i]), flush=True)
|
||||
addFlag = True
|
||||
break
|
||||
except:
|
||||
print('Skipped: ' + re.sub(f'[^{re.escape(string.printable)}]', '', names[i]), flush=True)
|
||||
addFlag = False
|
||||
|
||||
s.close()
|
||||
|
||||
if allDomains or addFlag:
|
||||
|
||||
f.write(("//" + dates[i] + " " + re.sub(f'[^{re.escape(string.printable)}]', '', names[i]) + "\n"))
|
||||
|
||||
ssl = Popen([openssl,'x509','-inform','PEM','-outform','DER','-out', certName + '.der'], shell = False, stdin = PIPE)
|
||||
pipe = ssl.stdin
|
||||
pipe.write(thisPem.encode('utf-8'))
|
||||
pipe.close()
|
||||
ssl.wait()
|
||||
|
||||
if os.path.exists(certName + '.der'):
|
||||
certFiles.append(certName)
|
||||
|
||||
der = open(certName + '.der','rb')
|
||||
|
||||
bytestr = der.read();
|
||||
sizes.append(len(bytestr))
|
||||
cert = Certificate.load(bytestr)
|
||||
idxHash = hashlib.sha256(cert.issuer.dump()).digest()
|
||||
|
||||
f.write("const uint8_t cert_" + str(idx) + "[] PROGMEM = {")
|
||||
for j in range(0, len(bytestr)):
|
||||
totalbytes+=1
|
||||
f.write(hex(bytestr[j]))
|
||||
if j<len(bytestr)-1:
|
||||
f.write(", ")
|
||||
f.write("};\n")
|
||||
|
||||
f.write("const uint8_t idx_" + str(idx) + "[] PROGMEM = {")
|
||||
for j in range(0, len(idxHash)):
|
||||
totalbytes+=1
|
||||
f.write(hex(idxHash[j]))
|
||||
if j<len(idxHash)-1:
|
||||
f.write(", ")
|
||||
f.write("};\n\n")
|
||||
|
||||
der.close()
|
||||
idx = idx + 1
|
||||
|
||||
os.unlink(certName + '.der')
|
||||
|
||||
os.unlink(certName + '.pem')
|
||||
|
||||
f.write("//global variables for certificates using " + str(totalbytes) + " bytes\n")
|
||||
f.write("const uint16_t numberOfCertificates PROGMEM = " + str(idx) + ";\n\n")
|
||||
|
||||
f.write("const uint16_t certSizes[] PROGMEM = {")
|
||||
for i in range(0, idx):
|
||||
f.write(str(sizes[i]))
|
||||
if i<idx-1:
|
||||
f.write(", ")
|
||||
f.write("};\n\n")
|
||||
|
||||
f.write("const uint8_t* const certificates[] PROGMEM = {")
|
||||
for i in range(0, idx):
|
||||
f.write("cert_" + str(i))
|
||||
if i<idx-1:
|
||||
f.write(", ")
|
||||
f.write("};\n\n")
|
||||
|
||||
f.write("const uint8_t* const indices[] PROGMEM = {")
|
||||
for i in range(0, idx):
|
||||
f.write("idx_" + str(i))
|
||||
if i<idx-1:
|
||||
f.write(", ")
|
||||
f.write("};\n\n#endif" + "\n")
|
||||
|
||||
f.close()
|
||||
|
||||
domains = ''
|
||||
openssl = 'openssl'
|
||||
preBuildCertificatesFun(domains, openssl)
|
@ -2,5 +2,6 @@
|
||||
"app-name": "GravityMon ",
|
||||
"app-ver": "0.0.0",
|
||||
"id": "7376ef",
|
||||
"certs": true,
|
||||
"mdns": "gravmon"
|
||||
}
|
@ -3,5 +3,6 @@
|
||||
"device": false,
|
||||
"config": false,
|
||||
"calibration": false,
|
||||
"certs": false,
|
||||
"about": true
|
||||
}
|
Reference in New Issue
Block a user