aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2022-08-04 18:03:04 -0700
committerMike Crute <mike@crute.us>2022-08-04 18:03:04 -0700
commit74261a98d6cf34006a28bb90433ccf2ee4ec915d (patch)
treee2cbc36679b462884db73c28b1ea0787a74744a7
parent3eb5a943550842f4614d6873a7acbf63b82a0651 (diff)
downloadgo-inform-74261a98d6cf34006a28bb90433ccf2ee4ec915d.tar.bz2
go-inform-74261a98d6cf34006a28bb90433ccf2ee4ec915d.tar.xz
go-inform-74261a98d6cf34006a28bb90433ccf2ee4ec915d.zip
Remove old demo server
-rw-r--r--inform/inform.go13
-rw-r--r--inform/rx_messages.go93
-rw-r--r--inform/server.go218
-rw-r--r--inform/tx_messages.go83
4 files changed, 0 insertions, 407 deletions
diff --git a/inform/inform.go b/inform/inform.go
index 59f969c..da2e095 100644
--- a/inform/inform.go
+++ b/inform/inform.go
@@ -62,19 +62,6 @@ func (i *InformWrapper) UpdatePayload(v interface{}) error {
62 } 62 }
63} 63}
64 64
65// Unmarshal a payload body that we received from a device. Does not work for
66// user-set messages
67func (i *InformWrapper) UnmarshalPayload() (*DeviceMessage, error) {
68 var m DeviceMessage
69
70 err := json.Unmarshal(i.Payload, &m)
71 if err != nil {
72 return nil, err
73 }
74
75 return &m, nil
76}
77
78// Format Mac address bytes as lowercase string with colons 65// Format Mac address bytes as lowercase string with colons
79func (i *InformWrapper) FormattedMac() string { 66func (i *InformWrapper) FormattedMac() string {
80 return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", 67 return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
diff --git a/inform/rx_messages.go b/inform/rx_messages.go
deleted file mode 100644
index 68fd393..0000000
--- a/inform/rx_messages.go
+++ /dev/null
@@ -1,93 +0,0 @@
1package inform
2
3// Messages we receive from devices
4
5import (
6 "encoding/json"
7)
8
9type DeviceMessage struct {
10 IsDefault bool `json:"default"`
11 IP string `json:"ip"`
12 MacAddr string `json:"mac"`
13 ModelNumber string `json:"model"`
14 ModelName string `json:"model_display"`
15 Serial string `json:"serial"`
16 FirmwareVersion string `json:"version"`
17 Outputs []*OutputInfo
18}
19
20type OutputInfo struct {
21 Id string
22 Port int
23 OutputState bool
24 EnergySum float64
25 VoltageRMS float64
26 PowerFactor float64
27 CurrentRMS float64
28 Watts float64
29 ThisMonth float64
30 LastMonth float64
31 Dimmer bool
32 DimmerLevel int
33 DimmerLockSetting int
34}
35
36func (m *DeviceMessage) UnmarshalJSON(data []byte) error {
37 type Alias DeviceMessage
38 aux := &struct {
39 Alarm []struct {
40 Entries []struct {
41 Tag string `json:"tag"`
42 Type string `json:"type"`
43 Val interface{} `json:"val"`
44 } `json:"entries"`
45 Sensor string `json:"sId"`
46 } `json:"alarm"`
47 *Alias
48 }{
49 Alias: (*Alias)(m),
50 }
51
52 if err := json.Unmarshal(data, &aux); err != nil {
53 return err
54 }
55
56 m.Outputs = make([]*OutputInfo, len(aux.Alarm))
57
58 for i, a := range aux.Alarm {
59 o := &OutputInfo{
60 Id: a.Sensor,
61 Port: i + 1,
62 Dimmer: m.ModelNumber == "IWD1U",
63 }
64 m.Outputs[i] = o
65
66 for _, e := range a.Entries {
67 switch t := e.Val; e.Tag {
68 case "output":
69 o.OutputState = t.(float64) == 1
70 case "pf":
71 o.PowerFactor = t.(float64)
72 case "energy_sum":
73 o.EnergySum = t.(float64)
74 case "v_rms":
75 o.VoltageRMS = t.(float64)
76 case "i_rms":
77 o.CurrentRMS = t.(float64)
78 case "active_pwr":
79 o.Watts = t.(float64)
80 case "thismonth":
81 o.ThisMonth = t.(float64)
82 case "lastmonth":
83 o.LastMonth = t.(float64)
84 case "dimmer_level":
85 o.DimmerLevel = int(t.(float64))
86 case "dimmer_lock_setting":
87 o.DimmerLockSetting = int(t.(float64))
88 }
89 }
90 }
91
92 return nil
93}
diff --git a/inform/server.go b/inform/server.go
deleted file mode 100644
index 710b8ac..0000000
--- a/inform/server.go
+++ /dev/null
@@ -1,218 +0,0 @@
1package inform
2
3import (
4 "container/list"
5 "errors"
6 "fmt"
7 "log"
8 "net/http"
9 "sync"
10 "time"
11)
12
13func Log(handler http.Handler) http.Handler {
14 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15 handler.ServeHTTP(w, r)
16 t := time.Now().Format("02/Jan/2006 15:04:05")
17 log.Printf("%s - - [%s] \"%s %s %s\"", r.RemoteAddr, t, r.Method, r.URL, r.Proto)
18 })
19}
20
21type Device struct {
22 Initialized bool
23 CurrentState bool
24 DesiredState bool
25 *sync.Mutex
26}
27
28type InformHandler struct {
29 Codec *Codec
30 ports map[string]map[int]*Device
31 queue map[string]*list.List
32 *sync.RWMutex
33}
34
35func NewInformHandler(c *Codec) *InformHandler {
36 return &InformHandler{
37 Codec: c,
38 ports: make(map[string]map[int]*Device),
39 queue: make(map[string]*list.List),
40 RWMutex: &sync.RWMutex{},
41 }
42}
43
44func (h *InformHandler) AddPort(dev string, port int) {
45 h.Lock()
46 defer h.Unlock()
47
48 _, ok := h.ports[dev]
49 if !ok {
50 h.ports[dev] = make(map[int]*Device)
51 }
52
53 _, ok = h.queue[dev]
54 if !ok {
55 log.Printf("Adding queue for %s", dev)
56 h.queue[dev] = list.New()
57 }
58
59 log.Printf("Adding %s port %d", dev, port)
60 h.ports[dev][port] = &Device{
61 Mutex: &sync.Mutex{},
62 }
63}
64
65func (h *InformHandler) getPort(dev string, port int) (*Device, error) {
66 h.RLock()
67 defer h.RUnlock()
68
69 _, ok := h.ports[dev]
70 if !ok {
71 return nil, errors.New("No device found")
72 }
73
74 p, ok := h.ports[dev][port]
75 if !ok {
76 return nil, errors.New("No port found")
77 }
78
79 return p, nil
80}
81
82func (h *InformHandler) SetState(dev string, port int, state bool) error {
83 p, err := h.getPort(dev, port)
84 if err != nil {
85 return err
86 }
87
88 p.Lock()
89 defer p.Unlock()
90
91 log.Printf("Set state to %t for %s port %d", state, dev, port)
92 p.DesiredState = state
93 return nil
94}
95
96func (h *InformHandler) buildCommands(dev string, pl *DeviceMessage) error {
97 for _, o := range pl.Outputs {
98 ds, err := h.getPort(dev, o.Port)
99 if err != nil {
100 return err
101 }
102 ds.Lock()
103
104 // Get initial state
105 if !ds.Initialized {
106 ds.CurrentState = o.OutputState
107 ds.Initialized = true
108 return nil
109 }
110
111 // State didn't change at the sensor
112 if ds.CurrentState == o.OutputState {
113 if ds.DesiredState != o.OutputState {
114 log.Printf("Toggle state %t for %s port %d", ds.DesiredState, dev, o.Port)
115 // Generate change command
116 // TODO: Don't lock the whole handler
117 h.Lock()
118 h.queue[dev].PushFront(NewOutputCommand(o.Port, ds.DesiredState, 0))
119 h.Unlock()
120 }
121 } else { // Sensor caused the change, leave it alone
122 log.Printf("Sensor state changed %s port %d", dev, o.Port)
123 ds.DesiredState = o.OutputState
124 }
125
126 ds.CurrentState = o.OutputState
127 ds.Unlock() // Don't hold the lock the entire loop
128 }
129
130 return nil
131}
132
133func (h *InformHandler) pop(dev string) *CommandMessage {
134 // TODO: Don't lock the whole handler
135 h.Lock()
136 defer h.Unlock()
137
138 q, ok := h.queue[dev]
139 if !ok {
140 log.Printf("No queue for %s", dev)
141 return nil
142 }
143
144 e := q.Front()
145 if e != nil {
146 h.queue[dev].Remove(e)
147 cmd := e.Value.(*CommandMessage)
148 cmd.Freshen()
149 return cmd
150 }
151
152 return nil
153}
154
155func (h *InformHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
156 if r.Method != http.MethodPost {
157 http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
158 return
159 }
160
161 msg, err := h.Codec.Unmarshal(r.Body)
162 if err != nil {
163 log.Printf("Unmarshal message: %s", err.Error())
164 http.Error(w, "400 Bad Request", http.StatusBadRequest)
165 return
166 }
167
168 pl, err := msg.UnmarshalPayload()
169 if err != nil {
170 log.Printf("Unmarshal payload: %s", err.Error())
171 http.Error(w, "400 Bad Request", http.StatusBadRequest)
172 return
173 }
174
175 dev := msg.FormattedMac()
176 ret := NewInformWrapperResponse(msg)
177 log.Printf("Inform from %s", dev)
178
179 // Send a command until the queue is empty
180 if cmd := h.pop(dev); cmd != nil {
181 ret.UpdatePayload(cmd)
182 } else {
183 // Update internal state vs reality
184 if err := h.buildCommands(msg.FormattedMac(), pl); err != nil {
185 http.Error(w, "500 Server Error", 500)
186 return
187 }
188
189 // If that generated a command send it
190 if cmd = h.pop(dev); cmd != nil {
191 ret.UpdatePayload(cmd)
192 } else {
193 // Otherwise noop
194 ret.UpdatePayload(NewNoop(10))
195 }
196 }
197
198 res, err := h.Codec.Marshal(ret)
199 if err != nil {
200 http.Error(w, "500 Server Error", 500)
201 return
202 }
203
204 fmt.Fprintf(w, "%s", res)
205}
206
207// Create a new server, returns the mux so users can add other methods (for
208// example, if they want to share a process to build a console that also
209// accepts informs)
210func NewServer(handler *InformHandler) (*http.Server, *http.ServeMux) {
211 mux := http.NewServeMux()
212 mux.Handle("/inform", handler)
213
214 return &http.Server{
215 Addr: ":6080",
216 Handler: Log(mux),
217 }, mux
218}
diff --git a/inform/tx_messages.go b/inform/tx_messages.go
deleted file mode 100644
index 8962cad..0000000
--- a/inform/tx_messages.go
+++ /dev/null
@@ -1,83 +0,0 @@
1package inform
2
3// Messages we send to devices
4
5import (
6 "strconv"
7 "time"
8)
9
10type CommandMessage struct {
11 Id string `json:"_id,omitempty"`
12 Type string `json:"_type"`
13 Command string `json:"cmd"`
14 DateTime string `json:"datetime"`
15 DeviceId string `json:"device_id,omitempty"`
16 MacAddress string `json:"mac,omitempty"`
17 Model string `json:"model,omitempty"`
18 OffVoltage int `json:"off_volt,omitempty"`
19 Port int `json:"port"`
20 SensorId string `json:"sId,omitempty"`
21 ServerTime string `json:"server_time_in_utc"`
22 Time int64 `json:"time"`
23 Timer int `json:"timer"`
24 Value int `json:"val"`
25 Voltage int `json:"volt,omitempty"`
26}
27
28// Freshen timestamps
29func (m *CommandMessage) Freshen() {
30 m.DateTime = time.Now().Format(time.RFC3339)
31 m.ServerTime = unixMicroPSTString()
32 m.Time = unixMicroPST()
33}
34
35func NewOutputCommand(port int, val bool, timer int) *CommandMessage {
36 m := &CommandMessage{
37 Type: "cmd",
38 Command: "mfi-output",
39 DateTime: time.Now().Format(time.RFC3339),
40 Port: port,
41 ServerTime: unixMicroPSTString(),
42 Time: unixMicroPST(),
43 Timer: timer,
44 }
45
46 if val {
47 m.Value = 1
48 } else {
49 m.Value = 0
50 }
51
52 return m
53}
54
55type NoopMessage struct {
56 Type string `json:"_type"`
57 Interval int `json:"interval"`
58 ServerTimeUTC string `json:"server_time_in_utc"`
59}
60
61func unixMicroPST() int64 {
62 l, _ := time.LoadLocation("America/Los_Angeles")
63 tnano := time.Now().In(l).UnixNano()
64 return tnano / int64(time.Millisecond)
65}
66
67func unixMicroPSTString() string {
68 return strconv.FormatInt(unixMicroPST(), 10)
69}
70
71func unixMicroUTCString() string {
72 tnano := time.Now().UTC().UnixNano()
73 t := tnano / int64(time.Millisecond)
74 return strconv.FormatInt(t, 10)
75}
76
77func NewNoop(interval int) *NoopMessage {
78 return &NoopMessage{
79 Type: "noop",
80 Interval: interval,
81 ServerTimeUTC: unixMicroUTCString(),
82 }
83}