-
Notifications
You must be signed in to change notification settings - Fork 1
/
control.go
126 lines (108 loc) · 2.46 KB
/
control.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// hat tip to github.com/aqua/raspberrypi for inspiration for the GPIO code
package main
import (
"fmt"
"log"
"os"
"time"
)
const HeaterGpioPin = 18
func checkHeaterExported() error {
_, err := os.Stat(fmt.Sprintf("/sys/class/gpio/gpio%d", HeaterGpioPin))
if err == nil {
return nil
}
if !os.IsNotExist(err) {
return err
}
fd, err := os.OpenFile(
"/sys/class/gpio/export", os.O_WRONLY|os.O_SYNC, 0666)
if err != nil {
return err
}
defer fd.Close()
_, err = fmt.Fprintf(fd, "%d\n", HeaterGpioPin)
return err
}
func setHeaterOutputMode() error {
fd, err := os.OpenFile(
fmt.Sprintf("/sys/class/gpio/gpio%d/direction", HeaterGpioPin),
os.O_WRONLY|os.O_SYNC, 0666)
if err != nil {
return err
}
defer fd.Close()
fmt.Fprintf(fd, "out")
return nil
}
func (s *SousVide) InitGpio() error {
var err error
if s.Gpio.Stub {
s.Gpio.HeaterFd, err = os.Open("/dev/null")
if err != nil {
log.Fatalf("could not open /dev/null: %v", err)
}
return nil
}
err = checkHeaterExported()
if err != nil {
return err
}
err = setHeaterOutputMode()
if err != nil {
return err
}
s.Gpio.HeaterFd, err = os.OpenFile(
fmt.Sprintf("/sys/class/gpio/gpio%d/value", HeaterGpioPin),
os.O_WRONLY|os.O_SYNC, 0666)
if err != nil {
return err
}
return nil
}
func (s *SousVide) StartControlLoop() {
tick := time.Tick(InterruptDelay)
for _ = range tick {
s.DataLock.Lock()
err := s.MeasureTemp()
if err != nil {
log.Printf("could not read temperature: %v", err)
} else {
co := s.ControllerResult()
s.Heating = co > 0 && s.Enabled
s.UpdateHardware()
s.checkpoint()
}
s.DataLock.Unlock()
}
}
func (s *SousVide) ControllerResult() Celsius {
s.lastPOutput = s.Pid.P * float64(s.Error())
if len(s.History) > 0 {
integral := float64(0)
for _, h := range s.History {
integral += h.AbsError
}
integral /= float64(len(s.History))
s.lastIOutput = s.Pid.I * integral
}
// ignore derivative term if we have no history to use
if len(s.History) > LowpassSamples {
// use weighted window over three samples instead of two to act as a
// low-pass filter
N := len(s.History)
d := (s.History[N-LowpassSamples-1].Temp - s.History[N-1].Temp) / 2
s.lastDOutput = s.Pid.D * d
}
s.lastControl = s.lastPOutput + s.lastIOutput + s.lastDOutput
return Celsius(s.lastControl)
}
func (s *SousVide) UpdateHardware() {
var heatVal string
if s.Heating {
heatVal = "1\n"
} else {
heatVal = "0\n"
}
fmt.Fprintf(s.Gpio.HeaterFd, heatVal)
}