init commit

This commit is contained in:
Hans Perera
2020-07-04 12:17:21 -05:00
parent 12cb13e806
commit 25b8e2ca72
14 changed files with 1291 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json
+7
View File
File diff suppressed because one or more lines are too long
+110
View File
@@ -0,0 +1,110 @@
/*
* Globals
*/
/* Links */
a,
a:focus,
a:hover {
color: #fff;
}
/* Custom default button */
.btn-secondary,
.btn-secondary:hover,
.btn-secondary:focus {
color: #333;
text-shadow: none; /* Prevent inheritance from `body` */
background-color: #fff;
border: .05rem solid #fff;
}
/*
* Base structure
*/
html,
body {
height: 100%;
background-color: #333;
}
body {
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-pack: center;
-webkit-box-pack: center;
justify-content: center;
color: #fff;
text-shadow: 0 .05rem .1rem rgba(0, 0, 0, .5);
box-shadow: inset 0 0 5rem rgba(0, 0, 0, .5);
}
/* .cover-container {
max-width: 42em;
} */
/*
* Header
*/
.masthead {
margin-bottom: 2rem;
}
.masthead-brand {
margin-bottom: 0;
}
.nav-masthead .nav-link {
padding: .25rem 0;
font-weight: 700;
color: rgba(255, 255, 255, .5);
background-color: transparent;
border-bottom: .25rem solid transparent;
}
.nav-masthead .nav-link:hover,
.nav-masthead .nav-link:focus {
border-bottom-color: rgba(255, 255, 255, .25);
}
.nav-masthead .nav-link + .nav-link {
margin-left: 1rem;
}
.nav-masthead .active {
color: #fff;
border-bottom-color: #fff;
}
@media (min-width: 48em) {
.masthead-brand {
float: left;
}
.nav-masthead {
float: right;
}
}
/*
* Cover
*/
.cover {
padding: 0 1.5rem;
}
.cover .btn-lg {
padding: .75rem 1.25rem;
font-weight: 700;
}
/*
* Footer
*/
.mastfoot {
color: rgba(255, 255, 255, .5);
}
+204
View File
@@ -0,0 +1,204 @@
/* Regular Styles */
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
font-family: 'Open Sans', sans-serif;
color: #4e555f;
font-size: 14px;
}
/* Bar Delays Animation Function */
/* Chart Styles */
.chart-wrap {
max-width: 900px;
margin: 0 auto;
padding: 10px;
/* border: 1px solid #eeeeee; */
}
.chart-wrap .chart-title {
margin-bottom: 10px;
font-size: 16px;
text-align: center;
text-transform: uppercase;
}
.chart-wrap .row {
position: relative;
height: 26px;
margin-bottom: 15px;
}
.chart-wrap .bar-wrap {
position: relative;
background: #d7dbde;
width: 100%;
height: 100%;
overflow: hidden;
}
.chart-wrap .bar-wrap .bar {
position: absolute;
top: 0;
left: 0;
height: 100%;
background: #f37167;
transition: all 1s;
transform: translateX(-100%);
}
.chart-wrap .bar-wrap .bar.in {
transform: translateX(0%);
}
.chart-wrap .row:nth-of-type(1) .bar {
transition: 1s 400ms;
}
.chart-wrap .row:nth-of-type(2) .bar {
transition: 1s 600ms;
}
.chart-wrap .row:nth-of-type(3) .bar {
transition: 1s 800ms;
}
.chart-wrap .row:nth-of-type(4) .bar {
transition: 1s 1000ms;
}
.chart-wrap .row:nth-of-type(5) .bar {
transition: 1s 1200ms;
}
.chart-wrap .row:nth-of-type(6) .bar {
transition: 1s 1400ms;
}
.chart-wrap .row:nth-of-type(7) .bar {
transition: 1s 1600ms;
}
.chart-wrap .label {
position: absolute;
top: 0;
left: 0;
width: 30px;
padding-left: 10px;
text-align: left;
line-height: 26px;
text-transform: uppercase;
font-weight: bold;
z-index: 10;
}
.chart-wrap .number {
position: absolute;
top: 0;
right: 0;
width: 30px;
padding-right: 10px;
font-size: 18px;
line-height: 26px;
font-weight: bold;
text-align: right;
z-index: 10;
}
@media screen and (min-width: 800px) {
.chart-wrap .row {
padding: 0 40px 0 40px;
}
.chart-wrap .label {
text-align: right;
padding-left: 0;
}
.chart-wrap .number {
text-align: left;
padding-right: 0;
}
}
/* Battery indicator*/
#wrapper { width:200px;}
.battery2 {
position: relative;
display: block;
background: #ffffff;
width: 10px;
height: 28px;
float: left;
}
.battery2 {
margin-top:48px;
background: rgb(157,213,58); /* Old browsers */
background: -moz-linear-gradient(top, rgba(157,213,58,1) 0%, rgba(161,213,79,1) 50%, rgba(128,194,23,1) 51%, rgba(124,188,10,1) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(157,213,58,1)), color-stop(50%,rgba(161,213,79,1)), color-stop(51%,rgba(128,194,23,1)), color-stop(100%,rgba(124,188,10,1)));
background: -webkit-linear-gradient(top, rgba(157,213,58,1) 0%,rgba(161,213,79,1) 50%,rgba(128,194,23,1) 51%,rgba(124,188,10,1) 100%);
background: -o-linear-gradient(top, rgba(157,213,58,1) 0%,rgba(161,213,79,1) 50%,rgba(128,194,23,1) 51%,rgba(124,188,10,1) 100%);
background: -ms-linear-gradient(top, rgba(157,213,58,1) 0%,rgba(161,213,79,1) 50%,rgba(128,194,23,1) 51%,rgba(124,188,10,1) 100%);
background: linear-gradient(to bottom, rgba(157,213,58,1) 0%,rgba(161,213,79,1) 50%,rgba(128,194,23,1) 51%,rgba(124,188,10,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 );
}
.battery2:before {
content: "";
display: block;
background: transparent;
border: 6px solid #ffffff;
margin: -12px;
width: 85px;
height: 40px;
position: absolute;
border-radius: 2px;
-moz-box-shadow: 0 0 5px 5px #888;
-webkit-box-shadow: 0 0 5px 5px#888;
box-shadow: -2px 0 5px 2px #222;
z-index:2000;
}
.battery2:after {
content: "";
display: block;
background: #ffffff;
border: 6px solid #ffffff;
margin: 0px 80px;
width: 6px;
height: 16px;
position: absolute;
border-radius: 2px;
-moz-box-shadow: 0 0 5px 5px #888;
-webkit-box-shadow: 0 0 5px 5px #888;
box-shadow: 2px 0 5px 2px #222;
z-index:0;
}
@-webkit-keyframes charging2 {
from { width: 0px; background: #f00; }
to { width: 72px; background: #0f0; }
}
@-webkit-keyframes showcharging {
from { width: 0px; background: #f00; }
to { width: 20px; background: #0f0; }
}
.charging2 {
-webkit-animation: charging2 20s ease-in-out infinite;
}
.showcharging {
-webkit-animation: showcharging 20s ease-in-out infinite;
}
.charging_txt {
font-family: arial, helvetica, sans-serif;
font-weight:bold;
font-style:italic;
text-align:center;
clear:both;
color:#fff;
text-shadow:0 2px #000;
}
@-webkit-keyframes glow {
from { opacity:0;}
to { opacity:1;}
}
.glow {
-webkit-animation: glow 3s ease-in-out infinite;
-webkit-animation-direction: alternate;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+453
View File
@@ -0,0 +1,453 @@
<!DOCTYPE html>
<html charset="UTF-8">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/css/custom.css">
<link href="/css/bootstrap.min.css" rel="stylesheet" title="main">
<link rel="stylesheet" href="/css/cover.css">
<!-- <link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/superhero/bootstrap.min.css" rel="stylesheet" title="main"> -->
<!-- <script src="//cdn.rawgit.com/Mikhus/canvas-gauges/gh-pages/download/2.1.5/all/gauge.min.js"></script> -->
<title>BBQ Master</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container d-flex h-100 p-3 mx-auto flex-column">
<header class="masthead">
<div class="inner">
<h3 class="masthead-brand">BBQ Master</h3>
<nav class="nav nav-masthead justify-content-center navbar-expand-lg tabs" id="tab">
<a class="nav-link active" href="#tab_mesures" data-toggle="tab">Home</a>
<a class="nav-link" href="#tab_configuration" data-toggle="tab">Settings</a>
</nav>
</div>
</header>
<main role="main" class="container">
<!-- <div class="row">
<div class="col-md-4">
<h1>BBQ Master</h1>
</div>
<div class="col-md-4 pull-right" id="wrapper">
<div class="battery2 showcharging"></div>
</div>
</div> -->
<!-- <div class="row"> -->
<!-- <ul class="nav nav-tabs" id="tab">
<li class="active"><a href="#tab_mesures" data-toggle="tab">Dashboard</a></li>
<li><a href="#tab_configuration" data-toggle="tab">Configuration</a></li>
</ul> -->
<div class="content position-relative">
<div class="tab-pane fade in active show" id="tab_mesures">
<!-- <h2>Smoker Controller</h2> -->
<!-- <div class="alert alert-danger" role="alert" style="display:none;">
<strong>Oh snap!</strong> Running out of wood!
</div> -->
<div class="row bg-dark rounded p-3 p-md-5 m-md-3 text-center">
<ul class="nav nav-pills">
<li><a href="#">
<div class="span badge pull-right" id="batvoltage">-</div>Battery Voltage</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor0">-</div>Sen 1</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor1">-</div>Sen 2</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor2">-</div>Sen 3</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor3">-</div>Sen 4</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor4">-</div>Sen 5</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor5">-</div>Sen 6</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor6">-</div>Sen 7</a></li>
<li><a href="#">
<div class="span badge pull-right" id="sensor7">-</div>Sen 8</a></li>
</ul>
</div>
<!-- <div class="row "> -->
<div class="chart-wrap text-center rounded p-3 p-md-5 m-md-3">
<!-- <div class="chart-title">
BBQ Tempurature
</div> -->
<div id="dashboard-stats" class="chart bars-horizontal brand-primary">
<div class="row" id="sensorBar0">
<span class="label">Interest 1</span>
<div class="bar-wrap">
<div class="bar" data-value="62"></div>
</div>
<span class="number">62</span>
</div>
<div class="row" id="sensorBar1">
<span class="label">Interest 2</span>
<div class="bar-wrap">
<div class="bar" data-value="35"></div>
</div>
<span class="number">35</span>
</div>
<div class="row" id="sensorBar2">
<span class="label">Interest 3</span>
<div class="bar-wrap">
<div class="bar" data-value="18"></div>
</div>
<span class="number">18</span>
</div>
<div class="row" id="sensorBar3">
<span class="label">Interest 4</span>
<div class="bar-wrap">
<div class="bar" data-value="13"></div>
</div>
<span class="number">13</span>
</div>
<div class="row" id="sensorBar4">
<span class="label">Interest 5</span>
<div class="bar-wrap">
<div class="bar" data-value="8"></div>
</div>
<span class="number">8</span>
</div>
<div class="row" id="sensorBar5">
<span class="label">Interest 6</span>
<div class="bar-wrap">
<div class="bar" data-value="5"></div>
</div>
<span class="number">5</span>
</div>
<div class="row" id="sensorBar6">
<span class="label">Interest 7</span>
<div class="bar-wrap">
<div class="bar" data-value="2"></div>
</div>
<span class="number">2</span>
</div>
<div class="row" id="sensorBar7">
<span class="label">Interest 8</span>
<div class="bar-wrap">
<div class="bar" data-value="9"></div>
</div>
<span class="number">9</span>
</div>
</div>
</div>
<!-- </div> -->
<div class="row col-xs-12">
<!-- style="display: block; width: 701px; height: 350px;" -->
<canvas id="myChart" class="chartjs-render-monitor" ></canvas>
</div>
</div>
<div class="tab-pane fade" id="tab_configuration">
<h2>Configuration </h2>
<div class="btn-group">
<button class="btn btn-default" id="labelTheme">Theme</button>
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a class="change-style-menu-item" href="#" rel="bootstrap">Boostrap</a></li>
<li><a class="change-style-menu-item" href="#" rel="cerulean">Cerulean</a></li>
<li><a class="change-style-menu-item" href="#" rel="cosmo">Cosmo</a></li>
<li><a class="change-style-menu-item" href="#" rel="cyborg">Cyborg</a></li>
<li><a class="change-style-menu-item" href="#" rel="darkly">Darkly</a></li>
<li><a class="change-style-menu-item" href="#" rel="flatly">Flatly</a></li>
<li><a class="change-style-menu-item" href="#" rel="journal">Journal</a></li>
<li><a class="change-style-menu-item" href="#" rel="lumen">Lumen</a></li>
<li><a class="change-style-menu-item" href="#" rel="paper">Paper</a></li>
<li><a class="change-style-menu-item" href="#" rel="readable">Readable</a></li>
<li><a class="change-style-menu-item" href="#" rel="sandstone">Sandstone</a></li>
<li><a class="change-style-menu-item" href="#" rel="simplex">Simplex</a></li>
<li><a class="change-style-menu-item" href="#" rel="slate">Slate</a></li>
<li><a class="change-style-menu-item" href="#" rel="spacelab">Spacelab</a></li>
<li><a class="change-style-menu-item" href="#" rel="superhero">Superhero</a></li>
<li><a class="change-style-menu-item" href="#" rel="united">United</a></li>
<li><a class="change-style-menu-item" href="#" rel="yeti">Yeti </a></li>
</ul>
</div>
</div>
</div>
<!-- </div> -->
</main>
<footer class="page-footer font-small">
<div class="col-xs-2 col-md-2"><img src="/img/logo.png" width="30" height="30"></div>
<div class="col-xs-5 col-md-5">
<p><a href="https://github.com/hansaya/BBQ_MASTER">Github</a></p>
</div>
<div class="col-xs-5 col-md-5">
</div>
</footer>
</div>
<!-- Placed at the end of the document so the pages load faster -->
<script src="/js/jquery.min.js"></script>
<script src="/js/popper.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/moment.min.js"></script>
<script src="/js/Chart.min.js"></script>
<script>
var Timer_UdpateMesures;
var tab_pane = '#tab_mesures';
var tabRefresh = false;
//New front page grapgh
var canvas = document.getElementById('myChart'),
ctx = canvas.getContext('2d'),
startingData = {
labels: [],
datasets: []
};
// Reduce the animation steps for demo clarity.
var liveChart = new Chart(ctx, {
type: "line",
data: startingData,
animationSteps: 15,
borderColor: 'rgba(244, 245, 244, 0.1)',
options: {
title: {
text: 'Temp Scale'
},
scales: {
xAxes: [{
type: 'time',
time: {
displayFormats: {
quarter: 'h:mm:ss a'
}
},
distribution: 'linear',
scaleLabel: {
display: true,
labelString: 'Date'
},
ticks: {
fontColor : "#F4F5F4"
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: 'Fahrenheit'
},
ticks: {
fontColor : "#F4F5F4"
}
}]
},
}
});
function addGraphData(chart, name, newEntry) {
var foundSen = false;
chart.data.datasets.forEach((dataset) => {
if (dataset.label == name)
{
dataset.data.push(newEntry);
foundSen = true;
return false;
}
});
if (!foundSen)
{
var colorR = Math.floor((Math.random() * 256));
var colorG = Math.floor((Math.random() * 256));
var colorB = Math.floor((Math.random() * 256));
chart.data.datasets.push ({
fill: false,
label: name,
backgroundColor: "rgba("+colorR+","+colorG+","+colorB+",1.00)",
borderColor: "rgba("+colorR+","+colorG+","+colorB+",1.00)",
data: []
});
chart.data.datasets[chart.data.datasets.length - 1].data.push(newEntry);
}
chart.update();
}
// Update the history at start
updateTheHistory ();
// Update the grapgh at start
updateBarGrapgh ();
setInterval(updateBarGrapgh, 5000); //60000 MS == 1 minutes
function updateTheHistory ()
{
console.log("Getting history!");
$.getJSON('/history.json', function(json){
// Data line chart
for ( var i = 0; i < json.hist.length; i++ ) {
// Update the time label
liveChart.data.labels.push(new Date(json.hist[i].t * 1000));
for ( var j = 0; j < json.hist[i].sensors.length; j++ ) {
// Update the grapgh the new data
addGraphData (liveChart, json.hist[i].sensors[j].n, {t: new Date(json.hist[i].t * 1000), y: json.hist[i].sensors[j].v});
}
}
}).fail(function(err){
console.log("err getJSON history.json "+JSON.stringify(err));
});
}
function updateBarGrapgh ()
{
if (tab_pane=='#tab_mesures')
{
// console.log("Updating the gauge");
$.getJSON('/measures.json', function(data){
// Update the time label
liveChart.data.labels.push(new Date(data.t * 1000));
for ( var i = 0; i < data.sensors.length; i++ ) {
if (data.sensors[i].i == 1)
{
$('#sensor'+ i).html("<b>" + data.sensors[i].v + "&#176F </b>");
document.getElementById('sensorBar' + i).getElementsByClassName('bar-wrap')[0].getElementsByClassName('bar')[0].setAttribute ('data-value', data.sensors[i].v);
document.getElementById('sensorBar' + i).getElementsByClassName('number')[0].innerText = data.sensors[i].v;
// Update the grapgh the new data
addGraphData (liveChart, data.sensors[i].n, {t: new Date(data.t * 1000), y: data.sensors[i].v});
if (document.getElementById('sensorBar' + i).style.display == "none")
{
document.getElementById('sensorBar' + i).style.display = "block";
}
}
else
{
$('#sensor'+ i).html("<b> N/A </b>");
// Hide the bar element if value is invalid
document.getElementById('sensorBar' + i).style.display = "none";
}
$('#batvoltage').html("<b>" + data.bat + "v </b>");
// TempValues.push(data.sensors[i].v);
document.getElementById('sensorBar' + i).getElementsByClassName('label')[0].innerText = data.sensors[i].n;
}
// Update the Fancy grapgh
generateBarGraph('#dashboard-stats');
}).fail(function(err){
console.log("err getJSON measures.json "+JSON.stringify(err));
});
}
}
function generateBarGraph(wrapper) {
// Set Up Values Array
var values = [];
// Get Values and save to Array
$(wrapper + ' .bar').each(function(index, el) {
if ($(this).closest(".row").is(":visible"))
{
values.push($(this).attr('data-value'));
}
});
// Get Max Value From Array
var max_value = Math.max.apply(Math, values)+10;
console.log ('Max value ' + max_value);
// Set width of bar to percent of max value
$(wrapper + ' .bar').each(function(index, el) {
var bar = $(this),
value = bar.attr('data-value'),
percent = Math.ceil((value / max_value) * 100);
// Set Width & Add Class
bar.width(percent + '%');
bar.addClass('in');
});
}
$(".tabs a[title='tab_mesures']").click()
$('a[data-toggle=\"tab\"]').on('shown.bs.tab', function (e) {
//On supprime tous les timers lorsqu'on change d'onglet
clearTimeout(Timer_UdpateMesures);
tab_pane = $(e.target).attr("href")
console.log('activated ' + tab_pane );
// tabRefresh = true;
window.dispatchEvent(new Event('resize'));
// IE10, Firefox, Chrome, etc.
if (history.pushState)
window.history.pushState(null, null, tab_pane);
else
window.location.hash = tab_pane;
});
// Changement du theme - Change current theme
// Adapté de - Adapted from : https://wdtz.org/bootswatch-theme-selector.html
var supports_storage = supports_html5_storage();
if (supports_storage) {
var theme = localStorage.theme;
console.log("Recharge le theme " + theme);
if (theme) {
// set_theme(get_themeUrl(theme));
}
}
// Nouveau theme sélectionne - New theme selected
jQuery(function($){
$('body').on('click', '.change-style-menu-item', function() {
var theme_name = $(this).attr('rel');
console.log("Change theme " + theme_name);
var theme_url = get_themeUrl(theme_name);
console.log("URL theme : " + theme_url);
set_theme(theme_url);
});
});
// Recupere l'adresse du theme - Get theme URL
function get_themeUrl(theme_name){
$('#labelTheme').html("Th&egrave;me : " + theme_name);
var url_theme = "";
if ( theme_name === "bootstrap" ) {
url_theme = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css";
} else {
url_theme = "https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/" + theme_name + "/bootstrap.min.css";
}
if (supports_storage) {
// Enregistre le theme sélectionné en local - save into the local database the selected theme
localStorage.theme = theme_name;
}
return url_theme;
}
// Applique le thème - Apply theme
function set_theme(theme_url) {
$('link[title="main"]').attr('href', theme_url);
}
// Stockage local disponible ? - local storage available ?
function supports_html5_storage(){
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
</script>
</body>
</html>
<!-- TODO -->
<!--
* Stick to one theme
* Be able to set the Tempurature probe names
* Have everything surved under local storage #done
* Select a proper output for sensor data #done
* Battery output indication
* Be able to set a warninig level -> push that to MQTT (min and max values)
* -->
+10
View File
File diff suppressed because one or more lines are too long
+7
View File
File diff suppressed because one or more lines are too long
+2
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+5
View File
File diff suppressed because one or more lines are too long
+46
View File
@@ -0,0 +1,46 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; http://docs.platformio.org/page/projectconf.html
[env:myenv]
platform = espressif32
board = featheresp32
framework = arduino
; Library options
lib_deps =
OneWire
; WebServer
DallasTemperature
; NtpClientLib
AsyncMqttClient
Time
; https://github.com/gmag11/NtpClient.git
https://github.com/mcspr/NtpClient.git#platformio-manifest
https://github.com/adafruit/Adafruit_ADS1X15.git
https://github.com/me-no-dev/ESPAsyncWebServer.git
https://github.com/alanswx/ESPAsyncWiFiManager.git
https://github.com/bblanchon/ArduinoJson.git
https://github.com/Phillaf/Arduino-SimpleList.git
; lib_ignore=Time
; upload_flags = --spiffs
upload_protocol = espota
upload_speed = 921600
upload_port = COM7
upload_port = 172.168.1.75
;upload_port = 172.168.1.89
; [env:nodemcuv2_spiffs]
; platform = espressif8266
; framework = arduino
; board = nodemcuv2
; ; ; upload_flags = --spiffs
; targets = uploadfs
; upload_port = 172.168.1.29
+442
View File
@@ -0,0 +1,442 @@
#include <Adafruit_ADS1015.h>
#include <DNSServer.h>
#include <OneWire.h>
// #include <WebServer.h>
// #include <WiFiManager.h>
//needed for library
#include <ESPAsyncWebServer.h>
#include <ESPAsyncWiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <Wire.h>
#include <ArduinoJson.h>
#include <SimpleList.h>
#include "DallasTemperature.h"
// Remote firmware update
#include <ArduinoOTA.h>
#include <FS.h>
#include <NtpClientLib.h>
#include <Time.h>
#include <TimeLib.h>
#include <WiFi.h>
#include "SPIFFS.h"
#define DEBUG
#define I2C_SDA_PIN 23
#define I2C_SCL_PIN 22
#define BATTERY_V_PIN 35
#define BAT_RATIO 0.0017240449438202
#define NTC_MAX_ADC 27550.00
#define NTC_REF_RES 62000
#define NTC_SMP_TMP 25.81
#define NTC_SMP_RES 52250
#define NTC_BCOEFFICIENT 4200 //3950
#define HIST_SIZE 20
#define HIST_INT 5000
#define SENSOR_READ_INT 1500
#define NTP_RETRY_INT 5000
Adafruit_ADS1115 g_ads (0x49); /* Use this for the 16-bit version */
AsyncWebServer g_server (80);
OneWire g_oneWireTemp (17);
DallasTemperature g_thermoCouples (&g_oneWireTemp);
DNSServer dns;
// MAX31850KATB+-ND
DeviceAddress g_sensor1 = {0x3B, 0x45, 0x21, 0x18, 0x00, 0x00, 0x00, 0x2D};
DeviceAddress g_sensor2 = {0x3B, 0xF1, 0x22, 0x18, 0x00, 0x00, 0x00, 0xB8};
DeviceAddress g_sensor3 = {0x3B, 0xF3, 0x22, 0x18, 0x00, 0x00, 0x00, 0xD6};
DeviceAddress g_sensor4 = {0x3B, 0x43, 0x21, 0x18, 0x00, 0x00, 0x00, 0x9F};
unsigned long g_currentMillis = 0;
unsigned long g_prevMilForInputRead = 0;
unsigned long g_histPreviousMillis = 0;
unsigned long g_prevMilForNTPRetry = 0;
double g_batteryVoltage = 0;
bool g_ntpError = false;
short g_maxWorkingSensors = 0;
// Representation of one temperature probe
struct Sensor
{
bool m_ind;
double m_tempF;
String m_name;
public:
Sensor (bool ind, double tempF, String name) : m_ind (ind), m_tempF (tempF), m_name (name) {}
Sensor () : m_ind (false), m_tempF (0), m_name ("N/A") {}
};
// Data point completed with multiple probes
struct DataPoint
{
int m_time;
Sensor m_sensors[8];
};
// Current data set
DataPoint g_lastSenUpdate;
// Saving the history for the histogram
SimpleList<DataPoint> g_tempHist;
// Flag for saving data from wifi portal
bool g_shouldSaveConfig = false;
// Setup OTA
void setupOTA(char *hostname)
{
// Config OTA updates
ArduinoOTA.onStart([]() { Serial.println("Start"); });
ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); });
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.setHostname(hostname);
ArduinoOTA.begin();
}
// Call back for saving the config.
void SaveConfigCallback () {
Serial.println("Should save config");
g_shouldSaveConfig = true;
}
// Manages wifi portal
void ManageWifi (bool reset_config = false)
{
// WiFiManager wifiManager;
AsyncWiFiManager wifiManager(&g_server, &dns);
wifiManager.setConnectTimeout(60);
wifiManager.setConfigPortalTimeout(60);
wifiManager.setMinimumSignalQuality(10);
wifiManager.setSaveConfigCallback(&SaveConfigCallback);
if (reset_config)
wifiManager.startConfigPortal("BBQ_Master");
else
wifiManager.autoConnect("BBQ_Master"); // This will hold the connection till it get a connection
if (g_shouldSaveConfig)
{
g_shouldSaveConfig = false;
}
}
// Calculate NTC temp using steinhart equation
float NTCTemp (int16_t adcValue)
{
if (adcValue < NTC_MAX_ADC)
{
float steinhart;
// Resistance calculation
steinhart = (NTC_MAX_ADC / adcValue) - 1;
steinhart = NTC_REF_RES / steinhart;
// Rest of the steinhart equation
steinhart = steinhart / NTC_SMP_RES;
steinhart = log(steinhart); // ln(R/Ro)
steinhart /= NTC_BCOEFFICIENT; // 1/B * ln(R/Ro)
steinhart += 1.0 / (NTC_SMP_TMP + 273.15); // + (1/To)
steinhart = 1.0 / steinhart; // Invert
steinhart -= 273.15;
return DallasTemperature::toFahrenheit(steinhart);
}
else
return 0.0;
}
// Check whether temperature is valid.
bool IsValidTemp (float temp)
{
if (temp <= 32.0 || temp > 1000.00)
return false;
else
return true;
}
// Read all the sensors
void ReadSensors ()
{
int16_t adc0, adc1, adc2, adc3;
float ntc0, ntc1, ntc2, ntc3;
// NTC readings
adc0 = g_ads.readADC_SingleEnded (0);
adc1 = g_ads.readADC_SingleEnded (1);
adc2 = g_ads.readADC_SingleEnded (2);
adc3 = g_ads.readADC_SingleEnded (3);
ntc0 = NTCTemp (adc0);
ntc1 = NTCTemp (adc1);
ntc2 = NTCTemp (adc2);
ntc3 = NTCTemp (adc3);
float tcp0, tcp1, tcp2, tcp3;
tcp0 = g_thermoCouples.getTempF (g_sensor1);
tcp1 = g_thermoCouples.getTempF (g_sensor2);
tcp2 = g_thermoCouples.getTempF (g_sensor3);
tcp3 = g_thermoCouples.getTempF (g_sensor4);
long int tps = now ();
if (tps > 0)
{
g_lastSenUpdate.m_time = tps;
g_lastSenUpdate.m_sensors[0] = Sensor (IsValidTemp (ntc0), ntc0, "NTC1");
g_lastSenUpdate.m_sensors[1] = Sensor (IsValidTemp (ntc1), ntc1, "NTC2");
g_lastSenUpdate.m_sensors[2] = Sensor (IsValidTemp (ntc2), ntc2, "NTC3");
g_lastSenUpdate.m_sensors[3] = Sensor (IsValidTemp (ntc3), ntc3, "NTC4");
// Thermocouple readings
g_lastSenUpdate.m_sensors[4] = Sensor (IsValidTemp (tcp0), tcp0, "TC1");
g_lastSenUpdate.m_sensors[5] = Sensor (IsValidTemp (tcp1), tcp1, "TC2");
g_lastSenUpdate.m_sensors[6] = Sensor (IsValidTemp (tcp2), tcp2, "TC3");
g_lastSenUpdate.m_sensors[7] = Sensor (IsValidTemp (tcp3), tcp3, "TC4");
}
g_batteryVoltage = analogRead (BATTERY_V_PIN) * BAT_RATIO;
// Request thermo couple data
g_thermoCouples.setWaitForConversion(false); // makes it async
g_thermoCouples.requestTemperatures();
g_thermoCouples.setWaitForConversion(true);
#ifdef DEBUG
Serial.print ("AIN0: ");
Serial.println (adc0);
// Serial.print (" v: ");
// Serial.println ((adc0 * 0.125) / 1000);
// Serial.print ("AIN1: ");
// Serial.print (adc1);
// Serial.print (" v: ");
// Serial.println ((adc1 * 0.125) / 1000);
Serial.print ("Temp NTC 0 F: ");
Serial.println (g_lastSenUpdate.m_sensors[0].m_tempF);
Serial.print ("Temp NTC 1 F: ");
Serial.println (g_lastSenUpdate.m_sensors[1].m_tempF);
Serial.print ("Temp NTC 2 F: ");
Serial.println (g_lastSenUpdate.m_sensors[2].m_tempF);
Serial.print ("Temp NTC 3 F: ");
Serial.println (g_lastSenUpdate.m_sensors[3].m_tempF);
// Serial.print ("AIN2: ");
// Serial.print (adc2);
// Serial.print (" v: ");
// Serial.println ((adc2 * 0.125) / 1000);
// Serial.print ("AIN3: ");
// Serial.print (adc3);
// Serial.print (" v: ");
// Serial.println ((adc3 * 0.125) / 1000);
// Serial.println (" ");
Serial.print ("Temp 0 F: ");
Serial.println (tcp0);
Serial.print ("Temp 1 F: ");
Serial.println (tcp1);
Serial.print ("Temp 2 F: ");
Serial.println (tcp2);
Serial.print ("Temp 3 F: ");
Serial.println (tcp3);
// Serial.print ("battery voltage: ");
// Serial.print (g_batteryVoltage);
// Serial.println ("v");
#endif
}
// Send the last sensor data reading in json format.
void SendMeasures (AsyncWebServerRequest *request)
{
Serial.println ("Sending measurements!");
DynamicJsonDocument json(1024); // Current JSON static buffer
// JsonObject& root = jsonBuffer.createObject ();
json["bat"] = g_batteryVoltage;
json["t"] = g_lastSenUpdate.m_time;
JsonArray sensors = json.createNestedArray ("sensors");
for (int i = 0; i < 8; ++i)
{
JsonObject tempS = sensors.createNestedObject ();
tempS["i"] = g_lastSenUpdate.m_sensors[i].m_ind;
tempS["v"] = int(g_lastSenUpdate.m_sensors[i].m_tempF);
tempS["n"] = g_lastSenUpdate.m_sensors[i].m_name;
}
char tempJson[2400];
serializeJson(json, tempJson); // Export JSON object as a String
request->send (200, "application/json", tempJson); // Send history data to the web client
}
// Send the history in JSON format.
void SendHistory (AsyncWebServerRequest *request)
{
Serial.println ("Sending History");
DynamicJsonDocument json(5560 + 100); // Current JSON static buffer
JsonArray hist = json.createNestedArray ("hist");
for (SimpleList<DataPoint>::iterator it = g_tempHist.begin (); it != g_tempHist.end (); ++it)
{
JsonObject item = hist.createNestedObject ();
item["t"] = it->m_time;
JsonArray jsonSens = item.createNestedArray ("sensors");
for (int i = 0; i < 8; i++)
{
if (it->m_sensors[i].m_ind)
{
JsonObject sensor = jsonSens.createNestedObject ();
sensor["n"] = it->m_sensors[i].m_name;
sensor["v"] = int(it->m_sensors[i].m_tempF); // Lose the decimal places for space saving.
}
}
}
char tempJson[5560 + 100];
serializeJson(json, tempJson); // Export JSON object as a String
request->send (200, "application/json", tempJson); // Send history data to the web client
}
// Add a data point to the history
void AddDataPointToHistory ()
{
short validSensors = 0;
bool validData = false;
for (int i = 0; i < 8; i++)
{
if (g_lastSenUpdate.m_sensors[i].m_ind)
{
validSensors++;
validData = true;
}
}
// Clear the history if the count does not match.
if (g_maxWorkingSensors != validSensors)
{
g_tempHist.clear ();
g_maxWorkingSensors = validSensors;
}
if (validData)
{
g_tempHist.push_back (g_lastSenUpdate);
if (g_tempHist.size () > HIST_SIZE)
{
g_tempHist.erase (g_tempHist.begin ());
}
}
#ifdef DEBUG
Serial.print ("size g_tempHist ");
Serial.println (g_tempHist.size ());
#endif
}
void setup (void)
{
Serial.begin (115200);
// For battery voltage readings
analogReadResolution (12);
analogSetAttenuation (ADC_11db); // For all pins
// Temp probes
g_ads.setGain (GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 2mV 0.125mV
g_ads.begin ();
g_thermoCouples.begin ();
g_thermoCouples.requestTemperatures ();
g_thermoCouples.setResolution (12);
// WIFI connect
ManageWifi ();
// OTA stuff
setupOTA ("BBQ_Master");
WiFi.setHostname("BBQ_Master");
// Web server stuff
if (!SPIFFS.begin ())
Serial.println ("SPIFFS Mount failed"); // Problème avec le stockage SPIFFS - Serious problem with SPIFFS
g_server.on ("/measures.json", SendMeasures);
g_server.on ("/history.json", SendHistory);
g_server.serveStatic ("/js/bootstrap.min.js", SPIFFS, "/js/bootstrap.min.js", "max-age=86400");
g_server.serveStatic ("/js/jquery.min.js", SPIFFS, "/js/jquery-3.3.1.min.js", "max-age=86400");
g_server.serveStatic ("/js/Chart.min.js", SPIFFS, "/js/Chart.min.js", "max-age=86400");
g_server.serveStatic ("/js/moment.min.js", SPIFFS, "/js/moment.min.js", "max-age=86400");
g_server.serveStatic ("/js/popper.min.js", SPIFFS, "/js/popper.min.js", "max-age=86400");
g_server.serveStatic ("/css/custom.css", SPIFFS, "/css/custom.css");
g_server.serveStatic ("/css/cover.css", SPIFFS, "/css/cover.css", "max-age=86400");
g_server.serveStatic ("/css/bootstrap.min.css", SPIFFS, "/css/bootstrap.min.css", "max-age=86400");
g_server.serveStatic ("/img/logo.png", SPIFFS, "/img/android-icon-72x72.png", "max-age=86400");
g_server.serveStatic ("/favicon.ico", SPIFFS, "/img/favicon.ico", "max-age=86400");
g_server.serveStatic ("/", SPIFFS, "/index.html");
g_server.begin ();
Serial.println ("HTTP server started");
// Real time
NTP.onNTPSyncEvent ([](NTPSyncEvent_t error) {
if (error)
{
Serial.print ("Time Sync error: ");
if (error == noResponse)
Serial.println ("NTP server not reachable");
else if (error == invalidAddress)
Serial.println ("Invalid NTP server address");
g_ntpError = true;
}
else
{
Serial.print ("Got NTP time: ");
Serial.println (NTP.getTimeDateString (NTP.getLastNTPSync ()));
}
});
// NTP Server, time offset, daylight
NTP.begin ("pool.ntp.org", -1, true);
NTP.setInterval (60);
}
void loop (void)
{
g_currentMillis = millis (); // Time now
// Handle server requests
ArduinoOTA.handle ();
// Add a point to the history over time.
if (g_currentMillis - g_histPreviousMillis > HIST_INT)
{
g_histPreviousMillis = g_currentMillis;
AddDataPointToHistory ();
}
// If there is an error grabbing NTP time, try again.
if (g_ntpError)
{
if (g_currentMillis - g_histPreviousMillis > NTP_RETRY_INT)
{
g_prevMilForNTPRetry = g_currentMillis;
g_ntpError = false;
NTP.getTime ();
}
}
// Read the sensor data.
if (g_currentMillis - g_prevMilForInputRead >= SENSOR_READ_INT)
{
g_prevMilForInputRead = g_currentMillis;
ReadSensors ();
}
}