diff options
author | Mike Crute <mike@crute.us> | 2022-08-04 18:03:04 -0700 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2022-08-04 18:03:04 -0700 |
commit | 74261a98d6cf34006a28bb90433ccf2ee4ec915d (patch) | |
tree | e2cbc36679b462884db73c28b1ea0787a74744a7 | |
parent | 3eb5a943550842f4614d6873a7acbf63b82a0651 (diff) | |
download | go-inform-74261a98d6cf34006a28bb90433ccf2ee4ec915d.tar.bz2 go-inform-74261a98d6cf34006a28bb90433ccf2ee4ec915d.tar.xz go-inform-74261a98d6cf34006a28bb90433ccf2ee4ec915d.zip |
Remove old demo server
-rw-r--r-- | inform/inform.go | 13 | ||||
-rw-r--r-- | inform/rx_messages.go | 93 | ||||
-rw-r--r-- | inform/server.go | 218 | ||||
-rw-r--r-- | inform/tx_messages.go | 83 |
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 | ||
67 | func (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 |
79 | func (i *InformWrapper) FormattedMac() string { | 66 | func (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 @@ | |||
1 | package inform | ||
2 | |||
3 | // Messages we receive from devices | ||
4 | |||
5 | import ( | ||
6 | "encoding/json" | ||
7 | ) | ||
8 | |||
9 | type 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 | |||
20 | type 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 | |||
36 | func (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 @@ | |||
1 | package inform | ||
2 | |||
3 | import ( | ||
4 | "container/list" | ||
5 | "errors" | ||
6 | "fmt" | ||
7 | "log" | ||
8 | "net/http" | ||
9 | "sync" | ||
10 | "time" | ||
11 | ) | ||
12 | |||
13 | func 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 | |||
21 | type Device struct { | ||
22 | Initialized bool | ||
23 | CurrentState bool | ||
24 | DesiredState bool | ||
25 | *sync.Mutex | ||
26 | } | ||
27 | |||
28 | type InformHandler struct { | ||
29 | Codec *Codec | ||
30 | ports map[string]map[int]*Device | ||
31 | queue map[string]*list.List | ||
32 | *sync.RWMutex | ||
33 | } | ||
34 | |||
35 | func 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 | |||
44 | func (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 | |||
65 | func (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 | |||
82 | func (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 | |||
96 | func (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 | |||
133 | func (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 | |||
155 | func (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) | ||
210 | func 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 @@ | |||
1 | package inform | ||
2 | |||
3 | // Messages we send to devices | ||
4 | |||
5 | import ( | ||
6 | "strconv" | ||
7 | "time" | ||
8 | ) | ||
9 | |||
10 | type 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 | ||
29 | func (m *CommandMessage) Freshen() { | ||
30 | m.DateTime = time.Now().Format(time.RFC3339) | ||
31 | m.ServerTime = unixMicroPSTString() | ||
32 | m.Time = unixMicroPST() | ||
33 | } | ||
34 | |||
35 | func 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 | |||
55 | type NoopMessage struct { | ||
56 | Type string `json:"_type"` | ||
57 | Interval int `json:"interval"` | ||
58 | ServerTimeUTC string `json:"server_time_in_utc"` | ||
59 | } | ||
60 | |||
61 | func unixMicroPST() int64 { | ||
62 | l, _ := time.LoadLocation("America/Los_Angeles") | ||
63 | tnano := time.Now().In(l).UnixNano() | ||
64 | return tnano / int64(time.Millisecond) | ||
65 | } | ||
66 | |||
67 | func unixMicroPSTString() string { | ||
68 | return strconv.FormatInt(unixMicroPST(), 10) | ||
69 | } | ||
70 | |||
71 | func unixMicroUTCString() string { | ||
72 | tnano := time.Now().UTC().UnixNano() | ||
73 | t := tnano / int64(time.Millisecond) | ||
74 | return strconv.FormatInt(t, 10) | ||
75 | } | ||
76 | |||
77 | func NewNoop(interval int) *NoopMessage { | ||
78 | return &NoopMessage{ | ||
79 | Type: "noop", | ||
80 | Interval: interval, | ||
81 | ServerTimeUTC: unixMicroUTCString(), | ||
82 | } | ||
83 | } | ||