-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgoraven.go
148 lines (132 loc) · 3.04 KB
/
goraven.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// A simple library for interacting with the Rainforest Automation RAVEn
package goraven
import (
"bufio"
"bytes"
"encoding/xml"
"errors"
"github.com/schleibinger/sio"
"syscall"
)
// Types of events
const (
TIME = "time"
PRICE = "price"
DEMAND = "demand"
SUMMATION = "summation"
MESSAGE = "message"
)
type Raven struct {
p *sio.Port
reader *bufio.Reader
}
// The structure for a simple command with a single argument (Name)
type smplCommand struct {
XMLName xml.Name `xml:"Command"`
Name string `xml:"Name"`
MeterMacId string `xml:"MeterMacId,omitempty"`
Refresh string `xml:"Refresh,omitempty"`
}
// Connect opens a connection to a RAVEn, given the port name (/dev/ttyUSB0)
func Connect(dev string) (*Raven, error) {
p, err := sio.Open(dev, syscall.B115200)
if err != nil {
return nil, err
}
r := bufio.NewReader(p)
err = initReader(r)
if err != nil {
return nil, err
}
return &Raven{p, r}, nil
}
// Close closes the RAVEn's port safely
func (r *Raven) Close() error {
return r.p.Close()
}
// simpleCommand sends a simple command
func (r *Raven) simpleCommand(command string, refresh bool) error {
v := &smplCommand{Name: command}
if refresh {
v.Refresh = "Y"
}
return r.sendCommand(v)
}
// sendCommand sends a generic command
func (r *Raven) sendCommand(v interface{}) error {
enc := xml.NewEncoder(r.p)
if err := enc.Encode(v); err != nil {
return err
}
return nil
}
// initReader reads and discards the first message
func initReader(r *bufio.Reader) error {
for {
line, err := r.ReadBytes(10)
if err != nil {
return err
}
if isEndElement(line) {
return nil
}
}
panic("unreachable")
}
func nextStart(dec *xml.Decoder) (xml.StartElement, error) {
// Find the next starting element
for {
t, err := dec.Token()
if err != nil {
return xml.StartElement{}, err
}
switch t := t.(type) {
case xml.StartElement:
return t, nil
}
}
}
// Receive grabs the next "Notify" message in the stream
func (r *Raven) Receive() (notify interface{}, err error) {
dec := xml.NewDecoder(r.p)
se, err := nextStart(dec)
if err != nil {
return nil, err
}
switch se.Name.Local {
case "ConnectionStatus":
notify = &ConnectionStatus{}
case "DeviceInfo":
notify = &DeviceInfo{}
case "ScheduleInfo":
notify = &ScheduleInfo{}
case "MeterList":
notify = &MeterList{}
case "NetworkInfo":
notify = &NetworkInfo{}
case "MeterInfo":
notify = &MeterInfo{}
case "MessageCluster":
notify = &MessageCluster{}
case "InstantaneousDemand":
notify = &InstantaneousDemand{}
case "CurrentSummationDelivered":
notify = &CurrentSummationDelivered{}
case "TimeCluster":
notify = &TimeCluster{}
case "PriceCluster":
notify = &PriceCluster{}
default:
return nil, errors.New("Unrecognized Notify Message")
}
err = dec.DecodeElement(notify, &se)
return notify, err
}
// Begins with ' <'
func isMidElement(line []byte) bool {
return bytes.HasPrefix(line, []byte{32, 32, 60})
}
// Begins with '</'
func isEndElement(line []byte) bool {
return bytes.HasPrefix(line, []byte{60, 47})
}