This commit is contained in:
kislovdm
2024-04-18 08:23:49 +03:00
parent 5954f21fd9
commit 6a57d49da8
33 changed files with 1124 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
+10
View File
@@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}
+14
View File
@@ -0,0 +1,14 @@
{
"files.associations": {
"array": "cpp",
"deque": "cpp",
"list": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp",
"ranges": "cpp",
"regex": "cpp"
}
}
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.0.0 (https://getbootstrap.com)
* Copyright 2011-2018 The Bootstrap Authors
* Copyright 2011-2018 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
+12
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400}
+273
View File
@@ -0,0 +1,273 @@
body {
padding-top: 120px;
background-color: #212529;
}
pre {
background: #f7f7f9;
}
iframe {
overflow: hidden;
border: none;
}
@media (min-width: 768px) {
body > .navbar-transparent {
box-shadow: none;
}
body > .navbar-transparent .navbar-nav > .open > a {
box-shadow: none;
}
}
.dark-bg {
background-color: #212529;
}
.light-bg {
background-color: #212529;
}
#home,
#help {
font-size: 0.9rem;
}
#home .navbar,
#help .navbar {
background: #349aed;
background: linear-gradient(145deg, #349aed 50%, #34d8ed 100%);
transition: box-shadow 200ms ease-in;
}
#home .navbar-transparent,
#help .navbar-transparent {
background: none !important;
box-shadow: none;
}
#home .navbar-brand .nav-link,
#help .navbar-brand .nav-link {
display: inline-block;
margin-right: -30px;
}
#home .nav-link,
#help .nav-link {
text-transform: uppercase;
font-weight: 500;
color: #fff;
}
#home {
padding-top: 0;
}
#home .btn {
padding: 0.6rem 0.55rem 0.5rem;
box-shadow: none;
font-size: 0.7rem;
font-weight: 500;
}
.bs-docs-section {
margin-top: 4em;
}
.bs-docs-section .page-header h1 {
padding: 2rem 0;
font-size: 3rem;
}
.dropdown-menu.show[aria-labelledby="themes"] {
display: -ms-flexbox;
display: flex;
width: 420px;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.dropdown-menu.show[aria-labelledby="themes"] .dropdown-item {
width: 33.333%;
}
.dropdown-menu.show[aria-labelledby="themes"] .dropdown-item:first-child {
width: 100%;
}
.bs-component {
position: relative;
}
.bs-component + .bs-component {
margin-top: 1rem;
}
.bs-component .card {
margin-bottom: 1rem;
}
.bs-component .modal {
position: relative;
top: auto;
right: auto;
left: auto;
bottom: auto;
z-index: 1;
display: block;
}
.bs-component .modal-dialog {
width: 90%;
}
.bs-component .popover {
position: relative;
display: inline-block;
width: 220px;
margin: 20px;
}
.source-button {
display: none;
position: absolute;
top: 0;
right: 0;
z-index: 100;
font-weight: 700;
}
.source-button:hover {
cursor: pointer;
}
.bs-component:hover .source-button {
display: block;
}
#source-modal pre {
max-height: calc(100vh - 11rem);
background-color: rgba(0, 0, 0, 0.7);
color: rgba(255, 255, 255, 0.7);
}
.nav-tabs {
margin-bottom: 15px;
}
.progress {
margin-bottom: 10px;
}
#footer {
margin: 5em 0;
}
#footer li {
float: left;
margin-right: 1.5em;
margin-bottom: 1.5em;
}
#footer p {
clear: left;
margin-bottom: 0;
}
.splash {
padding: 12em 0 6em;
background: #349aed;
background: linear-gradient(145deg, #349aed 50%, #34d8ed 100%);
color: #fff;
text-align: center;
}
.splash .logo {
width: 160px;
}
.splash h1 {
font-size: 3em;
color: #fff;
}
.splash #social {
margin: 2em 0 3em;
}
.splash .alert {
margin: 2em 0;
border: none;
}
.splash .sponsor a {
color: #fff;
}
.section-tout {
padding: 6em 0 1em;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
background-color: #eaf1f1;
text-align: center;
}
.section-tout .icon {
display: -ms-flexbox;
display: flex;
-ms-flex-pack: center;
justify-content: center;
-ms-flex-align: center;
align-items: center;
width: 80px;
height: 80px;
margin: 0 auto 1rem;
background: #349aed;
background: linear-gradient(145deg, #3b9cea 50%, #3db8eb 100%);
border-radius: 50%;
font-size: 2rem;
color: rgba(0, 0, 0, 0.5);
}
.section-tout p {
margin-bottom: 5em;
}
.section-preview {
padding: 4em 0;
}
.section-preview .preview {
margin-bottom: 4em;
background-color: #eaf1f1;
}
.section-preview .preview .image {
position: relative;
}
.section-preview .preview .image::before {
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: "";
pointer-events: none;
}
.section-preview .preview .options {
padding: 2em;
border: 1px solid rgba(0, 0, 0, 0.05);
border-top: none;
text-align: center;
}
.section-preview .preview .options p {
margin-bottom: 2em;
}
.section-preview .dropdown-menu {
text-align: left;
}
.section-preview .lead {
margin-bottom: 2em;
}
.sponsor #carbonads {
max-width: 240px;
margin: 0 auto;
}
.sponsor .carbon-text {
display: block;
margin-top: 1em;
font-size: 12px;
}
.sponsor .carbon-poweredby {
float: right;
margin-top: 1em;
font-size: 10px;
}
@media (max-width: 767px) {
.splash {
padding-top: 8em;
}
.splash .logo {
width: 100px;
}
.splash h1 {
font-size: 2em;
}
#banner {
margin-bottom: 2em;
text-align: center;
}
}
.btn-group {
font-size: 13px;
}
.tooltip {
display: none;
position: absolute;
padding: 10px;
color: #777;
background-color: #fff;
border: 1px solid #777;
box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.5);
border-radius: 3px;
}
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
/*!
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900}
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+186
View File
@@ -0,0 +1,186 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Filament Dry Box - Sasa Karanovic</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="css/bootstrap.min.css" />
<link rel="stylesheet" href="css/custom.min.css" />
<link href="css/fontawesome.min.css" rel="stylesheet">
<link href="css/brands.min.css" rel="stylesheet">
<link href="css/solid.min.css" rel="stylesheet">
<link rel="shortcut icon" type="image/jpg" href="img/favicon.png" />
</head>
<body class="d-flex flex-column min-vh-100">
<div class="navbar navbar-expand-lg fixed-top navbar-dark dark-bg">
<div class="container">
<a href="#" class="navbar-brand">Filament Dry Box</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<!-- <ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="https://sasakaranovic.com/projects/diy-filament-dry-box/">How it works?</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://sasakaranovic.com/projects/diy-filament-dry-box/">Project Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/SasaKaranovic/DIY-Filament-Dryer" target="_blank">Source Code</a>
</li>
</ul> -->
</div>
</div>
</div>
<!-- Container -->
<div class="container">
<!-- Content - First row -->
<div class="row">
<!-- Left column -->
<div class="col-lg-5 col-md-5 col-sm-5">
<!-- DryBox status title -->
<div class="page-header">
<h2>DryBox Status</h2>
</div>
<!-- DryBox status title end -->
<!-- DryBox status -->
<div class="row">
<div class="col-lg-12">
<div class="table-responsive">
<table class="table table-sm table-borderless">
<tr>
<td colspan="4"><span id="status-box" class="badge badge-pill badge-warning">Dry box is ??</span></td>
</tr>
<tr>
<th scope="col"></th>
<th scope="col">Inside</th>
<th scope="col">Outside</th>
<th scope="col"></th>
</tr>
<tr>
<td>Temperature</td>
<td><span id="status-temp-inside">??</span>°C</td>
<td><span id="status-temp-outside">??</span>°C</td>
<td></td>
</tr>
<tr>
<td>Humidity</td>
<td><span id="status-humidity-inside">??</span>%</td>
<td><span id="status-humidity-outside">??</span>%</td>
<td></td>
</tr>
<tr>
<td colspan="4"></td>
</tr>
<tr>
<th scope="col"></th>
<th scope="col">Current</th>
<th scope="col">Limit</th>
<th scope="col"></th>
</tr>
<tr>
<td>Heater Temperature</td>
<td><span id="status-temp-heater">??</span>°C</td>
<td><span id="status-temp-heater-max">??</span>°C</td>
<td></td>
</tr>
<tr>
<td>Temperature Inside</td>
<td><span id="status-temp-inside2">??</span>°C</td>
<td><span id="status-temp-inside-target">??</span>°C</td>
<td></td>
</tr>
<tr>
<td colspan="1">Fan speed</td>
<td colspan="3"><span id="status-fan-speed">??</span>%</td>
</tr>
</table>
</div>
</div>
</div>
<!-- DryBox status END -->
</div>
<!-- Left column END -->
<!-- Right column -->
<div class="col-lg-5 col-md-5 col-sm-5 offset-lg-1">
<div class="row">
<!-- DryBox control -->
<div class="col-lg-12">
<h2>Control</h2>
</div>
<!-- DryBox control END -->
<div class="col-lg-12">
<legend>Temperature</legend>
<fieldset class="form-group">
<label for="labelSetTemperature">Target temperature: 25°C</label>
<div class="float-right">
<a href="#" id="pop-temp"><img src="img/help.png" /></a>
</div>
<input type="range" class="custom-range" id="sliderTemperature" min="15" max="80" step="1" value="25" />
</fieldset>
<legend>Fan speed</legend>
<fieldset class="form-group">
<label for="labelSetFanSpeed">Fan speed: 50%</label>
<div class="float-right">
<a href="#" id="pop-fan"><img src="img/help.png" /></a>
</div>
<input type="range" class="custom-range" id="sliderFanSpeed" min="0" max="100" step="5" value="50" />
</fieldset>
<legend>Max Heater Temperature</legend>
<fieldset class="form-group">
<label for="labelSetHeaterTemperature">Max heater temperature: 55°C</label>
<div class="float-right">
<a href="#" id="pop-heater"><img src="img/help.png" /></a>
</div>
<input type="range" class="custom-range" id="sliderHeaterTemperature" min="30" max="80" step="1" value="55" />
</fieldset>
</div>
<div class="col-lg-12">
<div class="btn-group btn-group-md mr-2" role="group">
<button type="button" id="btnSet" class="btn btn-primary mr-2"><i class="fas fa-fire"></i> Turn ON</button>
<button type="button" id="btnTurnOff" class="btn btn-primary"><i class="fas fa-power-off"></i> Turn OFF</button>
</div>
</div>
</div>
</div>
<!-- Right column END -->
</div>
<!-- Content - First row END -->
</div>
<!-- Container END -->
<!-- FOOTER -->
<footer class="w-100 py-2 flex-shrink-0 mt-auto dark-bg">
<div class="container py-2">
<div class="row gy-4 gx-5">
<div class="col-lg-6 col-md-6">
<p class="small text-muted">
Dry box controller <!-- by <a href="https://SasaKaranovic.com/" target="_blank">Saša Karanović</a><br> -->
Released as open-source and open-hardware for you to hack and enjoy! :)<br>
</p>
</div>
<div class="col-lg-6 col-md-6">
<!-- <a class="m-2" href="https://sasakaranovic.com/" target="_blank"><i class="fas fa-home"></i> Blog</a>
<a class="m-2" href="https://youtube.com/c/sasakaranovic" target="_blank"><i class="fab fa-youtube"></i> YouTube</a>
<a class="m-2" href="https://twitter.com/_sasakaranovic_" target="_blank"><i class="fab fa-twitter"></i> Twitter</a>
<a class="m-2" href="https://github.com/sasakaranovic" target="_blank"><i class="fab fa-github"></i> GitHub</a>
<a class="m-2" href="https://buymeacoffee.com/sasakaranovic" target="_blank"><i class="fas fa-mug-hot"></i> Buy me a coffee</a> -->
</div>
</div>
</div>
</footer>
<script src="js/jquery-3.6.0.min.js"></script>
<script src="js/bootstrap.bundle.min.js"></script>
<script src="js/drybox.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
+144
View File
@@ -0,0 +1,144 @@
// Shorthand for $( document ).ready()
$(function() {
$('#sliderTemperature').on('input change', function(){
$("label[for='labelSetTemperature']").text("Target temperature: " + parseInt(this.value) +"°C");
});
$('#sliderHeaterTemperature').on('input change', function(){
$("label[for='labelSetHeaterTemperature']").text("Max heater temperature: " + parseInt(this.value) +"°C");
});
$('#sliderFanSpeed').on('input change', function(){
$("label[for='labelSetFanSpeed']").text("Fan speed: " + parseInt(this.value) +"%");
});
$('#btnSet').on('click', function(){
var temperature = $("#sliderTemperature").val();
var heater = $("#sliderHeaterTemperature").val();
var fanspeed = $("#sliderFanSpeed").val();
// Send
$.ajax({
url: "/set",
type: "get", //send it through get method
data: {
temperature: temperature,
heater: heater,
fanspeed: fanspeed,
},
success: function(response) {
//Do Something
console.log(response);
update_status(false);
},
error: function(xhr) {
//Do Something to handle error
}
});
});
$('#btnTurnOff').on('click', function(){
// Send
$.ajax({
url: "/off",
type: "get", //send it through get method
success: function(response) {
console.log(response);
update_status(false);
},
error: function(xhr) {
//Do Something to handle error
}
});
});
$('a#pop-temp').popover({
trigger : 'click',
placement : 'top',
html: true,
title : 'Target Temperature',
content : 'This is the maximum/desired temperature we want inside the box.<br>\
The system will try to reach and then maintain this temperature.',
});
$('a#pop-fan').popover({
trigger : 'click',
placement : 'top',
html: true,
title : 'Fan speed',
content : 'Set the speed of the internal fan used to circulate the air inside the dry box.<br><br>\
0% = Off<br>\
50% = 50% of fan\'s maximum RPM<br>\
100% = Full speed',
});
$('a#pop-heater').popover({
trigger : 'click',
placement : 'top',
html: true,
title : 'Max heater temperature',
content : 'Maximum allowed temerature heater can reach.<br>This limit should be set to avoid damage to your filament or the box/heater itself.<br>\
For example; Let\'s say that your heater could reach max temperature of 150°C. However this could start to melt your filament, melt the dry box or start a fire.<br>\
In order to avoid this, we set a heater temperature limit to make sure heater never passes this set temperature during normal operation.<br>\
This limit will also affect how long it takes to reach target temperature.',
});
// Finally update status
update_status(true);
});
function update_status(rearm)
{
var ajaxTime= new Date().getTime();
$.ajax({
url: "/status",
type: "get", //send it through get method
success: function(response) {
console.log(response);
var res = jQuery.parseJSON(response);
$('#status-temp-target').text(res.target_temp_in);
$('#status-fan-speed').text(res.fan_speed);
$('#status-temp-inside').text(res.temp_in.toFixed(1));
$('#status-temp-inside2').text(res.temp_in.toFixed(1));
$('#status-temp-inside-target').text(res.target_temp_in.toFixed(1));
$('#status-temp-heater').text(res.temp_heater.toFixed(0));
$('#status-temp-heater-max').text(res.max_temp_heater.toFixed(0));
$('#status-humidity-inside').text(res.humid_in.toFixed(0));
$('#status-temp-outside').text(res.temp_out.toFixed(0));
$('#status-humidity-outside').text(res.humid_out.toFixed(0));
// Homed status
if(res.status == 1) {
$('#status-box').text('Dry box is on').addClass('badge-success').removeClass('badge-warning').removeClass('badge-danger');
}
else {
$('#status-box').text('Dry box is off').addClass('badge-warning').removeClass('badge-success').removeClass('badge-danger');
}
},
error: function(xhr) {
$('#status-box').text('Dry box is unreachable!').addClass('badge-danger').removeClass('badge-success').removeClass('badge-warning');
}
}).done(function () {
var totalTime = new Date().getTime()-ajaxTime;
// Here I want to get the how long it took to load some.php and use it further
});;
if(rearm) {
rearm_status();
}
}
function rearm_status()
{
setTimeout(function () {
update_status(true);
}, 2000);
}
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+39
View File
@@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
+46
View File
@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html
+21
View File
@@ -0,0 +1,21 @@
; 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
; https://docs.platformio.org/page/projectconf.html
[env:d1_mini_pro]
platform = espressif8266
board = d1_mini
framework = arduino
upload_speed = 721000
lib_deps =
gyverlibs/GyverOS@^1.2.1
adafruit/Adafruit BME280 Library@^2.2.4
me-no-dev/ESP Async WebServer@^1.2.3
gyverlibs/GyverPID@^3.3.2
board_build.filesystem = littlefs
+40
View File
@@ -0,0 +1,40 @@
#ifndef _DIY_DRYBOX_CFG_H_
#define _DIY_DRYBOX_CFG_H_
// clang-format off
#define PIN_ONE_WIRE_BUS 18
#define PIN_HEATER_CTL 16
#define PIN_FAN_PWM 17
#define PIN_FAN_TACHO 5
#define PID_KP 65
#define PID_KI 81
#define PID_KD 50
#define PWM_HEATER_INVERT_VALUES 1
#define PWM_FREQ_HEATER 500
#define PWM_FREQ_FAN 25000
#define PWM_RESOLUTION 10
#define PWM_MAX_VALUE 1024L
#define HEATER_DUTY_OFF 0
#define HEATER_PWM_OFF PWM_MAX_VALUE
#define PWM_CH_HEATER 0
#define PWM_CH_FAN 2
#define TEMPERATURE_PRECISION 10
#define LIMIT_TEMP_IN_MIN 20
#define LIMIT_TEMP_IN_MAX 70
#define LIMIT_TEMP_HEATER_MIN 20
#define LIMIT_TEMP_HEATER_MAX 75
#define LIMIT_FAN_SPEED_MIN 0
#define LIMIT_FAN_SPEED_MAX 100
#define DEF_USE_WEB 1
#define DEF_DEBUG_CALIBRATE_ADC 0
#define DEF_DEBUG_PID 0
#define DEF_DEBUG_HETER_SAMPLES 0
#define DEF_DEBUG_SENSOR_SAMPLES 0
#define DEF_DEBUG_PWM_VALUES 0
#define DEF_DEUG_SPIFFS 0
// clang-format on
#endif
+8
View File
@@ -0,0 +1,8 @@
void setup_fan() {
}
void set_fan_duty(uint8_t duty)
{
fan_duty = duty;
}
+47
View File
@@ -0,0 +1,47 @@
#include "GyverPID.h"
GyverPID regulator(PID_KP, PID_KI, PID_KD);
void setup_heater(){
regulator.setDirection(NORMAL);
//regulator.setLimits(0, 1023);
regulator.setpoint = LIMIT_TEMP_HEATER_MIN;
}
void change_target_temperature(int temperature){
regulator.setpoint = temperature;
}
void current_temperature_updated(float temperature){
regulator.input = temperature;
}
void task_heater() {
if (box_status == 0){
set_heater_duty(HEATER_DUTY_OFF);
return;
}
int pwm_value = 0;
if (temperature_heater > max_temperature_heater || temperature_in > max_temperature_heater){
Serial.println("Max temperature reached. Setting off heater!");
pwm_value = 0;
}else{
pwm_value = regulator.getResult();
};
#if DEF_DEBUG_PID
Serial.print("current pid value: ");
Serial.println(pwm_value);
#endif
set_heater_duty(pwm_value);
}
void set_heater_duty(uint8_t duty)
{
fan_duty = duty;
analogWrite(LED_PIN, 255-duty);
}
+46
View File
@@ -0,0 +1,46 @@
#include <Arduino.h>
#include <GyverOS.h>
// Local import
#include "config.h"
#include "wifi_credentials.h"
#define DEBUG 1
#define LED_PIN D4
GyverOS<5> OS; // указать макс. количество задач
//web
float target_temperature_in = 0.0;
float max_temperature_heater = 0.0;
float target_fan_percent = 10.0;
uint8_t box_status = 0;
float temperature_in = 0;
float temperature_out = 0;
float temperature_heater = 0;
float humidity_in = 0;
uint8_t fan_duty = 0;
uint8_t humidity_out = 0;
//end web
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
setup_temperature_sensors();
setup_heater();
setup_fan();
setup_web();
// подключаем задачи (порядковый номер, имя функции, период в мс)
OS.attach(0, task_setup_web, 1000);
OS.attach(1, task_read_temperatures, 1000);
OS.attach(2, task_heater, 1000);
}
void loop() {
OS.tick(); // вызывать как можно чаще, задачи выполняются здесь
// OS.getLeft() возвращает время в мс до ближайшей задачи
// на это время можно усыпить МК, например при помощи narcoleptic или GyverPower
// для примера просто поставим delay на это время
delay(OS.getLeft());
}
+32
View File
@@ -0,0 +1,32 @@
#include <Adafruit_BME280.h>
Adafruit_BME280 bme280; // BME280 на I2C
void setup_temperature_sensors(){
bme280.begin(0x76);
}
void task_read_temperatures() {
//digitalWrite(LED_PIN, !digitalRead(LED_PIN));
// температура
temperature_in = bme280.readTemperature();
humidity_in = bme280.readHumidity();
current_temperature_updated(temperature_in);
#if DEBUG
String txt1="";
txt1="Temp=";
txt1= txt1+String(temperature_in);
txt1= txt1+" *C";
Serial.println(txt1);
// влажность
txt1="Hum=";
txt1= txt1+String(humidity_in);
txt1= txt1+" %";
Serial.println(txt1);
Serial.println("--------------------------------------------");
#endif
}
+144
View File
@@ -0,0 +1,144 @@
//web
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include "ESPAsyncWebServer.h"
#include <FS.h>
#include <LittleFS.h>
AsyncWebServer server(80);
int WiFi_status = WL_IDLE_STATUS;
int wifi_debug_printed = 0;
void setup_web() {
WiFi.begin(ssid, password);
wifi_debug_printed = 0;
LittleFS.begin();
}
void task_setup_web() {
int wifi_status = WiFi.status();
if (wifi_status != WL_CONNECTED){
Serial.print("WiFi Status: ");
Serial.println(wifi_status);
wifi_debug_printed = 0;
} else if (wifi_status == WL_CONNECTED && wifi_debug_printed == 0) {
Serial.print("WiFi IP: ");
Serial.println(WiFi.localIP());
wifi_debug_printed = 1;
setupWebServer();
server.begin();
}
}
void setupWebServer(void)
{
server.onNotFound([](AsyncWebServerRequest *request) {
Serial.println("404:");
Serial.println(request->url());
request->send(404);
});
// // send a file when /index is requested
server.on("/index.html", HTTP_ANY, [](AsyncWebServerRequest *request) {
request->send(LittleFS, "/index.html");
});
// send a file when /index is requested
server.on("/", HTTP_ANY, [](AsyncWebServerRequest *request) {
request->send(LittleFS, "/index.html");
});
server.serveStatic("/img/", LittleFS, "/img/");
server.serveStatic("/css/", LittleFS, "/css/");
server.serveStatic("/js/", LittleFS, "/js/");
server.serveStatic("/webfonts/", LittleFS, "/webfonts/");
// Get dry box status
server.on("/status", HTTP_GET, [](AsyncWebServerRequest *request) {
char buff[200] = {0};
int len;
len =
snprintf(buff, 200,
"{\"status\":%d, \"target_temp_in\":%f, "
"\"max_temp_heater\":%f, "
"\"temp_in\":%f, \"temp_heater\":%f, \"humid_in\":%f, "
"\"fan_speed\":%d, \"temp_out\":%f, \"humid_out\":%d}",
box_status, target_temperature_in, max_temperature_heater,
temperature_in, temperature_heater, humidity_in, fan_duty,
temperature_out, humidity_out);
if (len)
{
request->send(200, "text/plain", buff);
}
else
{
request->send(500, "text/plain",
"{\"status\": \"Internal server error\"}");
}
});
//Turn OFF dry box
server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request) {
box_status = 0;
set_fan_duty(0);
set_heater_duty(HEATER_DUTY_OFF);
target_temperature_in = 0;
max_temperature_heater = 0;
request->send(200, "text/plain", "{\"status\": \"OK\"}");
});
// Turn ON dry box and set target temperature, max heater temperature and fan speed
server.on("/set", HTTP_GET, [](AsyncWebServerRequest *request) {
// Check if temperature, heater temperature and fan speed arguments are present
if (request->hasParam("temperature") && request->hasParam("heater") &&
request->hasParam("fanspeed"))
{
int32_t temperature = 0;
int32_t heater = 0;
int32_t fanspeed = 0;
temperature = request->getParam("temperature")->value().toInt();
heater = request->getParam("heater")->value().toInt();
fanspeed = request->getParam("fanspeed")->value().toInt();
#if DEF_DEUG_WEB_API
Serial.println((String) "Target temp: " + temperature +
"C Heater: " + heater + "C Fan Speed: " + fanspeed);
#endif
// Check drybox temperature and heater temperature limit
if ( (temperature <= LIMIT_TEMP_IN_MAX) && (heater <= LIMIT_TEMP_HEATER_MAX) )
{
target_temperature_in = temperature;
max_temperature_heater = heater;
change_target_temperature(temperature);
set_fan_duty(fanspeed);
box_status = 1;
request->send(200, "text/plain", "{\"status\": \"OK\"}");
return;
}
// If either of them are out of range, return bad request status
else
{
request->send(400, "text/plain", "{\"status\": \"Bad request. Limit error!\"}");
return;
}
}
// Not all arguments are present in the request
else
{
request->send(400, "text/plain", "{\"status\": \"Bad request\"}");
return;
}
});
}
+7
View File
@@ -0,0 +1,7 @@
#ifndef __WIFI_CRED_CFG_
#define __WIFI_CRED_CFG_
// clang-format off
const char* ssid = "MonsterFNV";
const char* password = "*******";
// clang-format on
#endif
+11
View File
@@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html