Merge branch 'bs5' into dev
This commit is contained in:
commit
d8cb4fe622
155
html/about.htm
155
html/about.htm
@ -5,74 +5,117 @@
|
||||
<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>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</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>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
|
||||
<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">
|
||||
<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 active">
|
||||
<a class="nav-link" href="/about.htm">About</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home</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 active">
|
||||
<a class="nav-link" href="#"><b>About</b></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START BODY -->
|
||||
|
||||
<div class="container">
|
||||
<hr class="my-4">
|
||||
<div class="row mb-3">
|
||||
<h3>Beer Gravity Monitor</h3>
|
||||
This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<h3>MIT License</h3>
|
||||
<div class="container row-margin-10">
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
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.
|
||||
<div class="accordion row-margin-10" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingAbout">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout">
|
||||
<b>About</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row h3 col-sm-8">
|
||||
Beer Gravity Monitor
|
||||
</div>
|
||||
<div class="row col-sm-8 mb-3">
|
||||
This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.
|
||||
</div>
|
||||
<div class="row h3 col-sm-8 mb-3">
|
||||
MIT License
|
||||
</div>
|
||||
<div class="row col-sm-8 mb-3">
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
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.
|
||||
</div>
|
||||
|
||||
<div class="row h3 col-sm-8 mb-3">
|
||||
Credits to
|
||||
</div>
|
||||
<div class="row col-sm-8 mb-3">
|
||||
This software uses
|
||||
the following libraries and without these this software would have been much more difficult to acheive:<br><br>
|
||||
<ul>
|
||||
<li>https://github.com/jrowberg/i2cdevlib</li>
|
||||
<li>https://github.com/codeplea/tinyexpr</li>
|
||||
<li>https://github.com/graphitemaster/incbin</li>
|
||||
<li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li>
|
||||
<li>https://github.com/khoih-prog/ESP_WiFiManager</li>
|
||||
<li>https://github.com/thijse/Arduino-Log</li>
|
||||
<li>https://github.com/bblanchon/ArduinoJson</li>
|
||||
<li>https://github.com/PaulStoffregen/OneWire</li>
|
||||
<li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li>
|
||||
<li>https://github.com/Rotario</li>
|
||||
<li>https://github.com/256dpi/arduino-mqtt</li>
|
||||
<li>https://graphjs.com</li>
|
||||
<li>https://getbootstrap.com</li>
|
||||
<li>https://github.com/lorol/LITTLEFS</li>
|
||||
<li>https://github.com/h2zero/NimBLE-Arduino</li>
|
||||
<li>https://github.com/spouliot/tilt-sim</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
@ -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"><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 active"><a class="nav-link" href="/about.htm">About</a></li></ul></div></nav><!-- START MAIN INDEX --><div class="container"><hr class="my-4"><div class="row mb-3"><h3>Beer Gravity Monitor</h3>This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row mb-3"><h3>MIT License</h3>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.</div><hr class="my-4"></div><!-- 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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><!-- START MENU --><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/index.htm">Home</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 active"><a class="nav-link" href="#"><b>About</b></a></li></ul></div></div></nav><!-- START BODY --><div class="container row-margin-10"><div class="accordion row-margin-10" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingAbout"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAbout" aria-expanded="true" aria-controls="collapseAbout"><b>About</b></button></h2><div id="collapseAbout" class="accordion-collapse collapse show" aria-labelledby="headingAbout" data-bs-parent="#accordion"><div class="accordion-body"><div class="row h3 col-sm-8">Beer Gravity Monitor</div><div class="row col-sm-8 mb-3">This is a piece of software for the iSpindle hardware and will work in a similar way. No part of this software is copied from the iSpindle project.</div><div class="row h3 col-sm-8 mb-3">MIT License</div><div class="row col-sm-8 mb-3">Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.</div><div class="row h3 col-sm-8 mb-3">Credits to</div><div class="row col-sm-8 mb-3">This software uses the following libraries and without these this software would have been much more difficult to acheive:<br><br><ul><li>https://github.com/jrowberg/i2cdevlib</li><li>https://github.com/codeplea/tinyexpr</li><li>https://github.com/graphitemaster/incbin</li><li>https://github.com/khoih-prog/ESP_DoubleResetDetector</li><li>https://github.com/khoih-prog/ESP_WiFiManager</li><li>https://github.com/thijse/Arduino-Log</li><li>https://github.com/bblanchon/ArduinoJson</li><li>https://github.com/PaulStoffregen/OneWire</li><li>https://github.com/milesburton/Arduino-Temperature-Control-Library</li><li>https://github.com/Rotario</li><li>https://github.com/256dpi/arduino-mqtt</li><li>https://graphjs.com</li><li>https://getbootstrap.com</li><li>https://github.com/lorol/LITTLEFS</li><li>https://github.com/h2zero/NimBLE-Arduino</li><li>https://github.com/spouliot/tilt-sim</li></ul></div></div></div></div></div></div><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
@ -5,379 +5,422 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<!-- START MENU -->
|
||||
|
||||
<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">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<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>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/index.htm">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config.htm">Configuration</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="#"><b>Calibration</b></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>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START BODY -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<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>
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingOne">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseCalibration" aria-expanded="true" aria-controls="collapseCalibration">
|
||||
Formula calculation
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingFormula">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFormula" aria-expanded="true" aria-controls="collapseFormula">
|
||||
<b>Formula calculation</b>
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseFormula" class="accordion-collapse collapse show" aria-labelledby="headingFormula" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<form action="/api/formula" method="post">
|
||||
<input type="text" name="gravity-format" id="gravity-format" hidden>
|
||||
<input type="text" name="id" id="id" hidden>
|
||||
|
||||
<div id="collapseCalibration" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<form action="/api/formula" method="post">
|
||||
<input type="text" name="gravity-format" id="gravity-format" hidden>
|
||||
<input type="text" name="id" id="id" hidden>
|
||||
<div class="row mb-3">
|
||||
Here you can create your gravity formula by entering angles/tilt and the corresponding gravity. These values
|
||||
will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the
|
||||
formula and if the deviation is more than 1.5SG / 0.38P on any of the provided points then the forumla will be
|
||||
rejected. On the bottom of the page you can see a graph over the entered values + values calcualated by the formula.
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
Here you can create your gravity formula by entering angles/tilt and the corresponding gravity. These values
|
||||
will be saved for future use. Angles with 0 (zero) will be skipped. The values below will be used to check the
|
||||
formula and if the deviation is more than 1.5SG / 0.38P on any of the provided points then the forumla will be
|
||||
rejected. On the bottom of the page you can see a graph over the entered values + values calcualated by the formula.
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-sm-1 col-form-label">#</label>
|
||||
<label class="col-sm-4 col-form-label">Angle/Tilt</label>
|
||||
<label class="col-sm-4 col-form-label" id="gravity-header">Gravity</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">#:</label>
|
||||
<label class="col-sm-4 col-form-label">Angle/Tilt:</label>
|
||||
<label class="col-sm-4 col-form-label" id="gravity-header">Gravity (SG):</label>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<label for="angle1" class="col-sm-1 col-form-label">1.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a1" id="a1" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g1" id="g1" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle1" class="col-sm-2 col-form-label">1.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a1" id="a1">
|
||||
<div class="row mb-3">
|
||||
<label for="angle2" class="col-sm-1 col-form-label">2.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a2" id="a2" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g2" id="g2" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g1" id="g1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle2" class="col-sm-2 col-form-label">2.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a2" id="a2">
|
||||
<div class="row mb-3">
|
||||
<label for="angle3" class="col-sm-1 col-form-label">3.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a3" id="a3" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g3" id="g3" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g2" id="g2">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle3" class="col-sm-2 col-form-label">3.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a3" id="a3">
|
||||
<div class="row mb-3">
|
||||
<label for="angle4" class="col-sm-1 col-form-label">4.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a4" id="a4" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g4" id="g4" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g3" id="g3">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle4" class="col-sm-2 col-form-label">4.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a4" id="a4">
|
||||
<div class="row mb-3">
|
||||
<label for="angle5" class="col-sm-1 col-form-label">5.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a5" id="a5" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g5" id="g5" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g4" id="g4">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="angle5" class="col-sm-2 col-form-label">5.</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="90" step="0.001" class="form-control" name="a5" id="a5">
|
||||
<div class="row mb-3">
|
||||
<label for="angle6" class="col-sm-1 col-form-label">6.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a6" id="a6" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g6" id="g6" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="number" min="0" max="26" step="0.0001" class="form-control" name="g5" id="g5">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-0">
|
||||
<button type="submit" class="btn btn-primary" id="calculate-btn">Save & Calculate</button>
|
||||
<div class="row mb-3">
|
||||
<label for="angle7" class="col-sm-1 col-form-label">7.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a7" id="a7" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g7" id="g7" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label">Current angle: </label>
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label" id="angle"></label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="row mb-3">
|
||||
<label for="angle8" class="col-sm-1 col-form-label">8.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a8" id="a8" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g8" id="g8" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="angle9" class="col-sm-1 col-form-label">9.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a9" id="a9" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g9" id="g9" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label for="angle10" class="col-sm-1 col-form-label">10.</label>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="90" step="0.001" class="form-control" name="a10" id="a10" data-bs-toggle="tooltip" title="Enter the angle for the gravity. Zero value will be ignored"></div>
|
||||
<div class="col-sm-4"><input type="number" min="0" max="26" step="0.0001" class="form-control" name="g10" id="g10" data-bs-toggle="tooltip" title="Enter the gravity for the angle."></div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-8 offset-sm-1"><button type="submit" class="btn btn-primary" id="calculate-btn" data-bs-toggle="tooltip" title="Save the values and try to create a formula">Save & Create</button></div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<div class="row">
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label">Current angle: </label>
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label" id="angle"></label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="calculate-btn" class="col-sm-2 col-form-label">Formula: </label>
|
||||
<label for="calculate-btn" class="col-sm-8 col-form-label" id="formula">Loading...</label>
|
||||
</div>
|
||||
</form>
|
||||
<label for="calculate-btn" class="col-sm-8 col-form-label" id="formula">Loading...</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingGraph">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseGraph" aria-expanded="false" aria-controls="collapseGraph">
|
||||
<b>Formula graph</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseGraph" class="accordion-collapse collapse" aria-labelledby="headingGraph" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<canvas id="gravityChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
<div>
|
||||
<canvas id="gravityChart"></canvas>
|
||||
</div>
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var chartDataForm = [];
|
||||
var chartDataCalc = [];
|
||||
<script type="text/javascript">
|
||||
var chartDataForm = [];
|
||||
var chartDataCalc = [];
|
||||
|
||||
const dataSetChart = {
|
||||
datasets: [{
|
||||
label: 'Raw data',
|
||||
borderColor: 'blue',
|
||||
backgroundColor: 'blue',
|
||||
data: chartDataForm
|
||||
}, {
|
||||
label: 'Calculated',
|
||||
borderColor: 'green',
|
||||
backgroundColor: 'green',
|
||||
data: chartDataCalc
|
||||
}]
|
||||
}
|
||||
const dataSetChart = {
|
||||
datasets: [{
|
||||
label: 'Raw data',
|
||||
borderColor: 'blue',
|
||||
backgroundColor: 'blue',
|
||||
data: chartDataForm
|
||||
}, {
|
||||
label: 'Calculated',
|
||||
borderColor: 'green',
|
||||
backgroundColor: 'green',
|
||||
data: chartDataCalc
|
||||
}]
|
||||
}
|
||||
|
||||
const configChart = {
|
||||
type: 'line',
|
||||
data: dataSetChart,
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
type: 'linear',
|
||||
grace: '5%',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Angle/Tilt'
|
||||
},
|
||||
ticks: {
|
||||
crossAlign: 'far'
|
||||
},
|
||||
suggestedMin: 25
|
||||
const configChart = {
|
||||
type: 'line',
|
||||
data: dataSetChart,
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
intersect: false,
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
text: 'Gravity'
|
||||
type: 'linear',
|
||||
grace: '5%',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Angle/Tilt'
|
||||
},
|
||||
ticks: {
|
||||
crossAlign: 'far'
|
||||
},
|
||||
suggestedMin: 25
|
||||
},
|
||||
suggestedMin: 1.000
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Gravity'
|
||||
},
|
||||
suggestedMin: 1.000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var myChart = 0;
|
||||
</script>
|
||||
var myChart = 0;
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
g1.onchange = setGravityDecimal
|
||||
g2.onchange = setGravityDecimal
|
||||
g3.onchange = setGravityDecimal
|
||||
g4.onchange = setGravityDecimal
|
||||
g5.onchange = setGravityDecimal
|
||||
<script type="text/javascript">
|
||||
g1.onchange = setGravityDecimal
|
||||
g2.onchange = setGravityDecimal
|
||||
g3.onchange = setGravityDecimal
|
||||
g4.onchange = setGravityDecimal
|
||||
g5.onchange = setGravityDecimal
|
||||
g6.onchange = setGravityDecimal
|
||||
g7.onchange = setGravityDecimal
|
||||
g8.onchange = setGravityDecimal
|
||||
g9.onchange = setGravityDecimal
|
||||
g10.onchange = setGravityDecimal
|
||||
|
||||
a1.onchange = setAngleDecimal
|
||||
a2.onchange = setAngleDecimal
|
||||
a3.onchange = setAngleDecimal
|
||||
a4.onchange = setAngleDecimal
|
||||
a5.onchange = setAngleDecimal
|
||||
a1.onchange = setAngleDecimal
|
||||
a2.onchange = setAngleDecimal
|
||||
a3.onchange = setAngleDecimal
|
||||
a4.onchange = setAngleDecimal
|
||||
a5.onchange = setAngleDecimal
|
||||
a6.onchange = setAngleDecimal
|
||||
a7.onchange = setAngleDecimal
|
||||
a8.onchange = setAngleDecimal
|
||||
a9.onchange = setAngleDecimal
|
||||
a10.onchange = setAngleDecimal
|
||||
|
||||
window.onload = getConfig;
|
||||
setButtonDisabled( true );
|
||||
|
||||
function convertToPlato(sg) {
|
||||
return 259-(259/sg);
|
||||
}
|
||||
|
||||
function convertToSG(plato) {
|
||||
return 259/(259-plato);
|
||||
}
|
||||
|
||||
function setAngleDecimal(event) {
|
||||
this.value = parseFloat(this.value).toFixed(2);
|
||||
populateChart();
|
||||
}
|
||||
|
||||
function setGravityDecimal(event) {
|
||||
if(isPlato())
|
||||
this.value = parseFloat(this.value).toFixed(1);
|
||||
else
|
||||
this.value = parseFloat(this.value).toFixed(4);
|
||||
|
||||
populateChart();
|
||||
}
|
||||
|
||||
function populateChartForm(a, g) {
|
||||
if( a != 0)
|
||||
chartDataForm.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
|
||||
chartDataForm.sort(function (a, b) {
|
||||
return a.x - b.x;
|
||||
});
|
||||
}
|
||||
|
||||
function populateChartCalc(a, g) {
|
||||
chartDataCalc.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
}
|
||||
|
||||
function isPlato() {
|
||||
return $("#gravity-format").text() == "P";
|
||||
}
|
||||
|
||||
function populateChart() {
|
||||
|
||||
chartDataCalc.length = 0
|
||||
|
||||
for( i = 25.0; i<80.0; i+=5.0) {
|
||||
var formula = $("#formula").text();
|
||||
var angle = i.toString();
|
||||
formula=formula.replaceAll( "tilt^3", angle+"*"+angle+"*"+angle );
|
||||
formula=formula.replaceAll( "tilt^2", angle+"*"+angle );
|
||||
formula=formula.replaceAll( "tilt", angle );
|
||||
var g = eval( formula );
|
||||
|
||||
if(isPlato())
|
||||
g = convertToPlato(g);
|
||||
|
||||
populateChartCalc( i, g );
|
||||
}
|
||||
|
||||
chartDataForm.length = 0
|
||||
populateChartForm( $("#a1").val(), $("#g1").val() );
|
||||
populateChartForm( $("#a2").val(), $("#g2").val() );
|
||||
populateChartForm( $("#a3").val(), $("#g3").val() );
|
||||
populateChartForm( $("#a4").val(), $("#g4").val() );
|
||||
populateChartForm( $("#a5").val(), $("#g5").val() );
|
||||
|
||||
if( myChart )
|
||||
myChart.destroy();
|
||||
|
||||
myChart = new Chart(
|
||||
document.getElementById('gravityChart'),
|
||||
configChart
|
||||
);
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#calculate-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
window.onload = getConfig;
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/formula";
|
||||
//var url = "/test/formula.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#id").val(cfg["id"]);
|
||||
$("#angle").text(cfg["angle"]);
|
||||
$("#formula").text(cfg["gravity-formula"]);
|
||||
$("#gravity-format").text(cfg["gravity-format"]); // Sets the variable used by isPlato()
|
||||
function convertToPlato(sg) {
|
||||
return 259-(259/sg);
|
||||
}
|
||||
|
||||
if(isPlato()) {
|
||||
$("#gravity-header").text("Gravity (Plato):");
|
||||
$("#g1").val( parseFloat(cfg["g1"]).toFixed(1) );
|
||||
$("#g2").val( parseFloat(cfg["g2"]).toFixed(1) );
|
||||
$("#g3").val( parseFloat(cfg["g3"]).toFixed(1) );
|
||||
$("#g4").val( parseFloat(cfg["g4"]).toFixed(1) );
|
||||
$("#g5").val( parseFloat(cfg["g5"]).toFixed(1) );
|
||||
} else {
|
||||
$("#gravity-header").text("Gravity (SG):");
|
||||
$("#g1").val( parseFloat(cfg["g1"]).toFixed(4) );
|
||||
$("#g2").val( parseFloat(cfg["g2"]).toFixed(4) );
|
||||
$("#g3").val( parseFloat(cfg["g3"]).toFixed(4) );
|
||||
$("#g4").val( parseFloat(cfg["g4"]).toFixed(4) );
|
||||
$("#g5").val( parseFloat(cfg["g5"]).toFixed(4) );
|
||||
}
|
||||
function convertToSG(plato) {
|
||||
return 259/(259-plato);
|
||||
}
|
||||
|
||||
$("#a1").val( parseFloat(cfg["a1"]).toFixed(2) );
|
||||
$("#a2").val( parseFloat(cfg["a2"]).toFixed(2) );
|
||||
$("#a3").val( parseFloat(cfg["a3"]).toFixed(2) );
|
||||
$("#a4").val( parseFloat(cfg["a4"]).toFixed(2) );
|
||||
$("#a5").val( parseFloat(cfg["a5"]).toFixed(2) );
|
||||
function setAngleDecimal(event) {
|
||||
this.value = parseFloat(this.value).toFixed(2);
|
||||
populateChart();
|
||||
}
|
||||
|
||||
if( cfg["error"]!="" ) {
|
||||
showError(cfg["error"]);
|
||||
}
|
||||
function setGravityDecimal(event) {
|
||||
if(isPlato())
|
||||
this.value = parseFloat(this.value).toFixed(1);
|
||||
else
|
||||
this.value = parseFloat(this.value).toFixed(4);
|
||||
|
||||
populateChart();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
<!-- START FOOTER -->
|
||||
function populateChartForm(a, g) {
|
||||
if( a != 0)
|
||||
chartDataForm.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
chartDataForm.sort(function (a, b) {
|
||||
return a.x - b.x;
|
||||
});
|
||||
}
|
||||
|
||||
function populateChartCalc(a, g) {
|
||||
chartDataCalc.push( { x: parseFloat(a), y: parseFloat(g) });
|
||||
}
|
||||
|
||||
function isPlato() {
|
||||
return $("#gravity-format").text() == "P";
|
||||
}
|
||||
|
||||
function populateChart() {
|
||||
|
||||
chartDataCalc.length = 0
|
||||
|
||||
for( i = 25.0; i<80.0; i+=5.0) {
|
||||
var formula = $("#formula").text();
|
||||
var angle = i.toString();
|
||||
formula=formula.replaceAll( "tilt^3", angle+"*"+angle+"*"+angle );
|
||||
formula=formula.replaceAll( "tilt^2", angle+"*"+angle );
|
||||
formula=formula.replaceAll( "tilt", angle );
|
||||
var g = eval( formula );
|
||||
|
||||
if(isPlato())
|
||||
g = convertToPlato(g);
|
||||
|
||||
populateChartCalc( i, g );
|
||||
}
|
||||
|
||||
chartDataForm.length = 0
|
||||
populateChartForm( $("#a1").val(), $("#g1").val() );
|
||||
populateChartForm( $("#a2").val(), $("#g2").val() );
|
||||
populateChartForm( $("#a3").val(), $("#g3").val() );
|
||||
populateChartForm( $("#a4").val(), $("#g4").val() );
|
||||
populateChartForm( $("#a5").val(), $("#g5").val() );
|
||||
populateChartForm( $("#a6").val(), $("#g6").val() );
|
||||
populateChartForm( $("#a7").val(), $("#g7").val() );
|
||||
populateChartForm( $("#a8").val(), $("#g8").val() );
|
||||
populateChartForm( $("#a9").val(), $("#g9").val() );
|
||||
populateChartForm( $("#a10").val(), $("#g10").val() );
|
||||
|
||||
if( myChart )
|
||||
myChart.destroy();
|
||||
|
||||
myChart = new Chart(
|
||||
document.getElementById('gravityChart'),
|
||||
configChart
|
||||
);
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#calculate-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/formula";
|
||||
//var url = "/test/formula.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#id").val(cfg["id"]);
|
||||
$("#angle").text(cfg["angle"]);
|
||||
$("#formula").text(cfg["gravity-formula"]);
|
||||
$("#gravity-format").text(cfg["gravity-format"]); // Sets the variable used by isPlato()
|
||||
|
||||
if(isPlato()) {
|
||||
$("#gravity-header").text("Gravity (Plato):");
|
||||
$("#g1").val( parseFloat(cfg["g1"]).toFixed(1) );
|
||||
$("#g2").val( parseFloat(cfg["g2"]).toFixed(1) );
|
||||
$("#g3").val( parseFloat(cfg["g3"]).toFixed(1) );
|
||||
$("#g4").val( parseFloat(cfg["g4"]).toFixed(1) );
|
||||
$("#g5").val( parseFloat(cfg["g5"]).toFixed(1) );
|
||||
$("#g6").val( parseFloat(cfg["g6"]).toFixed(1) );
|
||||
$("#g7").val( parseFloat(cfg["g7"]).toFixed(1) );
|
||||
$("#g8").val( parseFloat(cfg["g8"]).toFixed(1) );
|
||||
$("#g9").val( parseFloat(cfg["g9"]).toFixed(1) );
|
||||
$("#g10").val( parseFloat(cfg["g10"]).toFixed(1) );
|
||||
} else {
|
||||
$("#gravity-header").text("Gravity (SG):");
|
||||
$("#g1").val( parseFloat(cfg["g1"]).toFixed(4) );
|
||||
$("#g2").val( parseFloat(cfg["g2"]).toFixed(4) );
|
||||
$("#g3").val( parseFloat(cfg["g3"]).toFixed(4) );
|
||||
$("#g4").val( parseFloat(cfg["g4"]).toFixed(4) );
|
||||
$("#g5").val( parseFloat(cfg["g5"]).toFixed(4) );
|
||||
$("#g6").val( parseFloat(cfg["g6"]).toFixed(4) );
|
||||
$("#g7").val( parseFloat(cfg["g7"]).toFixed(4) );
|
||||
$("#g8").val( parseFloat(cfg["g8"]).toFixed(4) );
|
||||
$("#g9").val( parseFloat(cfg["g9"]).toFixed(4) );
|
||||
$("#g10").val( parseFloat(cfg["g10"]).toFixed(4) );
|
||||
}
|
||||
|
||||
$("#a1").val( parseFloat(cfg["a1"]).toFixed(2) );
|
||||
$("#a2").val( parseFloat(cfg["a2"]).toFixed(2) );
|
||||
$("#a3").val( parseFloat(cfg["a3"]).toFixed(2) );
|
||||
$("#a4").val( parseFloat(cfg["a4"]).toFixed(2) );
|
||||
$("#a5").val( parseFloat(cfg["a5"]).toFixed(2) );
|
||||
$("#a6").val( parseFloat(cfg["a6"]).toFixed(2) );
|
||||
$("#a7").val( parseFloat(cfg["a7"]).toFixed(2) );
|
||||
$("#a8").val( parseFloat(cfg["a8"]).toFixed(2) );
|
||||
$("#a9").val( parseFloat(cfg["a9"]).toFixed(2) );
|
||||
$("#a10").val( parseFloat(cfg["a10"]).toFixed(2) );
|
||||
|
||||
if( cfg["error"]!="" ) {
|
||||
showError(cfg["error"]);
|
||||
}
|
||||
|
||||
populateChart();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
1093
html/config.htm
1093
html/config.htm
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -5,184 +5,191 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<a class="navbar-brand" href="/firmware.htm">Beer Gravity Monitor - Firmware upgrade</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>
|
||||
<!-- START MENU -->
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<hr class="my-4">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
</script>
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingFirmware">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFirmware" aria-expanded="true" aria-controls="collapseFirmware">
|
||||
<b>Upload firmware</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseFirmware" class="accordion-collapse collapse show" aria-labelledby="headingFirmware" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">Current version:</div>
|
||||
<div class="col-md-10 themed-grid-col bg-light" id="app-ver">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">Platform:</div>
|
||||
<div class="col-md-10 themed-grid-col bg-light" id="platform">Loading...</div>
|
||||
</div>
|
||||
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()" data-bs-toggle="tooltip" title="Select a firmware file to upload">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Update the device with the selected firmware">Flash firmware</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Platform:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</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" accept=".bin" 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">Flash firmware</button>
|
||||
</form>
|
||||
-->
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()">
|
||||
<label class="custom-file-label" for="name">Choose file</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
<script type="text/javascript">
|
||||
window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
function getStatus() {
|
||||
|
||||
setButtonDisabled( true );
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
</script>
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
@ -1,87 +1,79 @@
|
||||
<!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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" 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="/firmware.htm">Beer Gravity Monitor - Firmware upgrade</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="javascript:history.back()">Back to configuration</a></li></ul><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-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.</div></div><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"><div class="col-md-8 themed-grid-col bg-light">Platform:</div><div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</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" accept=".bin" 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">Flash firmware</button>
|
||||
</form>
|
||||
--><form id="uploadForm" enctype="multipart/form-data"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()"> <label class="custom-file-label" for="name">Choose file</label></div><button type="submit" class="btn btn-primary" id="upload-btn" value="upload">Flash firmware</button></form></div><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div><hr class="my-4"></div><script type="text/javascript">window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
<!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://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"><style>.row-margin-10{margin-top:1em}</style></head><body class="py-4"><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script><script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script><!-- START MENU --><nav class="navbar navbar-expand-lg navbar-dark bg-primary"><div class="container"><a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav mr-auto"><li class="nav-item"><a class="nav-link" href="javascript:history.back()">Back to configuration</a></li></ul></div><div class="spinner-border text-light" id="spinner" role="status"></div></div></nav><!-- START MAIN INDEX --><div class="container row-margin-10"><div class="alert alert-success alert-dismissible hide fade d-none" role="alert"><div id="alert"></div><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div><script type="text/javascript">function showError(s){$(".alert").removeClass("alert-success").addClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}function showSuccess(s){$(".alert").addClass("alert-success").removeClass("alert-danger").removeClass("hide").addClass("show").removeClass("d-none"),$("#alert").text(s)}$("#alert-btn").click(function(s){$(".alert").addClass("hide").removeClass("show").addClass("d-none")})</script><div class="accordion" id="accordion"><div class="accordion-item"><h2 class="accordion-header" id="headingFirmware"><button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFirmware" aria-expanded="true" aria-controls="collapseFirmware"><b>Upload firmware</b></button></h2><div id="collapseFirmware" class="accordion-collapse collapse show" aria-labelledby="headingFirmware" data-bs-parent="#accordion"><div class="accordion-body"><div class="row mb-3"><div class="col-md-12 themed-grid-col bg-light">Here you can upload a new firmware version, it will not check the version number so you can also downgrade the firmware here.</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Current version:</div><div class="col-md-10 themed-grid-col bg-light" id="app-ver">Loading...</div></div><div class="row mb-3"><div class="col-md-2 themed-grid-col bg-light">Platform:</div><div class="col-md-10 themed-grid-col bg-light" id="platform">Loading...</div></div><form id="uploadForm" enctype="multipart/form-data"><div class="row mb-3"><div class="col-md-8 custom-file"><input type="file" accept=".bin" class="custom-file-input" name="name" id="name" onchange="checkName()" data-bs-toggle="tooltip" title="Select a firmware file to upload"></div></div><div class="row mb-3"><div class="col-md-4"><button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Update the device with the selected firmware">Flash firmware</button></div></div></form><div class="progress"><div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div></div></div></div></div></div><script type="text/javascript">window.onload = getStatus;
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#uploadForm").on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax( {
|
||||
xhr: function() {
|
||||
var xhr = new window.XMLHttpRequest();
|
||||
xhr.upload.addEventListener("progress", function(evt) {
|
||||
if (evt.lengthComputable) {
|
||||
progressHandler(evt);
|
||||
}
|
||||
}, false);
|
||||
return xhr;
|
||||
},
|
||||
type: 'POST',
|
||||
url: '/api/upload',
|
||||
data: new FormData(this),
|
||||
contentType: false,
|
||||
cache: false,
|
||||
processData:false,
|
||||
beforeSend: function() {
|
||||
setProgress(0);
|
||||
},
|
||||
error:function() {
|
||||
showError("Upload failed");
|
||||
},
|
||||
success: function(resp) {
|
||||
showSuccess("Upload completed, device is restarting. Waiting 10 seconds to refresh browser.");
|
||||
setTimeout(() => {
|
||||
window.location = "/";
|
||||
}, 10000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
function checkName() {
|
||||
setButtonDisabled( $("#name").val()!="" ? false : true );
|
||||
}
|
||||
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
function progressHandler(event) {
|
||||
var percent = (event.loaded / event.total) * 100;
|
||||
setProgress(Math.round(percent));
|
||||
}
|
||||
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
function setProgress(val) {
|
||||
$('.progress-bar').css('width', val+'%').attr('aria-valuenow', val).text(val + "%");
|
||||
}
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
function setButtonDisabled( b ) {
|
||||
$("#upload-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
function getStatus() {
|
||||
|
||||
setButtonDisabled( true );
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
$("#app-ver").text(cfg["app-ver"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}</script><!-- START FOOTER --><div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div></body></html>
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}</script><!-- START FOOTER --><div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div></div></body></html>
|
460
html/format.htm
460
html/format.htm
@ -5,264 +5,302 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<!-- START MENU -->
|
||||
|
||||
<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="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<hr class="my-2">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('#alert').addClass('d-none').removeClass('show');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="headingFormat">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#collapseFormat" aria-expanded="true" aria-controls="collapseFormat">
|
||||
Push Format Templates
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingFormat">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFormat" aria-expanded="true" aria-controls="collapseFormat">
|
||||
<b>Push Format Templates</b>
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="collapseFormat" class="collapse show" aria-labelledby="headingFormat" data-parent="#accordion">
|
||||
<div class="card-body">
|
||||
<div id="collapseFormat" class="accordion-collapse collapse show" aria-labelledby="headingFormat" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<input type="text" name="id" id="id" hidden>
|
||||
<input type="text" name="http-1" id="http-1" hidden>
|
||||
<input type="text" name="http-2" id="http-2" hidden>
|
||||
<input type="text" name="http-3" id="http-3" hidden>
|
||||
<!--<input type="text" name="brewfather" id="brewfather" hidden>-->
|
||||
<input type="text" name="influxdb" id="influxdb" hidden>
|
||||
<input type="text" name="mqtt" id="mqtt" hidden>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="row mb-3">
|
||||
<label for="push-target" class="col-sm-2 col-form-label">Push target:</label>
|
||||
<select class="custom-select col-sm-4" required name="push-target" id="push-target">
|
||||
<select class="custom-select col-sm-4" required name="push-target" id="push-target" data-bs-toggle="tooltip" title="Select the push target to edit format template for">
|
||||
<option value="http-1">HTTP option 1 (post)</option>
|
||||
<option value="http-2">HTTP option 2 (post)</option>
|
||||
<option value="http-3">HTTP option 3 (get)</option>
|
||||
<!--<option value="brewfather">Brewfather</option>-->
|
||||
<option value="influxdb">Influx DB</option>
|
||||
<option value="mqtt">MQTT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-12">
|
||||
<textarea rows="5" class="form-control" name="format" id="format">
|
||||
<textarea rows="10" class="form-control" name="format" id="format">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8 offset-sm-2">
|
||||
<button class="btn btn-primary" id="format-btn">Save</button>
|
||||
<button class="btn btn-secondary" id="test-btn">Test</button>
|
||||
<script>
|
||||
let formatTemplates = [
|
||||
{ "id": "GravityMon-Post", "format": "%7B%0A%20%22name%22%20%3A%20%22%24%7Bmdns%7D%22%2C%0A%20%22ID%22%3A%20%22%24%7Bid%7D%22%2C%0A%20%22token%22%20%3A%20%22gravmon%22%2C%0A%20%22interval%22%3A%20%24%7Bsleep-interval%7D%2C%0A%20%22temperature%22%3A%20%24%7Btemp%7D%2C%0A%20%22temp_units%22%3A%20%22%24%7Btemp-unit%7D%22%2C%0A%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%0A%20%22angle%22%3A%20%24%7Bangle%7D%2C%0A%20%22battery%22%3A%20%24%7Bbattery%7D%2C%0A%20%22RSSI%22%3A%20%24%7Brssi%7D%2C%0A%20%22corr-gravity%22%3A%20%24%7Bcorr-gravity%7D%2C%0A%20%22gravity-unit%22%3A%20%22%24%7Bgravity-unit%7D%22%2C%0A%20%22run-time%22%3A%20%24%7Brun-time%7D%0A%7D" },
|
||||
{ "id": "GravityMon-Get", "format": "%3Fname%3D%24%7Bmdns%7D%26id%3D%24%7Bid%7D%26token%3D%24%7Btoken2%7D%26interval%3D%24%7Bsleep-interval%7D%26temperature%3D%24%7Btemp%7D%26%0Atemp-units%3D%24%7Btemp-unit%7D%26gravity%3D%24%7Bgravity%7D%26angle%3D%24%7Bangle%7D%26battery%3D%24%7Bbattery%7D%26rssi%3D%24%7Brssi%7D%26%0Acorr-gravity%3D%24%7Bcorr-gravity%7D%26gravity-unit%3D%24%7Bgravity-unit%7D%26run-time%3D%24%7Brun-time%7D" },
|
||||
{ "id": "iSpindle-Post", "format": "%7B%0A%20%22name%22%20%3A%20%22%24%7Bmdns%7D%22%2C%0A%20%22ID%22%3A%20%22%24%7Bid%7D%22%2C%0A%20%22token%22%20%3A%20%22gravmon%22%2C%0A%20%22interval%22%3A%20%24%7Bsleep-interval%7D%2C%0A%20%22temperature%22%3A%20%24%7Btemp%7D%2C%0A%20%22temp_units%22%3A%20%22%24%7Btemp-unit%7D%22%2C%0A%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%0A%20%22angle%22%3A%20%24%7Bangle%7D%2C%0A%20%22battery%22%3A%20%24%7Bbattery%7D%2C%0A%20%22RSSI%22%3A%20%24%7Brssi%7D%0A%7D" },
|
||||
{ "id": "BrewFatherCustom-Post", "format": "%7B%0A%20%20%20%22name%22%3A%20%22%24%7Bmdns%7D%22%2C%0A%20%20%20%22temp%22%3A%20%24%7Btemp%7D%2C%0A%20%20%20%22aux_temp%22%3A%200%2C%0A%20%20%20%22ext_temp%22%3A%200%2C%0A%20%20%20%22temp_unit%22%3A%20%22%24%7Btemp-unit%7D%22%2C%0A%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%0A%20%20%20%22gravity_unit%22%3A%20%22%24%7Bgravity-unit%7D%22%2C%0A%20%20%20%22pressure%22%3A%200%2C%0A%20%20%20%22pressure_unit%22%3A%20%22PSI%22%2C%0A%20%20%20%22ph%22%3A%200%2C%0A%20%20%20%22bpm%22%3A%200%2C%0A%20%20%20%22comment%22%3A%20%22%22%2C%0A%20%20%20%22beer%22%3A%20%22%22%2C%0A%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%0A%7D" },
|
||||
{ "id": "iSpindle-Mqtt", "format": "ispindel%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Bangle%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Ftemp_units%3A%24%7Btemp-unit%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fbattery%3A%24%7Bbattery%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Fgravity%3A%24%7Bgravity%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2Finterval%3A%24%7Bsleep-interval%7D%7C%0Aispindel%2F%24%7Bmdns%7D%2FRSSI%3A%24%7Brssi%7D%7C" },
|
||||
{ "id": "HomeAssistant-Mqtt", "format": "gravmon%2F%24%7Bmdns%7D%2Ftilt%3A%24%7Bangle%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftemperature%3A%24%7Btemp%7D%7C%0Agravmon%2F%24%7Bmdns%7D%2Ftemp_units%3A%24%7Btemp-unit%7D%7C" },
|
||||
{ "id": "UBIDots1-Post", "format": "%7B%0A%20%20%20%22temperature%22%3A%20%24%7Btemp%7D%2C%0A%20%20%20%22gravity%22%3A%20%24%7Bgravity%7D%2C%0A%20%20%20%22angle%22%3A%20%24%7Bangle%7D%2C%0A%20%20%20%22battery%22%3A%20%24%7Bbattery%7D%2C%0A%20%20%20%22rssi%22%3A%20%24%7Brssi%7D%0A%7D" } ];
|
||||
</script>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-2">
|
||||
<button class="btn btn-primary" id="format-btn" data-bs-toggle="tooltip" title="Save the format template, saving an empty form will reset the template to default">Save</button>
|
||||
<button class="btn btn-secondary" id="test-btn" data-bs-toggle="tooltip" title="Apply device data to template to see how it works">Test</button>
|
||||
</div>
|
||||
<select class="custom-select col-sm-4" required name="predefined" id="predefined" data-bs-toggle="tooltip" title="Select a pre-defined format template">
|
||||
<option value="iSpindle-Post">iSpindle (POST)</option>
|
||||
<option value="GravityMon-Post">GravityMon (POST)</option>
|
||||
<option value="iSpindle-Mqtt">iSpindle (MQTT)</option>
|
||||
<option value="HomeAssistant-Mqtt">Home Assistant (MQTT)</option>
|
||||
<option value="UBIDots1-Post">UBIdots - Alternative 1 (POST)</option>
|
||||
<option value="BrewFatherCustom-Post">Brewfather Custom Endpoint (POST)</option>
|
||||
<option value="GravityMon-Get">GravityMon (GET)</option>
|
||||
</select>
|
||||
<div class="col-sm-4">
|
||||
<button class="btn btn-secondary" id="copy-btn" data-bs-toggle="tooltip" title="Copy the selected format template to the selected push target">Copy format</button>
|
||||
<button class="btn btn-secondary" id="clear-btn" data-bs-toggle="tooltip" title="Clear the current format template">Clear format</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-2">
|
||||
|
||||
<pre class="card-preview" id="preview" name="preview"></pre>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = getConfig;
|
||||
|
||||
setButtonDisabled( true );
|
||||
|
||||
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
|
||||
$(document).ready(function () {
|
||||
if(location.hash != null && location.hash != ""){
|
||||
$('.collapse').removeClass('in');
|
||||
$(location.hash + '.collapse').collapse('show');
|
||||
}
|
||||
});
|
||||
|
||||
$("#push-target").change(function(e){
|
||||
console.log(e)
|
||||
selectFormat();
|
||||
});
|
||||
|
||||
// Store the format
|
||||
$("#format-btn").click(function(e) {
|
||||
var s = $("#format").val();
|
||||
s = s.replaceAll("\n", "");
|
||||
var obj = 'id=' + $("#id").val() + '&' + $("#push-target").val() + '=' + encodeURIComponent(s);
|
||||
console.log(obj);
|
||||
|
||||
$.ajax( {
|
||||
type: "POST",
|
||||
url: "/api/config/format",
|
||||
data: obj,
|
||||
success: function(result) { showSuccess('Format stored successfully.'); getConfig(); },
|
||||
error: function(result) { showError('Unable to store format.'); }
|
||||
} );
|
||||
});
|
||||
|
||||
// Test the calibration
|
||||
$("#test-btn").click(function(e) {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
var doc = $("#format").val();
|
||||
|
||||
if (cfg["temp-format"]=="C")
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-c"]);
|
||||
else
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-f"]);
|
||||
|
||||
if (cfg["gravity-format"]=="G") {
|
||||
var sg = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
var plato = 259 - (259 - sg);
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
}
|
||||
else {
|
||||
var plato = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
var sg = 259 / (259 - plato);
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
}
|
||||
|
||||
doc = doc.replaceAll("${mdns}", cfg["mdns"]);
|
||||
doc = doc.replaceAll("${id}", cfg["id"]);
|
||||
doc = doc.replaceAll("${sleep-interval}", cfg["sleep-interval"]);
|
||||
doc = doc.replaceAll("${token}", cfg["token"]);
|
||||
doc = doc.replaceAll("${token2}", cfg["token2"]);
|
||||
doc = doc.replaceAll("${temp-c}", cfg["temp-c"]);
|
||||
doc = doc.replaceAll("${temp-f}", cfg["temp-f"]);
|
||||
doc = doc.replaceAll("${temp-unit}", cfg["temp-format"]);
|
||||
doc = doc.replaceAll("${battery}", cfg["battery"]);
|
||||
doc = doc.replaceAll("${rssi}", cfg["rssi"]);
|
||||
doc = doc.replaceAll("${run-time}", cfg["runtime-average"]);
|
||||
doc = doc.replaceAll("${gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${gravity-unit}", cfg["gravity-format"]);
|
||||
doc = doc.replaceAll("${corr-gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${angle}", cfg["angle"]);
|
||||
doc = doc.replaceAll("${tilt}", cfg["angle"]);
|
||||
|
||||
// Format in a readable json string.
|
||||
try {
|
||||
var json = JSON.parse(doc);
|
||||
doc = JSON.stringify(json, null, 2);
|
||||
} catch(e) {
|
||||
console.log("Not a javascript object!")
|
||||
}
|
||||
|
||||
$("#preview").text(doc);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#format-btn").prop("disabled", b);
|
||||
$("#test-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function selectFormat() {
|
||||
var s = "#" + $("#push-target").val()
|
||||
console.log(s);
|
||||
s = decodeURIComponent($(s).val());
|
||||
console.log(s);
|
||||
s = s.replaceAll("|", "|\n");
|
||||
console.log(s);
|
||||
$("#format").val(s);
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/config/format";
|
||||
//var url = "/test/format.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#id").val(cfg["id"]);
|
||||
|
||||
$("#http-1").val(cfg["http-1"]);
|
||||
$("#http-2").val(cfg["http-2"]);
|
||||
$("#http-3").val(cfg["http-3"]);
|
||||
//$("#brewfather").val(cfg["brewfather"]);
|
||||
$("#influxdb").val(cfg["influxdb"]);
|
||||
$("#mqtt").val(cfg["mqtt"]);
|
||||
selectFormat();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
// Opens the targetet according (if URL has #collapseOne to #collapseFour)
|
||||
$(document).ready(function () {
|
||||
if(location.hash != null && location.hash != ""){
|
||||
$('.collapse').removeClass('in');
|
||||
$(location.hash + '.collapse').collapse('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
$("#push-target").change(function(e){
|
||||
console.log(e)
|
||||
selectFormat();
|
||||
});
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
// Copy the selected template
|
||||
$("#copy-btn").click(function(e) {
|
||||
var id = $("#predefined").val();
|
||||
|
||||
//console.log( encodeURIComponent( $("#format").val() ) );
|
||||
|
||||
formatTemplates.forEach(function (item, index) {
|
||||
if( item.id == id ) {
|
||||
$("#format").val( decodeURIComponent(item.format) );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Clear the selected template
|
||||
$("#clear-btn").click(function(e) {
|
||||
$("#format").val( "" );
|
||||
});
|
||||
|
||||
// Store the format
|
||||
$("#format-btn").click(function(e) {
|
||||
var s = $("#format").val();
|
||||
s = s.replaceAll("\n", "");
|
||||
var obj = 'id=' + $("#id").val() + '&' + $("#push-target").val() + '=' + encodeURIComponent(s);
|
||||
console.log(obj);
|
||||
|
||||
$.ajax( {
|
||||
type: "POST",
|
||||
url: "/api/config/format",
|
||||
data: obj,
|
||||
success: function(result) { showSuccess('Format stored successfully.'); getConfig(); },
|
||||
error: function(result) { showError('Unable to store format.'); }
|
||||
} );
|
||||
});
|
||||
|
||||
// Test the calibration
|
||||
$("#test-btn").click(function(e) {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
var doc = $("#format").val();
|
||||
|
||||
if (cfg["temp-format"]=="C")
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-c"]);
|
||||
else
|
||||
doc = doc.replaceAll("${temp}", cfg["temp-f"]);
|
||||
|
||||
if (cfg["gravity-format"]=="G") {
|
||||
var sg = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
var plato = 259 - (259 - sg);
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
}
|
||||
else {
|
||||
var plato = cfg["gravity"];
|
||||
doc = doc.replaceAll("${gravity-plato}", plato);
|
||||
doc = doc.replaceAll("${corr-gravity-plato}", plato);
|
||||
var sg = 259 / (259 - plato);
|
||||
doc = doc.replaceAll("${gravity-sg}", sg);
|
||||
doc = doc.replaceAll("${corr-gravity-sg}", sg);
|
||||
}
|
||||
|
||||
doc = doc.replaceAll("${mdns}", cfg["mdns"]);
|
||||
doc = doc.replaceAll("${id}", cfg["id"]);
|
||||
doc = doc.replaceAll("${sleep-interval}", cfg["sleep-interval"]);
|
||||
doc = doc.replaceAll("${token}", cfg["token"]);
|
||||
doc = doc.replaceAll("${token2}", cfg["token2"]);
|
||||
doc = doc.replaceAll("${temp-c}", cfg["temp-c"]);
|
||||
doc = doc.replaceAll("${temp-f}", cfg["temp-f"]);
|
||||
doc = doc.replaceAll("${temp-unit}", cfg["temp-format"]);
|
||||
doc = doc.replaceAll("${battery}", cfg["battery"]);
|
||||
doc = doc.replaceAll("${rssi}", cfg["rssi"]);
|
||||
doc = doc.replaceAll("${run-time}", cfg["runtime-average"]);
|
||||
doc = doc.replaceAll("${gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${gravity-unit}", cfg["gravity-format"]);
|
||||
doc = doc.replaceAll("${corr-gravity}", cfg["gravity"]);
|
||||
doc = doc.replaceAll("${angle}", cfg["angle"]);
|
||||
doc = doc.replaceAll("${tilt}", cfg["angle"]);
|
||||
|
||||
// Format in a readable json string.
|
||||
try {
|
||||
var json = JSON.parse(doc);
|
||||
doc = JSON.stringify(json, null, 2);
|
||||
} catch(e) {
|
||||
console.log("Not a javascript object!")
|
||||
}
|
||||
|
||||
$("#preview").text(doc);
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
});
|
||||
|
||||
function setButtonDisabled( b ) {
|
||||
$("#format-btn").prop("disabled", b);
|
||||
$("#test-btn").prop("disabled", b);
|
||||
}
|
||||
|
||||
function selectFormat() {
|
||||
var s = "#" + $("#push-target").val()
|
||||
console.log(s);
|
||||
s = decodeURIComponent($(s).val());
|
||||
console.log(s);
|
||||
s = s.replaceAll("|", "|\n");
|
||||
console.log(s);
|
||||
$("#format").val(s);
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
function getConfig() {
|
||||
setButtonDisabled( true );
|
||||
|
||||
var url = "/api/config/format";
|
||||
//var url = "/test/format.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
$("#id").val(cfg["id"]);
|
||||
|
||||
$("#http-1").val(cfg["http-1"]);
|
||||
$("#http-2").val(cfg["http-2"]);
|
||||
$("#http-3").val(cfg["http-3"]);
|
||||
$("#influxdb").val(cfg["influxdb"]);
|
||||
$("#mqtt").val(cfg["mqtt"]);
|
||||
selectFormat();
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
setButtonDisabled( false );
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
445
html/index.htm
445
html/index.htm
@ -5,237 +5,258 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<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>
|
||||
<!-- START MENU -->
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="/index.htm">Home <span class="sr-only">(current)</span></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>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#"><b>Home</b></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>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<div class="container">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
console.log("Error:" + msg);
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
<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>
|
||||
function showSuccess( msg ) {
|
||||
console.log("Success:" + msg);
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
console.log("Disable");
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingSoftware">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSoftware" aria-expanded="true" aria-controls="collapseSoftware">
|
||||
<b>Device</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseSoftware" class="accordion-collapse collapse show" aria-labelledby="headingSoftware" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Current version:</div>
|
||||
<div class="col-md-4 bg-light" id="app-ver">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3" id="h-app-ver-new" hidden>
|
||||
<div class="col-md-4 bg-light">New version:</div>
|
||||
<div class="col-md-4 bg-light" id="app-ver-new">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Host name:</div>
|
||||
<div class="col-md-4 bg-light" id="mdns">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Device ID:</div>
|
||||
<div class="col-md-4 bg-light" id="id">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Platform:</div>
|
||||
<div class="col-md-4 bg-light" id="platform">Loading...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#log-btn").click(function(e){
|
||||
loadLog();
|
||||
});
|
||||
setInterval(function() {
|
||||
loadLog();
|
||||
}, 3000); //5 seconds
|
||||
|
||||
function loadLog() {
|
||||
$("#logContent").load("/log");
|
||||
//$("#logContent").load("/test/status.json");
|
||||
};
|
||||
</script>
|
||||
|
||||
<button class="btn btn-primary btn-sm" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLog" aria-expanded="false" aria-controls="collapseLog" data-bs-toggle="tooltip" title="Load and show the last 10 errors that has occured on the device">
|
||||
View error log
|
||||
</button>
|
||||
|
||||
<div class="collapse row-margin-10" id="collapseLog">
|
||||
<div class="card card-body">
|
||||
<pre><code class="card-text" id="logContent"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="accordion-header" id="headingData">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseData" aria-expanded="true" aria-controls="collapseData">
|
||||
<b>Measurement</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseData" class="accordion-collapse collapse show" aria-labelledby="headingData" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Gravity:</div>
|
||||
<div class="col-md-4 bg-light" id="gravity">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Temperature:</div>
|
||||
<div class="col-md-4 bg-light" id="temp">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Angle/Tilt:</div>
|
||||
<div class="col-md-4 bg-light" id="angle">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Battery:</div>
|
||||
<div class="col-md-4 bg-light" id="battery">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4 bg-light">Average runtime:</div>
|
||||
<div class="col-md-4 bg-light" id="runtime">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 bg-light custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled>
|
||||
<label class="custom-control-label" for="sleep-mode">Do not enter sleep mode when floating (check this if you are collecting angles/tilt for calibration).</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
window.onload = start;
|
||||
|
||||
$("#sleep-mode").click(function(e){
|
||||
console.log( "Blocking sleep mode = " + $("#sleep-mode").is(":checked"));
|
||||
$.ajax( {
|
||||
type: "POST",
|
||||
url: "/api/status/sleepmode",
|
||||
data: { "id": $("#id").text(), "sleep-mode": $("#sleep-mode").is(":checked") },
|
||||
success: function(result) { },
|
||||
error: function(result) { showError('Could not update sleep mode for device.'); },
|
||||
} );
|
||||
});
|
||||
|
||||
function getStatus() {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
$("#app-ver").text(cfg["app-ver"] + " (" + cfg["app-build"] + ")");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
$("#runtime").text(cfg["runtime-average"] + " seconds");
|
||||
|
||||
var angle = cfg["angle"];
|
||||
|
||||
if(angle==0) {
|
||||
$("#angle").text("Gyro moving");
|
||||
$("#gravity").text("Gyro moving");
|
||||
} else {
|
||||
$("#angle").text(cfg["angle"]);
|
||||
|
||||
if( cfg["gravity-format"] == "G")
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
else
|
||||
$("#gravity").text(cfg["gravity"] + " °P");
|
||||
}
|
||||
|
||||
var batt = cfg["battery"];
|
||||
var charge = 0;
|
||||
|
||||
if(batt>4.15) charge = 100;
|
||||
else if(batt>4.05) charge = 90;
|
||||
else if(batt>3.97) charge = 80;
|
||||
else if(batt>3.91) charge = 70;
|
||||
else if(batt>3.86) charge = 60;
|
||||
else if(batt>3.81) charge = 50;
|
||||
else if(batt>3.78) charge = 40;
|
||||
else if(batt>3.76) charge = 30;
|
||||
else if(batt>3.73) charge = 20;
|
||||
else if(batt>3.67) charge = 10;
|
||||
else if(batt>3.44) charge = 5;
|
||||
|
||||
$("#battery").text(batt + " V (" + charge + "%)" );
|
||||
|
||||
if( cfg["temp-format"] == "C")
|
||||
$("#temp").text(cfg["temp-c"] + " C");
|
||||
else
|
||||
$("#temp").text(cfg["temp-f"] + " F");
|
||||
|
||||
if( cfg["sleep-mode"] )
|
||||
$("#sleep-mode").attr("checked", true );
|
||||
else
|
||||
$("#sleep-mode").attr("checked", false );
|
||||
$("#sleep-mode").removeAttr("disabled");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.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">Platform:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="platform">Loading...</div>
|
||||
</div>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<script>
|
||||
$("#log-btn").click(function(e){
|
||||
loadLog();
|
||||
});
|
||||
setInterval(function() {
|
||||
loadLog();
|
||||
}, 3000); //5 seconds
|
||||
|
||||
function loadLog() {
|
||||
$("#logContent").load("/log");
|
||||
//$("#logContent").load("/test/status.json");
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="row mb-3">
|
||||
<a class="badge badge-primary" data-toggle="collapse" href="#collapseLog" role="button" aria-expanded="false" aria-controls="collapseLog" id="log-btn">
|
||||
View error log
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse" id="collapseLog">
|
||||
<div class="card card-body">
|
||||
<pre><code id="logContent"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Gravity:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="gravity">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Temperature:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="temp">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Angle/Tilt:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="angle">Loading...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Battery:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="battery">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 themed-grid-col bg-light">Average runtime:</div>
|
||||
<div class="col-md-4 themed-grid-col bg-light" id="runtime">Loading...</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12 px-md-5 themed-grid-col bg-light custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" name="sleep-mode" id="sleep-mode" disabled>
|
||||
<label class="custom-control-label" for="sleep-mode">Do not enter sleep mode when floating (check this if you are collecting angles/tilt for calibration).</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = start;
|
||||
|
||||
$("#sleep-mode").click(function(e){
|
||||
console.log( "Blocking sleep mode = " + $("#sleep-mode").is(":checked"));
|
||||
$.ajax( {
|
||||
type: "POST",
|
||||
url: "/api/status/sleepmode",
|
||||
data: { "id": $("#id").text(), "sleep-mode": $("#sleep-mode").is(":checked") },
|
||||
success: function(result) { },
|
||||
error: function(result) { showError('Could not update sleep mode for device.'); },
|
||||
} );
|
||||
});
|
||||
|
||||
function getStatus() {
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
//$("#app-ver").text(cfg["app-ver"] + " (html 0.8.0)");
|
||||
$("#app-ver").text(cfg["app-ver"] + " (" + cfg["app-build"] + ")");
|
||||
$("#mdns").text(cfg["mdns"]);
|
||||
$("#id").text(cfg["id"]);
|
||||
$("#platform").text(cfg["platform"]);
|
||||
$("#runtime").text(cfg["runtime-average"] + " seconds");
|
||||
|
||||
var angle = cfg["angle"];
|
||||
|
||||
if(angle==0) {
|
||||
$("#angle").text("Gyro moving");
|
||||
$("#gravity").text("Gyro moving");
|
||||
} else {
|
||||
$("#angle").text(cfg["angle"]);
|
||||
|
||||
if( cfg["gravity-format"] == "G")
|
||||
$("#gravity").text(cfg["gravity"] + " SG");
|
||||
else
|
||||
$("#gravity").text(cfg["gravity"] + " °P");
|
||||
}
|
||||
|
||||
var batt = cfg["battery"];
|
||||
var charge = 0;
|
||||
|
||||
if(batt>4.15) charge = 100;
|
||||
else if(batt>4.05) charge = 90;
|
||||
else if(batt>3.97) charge = 80;
|
||||
else if(batt>3.91) charge = 70;
|
||||
else if(batt>3.86) charge = 60;
|
||||
else if(batt>3.81) charge = 50;
|
||||
else if(batt>3.78) charge = 40;
|
||||
else if(batt>3.76) charge = 30;
|
||||
else if(batt>3.73) charge = 20;
|
||||
else if(batt>3.67) charge = 10;
|
||||
else if(batt>3.44) charge = 5;
|
||||
|
||||
$("#battery").text(batt + " V (" + charge + "%)" );
|
||||
|
||||
if( cfg["temp-format"] == "C")
|
||||
$("#temp").text(cfg["temp-c"] + " C");
|
||||
else
|
||||
$("#temp").text(cfg["temp-f"] + " F");
|
||||
|
||||
if( cfg["sleep-mode"] )
|
||||
$("#sleep-mode").attr("checked", true );
|
||||
else
|
||||
$("#sleep-mode").attr("checked", false );
|
||||
$("#sleep-mode").removeAttr("disabled");
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function start() {
|
||||
setInterval(getStatus, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
384
html/test.htm
384
html/test.htm
@ -5,213 +5,219 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<nav class="navbar navbar-expand-sm navbar-dark bg-primary">
|
||||
<!-- START MENU -->
|
||||
|
||||
<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="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="javascript:history.back()">Back to configuration</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<hr class="my-2">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingTest">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTest" aria-expanded="true" aria-controls="collapseTest">
|
||||
<b>Testing push targets</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseTest" class="accordion-collapse collapse show" aria-labelledby="headingTest" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row mb-3">
|
||||
<pre class="card-preview" id="preview" name="preview">Press test button to start testing all defined push targets.</pre>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-8">
|
||||
<button class="btn btn-primary" id="test-btn" data-bs-toggle="tooltip" title="Test all push targets">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('#alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
$('#spinner').hide();
|
||||
|
||||
function clearLog() {
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('#alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show');
|
||||
$('#alert-msg').text( msg );
|
||||
function appendLog(log) {
|
||||
doc = $("#preview").text();
|
||||
doc += log + "\n";
|
||||
$("#preview").text(doc);
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('#alert').addClass('d-none').removeClass('show');
|
||||
});
|
||||
// Get the configuration values from the API
|
||||
$("#test-btn").click(function(e) {
|
||||
clearLog();
|
||||
appendLog( "Starting test of push targets" );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$("#test-btn").prop("disabled", true);
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
var id = cfg["id"];
|
||||
console.log( id );
|
||||
|
||||
testHttp( id, "http-1" );
|
||||
testHttp( id, "http-2" );
|
||||
testHttp( id, "http-3" );
|
||||
testInfluxdb( id );
|
||||
testMqtt( id );
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.always(function() {
|
||||
});
|
||||
});
|
||||
|
||||
function testMqtt(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=mqtt";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'mqtt' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'mqtt' successful" );
|
||||
} else{
|
||||
if(code==-3)
|
||||
appendLog( "Push target 'mqtt' failed to connect" );
|
||||
else if(code==-4)
|
||||
appendLog( "Push target 'mqtt' failed with error timeout" );
|
||||
else if(code==-10)
|
||||
appendLog( "Push target 'mqtt' failed with error denied" );
|
||||
else
|
||||
appendLog( "Push target 'mqtt' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'mqtt'");
|
||||
})
|
||||
}
|
||||
|
||||
function testInfluxdb(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=influxdb";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'influxdb' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'influxdb' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target 'influxdb' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target 'influxdb' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target 'influxdb' failed with error code 404, url not found" );
|
||||
else
|
||||
appendLog( "Push target 'influxdb' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'influxdb'");
|
||||
})
|
||||
}
|
||||
|
||||
function testHttp(id, target) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=" + target;
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target '" + target + "' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target '" + target + "' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target '" + target + "' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target '" + target + "' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target '" + target + "' failed with error code 404, url not found" );
|
||||
else
|
||||
appendLog( "Push target '" + target + "' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target '" + target + "'");
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<pre class="card-preview" id="preview" name="preview"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="form-group row">
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-8">
|
||||
<button class="btn btn-primary" id="test-btn">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$('#spinner').hide();
|
||||
|
||||
function clearLog() {
|
||||
$("#preview").text("");
|
||||
}
|
||||
|
||||
function appendLog(log) {
|
||||
doc = $("#preview").text();
|
||||
doc += log + "\n";
|
||||
$("#preview").text(doc);
|
||||
}
|
||||
|
||||
// Get the configuration values from the API
|
||||
$("#test-btn").click(function(e) {
|
||||
clearLog();
|
||||
appendLog( "Starting test of push targets" );
|
||||
|
||||
var url = "/api/status";
|
||||
//var url = "/test/status.json";
|
||||
$("#test-btn").prop("disabled", true);
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
var id = cfg["id"];
|
||||
console.log( id );
|
||||
|
||||
testHttp( id, "http-1" );
|
||||
testHttp( id, "http-2" );
|
||||
testHttp( id, "http-3" );
|
||||
testHttp( id, "brewfather" );
|
||||
testInfluxdb( id );
|
||||
testMqtt( id );
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
$('#spinner').hide();
|
||||
$("#test-btn").prop("disabled", false);
|
||||
})
|
||||
.always(function() {
|
||||
});
|
||||
});
|
||||
|
||||
function testMqtt(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=mqtt";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'mqtt' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'mqtt' successful" );
|
||||
} else{
|
||||
if(code==-3)
|
||||
appendLog( "Push target 'mqtt' failed to connect" );
|
||||
else if(code==-4)
|
||||
appendLog( "Push target 'mqtt' failed with error timeout" );
|
||||
else if(code==-10)
|
||||
appendLog( "Push target 'mqtt' failed with error denied" );
|
||||
else
|
||||
appendLog( "Push target 'mqtt' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'influxdb'");
|
||||
})
|
||||
}
|
||||
|
||||
function testInfluxdb(id) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=influxdb";
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target 'influxdb' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target 'influxdb' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target 'influxdb' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target 'influxdb' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target 'influxdb' failed with error code 404, url not found" );
|
||||
else
|
||||
appendLog( "Push target 'influxdb' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target 'influxdb'");
|
||||
})
|
||||
}
|
||||
|
||||
function testHttp(id, target) {
|
||||
var url = "/api/test/push";
|
||||
url += "?id=" + id + "&format=" + target;
|
||||
//var url = "/test/push.json";
|
||||
$.getJSON(url, function (cfg) {
|
||||
var code = cfg["code"];
|
||||
var success = cfg["success"];
|
||||
var enabled = cfg["enabled"];
|
||||
|
||||
if(!enabled) {
|
||||
appendLog( "Push target '" + target + "' is not configured/used" );
|
||||
} else if(success) {
|
||||
appendLog( "Push target '" + target + "' successful" );
|
||||
} else{
|
||||
if(code==400)
|
||||
appendLog( "Push target '" + target + "' failed with error code 400, bad request" );
|
||||
else if(code==401)
|
||||
appendLog( "Push target '" + target + "' failed with error code 401, unauthorized" );
|
||||
else if(code==404)
|
||||
appendLog( "Push target '" + target + "' failed with error code 404, url not found" );
|
||||
else
|
||||
appendLog( "Push target '" + target + "' failed with error code " + code );
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
appendLog( "Failed to test push target '" + target + "'");
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
289
html/upload.htm
289
html/upload.htm
@ -5,160 +5,175 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<title>Beer Gravity Monitor</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" 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://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-fQybjgWLrvvRgtW6bFlB7jaZrFsaBXjsOMm/tB9LTS58ONXgqbR9W8oWht/amnpF" crossorigin="anonymous"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
<style>
|
||||
.row-margin-10 { margin-top: 1.0em; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="py-4">
|
||||
|
||||
<!-- START MENU -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
|
||||
<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>
|
||||
<!-- START MENU -->
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar">
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/index.htm">Beer Gravity Monitor</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="spinner-border text-light" id="spinner" role="status"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- START MAIN INDEX -->
|
||||
<!-- START MAIN INDEX -->
|
||||
|
||||
<div class="container">
|
||||
<div class="container row-margin-10">
|
||||
|
||||
<hr class="my-4">
|
||||
<div class="alert alert-success alert-dismissible hide fade d-none" role="alert">
|
||||
<div id="alert"></div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('hide').addClass('show').removeClass('d-none');
|
||||
$('#alert').text( msg );
|
||||
}
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('hide').removeClass('show').addClass('d-none');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="accordion" id="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header" id="headingUpload">
|
||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseUpload" aria-expanded="true" aria-controls="collapseUpload">
|
||||
<b>Upload missing html files</b>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseUpload" class="accordion-collapse collapse show" aria-labelledby="headingUpload" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 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 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 bg-light">index.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="index">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">config.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="config">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">calibration.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="calibration">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">format.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="format">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">test.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="test">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 bg-light">about.min.htm</div>
|
||||
<div class="col-md-6 bg-light" id="about">Checking...</div>
|
||||
</div>
|
||||
|
||||
<form action="/api/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8 custom-file">
|
||||
<input type="file" accept=".min.htm" class="custom-file-input" name="name" id="name" data-bs-toggle="tooltip" title="Select a file that matches the name in the list above to upload it to the device">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-primary" id="upload-btn" value="upload" data-bs-toggle="tooltip" title="Upload the selected file to the device">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showError( msg ) {
|
||||
$('.alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
window.onload = getUpload;
|
||||
|
||||
function showSuccess( msg ) {
|
||||
$('.alert').addClass('alert-success').removeClass('alert-danger').removeClass('d-none').addClass('show')
|
||||
$('#alert-msg').text( msg );
|
||||
}
|
||||
// Add the following code if you want the name of the file appear on select
|
||||
$(".custom-file-input").on("change", function() {
|
||||
var fileName = $(this).val().split("\\").pop();
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
|
||||
});
|
||||
|
||||
$("#alert-btn").click(function(e){
|
||||
$('.alert').addClass('d-none').removeClass('show')
|
||||
});
|
||||
function getUpload() {
|
||||
var url = "/api/upload";
|
||||
//var url = "/test/upload.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
if( cfg["index"] )
|
||||
$("#index").text("Completed.");
|
||||
else
|
||||
$("#index").text("File is missing.");
|
||||
|
||||
if( cfg["config"] )
|
||||
$("#config").text("Completed.");
|
||||
else
|
||||
$("#config").text("File is missing.");
|
||||
|
||||
if( cfg["calibration"] )
|
||||
$("#calibration").text("Completed.");
|
||||
else
|
||||
$("#calibration").text("File is missing.");
|
||||
|
||||
if( cfg["test"] )
|
||||
$("#test").text("Completed.");
|
||||
else
|
||||
$("#test").text("File is missing.");
|
||||
|
||||
if( cfg["format"] )
|
||||
$("#format").text("Completed.");
|
||||
else
|
||||
$("#format").text("File is missing.");
|
||||
|
||||
if( cfg["about"] )
|
||||
$("#about").text("Completed.");
|
||||
else
|
||||
$("#about").text("File is missing.");
|
||||
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
</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>
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<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">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">format.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="format">Checking...</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 themed-grid-col bg-light">test.min.htm</div>
|
||||
<div class="col-md-6 themed-grid-col bg-light" id="test">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" accept=".min.htm" 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">
|
||||
window.onload = getUpload;
|
||||
|
||||
// Add the following code if you want the name of the file appear on select
|
||||
$(".custom-file-input").on("change", function() {
|
||||
var fileName = $(this).val().split("\\").pop();
|
||||
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
|
||||
});
|
||||
|
||||
function getUpload() {
|
||||
var url = "/api/upload";
|
||||
//var url = "/test/upload.json";
|
||||
$('#spinner').show();
|
||||
$.getJSON(url, function (cfg) {
|
||||
console.log( cfg );
|
||||
|
||||
if( cfg["index"] )
|
||||
$("#index").text("Completed.");
|
||||
else
|
||||
$("#index").text("File is missing.");
|
||||
|
||||
if( cfg["config"] )
|
||||
$("#config").text("Completed.");
|
||||
else
|
||||
$("#config").text("File is missing.");
|
||||
|
||||
if( cfg["calibration"] )
|
||||
$("#calibration").text("Completed.");
|
||||
else
|
||||
$("#calibration").text("File is missing.");
|
||||
|
||||
if( cfg["test"] )
|
||||
$("#test").text("Completed.");
|
||||
else
|
||||
$("#test").text("File is missing.");
|
||||
|
||||
if( cfg["format"] )
|
||||
$("#format").text("Completed.");
|
||||
else
|
||||
$("#format").text("File is missing.");
|
||||
|
||||
if( cfg["about"] )
|
||||
$("#about").text("Completed.");
|
||||
else
|
||||
$("#about").text("File is missing.");
|
||||
|
||||
|
||||
})
|
||||
.fail(function () {
|
||||
showError('Unable to get data from the device.');
|
||||
})
|
||||
.always(function() {
|
||||
$('#spinner').hide();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
|
||||
<div class="container-fluid themed-container bg-primary text-light">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
<div class="container themed-container bg-primary text-light row-margin-10">(C) Copyright 2021-22 Magnus Persson</div>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
@ -33,7 +33,7 @@ build_flags =
|
||||
-D EMBED_HTML # If this is not used the html files needs to be on the file system (can be uploaded)
|
||||
-D USER_SSID=\""\"" # =\""myssid\""
|
||||
-D USER_SSID_PWD=\""\"" # =\""mypwd\""
|
||||
-D CFG_APPVER="\"0.9.0\""
|
||||
-D CFG_APPVER="\"1.0.0\""
|
||||
!python script/git_rev.py
|
||||
lib_deps = # Switched to forks for better version control.
|
||||
# Using local copy of these libraries
|
||||
@ -66,6 +66,7 @@ build_flags =
|
||||
#-D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS
|
||||
#-D SKIP_SLEEPMODE
|
||||
#-D DOUBLERESETDETECTOR_DEBUG=true
|
||||
#-D FORCE_GRAVITY_MODE # used to debug gravity mode
|
||||
-D COLLECT_PERFDATA # This option will collect runtime data for a few defined methods to measure time, dumped to serial and/or influxdb
|
||||
-D LOG_LEVEL=6 # Maximum log level for the debug build.
|
||||
-D CFG_DISABLE_LOGGING # Turn off verbose/notice logging to reduce size and dont overload uart.
|
||||
@ -126,7 +127,7 @@ board_build.filesystem = littlefs
|
||||
|
||||
[env:gravity32-release]
|
||||
framework = arduino
|
||||
platform = espressif32
|
||||
platform = espressif32 @ 3.5.0
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
@ -161,7 +162,7 @@ monitor_filters = esp32_exception_decoder
|
||||
|
||||
[env:gravity32-perf]
|
||||
framework = arduino
|
||||
platform = espressif32
|
||||
platform = espressif32 @ 3.5.0
|
||||
upload_speed = ${common_env_data.upload_speed}
|
||||
monitor_speed = ${common_env_data.monitor_speed}
|
||||
extra_scripts =
|
||||
|
28
src/calc.cpp
28
src/calc.cpp
@ -33,14 +33,16 @@ SOFTWARE.
|
||||
int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
int formulaBufferSize, int order) {
|
||||
int noAngles = 0;
|
||||
RawFormulaData fd2;
|
||||
|
||||
// Check how many valid values we have got
|
||||
if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0 && fd.a[4] > 0)
|
||||
noAngles = 5;
|
||||
else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0 && fd.a[3] > 0)
|
||||
noAngles = 4;
|
||||
else if (fd.a[0] > 0 && fd.a[1] > 0 && fd.a[2] > 0)
|
||||
noAngles = 3;
|
||||
// Check how many valid values we have got and make sure we have a full series.
|
||||
for (int i = 0; i < FORMULA_DATA_SIZE; i++) {
|
||||
if (fd.a[i]) {
|
||||
fd2.a[noAngles] = fd.a[i];
|
||||
fd2.g[noAngles] = fd.g[i];
|
||||
noAngles++;
|
||||
}
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(
|
||||
@ -48,19 +50,19 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
order, noAngles);
|
||||
#endif
|
||||
|
||||
if (!noAngles) {
|
||||
if (noAngles <3) {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry(F("CALC: Not enough values for deriving formula"));
|
||||
return ERR_FORMULA_NOTENOUGHVALUES;
|
||||
} else {
|
||||
double coeffs[order + 1];
|
||||
int ret = fitCurve(order, noAngles, fd.a, fd.g,
|
||||
int ret = fitCurve(order, noAngles, fd2.a, fd2.g,
|
||||
sizeof(coeffs) / sizeof(double), coeffs);
|
||||
|
||||
// Returned value is 0 if no error
|
||||
if (ret == 0) {
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
Log.verbose(F("CALC: Finshied processing data points." CR));
|
||||
Log.verbose(F("CALC: Finshied processing data points, order = %d." CR), order);
|
||||
#endif
|
||||
|
||||
// Print the formula based on 'order'
|
||||
@ -93,12 +95,10 @@ int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
double dev = (g - fd.g[i]) < 0 ? (fd.g[i] - g) : (g - fd.g[i]);
|
||||
|
||||
// If the deviation is more than 2 degress we mark it as failed.
|
||||
if (dev * 1000 > myHardwareConfig.getMaxFormulaCreationDeviation()) {
|
||||
#if LOG_LEVEL == 6 && !defined(CALC_DISABLE_LOGGING)
|
||||
if (dev * 1000 > myAdvancedConfig.getMaxFormulaCreationDeviation()) {
|
||||
char s[20];
|
||||
snprintf(&s[0], sizeof(s), "%.8f", dev);
|
||||
Log.verbose(F("CALC: Deviation is: %s" CR), &s[0]);
|
||||
#endif
|
||||
Log.error(F("CALC: Deviation to large: %s" CR), &s[0]);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ double calculateGravity(double angle, double tempC,
|
||||
const char *tempFormula = 0);
|
||||
double gravityTemperatureCorrectionC(
|
||||
double gravity, double tempC,
|
||||
double calTempC = myHardwareConfig.getDefaultCalibrationTemp());
|
||||
double calTempC = myAdvancedConfig.getDefaultCalibrationTemp());
|
||||
int createFormula(RawFormulaData &fd, char *formulaBuffer,
|
||||
int formulaBufferSize, int order);
|
||||
|
||||
|
@ -26,7 +26,7 @@ SOFTWARE.
|
||||
#include <wifi.hpp>
|
||||
|
||||
Config myConfig;
|
||||
HardwareConfig myHardwareConfig;
|
||||
AdvancedConfig myAdvancedConfig;
|
||||
|
||||
//
|
||||
// Create the config class with default settings.
|
||||
@ -66,7 +66,6 @@ void Config::createJson(DynamicJsonDocument& doc) {
|
||||
doc[PARAM_PASS] = getWifiPass();
|
||||
doc[PARAM_BLE] = getColorBLE();
|
||||
doc[PARAM_TEMPFORMAT] = String(getTempFormat());
|
||||
doc[PARAM_PUSH_BREWFATHER] = getBrewfatherPushUrl();
|
||||
doc[PARAM_TOKEN] = getToken();
|
||||
doc[PARAM_TOKEN2] = getToken2();
|
||||
doc[PARAM_PUSH_HTTP] = getHttpUrl();
|
||||
@ -106,12 +105,22 @@ void Config::createJson(DynamicJsonDocument& doc) {
|
||||
cal2["a3"] = reduceFloatPrecision(_formulaData.a[2], 2);
|
||||
cal2["a4"] = reduceFloatPrecision(_formulaData.a[3], 2);
|
||||
cal2["a5"] = reduceFloatPrecision(_formulaData.a[4], 2);
|
||||
cal2["a6"] = reduceFloatPrecision(_formulaData.a[5], 2);
|
||||
cal2["a7"] = reduceFloatPrecision(_formulaData.a[6], 2);
|
||||
cal2["a8"] = reduceFloatPrecision(_formulaData.a[7], 2);
|
||||
cal2["a9"] = reduceFloatPrecision(_formulaData.a[8], 2);
|
||||
cal2["a10"] = reduceFloatPrecision(_formulaData.a[9], 2);
|
||||
|
||||
cal2["g1"] = reduceFloatPrecision(_formulaData.g[0], 4);
|
||||
cal2["g2"] = reduceFloatPrecision(_formulaData.g[1], 4);
|
||||
cal2["g3"] = reduceFloatPrecision(_formulaData.g[2], 4);
|
||||
cal2["g4"] = reduceFloatPrecision(_formulaData.g[3], 4);
|
||||
cal2["g5"] = reduceFloatPrecision(_formulaData.g[4], 4);
|
||||
cal2["g6"] = reduceFloatPrecision(_formulaData.g[5], 4);
|
||||
cal2["g7"] = reduceFloatPrecision(_formulaData.g[6], 4);
|
||||
cal2["g8"] = reduceFloatPrecision(_formulaData.g[7], 4);
|
||||
cal2["g9"] = reduceFloatPrecision(_formulaData.g[8], 4);
|
||||
cal2["g10"] = reduceFloatPrecision(_formulaData.g[9], 4);
|
||||
}
|
||||
|
||||
//
|
||||
@ -207,9 +216,6 @@ bool Config::loadFile() {
|
||||
setTempFormat(s.charAt(0));
|
||||
}
|
||||
|
||||
if (!doc[PARAM_PUSH_BREWFATHER].isNull())
|
||||
setBrewfatherPushUrl(doc[PARAM_PUSH_BREWFATHER]);
|
||||
|
||||
if (!doc[PARAM_TOKEN].isNull()) setToken(doc[PARAM_TOKEN]);
|
||||
if (!doc[PARAM_TOKEN2].isNull()) setToken2(doc[PARAM_TOKEN2]);
|
||||
if (!doc[PARAM_PUSH_HTTP].isNull()) setHttpUrl(doc[PARAM_PUSH_HTTP]);
|
||||
@ -281,6 +287,16 @@ bool Config::loadFile() {
|
||||
_formulaData.a[3] = doc[PARAM_FORMULA_DATA]["a4"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a5"].isNull())
|
||||
_formulaData.a[4] = doc[PARAM_FORMULA_DATA]["a5"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a6"].isNull())
|
||||
_formulaData.a[5] = doc[PARAM_FORMULA_DATA]["a6"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a7"].isNull())
|
||||
_formulaData.a[6] = doc[PARAM_FORMULA_DATA]["a7"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a8"].isNull())
|
||||
_formulaData.a[7] = doc[PARAM_FORMULA_DATA]["a8"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a9"].isNull())
|
||||
_formulaData.a[8] = doc[PARAM_FORMULA_DATA]["a9"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["a10"].isNull())
|
||||
_formulaData.a[9] = doc[PARAM_FORMULA_DATA]["a10"].as<double>();
|
||||
|
||||
if (!doc[PARAM_FORMULA_DATA]["g1"].isNull())
|
||||
_formulaData.g[0] = doc[PARAM_FORMULA_DATA]["g1"].as<double>();
|
||||
@ -292,6 +308,16 @@ bool Config::loadFile() {
|
||||
_formulaData.g[3] = doc[PARAM_FORMULA_DATA]["g4"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g5"].isNull())
|
||||
_formulaData.g[4] = doc[PARAM_FORMULA_DATA]["g5"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g6"].isNull())
|
||||
_formulaData.g[5] = doc[PARAM_FORMULA_DATA]["g6"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g7"].isNull())
|
||||
_formulaData.g[6] = doc[PARAM_FORMULA_DATA]["g7"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g8"].isNull())
|
||||
_formulaData.g[7] = doc[PARAM_FORMULA_DATA]["g8"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g9"].isNull())
|
||||
_formulaData.g[8] = doc[PARAM_FORMULA_DATA]["g9"].as<double>();
|
||||
if (!doc[PARAM_FORMULA_DATA]["g10"].isNull())
|
||||
_formulaData.g[9] = doc[PARAM_FORMULA_DATA]["g10"].as<double>();
|
||||
|
||||
/*if( doc[PARAM_CONFIG_VER].isNull() ) {
|
||||
// If this parameter is missing we need to reset the gyrocalibaration due to
|
||||
@ -333,7 +359,7 @@ void Config::checkFileSystem() {
|
||||
//
|
||||
// Save json document to file
|
||||
//
|
||||
bool HardwareConfig::saveFile() {
|
||||
bool AdvancedConfig::saveFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Saving hardware configuration to file." CR));
|
||||
#endif
|
||||
@ -352,8 +378,14 @@ bool HardwareConfig::saveFile() {
|
||||
doc[PARAM_HW_GYRO_READ_DELAY] = this->getGyroReadDelay();
|
||||
doc[PARAM_HW_GYRO_MOVING_THREASHOLD] = this->getGyroSensorMovingThreashold();
|
||||
doc[PARAM_HW_FORMULA_DEVIATION] = this->getMaxFormulaCreationDeviation();
|
||||
doc[PARAM_HW_WIFI_PORTALTIMEOUT] = this->getWifiPortalTimeout();
|
||||
doc[PARAM_HW_WIFI_PORTAL_TIMEOUT] = this->getWifiPortalTimeout();
|
||||
doc[PARAM_HW_WIFI_CONNECT_TIMEOUT] = this->getWifiConnectTimeout();
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP] = this->getDefaultCalibrationTemp();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP1] = this->getPushIntervalHttp1();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP2] = this->getPushIntervalHttp2();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP3] = this->getPushIntervalHttp3();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_INFLUX] = this->getPushIntervalInflux();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_MQTT] = this->getPushIntervalMqtt();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
@ -371,7 +403,7 @@ bool HardwareConfig::saveFile() {
|
||||
//
|
||||
// Load config file from disk
|
||||
//
|
||||
bool HardwareConfig::loadFile() {
|
||||
bool AdvancedConfig::loadFile() {
|
||||
#if LOG_LEVEL == 6 && !defined(DISABLE_LOGGING)
|
||||
Log.verbose(F("CFG : Loading hardware configuration from file." CR));
|
||||
#endif
|
||||
@ -424,10 +456,22 @@ bool HardwareConfig::loadFile() {
|
||||
if (!doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].isNull())
|
||||
this->SetDefaultCalibrationTemp(
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP].as<float>());
|
||||
if (!doc[PARAM_HW_WIFI_PORTALTIMEOUT].isNull())
|
||||
this->setWifiPortalTimeout(doc[PARAM_HW_WIFI_PORTALTIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_WIFI_PORTAL_TIMEOUT].isNull())
|
||||
this->setWifiPortalTimeout(doc[PARAM_HW_WIFI_PORTAL_TIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_WIFI_CONNECT_TIMEOUT].isNull())
|
||||
this->setWifiConnectTimeout(doc[PARAM_HW_WIFI_CONNECT_TIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_TIMEOUT].isNull())
|
||||
this->setPushTimeout(doc[PARAM_HW_PUSH_TIMEOUT].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_HTTP1].isNull())
|
||||
this->setPushIntervalHttp1(doc[PARAM_HW_PUSH_INTERVAL_HTTP1].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_HTTP2].isNull())
|
||||
this->setPushIntervalHttp2(doc[PARAM_HW_PUSH_INTERVAL_HTTP2].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_HTTP3].isNull())
|
||||
this->setPushIntervalHttp3(doc[PARAM_HW_PUSH_INTERVAL_HTTP3].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_INFLUX].isNull())
|
||||
this->setPushIntervalInflux(doc[PARAM_HW_PUSH_INTERVAL_INFLUX].as<int>());
|
||||
if (!doc[PARAM_HW_PUSH_INTERVAL_MQTT].isNull())
|
||||
this->setPushIntervalMqtt(doc[PARAM_HW_PUSH_INTERVAL_MQTT].as<int>());
|
||||
|
||||
Log.notice(F("CFG : Configuration file " CFG_HW_FILENAME " loaded." CR));
|
||||
return true;
|
||||
|
@ -47,41 +47,75 @@ struct RawGyroData {
|
||||
};
|
||||
|
||||
// Used for holding formulaData (used for calculating formula on device)
|
||||
#define FORMULA_DATA_SIZE 10
|
||||
|
||||
struct RawFormulaData {
|
||||
double a[5];
|
||||
double g[5];
|
||||
double a[FORMULA_DATA_SIZE];
|
||||
double g[FORMULA_DATA_SIZE];
|
||||
};
|
||||
|
||||
class HardwareConfig {
|
||||
class AdvancedConfig {
|
||||
private:
|
||||
int _wifiPortalTimeout = 120;
|
||||
int _wifiConnectTimeout = 20;
|
||||
float _maxFormulaCreationDeviation = 1.6;
|
||||
float _defaultCalibrationTemp = 20.0;
|
||||
int _gyroSensorMovingThreashold = 500;
|
||||
int _gyroReadCount = 50;
|
||||
int _gyroReadDelay = 3150; // us, empirical, to hold sampling to 200 Hz
|
||||
int _pushTimeout = 10; // seconds
|
||||
int _pushIntervalHttp1 = 0;
|
||||
int _pushIntervalHttp2 = 0;
|
||||
int _pushIntervalHttp3 = 0;
|
||||
int _pushIntervalInflux = 0;
|
||||
int _pushIntervalMqtt = 0;
|
||||
|
||||
public:
|
||||
int getWifiPortalTimeout() { return _wifiPortalTimeout; }
|
||||
void setWifiPortalTimeout(int t) { _wifiPortalTimeout = t; }
|
||||
|
||||
int getWifiConnectTimeout() { return _wifiConnectTimeout; }
|
||||
void setWifiConnectTimeout(int t) { _wifiConnectTimeout = t; }
|
||||
|
||||
float getMaxFormulaCreationDeviation() {
|
||||
return _maxFormulaCreationDeviation;
|
||||
}
|
||||
void setMaxFormulaCreationDeviation(float f) {
|
||||
_maxFormulaCreationDeviation = f;
|
||||
}
|
||||
|
||||
float getDefaultCalibrationTemp() { return _defaultCalibrationTemp; }
|
||||
void SetDefaultCalibrationTemp(float t) { _defaultCalibrationTemp = t; }
|
||||
|
||||
int getGyroSensorMovingThreashold() { return _gyroSensorMovingThreashold; }
|
||||
void setGyroSensorMovingThreashold(int t) { _gyroSensorMovingThreashold = t; }
|
||||
|
||||
int getGyroReadCount() { return _gyroReadCount; }
|
||||
void setGyroReadCount(int c) { _gyroReadCount = c; }
|
||||
|
||||
int getGyroReadDelay() { return _gyroReadDelay; }
|
||||
void setGyroReadDelay(int d) { _gyroReadDelay = d; }
|
||||
|
||||
int getPushTimeout() { return _pushTimeout; }
|
||||
void setPushTimeout(int t) { _pushTimeout = t; }
|
||||
|
||||
int getPushIntervalHttp1() { return _pushIntervalHttp1; }
|
||||
void setPushIntervalHttp1(int t) { _pushIntervalHttp1 = t; }
|
||||
|
||||
int getPushIntervalHttp2() { return _pushIntervalHttp2; }
|
||||
void setPushIntervalHttp2(int t) { _pushIntervalHttp2 = t; }
|
||||
|
||||
int getPushIntervalHttp3() { return _pushIntervalHttp3; }
|
||||
void setPushIntervalHttp3(int t) { _pushIntervalHttp3 = t; }
|
||||
|
||||
int getPushIntervalInflux() { return _pushIntervalInflux; }
|
||||
void setPushIntervalInflux(int t) { _pushIntervalInflux = t; }
|
||||
|
||||
int getPushIntervalMqtt() { return _pushIntervalMqtt; }
|
||||
void setPushIntervalMqtt(int t) { _pushIntervalMqtt = t; }
|
||||
|
||||
bool isPushIntervalActive() { return (_pushIntervalHttp1+_pushIntervalHttp2+_pushIntervalHttp3+_pushIntervalInflux+_pushIntervalMqtt) == 0 ? false : true; }
|
||||
|
||||
bool saveFile();
|
||||
bool loadFile();
|
||||
};
|
||||
@ -106,8 +140,6 @@ class Config {
|
||||
String _wifiPASS = "";
|
||||
|
||||
// Push target settings
|
||||
String _brewfatherPushUrl = "";
|
||||
|
||||
String _token = "";
|
||||
String _token2 = "";
|
||||
|
||||
@ -178,16 +210,6 @@ class Config {
|
||||
_saveNeeded = true;
|
||||
}
|
||||
|
||||
// Brewfather
|
||||
const char* getBrewfatherPushUrl() { return _brewfatherPushUrl.c_str(); }
|
||||
void setBrewfatherPushUrl(String s) {
|
||||
_brewfatherPushUrl = s;
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isBrewfatherActive() {
|
||||
return _brewfatherPushUrl.length() ? true : false;
|
||||
}
|
||||
|
||||
// Token parameter
|
||||
const char* getToken() { return _token.c_str(); }
|
||||
void setToken(String s) {
|
||||
@ -242,6 +264,7 @@ class Config {
|
||||
_saveNeeded = true;
|
||||
}
|
||||
bool isInfluxDb2Active() { return _influxDb2Url.length() ? true : false; }
|
||||
bool isInfluxSSL() { return _influxDb2Url.startsWith("https://"); }
|
||||
const char* getInfluxDb2PushOrg() { return _influxDb2Org.c_str(); }
|
||||
void setInfluxDb2PushOrg(String s) {
|
||||
_influxDb2Org = s;
|
||||
@ -361,7 +384,7 @@ class Config {
|
||||
bool isBLEActive() { return _colorBLE.length() ? true : false; }
|
||||
bool isWifiPushActive() {
|
||||
return (isHttpActive() || isHttp2Active() || isHttp3Active() ||
|
||||
isBrewfatherActive() || isInfluxDb2Active() || isMqttActive())
|
||||
isInfluxDb2Active() || isMqttActive())
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
@ -388,7 +411,7 @@ class Config {
|
||||
};
|
||||
|
||||
extern Config myConfig;
|
||||
extern HardwareConfig myHardwareConfig;
|
||||
extern AdvancedConfig myAdvancedConfig;
|
||||
|
||||
#endif // SRC_CONFIG_HPP_
|
||||
|
||||
|
@ -217,7 +217,7 @@ bool GyroSensor::isSensorMoving(RawGyroData &raw) {
|
||||
#endif
|
||||
|
||||
int x = abs(raw.gx), y = abs(raw.gy), z = abs(raw.gz);
|
||||
int threashold = myHardwareConfig.getGyroSensorMovingThreashold();
|
||||
int threashold = myAdvancedConfig.getGyroSensorMovingThreashold();
|
||||
|
||||
if (x > threashold || y > threashold || z > threashold) {
|
||||
Log.notice(F("GYRO: Movement detected (%d)\t%d\t%d\t%d." CR), threashold, x,
|
||||
@ -239,8 +239,8 @@ bool GyroSensor::read() {
|
||||
if (!_sensorConnected) return false;
|
||||
|
||||
readSensor(
|
||||
_lastGyroData, myHardwareConfig.getGyroReadCount(),
|
||||
myHardwareConfig.getGyroReadDelay()); // Last param is unused if
|
||||
_lastGyroData, myAdvancedConfig.getGyroReadCount(),
|
||||
myAdvancedConfig.getGyroReadDelay()); // Last param is unused if
|
||||
// GYRO_USE_INTERRUPT is defined.
|
||||
|
||||
// If the sensor is unstable we return false to signal we dont have valid
|
||||
|
@ -387,7 +387,7 @@ void PerfLogging::pushInflux() {
|
||||
// Send HTTP POST request
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
http.addHeader(F("Authorization"), auth.c_str());
|
||||
http.setTimeout(myHardwareConfig.getPushTimeout());
|
||||
http.setTimeout(myAdvancedConfig.getPushTimeout());
|
||||
int httpResponseCode = http.POST(body);
|
||||
|
||||
if (httpResponseCode == 204) {
|
||||
|
@ -60,6 +60,12 @@ void checkSleepMode(float angle, float volt) {
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if defined( FORCE_GRAVITY_MODE )
|
||||
Log.notice(
|
||||
F("MAIN: Forcing device into gravity mode for debugging" CR));
|
||||
runMode = RunMode::gravityMode;
|
||||
#endif
|
||||
|
||||
const RawGyroData &g = myConfig.getGyroCalibration();
|
||||
|
||||
if (!g.ax && !g.ay && !g.az && !g.gx && !g.gy && !g.gz) {
|
||||
@ -138,7 +144,7 @@ void setup() {
|
||||
myConfig.checkFileSystem();
|
||||
myConfig.loadFile();
|
||||
myWifi.init();
|
||||
myHardwareConfig.loadFile();
|
||||
myAdvancedConfig.loadFile();
|
||||
LOG_PERF_STOP("main-config-load");
|
||||
|
||||
// Setup watchdog
|
||||
|
@ -32,6 +32,74 @@ SOFTWARE.
|
||||
#include <pushtarget.hpp>
|
||||
#include <wifi.hpp>
|
||||
|
||||
#define PUSHINT_FILENAME "/push.dat"
|
||||
|
||||
//
|
||||
// Decrease counters
|
||||
//
|
||||
void PushIntervalTracker::update(const int index, const int defaultValue) {
|
||||
if (_counters[index] <= 0)
|
||||
_counters[index] = defaultValue;
|
||||
else
|
||||
_counters[index]--;
|
||||
}
|
||||
|
||||
//
|
||||
// Load data from file
|
||||
//
|
||||
void PushIntervalTracker::load() {
|
||||
File intFile = LittleFS.open(PUSHINT_FILENAME, "r");
|
||||
int i = 0;
|
||||
|
||||
if (intFile) {
|
||||
String line = intFile.readStringUntil('\n');
|
||||
Log.notice(F("PUSH: Read interval tracker %s." CR), line.c_str());
|
||||
|
||||
char temp[80];
|
||||
char *s, *p = &temp[0];
|
||||
int i = 0;
|
||||
|
||||
snprintf(&temp[0], sizeof(temp), "%s", line.c_str());
|
||||
while ((s = strtok_r(p, ":", &p)) != NULL) {
|
||||
_counters[i++] = atoi(s);
|
||||
}
|
||||
|
||||
intFile.close();
|
||||
}
|
||||
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: Parsed trackers: %d:%d:%d:%d:%d." CR), _counters[0], _counters[1], _counters[2], _counters[3], _counters[4] );
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Update and save counters
|
||||
//
|
||||
void PushIntervalTracker::save() {
|
||||
update(0, myAdvancedConfig.getPushIntervalHttp1());
|
||||
update(1, myAdvancedConfig.getPushIntervalHttp2());
|
||||
update(2, myAdvancedConfig.getPushIntervalHttp3());
|
||||
update(3, myAdvancedConfig.getPushIntervalInflux());
|
||||
update(4, myAdvancedConfig.getPushIntervalMqtt());
|
||||
|
||||
// If this feature is disabled we skip saving the file
|
||||
if (!myAdvancedConfig.isPushIntervalActive()) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Variabled push interval disabled." CR));
|
||||
#endif
|
||||
LittleFS.remove(PUSHINT_FILENAME);
|
||||
} else {
|
||||
Log.notice(F("PUSH: Variabled push interval enabled, updating counters." CR));
|
||||
File intFile = LittleFS.open(PUSHINT_FILENAME, "w");
|
||||
|
||||
if (intFile) {
|
||||
// Format=http1:http2:http3:influx:mqtt
|
||||
intFile.printf("%d:%d:%d:%d:%d\n", _counters[0], _counters[1], _counters[2], _counters[3], _counters[4] );
|
||||
intFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send the data to targets
|
||||
//
|
||||
@ -44,47 +112,76 @@ void PushTarget::sendAll(float angle, float gravitySG, float corrGravitySG,
|
||||
TemplatingEngine engine;
|
||||
engine.initialize(angle, gravitySG, corrGravitySG, tempC, runTime);
|
||||
|
||||
if (myConfig.isBrewfatherActive()) {
|
||||
LOG_PERF_START("push-brewfather");
|
||||
sendBrewfather(engine);
|
||||
LOG_PERF_STOP("push-brewfather");
|
||||
}
|
||||
PushIntervalTracker intDelay;
|
||||
intDelay.load();
|
||||
|
||||
if (myConfig.isHttpActive()) {
|
||||
if (myConfig.isHttpActive() && intDelay.useHttp1()) {
|
||||
LOG_PERF_START("push-http");
|
||||
sendHttpPost(engine, myConfig.isHttpSSL(), 0);
|
||||
LOG_PERF_STOP("push-http");
|
||||
}
|
||||
|
||||
if (myConfig.isHttp2Active()) {
|
||||
if (myConfig.isHttp2Active() && intDelay.useHttp2()) {
|
||||
LOG_PERF_START("push-http2");
|
||||
sendHttpPost(engine, myConfig.isHttp2SSL(), 1);
|
||||
LOG_PERF_STOP("push-http2");
|
||||
}
|
||||
|
||||
if (myConfig.isHttp3Active()) {
|
||||
if (myConfig.isHttp3Active() && intDelay.useHttp3()) {
|
||||
LOG_PERF_START("push-http3");
|
||||
sendHttpGet(engine, myConfig.isHttp3SSL());
|
||||
LOG_PERF_STOP("push-http3");
|
||||
}
|
||||
|
||||
if (myConfig.isInfluxDb2Active()) {
|
||||
if (myConfig.isInfluxDb2Active() && intDelay.useInflux()) {
|
||||
LOG_PERF_START("push-influxdb2");
|
||||
sendInfluxDb2(engine);
|
||||
sendInfluxDb2(engine, myConfig.isInfluxSSL());
|
||||
LOG_PERF_STOP("push-influxdb2");
|
||||
}
|
||||
|
||||
if (myConfig.isMqttActive()) {
|
||||
if (myConfig.isMqttActive() && intDelay.useMqtt()) {
|
||||
LOG_PERF_START("push-mqtt");
|
||||
sendMqtt(engine, myConfig.isMqttSSL());
|
||||
LOG_PERF_STOP("push-mqtt");
|
||||
}
|
||||
|
||||
intDelay.save();
|
||||
}
|
||||
|
||||
//
|
||||
// Check if the server can reduce the buffer size to save memory (ESP8266 only)
|
||||
//
|
||||
void PushTarget::probeMaxFragement( String& serverPath ) {
|
||||
#if defined(ESP8266) // Looks like this is feature is not supported by influxdb
|
||||
// Format: http:://servername:port/path
|
||||
int port = 443;
|
||||
String host =
|
||||
serverPath.substring(8); // remove the prefix or the probe will fail,
|
||||
// it needs a pure host name.
|
||||
// Remove the path if it exist
|
||||
int idx = host.indexOf("/");
|
||||
if (idx != -1) host = host.substring(0, idx);
|
||||
|
||||
// If a server port is defined, lets extract that part
|
||||
idx = host.indexOf(":");
|
||||
if (idx != -1) {
|
||||
String p = host.substring(idx+1);
|
||||
port = p.toInt();
|
||||
host = host.substring(0, idx);
|
||||
}
|
||||
|
||||
Log.notice(F("PUSH: Probing server to max fragment %s:%d" CR), host.c_str(), port);
|
||||
if (_wifiSecure.probeMaxFragmentLength(host, port, 512)) {
|
||||
Log.notice(F("PUSH: Server supports smaller SSL buffer." CR));
|
||||
_wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Send to influx db v2
|
||||
//
|
||||
void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
void PushTarget::sendInfluxDb2(TemplatingEngine& engine, bool isSecure) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to influxdb2." CR));
|
||||
#endif
|
||||
@ -97,17 +194,27 @@ void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
"&bucket=" + String(myConfig.getInfluxDb2PushBucket());
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_INFLUX);
|
||||
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
String auth = "Token " + String(myConfig.getInfluxDb2PushToken());
|
||||
_http.addHeader(F("Authorization"), auth.c_str());
|
||||
_lastCode = _http.POST(doc);
|
||||
|
||||
if (isSecure) {
|
||||
Log.notice(F("PUSH: InfluxDB, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
probeMaxFragement( serverPath );
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
_httpSecure.addHeader(F("Authorization"), auth.c_str());
|
||||
_lastCode = _httpSecure.POST(doc);
|
||||
} else {
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.addHeader(F("Authorization"), auth.c_str());
|
||||
_lastCode = _http.POST(doc);
|
||||
}
|
||||
|
||||
if (_lastCode == 204) {
|
||||
_lastSuccess = true;
|
||||
@ -117,52 +224,18 @@ void PushTarget::sendInfluxDb2(TemplatingEngine& engine) {
|
||||
errLog.addEntry("PUSH: Influxdb push failed response=" + String(_lastCode));
|
||||
}
|
||||
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
// Send data to brewfather
|
||||
//
|
||||
void PushTarget::sendBrewfather(TemplatingEngine& engine) {
|
||||
#if !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.notice(F("PUSH: Sending values to brewfather" CR));
|
||||
#endif
|
||||
_lastCode = 0;
|
||||
_lastSuccess = false;
|
||||
|
||||
String serverPath = myConfig.getBrewfatherPushUrl();
|
||||
String doc = engine.create(TemplatingEngine::TEMPLATE_BREWFATHER);
|
||||
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(PUSH_DISABLE_LOGGING)
|
||||
Log.verbose(F("PUSH: url %s." CR), serverPath.c_str());
|
||||
Log.verbose(F("PUSH: json %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
_http.addHeader(F("Content-Type"), F("application/json"));
|
||||
_lastCode = _http.POST(doc);
|
||||
|
||||
if (_lastCode == 200) {
|
||||
_lastSuccess = true;
|
||||
Log.notice(F("PUSH: Brewfather push successful, response=%d" CR),
|
||||
_lastCode);
|
||||
if (isSecure) {
|
||||
_httpSecure.end();
|
||||
_wifiSecure.stop();
|
||||
} else {
|
||||
ErrorFileLog errLog;
|
||||
errLog.addEntry("PUSH: Brewfather push failed response=" +
|
||||
String(_lastCode));
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
}
|
||||
|
||||
_http.end();
|
||||
_wifi.stop();
|
||||
tcp_cleanup();
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Add HTTP header to request
|
||||
//
|
||||
void PushTarget::addHttpHeader(HTTPClient& http, String header) {
|
||||
if (!header.length()) return;
|
||||
@ -211,22 +284,9 @@ void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
|
||||
if (isSecure) {
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
|
||||
#if defined(ESP8266)
|
||||
String host =
|
||||
serverPath.substring(8); // remove the prefix or the probe will fail,
|
||||
// it needs a pure host name.
|
||||
int idx = host.indexOf("/");
|
||||
if (idx != -1) host = host.substring(0, idx);
|
||||
|
||||
if (_wifiSecure.probeMaxFragmentLength(host, 443, 512)) {
|
||||
Log.notice(F("PUSH: HTTP server supports smaller SSL buffer." CR));
|
||||
_wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
|
||||
probeMaxFragement( serverPath );
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
_httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(_httpSecure, myConfig.getHttpHeader(0));
|
||||
@ -238,8 +298,8 @@ void PushTarget::sendHttpPost(TemplatingEngine& engine, bool isSecure,
|
||||
|
||||
_lastCode = _httpSecure.POST(doc);
|
||||
} else {
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
|
||||
if (index == 0) {
|
||||
addHttpHeader(_http, myConfig.getHttpHeader(0));
|
||||
@ -293,26 +353,13 @@ void PushTarget::sendHttpGet(TemplatingEngine& engine, bool isSecure) {
|
||||
if (isSecure) {
|
||||
Log.notice(F("PUSH: HTTP, SSL enabled without validation." CR));
|
||||
_wifiSecure.setInsecure();
|
||||
|
||||
#if defined(ESP8266)
|
||||
String host =
|
||||
serverPath.substring(8); // remove the prefix or the probe will fail,
|
||||
// it needs a pure host name.
|
||||
int idx = host.indexOf("/");
|
||||
if (idx != -1) host = host.substring(0, idx);
|
||||
|
||||
if (_wifiSecure.probeMaxFragmentLength(host, 443, 512)) {
|
||||
Log.notice(F("PUSH: HTTP server supports smaller SSL buffer." CR));
|
||||
_wifiSecure.setBufferSizes(512, 512);
|
||||
}
|
||||
#endif
|
||||
|
||||
probeMaxFragement( serverPath );
|
||||
_httpSecure.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_httpSecure.begin(_wifiSecure, serverPath);
|
||||
_httpSecure.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
_lastCode = _httpSecure.GET();
|
||||
} else {
|
||||
_http.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
_http.begin(_wifi, serverPath);
|
||||
_http.setTimeout(myHardwareConfig.getPushTimeout() * 1000);
|
||||
_lastCode = _http.GET();
|
||||
}
|
||||
|
||||
@ -360,8 +407,10 @@ void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
}
|
||||
#endif
|
||||
|
||||
mqtt.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
mqtt.begin(host.c_str(), port, _wifiSecure);
|
||||
} else {
|
||||
mqtt.setTimeout(myAdvancedConfig.getPushTimeout() * 1000);
|
||||
mqtt.begin(host.c_str(), port, _wifi);
|
||||
}
|
||||
|
||||
@ -373,9 +422,6 @@ void PushTarget::sendMqtt(TemplatingEngine& engine, bool isSecure) {
|
||||
Log.verbose(F("PUSH: data %s." CR), doc.c_str());
|
||||
#endif
|
||||
|
||||
// Send MQQT message(s)
|
||||
mqtt.setTimeout(myHardwareConfig.getPushTimeout()); // 10 seconds timeout
|
||||
|
||||
int lines = 1;
|
||||
// Find out how many lines are in the document. Each line is one
|
||||
// topic/message. | is used as new line.
|
||||
|
@ -45,12 +45,12 @@ class PushTarget {
|
||||
void sendHttpPost(TemplatingEngine& engine, bool isSecure, int index);
|
||||
void sendHttpGet(TemplatingEngine& engine, bool isSecure);
|
||||
void addHttpHeader(HTTPClient& http, String header);
|
||||
void probeMaxFragement( String& serverPath );
|
||||
|
||||
public:
|
||||
void sendAll(float angle, float gravitySG, float corrGravitySG, float tempC,
|
||||
float runTime);
|
||||
|
||||
void sendBrewfather(TemplatingEngine& engine);
|
||||
void sendHttp1(TemplatingEngine& engine, bool isSecure) {
|
||||
sendHttpPost(engine, isSecure, 0);
|
||||
}
|
||||
@ -60,12 +60,27 @@ class PushTarget {
|
||||
void sendHttp3(TemplatingEngine& engine, bool isSecure) {
|
||||
sendHttpGet(engine, isSecure);
|
||||
}
|
||||
void sendInfluxDb2(TemplatingEngine& engine);
|
||||
void sendInfluxDb2(TemplatingEngine& engine, bool isSecure);
|
||||
void sendMqtt(TemplatingEngine& engine, bool isSecure);
|
||||
int getLastCode() { return _lastCode; }
|
||||
bool getLastSuccess() { return _lastSuccess; }
|
||||
};
|
||||
|
||||
class PushIntervalTracker {
|
||||
private:
|
||||
int _counters[5] = { 0, 0, 0, 0, 0 };
|
||||
void update(const int index, const int defaultValue);
|
||||
|
||||
public:
|
||||
bool useHttp1() { return _counters[0] == 0 ? true : false; }
|
||||
bool useHttp2() { return _counters[1] == 0 ? true : false; }
|
||||
bool useHttp3() { return _counters[2] == 0 ? true : false; }
|
||||
bool useInflux() { return _counters[3] == 0 ? true : false; }
|
||||
bool useMqtt() { return _counters[4] == 0 ? true : false; }
|
||||
void load();
|
||||
void save();
|
||||
};
|
||||
|
||||
#endif // SRC_PUSHTARGET_HPP_
|
||||
|
||||
// EOF
|
||||
|
@ -32,7 +32,6 @@ SOFTWARE.
|
||||
#define PARAM_SSID "wifi-ssid"
|
||||
#define PARAM_PASS "wifi-pass"
|
||||
#define PARAM_RUNTIME_AVERAGE "runtime-average"
|
||||
#define PARAM_PUSH_BREWFATHER "brewfather-push"
|
||||
#define PARAM_TOKEN "token"
|
||||
#define PARAM_TOKEN2 "token2"
|
||||
#define PARAM_PUSH_HTTP "http-push"
|
||||
@ -80,12 +79,17 @@ SOFTWARE.
|
||||
#define PARAM_HW_GYRO_MOVING_THREASHOLD "gyro-moving-threashold"
|
||||
#define PARAM_HW_FORMULA_DEVIATION "formula-max-deviation"
|
||||
#define PARAM_HW_FORMULA_CALIBRATION_TEMP "formula-calibration-temp"
|
||||
#define PARAM_HW_WIFI_PORTALTIMEOUT "wifi-portaltimeout"
|
||||
#define PARAM_HW_WIFI_PORTAL_TIMEOUT "wifi-portal-timeout"
|
||||
#define PARAM_HW_WIFI_CONNECT_TIMEOUT "wifi-connect-timeout"
|
||||
#define PARAM_HW_PUSH_TIMEOUT "push-timeout"
|
||||
#define PARAM_HW_PUSH_INTERVAL_HTTP1 "int-http1"
|
||||
#define PARAM_HW_PUSH_INTERVAL_HTTP2 "int-http2"
|
||||
#define PARAM_HW_PUSH_INTERVAL_HTTP3 "int-http3"
|
||||
#define PARAM_HW_PUSH_INTERVAL_INFLUX "int-influx"
|
||||
#define PARAM_HW_PUSH_INTERVAL_MQTT "int-mqtt"
|
||||
#define PARAM_FORMAT_HTTP1 "http-1"
|
||||
#define PARAM_FORMAT_HTTP2 "http-2"
|
||||
#define PARAM_FORMAT_HTTP3 "http-3"
|
||||
#define PARAM_FORMAT_BREWFATHER "brewfather"
|
||||
#define PARAM_FORMAT_INFLUXDB "influxdb"
|
||||
#define PARAM_FORMAT_MQTT "mqtt"
|
||||
#define PARAM_PUSH_FORMAT "format"
|
||||
|
@ -42,7 +42,7 @@ const char iSpindleFormat[] PROGMEM =
|
||||
"\"gravity\": ${gravity}, "
|
||||
"\"angle\": ${angle}, "
|
||||
"\"battery\": ${battery}, "
|
||||
"\"rssi\": ${rssi}, "
|
||||
"\"RSSI\": ${rssi}, "
|
||||
"\"corr-gravity\": ${corr-gravity}, "
|
||||
"\"gravity-unit\": \"${gravity-unit}\", "
|
||||
"\"run-time\": ${run-time} "
|
||||
@ -64,24 +64,6 @@ const char iHttpGetFormat[] PROGMEM =
|
||||
"&gravity-unit=${gravity-unit}"
|
||||
"&run-time=${run-time}";
|
||||
|
||||
const char brewfatherFormat[] PROGMEM =
|
||||
"{"
|
||||
"\"name\": \"${mdns}\","
|
||||
"\"temp\": ${temp}, "
|
||||
"\"aux_temp\": 0, "
|
||||
"\"ext_temp\": 0, "
|
||||
"\"temp_unit\": \"${temp-unit}\", "
|
||||
"\"gravity\": ${gravity}, "
|
||||
"\"gravity_unit\": \"${gravity-unit}\", "
|
||||
"\"pressure\": 0, "
|
||||
"\"pressure_unit\": \"PSI\", "
|
||||
"\"ph\": 0, "
|
||||
"\"bpm\": 0, "
|
||||
"\"comment\": \"\", "
|
||||
"\"beer\": \"\", "
|
||||
"\"battery\": ${battery}"
|
||||
"}";
|
||||
|
||||
const char influxDbFormat[] PROGMEM =
|
||||
"measurement,host=${mdns},device=${id},temp-format=${temp-unit},gravity-"
|
||||
"format=${gravity-unit} "
|
||||
@ -174,10 +156,6 @@ const String& TemplatingEngine::create(TemplatingEngine::Templates idx) {
|
||||
baseTemplate = String(iHttpGetFormat);
|
||||
fname = TPL_FNAME_HTTP3;
|
||||
break;
|
||||
case TEMPLATE_BREWFATHER:
|
||||
baseTemplate = String(brewfatherFormat);
|
||||
// fname = TPL_FNAME_BREWFATHER;
|
||||
break;
|
||||
case TEMPLATE_INFLUX:
|
||||
baseTemplate = String(influxDbFormat);
|
||||
fname = TPL_FNAME_INFLUXDB;
|
||||
|
@ -57,13 +57,11 @@ SOFTWARE.
|
||||
#define TPL_FNAME_HTTP1 "/http-1.tpl"
|
||||
#define TPL_FNAME_HTTP2 "/http-2.tpl"
|
||||
#define TPL_FNAME_HTTP3 "/http-3.tpl"
|
||||
// #define TPL_FNAME_BREWFATHER "/brewfather.tpl"
|
||||
#define TPL_FNAME_INFLUXDB "/influxdb.tpl"
|
||||
#define TPL_FNAME_MQTT "/mqtt.tpl"
|
||||
|
||||
extern const char iSpindleFormat[] PROGMEM;
|
||||
extern const char iHttpGetFormat[] PROGMEM;
|
||||
extern const char brewfatherFormat[] PROGMEM;
|
||||
extern const char influxDbFormat[] PROGMEM;
|
||||
extern const char mqttFormat[] PROGMEM;
|
||||
|
||||
@ -133,9 +131,8 @@ class TemplatingEngine {
|
||||
TEMPLATE_HTTP1 = 0,
|
||||
TEMPLATE_HTTP2 = 1,
|
||||
TEMPLATE_HTTP3 = 2,
|
||||
TEMPLATE_BREWFATHER = 3,
|
||||
TEMPLATE_INFLUX = 4,
|
||||
TEMPLATE_MQTT = 5
|
||||
TEMPLATE_INFLUX = 3,
|
||||
TEMPLATE_MQTT = 4
|
||||
};
|
||||
|
||||
void initialize(float angle, float gravitySG, float corrGravitySG,
|
||||
|
@ -498,8 +498,6 @@ void WebServerHandler::webHandleConfigPush() {
|
||||
myConfig.setHttp2Header(_server->arg(PARAM_PUSH_HTTP2_H2).c_str(), 1);
|
||||
if (_server->hasArg(PARAM_PUSH_HTTP3))
|
||||
myConfig.setHttp3Url(_server->arg(PARAM_PUSH_HTTP3).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_BREWFATHER))
|
||||
myConfig.setBrewfatherPushUrl(_server->arg(PARAM_PUSH_BREWFATHER).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2))
|
||||
myConfig.setInfluxDb2PushUrl(_server->arg(PARAM_PUSH_INFLUXDB2).c_str());
|
||||
if (_server->hasArg(PARAM_PUSH_INFLUXDB2_ORG))
|
||||
@ -624,18 +622,18 @@ void WebServerHandler::webHandleConfigHardware() {
|
||||
}
|
||||
|
||||
//
|
||||
// Update device parameters.
|
||||
// Update advanced settings.
|
||||
//
|
||||
void WebServerHandler::webHandleDeviceParam() {
|
||||
LOG_PERF_START("webserver-api-device-param");
|
||||
void WebServerHandler::webHandleConfigAdvancedWrite() {
|
||||
LOG_PERF_START("webserver-api-config-advanced");
|
||||
String id = _server->arg(PARAM_ID);
|
||||
Log.notice(F("WEB : webServer callback for /api/device/param(post)." CR));
|
||||
Log.notice(F("WEB : webServer callback for /api/config/advaced(post)." CR));
|
||||
|
||||
if (!id.equalsIgnoreCase(myConfig.getID())) {
|
||||
Log.error(F("WEB : Wrong ID received %s, expected %s" CR), id.c_str(),
|
||||
myConfig.getID());
|
||||
_server->send(400, "text/plain", "Invalid ID.");
|
||||
LOG_PERF_STOP("webserver-api-device-param");
|
||||
LOG_PERF_STOP("webserver-api-config-advanced");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -643,41 +641,65 @@ void WebServerHandler::webHandleDeviceParam() {
|
||||
Log.verbose(F("WEB : %s." CR), getRequestArguments().c_str());
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < _server->args(); i++) {
|
||||
String s = _server->arg(i);
|
||||
if (_server->hasArg(PARAM_HW_GYRO_READ_COUNT))
|
||||
myAdvancedConfig.setGyroReadCount(_server->arg(PARAM_HW_GYRO_READ_COUNT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_GYRO_READ_DELAY))
|
||||
myAdvancedConfig.setGyroReadDelay(_server->arg(PARAM_HW_GYRO_READ_DELAY).toInt());
|
||||
if (_server->hasArg(PARAM_HW_GYRO_MOVING_THREASHOLD))
|
||||
myAdvancedConfig.setGyroSensorMovingThreashold(_server->arg(PARAM_HW_GYRO_MOVING_THREASHOLD).toInt());
|
||||
if (_server->hasArg(PARAM_HW_FORMULA_DEVIATION))
|
||||
myAdvancedConfig.setMaxFormulaCreationDeviation(_server->arg(PARAM_HW_FORMULA_DEVIATION).toFloat());
|
||||
if (_server->hasArg(PARAM_HW_FORMULA_CALIBRATION_TEMP))
|
||||
myAdvancedConfig.SetDefaultCalibrationTemp(_server->arg(PARAM_HW_FORMULA_CALIBRATION_TEMP).toFloat());
|
||||
if (_server->hasArg(PARAM_HW_WIFI_PORTAL_TIMEOUT))
|
||||
myAdvancedConfig.setWifiPortalTimeout(_server->arg(PARAM_HW_WIFI_PORTAL_TIMEOUT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_WIFI_CONNECT_TIMEOUT))
|
||||
myAdvancedConfig.setWifiConnectTimeout(_server->arg(PARAM_HW_WIFI_CONNECT_TIMEOUT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_TIMEOUT))
|
||||
myAdvancedConfig.setPushTimeout(_server->arg(PARAM_HW_PUSH_TIMEOUT).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_HTTP1))
|
||||
myAdvancedConfig.setPushIntervalHttp1(_server->arg(PARAM_HW_PUSH_INTERVAL_HTTP1).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_HTTP2))
|
||||
myAdvancedConfig.setPushIntervalHttp2(_server->arg(PARAM_HW_PUSH_INTERVAL_HTTP2).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_HTTP3))
|
||||
myAdvancedConfig.setPushIntervalHttp3(_server->arg(PARAM_HW_PUSH_INTERVAL_HTTP3).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_INFLUX))
|
||||
myAdvancedConfig.setPushIntervalInflux(_server->arg(PARAM_HW_PUSH_INTERVAL_INFLUX).toInt());
|
||||
if (_server->hasArg(PARAM_HW_PUSH_INTERVAL_MQTT))
|
||||
myAdvancedConfig.setPushIntervalMqtt(_server->arg(PARAM_HW_PUSH_INTERVAL_MQTT).toInt());
|
||||
|
||||
if (_server->argName(i).equalsIgnoreCase(PARAM_HW_GYRO_READ_COUNT))
|
||||
myHardwareConfig.setGyroReadCount(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_GYRO_READ_DELAY))
|
||||
myHardwareConfig.setGyroReadDelay(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(
|
||||
PARAM_HW_GYRO_MOVING_THREASHOLD))
|
||||
myHardwareConfig.setGyroSensorMovingThreashold(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_FORMULA_DEVIATION))
|
||||
myHardwareConfig.setMaxFormulaCreationDeviation(s.toFloat());
|
||||
else if (_server->argName(i).equalsIgnoreCase(
|
||||
PARAM_HW_FORMULA_CALIBRATION_TEMP))
|
||||
myHardwareConfig.SetDefaultCalibrationTemp(s.toFloat());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_WIFI_PORTALTIMEOUT))
|
||||
myHardwareConfig.setWifiPortalTimeout(s.toInt());
|
||||
else if (_server->argName(i).equalsIgnoreCase(PARAM_HW_PUSH_TIMEOUT))
|
||||
myHardwareConfig.setPushTimeout(s.toInt());
|
||||
}
|
||||
myAdvancedConfig.saveFile();
|
||||
_server->sendHeader("Location", "/config.htm#collapseAdvanced", true);
|
||||
_server->send(302, "text/plain", "Advanced config updated");
|
||||
LOG_PERF_STOP("webserver-api-config-advanced");
|
||||
}
|
||||
|
||||
myHardwareConfig.saveFile();
|
||||
|
||||
// Return the current configuration.
|
||||
//
|
||||
// Read advanced settings
|
||||
//
|
||||
void WebServerHandler::webHandleConfigAdvancedRead() {
|
||||
LOG_PERF_START("webserver-api-config-advanced");
|
||||
Log.notice(F("WEB : webServer callback for /api/config/advanced(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(512);
|
||||
|
||||
doc[PARAM_HW_GYRO_READ_COUNT] = myHardwareConfig.getGyroReadCount();
|
||||
doc[PARAM_HW_GYRO_READ_DELAY] = myHardwareConfig.getGyroReadDelay();
|
||||
doc[PARAM_HW_GYRO_READ_COUNT] = myAdvancedConfig.getGyroReadCount();
|
||||
doc[PARAM_HW_GYRO_READ_DELAY] = myAdvancedConfig.getGyroReadDelay();
|
||||
doc[PARAM_HW_GYRO_MOVING_THREASHOLD] =
|
||||
myHardwareConfig.getGyroSensorMovingThreashold();
|
||||
myAdvancedConfig.getGyroSensorMovingThreashold();
|
||||
doc[PARAM_HW_FORMULA_DEVIATION] =
|
||||
myHardwareConfig.getMaxFormulaCreationDeviation();
|
||||
doc[PARAM_HW_WIFI_PORTALTIMEOUT] = myHardwareConfig.getWifiPortalTimeout();
|
||||
myAdvancedConfig.getMaxFormulaCreationDeviation();
|
||||
doc[PARAM_HW_WIFI_PORTAL_TIMEOUT] = myAdvancedConfig.getWifiPortalTimeout();
|
||||
doc[PARAM_HW_WIFI_CONNECT_TIMEOUT] = myAdvancedConfig.getWifiConnectTimeout();
|
||||
doc[PARAM_HW_PUSH_TIMEOUT] = myAdvancedConfig.getPushTimeout();
|
||||
doc[PARAM_HW_FORMULA_CALIBRATION_TEMP] =
|
||||
myHardwareConfig.getDefaultCalibrationTemp();
|
||||
myAdvancedConfig.getDefaultCalibrationTemp();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP1] = myAdvancedConfig.getPushIntervalHttp1();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP2] = myAdvancedConfig.getPushIntervalHttp2();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_HTTP3] = myAdvancedConfig.getPushIntervalHttp3();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_INFLUX] = myAdvancedConfig.getPushIntervalInflux();
|
||||
doc[PARAM_HW_PUSH_INTERVAL_MQTT] = myAdvancedConfig.getPushIntervalMqtt();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
serializeJson(doc, Serial);
|
||||
@ -688,7 +710,7 @@ void WebServerHandler::webHandleDeviceParam() {
|
||||
out.reserve(512);
|
||||
serializeJson(doc, out);
|
||||
_server->send(200, "application/json", out.c_str());
|
||||
LOG_PERF_STOP("webserver-api-device-param");
|
||||
LOG_PERF_STOP("webserver-api-config-advanced");
|
||||
}
|
||||
|
||||
//
|
||||
@ -698,7 +720,7 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
LOG_PERF_START("webserver-api-formula-read");
|
||||
Log.notice(F("WEB : webServer callback for /api/formula(get)." CR));
|
||||
|
||||
DynamicJsonDocument doc(250);
|
||||
DynamicJsonDocument doc(512);
|
||||
const RawFormulaData& fd = myConfig.getFormulaData();
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
@ -731,6 +753,11 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
doc["a3"] = reduceFloatPrecision(fd.a[2], 2);
|
||||
doc["a4"] = reduceFloatPrecision(fd.a[3], 2);
|
||||
doc["a5"] = reduceFloatPrecision(fd.a[4], 2);
|
||||
doc["a6"] = reduceFloatPrecision(fd.a[5], 2);
|
||||
doc["a7"] = reduceFloatPrecision(fd.a[6], 2);
|
||||
doc["a8"] = reduceFloatPrecision(fd.a[7], 2);
|
||||
doc["a9"] = reduceFloatPrecision(fd.a[8], 2);
|
||||
doc["a10"] = reduceFloatPrecision(fd.a[9], 2);
|
||||
|
||||
if (myConfig.isGravityPlato()) {
|
||||
doc["g1"] = reduceFloatPrecision(convertToPlato(fd.g[0]), 1);
|
||||
@ -738,12 +765,22 @@ void WebServerHandler::webHandleFormulaRead() {
|
||||
doc["g3"] = reduceFloatPrecision(convertToPlato(fd.g[2]), 1);
|
||||
doc["g4"] = reduceFloatPrecision(convertToPlato(fd.g[3]), 1);
|
||||
doc["g5"] = reduceFloatPrecision(convertToPlato(fd.g[4]), 1);
|
||||
doc["g6"] = reduceFloatPrecision(convertToPlato(fd.g[5]), 1);
|
||||
doc["g7"] = reduceFloatPrecision(convertToPlato(fd.g[6]), 1);
|
||||
doc["g8"] = reduceFloatPrecision(convertToPlato(fd.g[7]), 1);
|
||||
doc["g9"] = reduceFloatPrecision(convertToPlato(fd.g[8]), 1);
|
||||
doc["g10"] = reduceFloatPrecision(convertToPlato(fd.g[9]), 1);
|
||||
} else {
|
||||
doc["g1"] = reduceFloatPrecision(fd.g[0], 4);
|
||||
doc["g2"] = reduceFloatPrecision(fd.g[1], 4);
|
||||
doc["g3"] = reduceFloatPrecision(fd.g[2], 4);
|
||||
doc["g4"] = reduceFloatPrecision(fd.g[3], 4);
|
||||
doc["g5"] = reduceFloatPrecision(fd.g[4], 4);
|
||||
doc["g6"] = reduceFloatPrecision(fd.g[5], 4);
|
||||
doc["g7"] = reduceFloatPrecision(fd.g[6], 4);
|
||||
doc["g8"] = reduceFloatPrecision(fd.g[7], 4);
|
||||
doc["g9"] = reduceFloatPrecision(fd.g[8], 4);
|
||||
doc["g10"] = reduceFloatPrecision(fd.g[9], 4);
|
||||
}
|
||||
|
||||
#if LOG_LEVEL == 6 && !defined(WEB_DISABLE_LOGGING)
|
||||
@ -792,10 +829,6 @@ void WebServerHandler::webHandleConfigFormatWrite() {
|
||||
} else if (_server->hasArg(PARAM_FORMAT_MQTT)) {
|
||||
success = writeFile(TPL_FNAME_MQTT, _server->arg(PARAM_FORMAT_MQTT));
|
||||
}
|
||||
/*else if (_server->hasArg(PARAM_FORMAT_BREWFATHER)) {
|
||||
success = writeFile(TPL_FNAME_BREWFATHER,
|
||||
_server->arg(PARAM_FORMAT_BREWFATHER));
|
||||
}*/
|
||||
|
||||
if (success) {
|
||||
_server->sendHeader("Location", "/format.htm", true);
|
||||
@ -841,11 +874,7 @@ void WebServerHandler::webHandleTestPush() {
|
||||
PushTarget push;
|
||||
bool enabled = false;
|
||||
|
||||
if (!type.compareTo(PARAM_FORMAT_BREWFATHER) &&
|
||||
myConfig.isBrewfatherActive()) {
|
||||
push.sendBrewfather(engine);
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_HTTP1) && myConfig.isHttpActive()) {
|
||||
if (!type.compareTo(PARAM_FORMAT_HTTP1) && myConfig.isHttpActive()) {
|
||||
push.sendHttp1(engine, myConfig.isHttpSSL());
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_HTTP2) && myConfig.isHttp2Active()) {
|
||||
@ -856,7 +885,7 @@ void WebServerHandler::webHandleTestPush() {
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_INFLUXDB) &&
|
||||
myConfig.isInfluxDb2Active()) {
|
||||
push.sendInfluxDb2(engine);
|
||||
push.sendInfluxDb2(engine, myConfig.isInfluxSSL());
|
||||
enabled = true;
|
||||
} else if (!type.compareTo(PARAM_FORMAT_MQTT) && myConfig.isMqttActive()) {
|
||||
push.sendMqtt(engine, myConfig.isMqttSSL());
|
||||
@ -955,12 +984,6 @@ void WebServerHandler::webHandleConfigFormatRead() {
|
||||
else
|
||||
doc[PARAM_FORMAT_HTTP3] = urlencode(String(&iHttpGetFormat[0]));
|
||||
|
||||
/*s = readFile(TPL_FNAME_BREWFATHER);
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_BREWFATHER] = urlencode(s);
|
||||
else
|
||||
doc[PARAM_FORMAT_BREWFATHER] = urlencode(&brewfatherFormat[0]);*/
|
||||
|
||||
s = readFile(TPL_FNAME_INFLUXDB);
|
||||
if (s.length())
|
||||
doc[PARAM_FORMAT_INFLUXDB] = urlencode(s);
|
||||
@ -1011,6 +1034,11 @@ void WebServerHandler::webHandleFormulaWrite() {
|
||||
fd.a[2] = _server->arg("a3").toDouble();
|
||||
fd.a[3] = _server->arg("a4").toDouble();
|
||||
fd.a[4] = _server->arg("a5").toDouble();
|
||||
fd.a[5] = _server->arg("a6").toDouble();
|
||||
fd.a[6] = _server->arg("a7").toDouble();
|
||||
fd.a[7] = _server->arg("a8").toDouble();
|
||||
fd.a[8] = _server->arg("a9").toDouble();
|
||||
fd.a[9] = _server->arg("a10").toDouble();
|
||||
|
||||
if (myConfig.isGravityPlato()) {
|
||||
fd.g[0] = convertToSG(_server->arg("g1").toDouble());
|
||||
@ -1018,12 +1046,22 @@ void WebServerHandler::webHandleFormulaWrite() {
|
||||
fd.g[2] = convertToSG(_server->arg("g3").toDouble());
|
||||
fd.g[3] = convertToSG(_server->arg("g4").toDouble());
|
||||
fd.g[4] = convertToSG(_server->arg("g5").toDouble());
|
||||
fd.g[5] = convertToSG(_server->arg("g6").toDouble());
|
||||
fd.g[6] = convertToSG(_server->arg("g7").toDouble());
|
||||
fd.g[7] = convertToSG(_server->arg("g8").toDouble());
|
||||
fd.g[8] = convertToSG(_server->arg("g9").toDouble());
|
||||
fd.g[9] = convertToSG(_server->arg("g10").toDouble());
|
||||
} else {
|
||||
fd.g[0] = _server->arg("g1").toDouble();
|
||||
fd.g[1] = _server->arg("g2").toDouble();
|
||||
fd.g[2] = _server->arg("g3").toDouble();
|
||||
fd.g[3] = _server->arg("g4").toDouble();
|
||||
fd.g[4] = _server->arg("g5").toDouble();
|
||||
fd.g[5] = _server->arg("g6").toDouble();
|
||||
fd.g[6] = _server->arg("g7").toDouble();
|
||||
fd.g[7] = _server->arg("g8").toDouble();
|
||||
fd.g[8] = _server->arg("g9").toDouble();
|
||||
fd.g[9] = _server->arg("g10").toDouble();
|
||||
}
|
||||
|
||||
myConfig.setFormulaData(fd);
|
||||
@ -1231,9 +1269,12 @@ bool WebServerHandler::setupWebServer() {
|
||||
_server->on("/api/config/format", HTTP_POST,
|
||||
std::bind(&WebServerHandler::webHandleConfigFormatWrite,
|
||||
this)); // Change template formats
|
||||
_server->on("/api/device/param", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleDeviceParam,
|
||||
this)); // Change device params
|
||||
_server->on("/api/config/advanced", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleConfigAdvancedRead,
|
||||
this)); // Read advanced settings
|
||||
_server->on("/api/config/advanced", HTTP_POST,
|
||||
std::bind(&WebServerHandler::webHandleConfigAdvancedWrite,
|
||||
this)); // Change advanced params
|
||||
_server->on("/api/test/push", HTTP_GET,
|
||||
std::bind(&WebServerHandler::webHandleTestPush,
|
||||
this)); //
|
||||
|
@ -60,6 +60,8 @@ class WebServerHandler {
|
||||
void webHandleConfig();
|
||||
void webHandleFormulaWrite();
|
||||
void webHandleFormulaRead();
|
||||
void webHandleConfigAdvancedRead();
|
||||
void webHandleConfigAdvancedWrite();
|
||||
void webHandleConfigHardware();
|
||||
void webHandleConfigGravity();
|
||||
void webHandleConfigPush();
|
||||
@ -74,7 +76,6 @@ class WebServerHandler {
|
||||
void webHandleCalibrate();
|
||||
void webHandleUploadFile();
|
||||
void webHandleUpload();
|
||||
void webHandleDeviceParam();
|
||||
void webHandlePageNotFound();
|
||||
|
||||
String readFile(String fname);
|
||||
|
@ -137,7 +137,7 @@ void WifiConnection::startPortal() {
|
||||
myWifiManager->setMinimumSignalQuality(-1);
|
||||
myWifiManager->setConfigPortalChannel(0);
|
||||
myWifiManager->setConfigPortalTimeout(
|
||||
myHardwareConfig.getWifiPortalTimeout());
|
||||
myAdvancedConfig.getWifiPortalTimeout());
|
||||
|
||||
String mdns("<p>Default mDNS name is: http://");
|
||||
mdns += myConfig.getMDNS();
|
||||
@ -221,7 +221,7 @@ bool WifiConnection::waitForConnection(int maxTime) {
|
||||
//
|
||||
bool WifiConnection::connect() {
|
||||
connectAsync();
|
||||
return waitForConnection(20); // 20 seconds.
|
||||
return waitForConnection(myAdvancedConfig.getWifiConnectTimeout());
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -24,7 +24,6 @@ Other parameters are the same as in the configuration guide.
|
||||
"ota-url": "http://192.168.1.50:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"ble": "color",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=Qwerty",
|
||||
"token": "token",
|
||||
"token2": "token2",
|
||||
"http-push": "http://192.168.1.50:9090/api/v1/Qwerty/telemetry",
|
||||
@ -63,11 +62,19 @@ Other parameters are the same as in the configuration guide.
|
||||
"a3":35,
|
||||
"a4":40,
|
||||
"a5":45,
|
||||
"a5":0,
|
||||
"a6":0,
|
||||
"a7":0,
|
||||
"a8":0,
|
||||
"g1":1,
|
||||
"g2":1.01,
|
||||
"g3":1.02,
|
||||
"g4":1.03,
|
||||
"g5":1.04
|
||||
"g4":1.04,
|
||||
"g5":1,
|
||||
"g6":1,
|
||||
"g7":1,
|
||||
"g8":1
|
||||
},
|
||||
"angle": 90.93,
|
||||
"gravity": 1.105,
|
||||
@ -120,8 +127,8 @@ GET: /api/config/formula
|
||||
|
||||
Retrive the data used for formula calculation data via an HTTP GET command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings in SG or Plato depending on the device-format.
|
||||
* ``a1``-``a8`` are the angles/tilt readings (up to 8 are currently supported)
|
||||
* ``g1``-``g8`` are the corresponding gravity reaadings in SG or Plato depending on the device-format.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@ -132,16 +139,52 @@ Retrive the data used for formula calculation data via an HTTP GET command. Payl
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"a6": 0,
|
||||
"a7": 0,
|
||||
"a8": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1,
|
||||
"g6": 1,
|
||||
"g7": 1,
|
||||
"g8": 1,
|
||||
"error": "Potential error message",
|
||||
"gravity-format": "G",
|
||||
"gravity-formula": "0.0*tilt^3+0.0*tilt^2+0.0017978*tilt+0.9436"
|
||||
}
|
||||
|
||||
|
||||
GET: /api/config/advanced
|
||||
=========================
|
||||
|
||||
Used for adjusting some internal constants and other advanced settings. Should be used with caution.
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"gyro-read-count": 50,
|
||||
"gyro-read-delay": 3150,
|
||||
"gyro-moving-threashold": 500,
|
||||
"formula-max-deviation": 1.6,
|
||||
"wifi-portaltimeout": 120,
|
||||
"formula-calibration-temp": 20,
|
||||
"int-http1": 0,
|
||||
"int-http2": 0,
|
||||
"int-http3": 0,
|
||||
"int-influx": 0,
|
||||
"int-mqtt": 0
|
||||
}
|
||||
|
||||
POST: /api/config/advanced
|
||||
==========================
|
||||
|
||||
Same parameters as above.
|
||||
|
||||
Payload should be in standard format used for posting a form
|
||||
|
||||
|
||||
GET: /api/clearwifi
|
||||
===================
|
||||
|
||||
@ -173,7 +216,7 @@ Trigger a push on one of the targets, used to validate the configuration from th
|
||||
|
||||
Requires to parameters to function /api/test/push?id=<deviceid>&format=<format>
|
||||
|
||||
* ``format`` defines which endpoint to test, valid values are; http-1, http-2, brewfather, influxdb, mqtt
|
||||
* ``format`` defines which endpoint to test, valid values are; http-1, http-2, http-3, influxdb, mqtt
|
||||
|
||||
The response is an json message with the following values.
|
||||
|
||||
@ -223,7 +266,6 @@ Payload should be in standard format used for posting a form. Such as as: `id=va
|
||||
http-push-h2=
|
||||
http-push2-h1=
|
||||
http-push2-h2=
|
||||
brewfather-push=
|
||||
influxdb2-push=http://192.168.1.50:8086
|
||||
influxdb2-org=
|
||||
influxdb2-bucket=
|
||||
@ -282,8 +324,8 @@ POST: /api/config/formula
|
||||
|
||||
Used to update formula calculation data via an HTTP POST command. Payload is in JSON format.
|
||||
|
||||
* ``a1``-``a4`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g4`` are the corresponding gravity reaadings (in SG)
|
||||
* ``a1``-``a8`` are the angles/tilt readings (up to 5 are currently supported)
|
||||
* ``g1``-``g8`` are the corresponding gravity reaadings (in SG)
|
||||
|
||||
Payload should be in standard format used for posting a form. Such as as: `id=value&mdns=value` etc. Key value pairs are shown below.
|
||||
|
||||
@ -295,11 +337,17 @@ Payload should be in standard format used for posting a form. Such as as: `id=va
|
||||
a3=58
|
||||
a4=0
|
||||
a5=0
|
||||
a6=0
|
||||
a7=0
|
||||
a8=0
|
||||
g1=1.000
|
||||
g2=1.053
|
||||
g3=1.062
|
||||
g4=1
|
||||
g5=1
|
||||
g6=1
|
||||
g7=1
|
||||
g8=1
|
||||
|
||||
|
||||
Calling the API's from Python
|
||||
@ -346,7 +394,6 @@ The requests package converts the json to standard form post format.
|
||||
"http-push-h2": "",
|
||||
"http-push2-h1": ""
|
||||
"http-push2-h2": "",
|
||||
"brewfather-push": "",
|
||||
"influxdb2-push": "",
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
@ -383,10 +430,16 @@ The requests package converts the json to standard form post format.
|
||||
"a3": 58,
|
||||
"a4": 0,
|
||||
"a5": 0,
|
||||
"a6": 0,
|
||||
"a7": 0,
|
||||
"a8": 0,
|
||||
"g1": 1.000,
|
||||
"g2": 1.053,
|
||||
"g3": 1.062,
|
||||
"g4": 1,
|
||||
"g5": 1
|
||||
"g5": 1,
|
||||
"g6": 1,
|
||||
"g7": 1,
|
||||
"g8": 1
|
||||
}
|
||||
set_config( url, json )
|
||||
|
@ -22,7 +22,7 @@ copyright = '2021-2022, Magnus Persson'
|
||||
author = 'Magnus Persson'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.9.0'
|
||||
release = '1.0.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
@ -121,12 +121,6 @@ If you add the prefix `https://` then the device will use SSL when sending data.
|
||||
|
||||
The token is included in the default format for the HTTP GET url but can be used for any of the formats. For HTTP GET use can use this for an authorization token with for instance ubidots or blynk http api.
|
||||
|
||||
* **Brewfather URL:**
|
||||
|
||||
Endpoint to send data via http to brewfather. Format used :ref:`data-formats-brewfather`
|
||||
|
||||
SSL is not supported for this target.
|
||||
|
||||
* **HTTP Headers**
|
||||
|
||||
.. image:: images/config-popup1.png
|
||||
@ -281,3 +275,14 @@ This option gives you the possibility to install an new version of the firmware
|
||||
:alt: Update firmware
|
||||
|
||||
|
||||
Advanded Settings
|
||||
+++++++++++++++++
|
||||
|
||||
.. image:: images/config5.png
|
||||
:width: 800
|
||||
:alt: Advanced Settings
|
||||
|
||||
* **Header:**
|
||||
|
||||
To be described
|
||||
|
||||
|
@ -26,7 +26,7 @@ This is the format used for standard http posts.
|
||||
"gravity": 1.0050,
|
||||
"angle": 45.34,
|
||||
"battery": 3.67,
|
||||
"rssi": -12,
|
||||
"RSSI": -12,
|
||||
|
||||
"corr-gravity": 1.0050,
|
||||
"gravity-unit": "G",
|
||||
@ -47,32 +47,13 @@ This is the format template used to create the json above.
|
||||
"gravity": ${gravity},
|
||||
"angle": ${angle},
|
||||
"battery": ${battery},
|
||||
"rssi": ${rssi},
|
||||
"RSSI": ${rssi},
|
||||
"corr-gravity": ${corr-gravity},
|
||||
"gravity-unit": "${gravity-unit}",
|
||||
"run-time": ${run-time}
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-brewfather:
|
||||
|
||||
Brewfather format
|
||||
=================
|
||||
|
||||
This is the format for Brewfather. See: `Brewfather API docs <https://docs.brewfather.app/integrations/custom-stream>`_
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"name" : "gravmon",
|
||||
"temp": 20.5,
|
||||
"temp_unit": "C",
|
||||
"battery": 3.67,
|
||||
"gravity": 1.0050,
|
||||
"gravity_unit": "G",
|
||||
}
|
||||
|
||||
|
||||
.. _data-formats-influxdb2:
|
||||
|
||||
HTTP Get
|
||||
|
@ -7,7 +7,7 @@ Welcome to GravityMon's documentation!
|
||||
######################################
|
||||
|
||||
.. note::
|
||||
This documentation reflects **v0.9**. Last updated 2022-04-23
|
||||
This documentation reflects **v1.0**. Last updated 2022-04-26
|
||||
|
||||
.. image:: images/gravitymon.gif
|
||||
:width: 800
|
||||
|
@ -3,7 +3,23 @@
|
||||
Releases
|
||||
########
|
||||
|
||||
v1.0.0
|
||||
------
|
||||
* Upgraded to bootstrap v5.1 for web pages.
|
||||
* Added tooltips to all fields in user interface
|
||||
* Removed brewfather option (can use standard HTTP options), the old apporach can still be used via changing format template.
|
||||
* Added 5 more points for formula creation, so a total of 10 angles/gravity values can be stored.
|
||||
* Added function on format page so that it's easy to copy a format template from the docs (simplify service integration).
|
||||
* Added https support for Influxdb
|
||||
* Added possibility to have variable push intervals for different endpoints so that different frequency can be used, for example; 5min mqtt, 15min brewfather.
|
||||
* Added advanced settings to configuration for adjusting some internal values (gyro reads, accepted formula deviation, timeouts, moving detection etc).
|
||||
* Added additional http error codes to troubleshooting documentation
|
||||
* Installation instructions updated on how to find the device after wifi has been configured.
|
||||
* Documentation on brewfather has been updated to adress SG/Plato conversion
|
||||
* BUG: Fixed issue in formula calculation in case there were a gap in the data series
|
||||
* BUG: Field name for wifi strenght changed from "rssi" to "RSSI"
|
||||
|
||||
* TODO: Fix documentation for advanced settings.
|
||||
|
||||
v0.9.0
|
||||
------
|
||||
|
@ -10,14 +10,26 @@ Brewfather
|
||||
|
||||
Brewfather is an all in one service that allows you to manage you recepies and brews.
|
||||
|
||||
.. tip::
|
||||
**Option 1** - iSpindle Endpoint
|
||||
|
||||
The integration named Brewfather is uses the custom stream endpoint in brewfather not the standard iSpindle
|
||||
endpoint. You can use the iSpindle endpoint as well. In that case just use the http-1 or http-2 fields.
|
||||
This opion makes use of the standard http (1 or 2) endpoints in the push section. If you are using SG then the device name needs to end with [SG] or brewfather will assume
|
||||
that the data is in plato. You can also modify the format template using the following options:
|
||||
|
||||
**Option 1** - Custom Stream
|
||||
Update the following part `"gravity": ${gravity-plato},` or `"name" : "${mdns}[SG]",``
|
||||
|
||||
This option makes use of the push endpoint called Brewfather in the UI. Just enter the http stream adress found
|
||||
This makes use of the standard format template, no changes needed.
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://log.brewfather.net/ispindel?id=<yourid>
|
||||
|
||||
|
||||
Documentation on this can be found under `Brewfather iSpindle Endpoint <https://docs.brewfather.app/integrations/ispindel>`_
|
||||
|
||||
|
||||
**Option 2** - Custom Stream
|
||||
|
||||
This option makes use of the http push endpoint with a custom format template. Just enter the http stream adress found
|
||||
on brewfather, not other settings are needed. The stream endpoint URL has the following format:
|
||||
|
||||
.. code-block::
|
||||
@ -48,18 +60,6 @@ The implementation is basically a http request with the following format templat
|
||||
}
|
||||
|
||||
|
||||
**Option 2** - iSpindle Endpoint
|
||||
|
||||
This opion makes use of the standard http (1 or 2) endpoints in the push section. If you are using SG then the device name needs to end with [SG] or brewfather will assume
|
||||
that the data is in plato. The brewfather iSpindle endpoint has the following format:
|
||||
|
||||
.. code-block::
|
||||
|
||||
http://log.brewfather.net/ispindel?id=<yourid>
|
||||
|
||||
|
||||
Documentation on this can be found under `Brewfather iSpindle Endpoint <https://docs.brewfather.app/integrations/ispindel>`_
|
||||
|
||||
Fermentrack
|
||||
+++++++++++
|
||||
|
||||
|
@ -31,7 +31,6 @@ Log errors
|
||||
Check the format for your custom header. This means it has not a correct format.
|
||||
|
||||
* Influxdb push failed response
|
||||
* Brewfather push failed response
|
||||
* HTTP push failed response
|
||||
|
||||
All these errors are standard http error codes. This are the commone ones;
|
||||
@ -41,6 +40,20 @@ Log errors
|
||||
* 403 - Forbidden. Could be an issue with token or URL.
|
||||
* 404 - Not found. Probably a wrong URL.
|
||||
|
||||
In some cases there can be negative error codes which have the following meaning:
|
||||
|
||||
* -1 - Connection refused
|
||||
* -2 - Send header failed
|
||||
* -3 - Send payload failed
|
||||
* -4 - Not connected
|
||||
* -5 - Connection lost
|
||||
* -6 - No stream
|
||||
* -7 - No HTTP server
|
||||
* -8 - Too little RAM available
|
||||
* -9 - Error encoding
|
||||
* -10 - Error writing to stream
|
||||
* -11 - Read timeout
|
||||
|
||||
* MQTT push on <topic> failed error
|
||||
|
||||
* -3 - Network failed connected
|
||||
|
15
test/adv.json
Normal file
15
test/adv.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"gyro-read-count": 51,
|
||||
"gyro-read-delay": 3151,
|
||||
"gyro-moving-threashold": 501,
|
||||
"formula-max-deviation": 1.7,
|
||||
"wifi-portal-timeout": 121,
|
||||
"wifi-connect-timeout": 21,
|
||||
"formula-calibration-temp": 21,
|
||||
"push-timeout": 10,
|
||||
"int-http1": 1,
|
||||
"int-http2": 2,
|
||||
"int-http3": 3,
|
||||
"int-influx": 4,
|
||||
"int-mqtt": 5
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
"id": "7376ef",
|
||||
"ota-url": "http://192.168.1.100:80/firmware/gravmon/",
|
||||
"temp-format": "C",
|
||||
"brewfather-push": "http://log.brewfather.net/stream?id=KfkJU43jUFfj",
|
||||
"http-push": "http://192.168.1.10:9090/api/v1/ZYfjlUNeiuyu9N/telemetry",
|
||||
"http-push-h1": "Auth: Basic T7IF9DD9fF3RDddE=",
|
||||
"http-push-h2": "Auth: Advanced T7IF9DD9fF3RDddE=",
|
||||
|
@ -37,7 +37,6 @@ json = { "id": id,
|
||||
"http-push-h2": "",
|
||||
"http-push2-h1": "Content-Type: application/json",
|
||||
"http-push2-h2": "",
|
||||
"brewfather-push": "", # Brewfather URL
|
||||
"influxdb2-push": "", # InfluxDB2 settings
|
||||
"influxdb2-org": "",
|
||||
"influxdb2-bucket": "",
|
||||
|
@ -6,9 +6,20 @@
|
||||
"a2": 45,
|
||||
"a4": 55,
|
||||
"a5": 30,
|
||||
"a6": 30,
|
||||
"a7": 30,
|
||||
"a8": 30,
|
||||
"a9": 30,
|
||||
"a10": 30,
|
||||
"g1": 1.000,
|
||||
"g3": 1.010,
|
||||
"g2": 1.025,
|
||||
"g4": 1.040,
|
||||
"g5": 1.005
|
||||
"g5": 1.005,
|
||||
"g6": 1.005,
|
||||
"g7": 1.005,
|
||||
"g8": 1.005,
|
||||
"g9": 1.005,
|
||||
"g10": 1.005,
|
||||
"error": ""
|
||||
}
|
Loading…
Reference in New Issue
Block a user