git.haldean.org sousvide / 025edb7
Starting to look pretty! Still missing plots, which is big, as well as temperature setting. Need some sort of ajaxy progress indicator for actions like enable/disable. Will Haldean Brown 8 years ago
7 changed file(s) with 197 addition(s) and 104 deletion(s). Raw diff Collapse all Expand all
00 package main
11
22 import (
3 "encoding/json"
34 "fmt"
5 "log"
46 "net/http"
57 )
68
1820 w.Write([]byte(h.ToCsv()))
1921 }
2022 }
23
24 func (s *SousVide) DumpJson(w http.ResponseWriter, _ *http.Request) {
25 w.Header().Set("Content-type", "application/json")
26
27 if len(s.History) == 0 {
28 w.WriteHeader(http.StatusNoContent)
29 return
30 }
31
32 b, err := json.Marshal(s.History)
33 if err != nil {
34 log.Panicf("could not marshal historical data to json: %v", err)
35 }
36 w.Write(b)
37 }
9797 })
9898
9999 http.HandleFunc("/enable", func(w http.ResponseWriter, r *http.Request) {
100 s.Enabled = !s.Enabled
100 s.Enabled = true
101101 log.Printf("set enabled to %v", s.Enabled)
102 http.Redirect(w, r, "/", http.StatusSeeOther)
102 w.Write([]byte("success"))
103 })
104 http.HandleFunc("/disable", func(w http.ResponseWriter, r *http.Request) {
105 s.Enabled = false
106 log.Printf("set enabled to %v", s.Enabled)
107 w.Write([]byte("success"))
103108 })
104109
105 http.HandleFunc("/csv", s.DumpCsv)
106 http.HandleFunc("/plot", s.GenerateChart2)
110 http.HandleFunc("/csv", func(w http.ResponseWriter, r *http.Request) {
111 s.DumpCsv(w, r)
112 })
113 http.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
114 s.DumpJson(w, r)
115 })
116 http.HandleFunc("/plot", func(w http.ResponseWriter, r *http.Request) {
117 s.GenerateChart2(w, r)
118 })
107119 http.HandleFunc("/timer", AddTimerHandler)
108120 http.HandleFunc("/timers", GetTimersHandler)
109121 http.HandleFunc("/delete_timer", DeleteTimerHandler)
1414 const (
1515 InterruptDelay = 1 * time.Second
1616 LogFile = "runlog.txt"
17 HistoryLength = 4096
17 HistoryLength = 8192
1818 LowpassSamples = 2
1919 AccErrorWindow = 32
2020 )
99 <script src="sousvide.js"></script>
1010 </head>
1111 <body>
12 <div id="temp_container"><span id="temp"></span> &deg;C</div>
12 <h1><a href="http://263a.net">0x263A</a> &mdash; SV2.1.0</h1>
13 <div id="floater">
14 <div id="display">
15 <div id="temp_container"><span id="temp"></span> &deg;C</div>
16 <div id="target_container">
17 <span class="label">target</span>
18 <span id="target"></span> &deg;C
19 </div>
20 <div id="error_container">
21 <span class="label">offset</span>
22 <span id="abs_err"></span> &deg;C
23 </div>
24 </div>
25 <div id="buttonbar">
26 <a id="button_enable" href="/enable">Enabled</a>
27 <a id="button_disable" href="/disable">Disabled</a>
28 </div>
29 </div>
30
1331 <section style="display:none;">
1432 <h2>timers</h2>
1533 <table id="timers">
2846 <h2>status</h2>
2947 <table>
3048 <tr>
31 <td class="label heater_label">Heater is:</td>
32 <td class="val">
33 <a href="/enable" id="enabled"></a>,
34 <span id="heating"></span>
35 </td>
36 </tr>
37 <tr>
38 <td class="label temp_label">Current temperature:</td>
39 <td class="val"><span id="old_temp"></span> &deg;C</td>
40 </tr>
41 <tr>
4249 <td class="label target_label">Target temperature:</td>
4350 <td class="val">
4451 <span id="target_display">
45 <span id="target"></span> &deg;C
52 <span id="old_target"></span> &deg;C
4653 </span>
4754 <span id="target_change">
4855 <form method="POST" action="/target">
5158 </form>
5259 </span>
5360 </td>
54 </tr>
55 <tr>
56 <td class="label error_label">Error:</td>
57 <td class="val" id="err_td"><span id="abs_err"></span> &deg;C</td>
5861 </tr>
5962 <tr>
6063 <td class="label">
00 @font-face {
11 font-family: 'Norwester';
22 src: url('webfonts/norwester.eot');
3 src: local('Norwester'),
4 local('Norwester Regular'),
3 src: local('Norwester'),
4 local('Norwester Regular'),
55 url('webfonts/norwester.woff') format('woff'),
66 url('webfonts/norwester.ttf') format('truetype'),
7 url('webfonts/norwester.svg#font') format('svg');
7 url('webfonts/norwester.svg#font') format('svg');
88 }
99
1010 body {
1111 font-family: 'Source Sans Pro', sans-serif;
12 margin: 50px;
1312 font-size: 14pt;
13 background: #FE4F00;
14 margin: 0;
1415 }
1516
16 h1, h2, h3, h4, h5, h6 {
17 font-weight: normal;
17 #floater {
18 position: fixed;
19 right: 32pt;
20 top: 32pt;
21 font-family: Norwester, monospace;
22 width: 500pt;
1823 }
1924
20 section {
21 border-left: 7pt solid #EEE;
22 padding-left: 10pt;
25 #display {
26 background: #FFF;
27 padding: 32pt;
28 text-align: right;
2329 }
2430
25 .val {
26 font-weight: bold;
27 text-align: right;
28 font-family: Norwester, monospace;
29 font-size: 18pt;
31 #temp_container {
32 font-size: 64pt;
33 text-align: right;
3034 }
3135
32 table {
33 width: 100%;
36 #temp {
37 font-size: 128pt;
3438 }
3539
36 #plot {
37 margin-top: 14pt;
38 width: 100%;
40 #target_container, #error_container {
41 font-size: 24pt;
42 line-height: 32pt;
43 text-align: right;
44 display: inline;
45 margin-left: 16pt;
3946 }
4047
41 .label {
42 border-left: 14pt solid #FFF;
43 padding-left: 14pt;
44 }
45 .temp_label { border-color: #F00; }
46 .target_label { border-color: #00F; }
47 .error_label { border-color: #666; }
48 .hot { color: #F00; }
49
50 #target_change {
51 display: none;
48 #target_container {
49 position: absolute;
50 right: 250pt;
5251 }
5352
54 #target_input, .pid_param {
55 font-size: 18pt;
56 font-family: 'Source Code Pro', monospace;
57 width: 100pt;
58 text-align: right;
53 #target, #abs_err {
54 font-size: 32pt;
5955 }
6056
61 .subtext {
62 display: block;
63 font-size: 9pt;
64 margin-top: -2pt;
65 color: #CCC;
57 #display .label {
58 color: #FFB213;
6659 }
6760
68 #enabled {
69 color: #666;
70 text-decoration: none;
61 #buttonbar {
62 width: 500pt;
63 float: left;
64 background: #FFB213;
65 color: #FE4F00;
7166 }
7267
73 .time {
74 width: 24pt;
75 text-align: center;
68 #buttonbar a {
69 display: block;
70 width: 216pt;
71 padding: 8pt 16pt;
72 text-transform: uppercase;
73 color: #FE4F00;
74 text-decoration: none;
7675 }
7776
78 .expired {
79 color: #F00;
77 #buttonbar #button_enable {
78 float: left;
8079 }
8180
82 #timers form {
83 margin-left: 10pt;
84 display: inline;
81 #buttonbar #button_disable {
82 float: right;
83 text-align: right;
8584 }
8685
87 input[type=submit] {
88 border: 1px solid #DDD;
89 background: #FFF;
90 position: relative;
91 top: -1px;
86 #buttonbar .selected {
87 background: #FFF;
88 color: #FFB213;
9289 }
9390
94 #enable_audio {
95 margin-top: 12pt;
96 font-size: 22pt;
91 h1 {
92 font-size: 12pt;
93 margin: 0;
94 padding: 0;
95 position: absolute;
96 bottom: 16pt;
97 right: 32pt;
98 color: #FFB213;
9799 }
100
101 h1 a {
102 color: #FFB213;
103 text-decoration: none;
104 }
0 // store 3 hours of data for graphing
1 var MAX_TEMP_STORE = 60 * 60 * 3;
2 var temps = new Array();
3
04 var tempElem, absErrElem, targetElem, heatingElem, plotElem, accErrElem
15 var targetDisplayElem, targetChangeElem, targetInputElem
26 var pInputElem, iInputElem, dInputElem
37 var enabledElem, maxErrElem
48 var timerElem, timerAudio
9
10 var enableButton, disableButton
11
12 function primeTempCache() {
13 $.ajax({
14 url: '/json',
15 type: 'json',
16 success: function(resp) {
17 for (var i = 0; i < resp.length; i++) {
18 temps.push(resp[i].Temp);
19 if (temps.length > MAX_TEMP_STORE) {
20 temps.shift();
21 }
22 }
23 console.log("initialized temps to:");
24 console.log(temps);
25 getApiData();
26 }
27 })
28 }
529
630 function getApiData() {
731 $.ajax({
2044 target = data.Target,
2145 err = temp - target;
2246
23 $(tempElem).text(temp.toFixed(2));
47 if (temp != undefined) {
48 temps.push(temp);
49 while (temps.length > MAX_TEMP_STORE) {
50 temps.shift();
51 }
52 }
53
54 $(tempElem).text(temp.toFixed(1));
2455 $(targetElem).text(target.toFixed(2));
2556 $(absErrElem).text((err >= 0 ? '+' : '') + err.toFixed(2));
57
58 if (data.Enabled) {
59 $(enableButton).addClass('selected')
60 $(disableButton).removeClass('selected')
61 } else {
62 $(enableButton).removeClass('selected')
63 $(disableButton).addClass('selected')
64 }
65
2666 $(accErrElem).text(data.AccError.toFixed(2))
2767 $(maxErrElem).text(data.MaxError.toFixed(2))
28 $(enabledElem).text(data.Enabled ? "ENABLED" : "DISABLED")
2968
3069 pInputElem.setAttribute('value', data.Pid.P)
3170 iInputElem.setAttribute('value', data.Pid.I)
3271 dInputElem.setAttribute('value', data.Pid.D)
33
34 if (data.Heating) {
35 $(heatingElem).addClass('hot')
36 $(heatingElem).removeClass('cold')
37 $(heatingElem).text('ON')
38 } else {
39 $(heatingElem).addClass('cold')
40 $(heatingElem).removeClass('hot')
41 $(heatingElem).text('OFF')
42 }
4372 }
4473
4574 function getTimerData() {
103132 timerElem.innerHTML = ''
104133 for (var i = 0; i < data.length; i++) {
105134 timer = data[i]
106 console.log(timer)
107 if (timer.Expired) {
108 timerAudio.play()
109 }
135 console.log(timer)
136 if (timer.Expired) {
137 timerAudio.play()
138 }
110139 timerElem.appendChild(makeTimer(timer));
111140 }
141 }
142
143 function attachRequest(elem, path, data) {
144 $(elem).click(function(e) {
145 // add spinnery thing
146 $.ajax({
147 url: path,
148 data: data,
149 type: 'html',
150 success: function(resp) {
151 console.log("got response to " + path + ": " + resp);
152 }
153 })
154 e.preventDefault();
155 });
156 console.log("sent onclick for elem to " + path)
112157 }
113158
114159 $(document).ready(function() {
115160 tempElem = document.getElementById('temp')
116161 absErrElem = document.getElementById('abs_err')
117162 targetElem = document.getElementById('target')
163
164 enableButton = document.getElementById('button_enable')
165 attachRequest(enableButton, "/enable");
166 disableButton = document.getElementById('button_disable')
167 attachRequest(disableButton, "/disable");
168
118169 heatingElem = document.getElementById('heating')
119170 plotElem = document.getElementById('plot')
120171 accErrElem = document.getElementById('acc_err')
135186 targetInputElem.setAttribute('value', $(targetElem).text())
136187 }
137188
138 getApiData()
189 primeTempCache()
139190
140191 timerElem = document.getElementById('timers')
141192 timerAudio = document.getElementById('timernoise')
142 audioEnable = document.getElementById('enable_audio')
143 audioEnable.onclick = function() {
144 $(audioEnable).css('display', 'none')
145 timerAudio.play()
146 }
193 audioEnable = document.getElementById('enable_audio')
194 audioEnable.onclick = function() {
195 $(audioEnable).css('display', 'none')
196 timerAudio.play()
197 }
147198 getTimerData()
148199 })
3232 func (s *SousVide) InitTherm() error {
3333 var err error
3434 if *FakeTemp {
35 s.Temp = s.Target
3536 return nil
3637 }
3738
6465 if s.Heating {
6566 s.Temp += Celsius(10 * rand.Float64())
6667 } else {
67 s.Temp -= Celsius(10 * rand.Float64())
68 if rand.Int() % 3 == 0 {
69 s.Temp -= Celsius(rand.Float64())
70 }
6871 }
6972 if s.Temp < 0 {
7073 s.Temp = 0