git.haldean.org sousvide / master
Add broadcast capabilities. These are used for the Android app. Will Haldean Brown 5 years ago
4 changed file(s) with 151 addition(s) and 7 deletion(s). Raw diff Collapse all Expand all
0 package main
1
2 import (
3 "flag"
4 "fmt"
5 "log"
6 "net"
7 "strings"
8 "time"
9 )
10
11 var bcastEnabled = flag.Bool("broadcast", true, "enable broadcast")
12 var bcastPort = flag.Int("broadcast_port", 9786, "broadcast port")
13
14 var SV_BCAST_MSG = []byte(fmt.Sprintf(
15 "NOTIFY * HTTP/1.1\r\nHost: 255.255.255.255:%d\r\nX-263A-Capabilities: sousvide\r\n", *bcastPort))
16 var SV_BCAST_FIELD_STREAM = "X-263A-Stream: %v:%d"
17
18 func StartBroadcast() {
19 if (!*bcastEnabled) {
20 log.Printf("broadcast disabled")
21 return
22 }
23 log.Printf("broadcast enabled, starting broadcast loop")
24
25 sock, err := net.DialUDP("udp4", nil, &net.UDPAddr{
26 IP: net.IPv4bcast, Port: *bcastPort,
27 })
28 if err != nil {
29 log.Panicf("error creating broadcast port: %v", err)
30 }
31 defer sock.Close()
32
33 var addr string
34 addrs, err := net.InterfaceAddrs()
35 if err != nil {
36 log.Panicf("error getting interface addresses: %v", err)
37 }
38 for _, a := range addrs {
39 ipAddr := a.String()
40 ipAddr = ipAddr[0:strings.Index(ipAddr, "/")]
41 ip := net.ParseIP(ipAddr)
42 if ip != nil && !ip.IsLoopback() && ip.To4() != nil {
43 log.Printf("broadcasting address %v", ipAddr)
44 addr = ipAddr
45 }
46 }
47
48 field := []byte(fmt.Sprintf(SV_BCAST_FIELD_STREAM, addr, *SockPort))
49 msg := make([]byte, len(SV_BCAST_MSG) + len(field))
50 copy(msg, SV_BCAST_MSG)
51 for i := 0; i < len(field); i++ {
52 msg[i + len(SV_BCAST_MSG)] = field[i]
53 }
54
55 msg_len := len(msg)
56 tick := time.Tick(5 * time.Second)
57 for _ = range tick {
58 n, err := sock.Write(msg)
59 if err != nil {
60 log.Printf("warning, could not send broadcast: %v", err)
61 continue
62 }
63 if n != msg_len {
64 log.Printf("warning, sent %d bytes of %d in broadcast", n, msg_len)
65 continue
66 }
67 }
68 }
6565
6666 t, err := floatData(resp, req, "target")
6767 if err != nil {
68 return
68 t = float64(s.Target)
6969 }
7070 p, err := floatData(resp, req, "p")
7171 if err != nil {
72 return
72 p = s.Pid.P
7373 }
7474 i, err := floatData(resp, req, "i")
7575 if err != nil {
76 return
76 i = s.Pid.I
7777 }
7878 d, err := floatData(resp, req, "d")
7979 if err != nil {
80 return
80 d = s.Pid.D
8181 }
8282 s.Pid.P = p
8383 s.Pid.I = i
0 package main
1
2 import (
3 "encoding/json"
4 "flag"
5 "fmt"
6 "log"
7 "net"
8 )
9
10 var SockPort = flag.Int("sockport", 7897, "port used for streaming temperature data")
11
12 func StartSockServer() chan HistorySample {
13 stream := make(chan HistorySample, 1)
14 ready := make(chan bool)
15 toSock := make(chan []byte, 1)
16 go dispatchStream(stream, ready, toSock)
17 go listenSock(ready, toSock)
18 return stream
19 }
20
21 func dispatchStream(
22 stream chan HistorySample, ready chan bool, toSock chan []byte) {
23 sockReady := false
24 for {
25 select {
26 case sample := <-stream:
27 if (!sockReady) {
28 continue
29 }
30 msg, err := json.Marshal(sample)
31 if err != nil {
32 log.Printf("warning: could not stream snapshot: %v", err)
33 continue
34 }
35 toSock <- msg
36
37 case sockReady = <-ready:
38 // all good
39 }
40 }
41 }
42
43 func listenSock(ready chan bool, toSock chan []byte) {
44 ss, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *SockPort))
45 if err != nil {
46 log.Fatalf("error: could not listen on %d: %v", *SockPort, err)
47 }
48 for {
49 log.Printf("accepting new client on :%d", *SockPort)
50 sock, err := ss.Accept()
51 if err != nil {
52 log.Printf("warning: could not accept on socket: %v", err)
53 }
54 log.Printf("sending snapshots to client %v", sock.RemoteAddr())
55 ready <- true
56 for {
57 msg := <-toSock
58 _, err := sock.Write(msg)
59 if err != nil {
60 log.Printf("warning: failed to write to socket: %v", err)
61 break
62 }
63 _, err = sock.Write([]byte("\n\x00"))
64 }
65 ready <- false
66 sock.Close()
67 }
68 }
2424 var PidFile = flag.String("pid_file", "pid.json", "file to save PID values in")
2525 var StartEnabled = flag.Bool("enabled", false, "start with heater enabled")
2626 var StartTarget = flag.Float64("target", 0, "initial target temperature, in C")
27
28 var Stream chan HistorySample
2729
2830 type SousVide struct {
2931 Heating bool
98100 }
99101
100102 func (s *SousVide) checkpoint() {
103 snapshot := s.Snapshot()
101104 if len(s.History) == HistoryLength {
102105 for i := 0; i < HistoryLength-1; i++ {
103106 s.History[i] = s.History[i+1]
104107 }
105 s.History[len(s.History)-1] = s.Snapshot()
108 s.History[len(s.History)-1] = snapshot
106109 } else {
107 s.History = append(s.History, s.Snapshot())
108 }
110 s.History = append(s.History, snapshot)
111 }
112 Stream <- snapshot
109113
110114 s.AccError = 0
111115 s.MaxError = 0
192196 return
193197 }
194198
199 Stream = StartSockServer()
195200 go StartTimerUpdateLoop()
196201 go s.StartControlLoop()
202 go StartBroadcast()
197203 s.StartServer()
198204 }