-
Notifications
You must be signed in to change notification settings - Fork 83
/
Copy pathEmonHubVEDirectInterfacer.py
193 lines (155 loc) · 6.09 KB
/
EmonHubVEDirectInterfacer.py
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
import time
import serial
import Cargo
from emonhub_interfacer import EmonHubInterfacer
"""class EmonhubSerialInterfacer
Monitors the serial port for data
"""
class EmonHubVEDirectInterfacer(EmonHubInterfacer):
WAIT_HEADER, IN_KEY, IN_VALUE, IN_CHECKSUM = range(4)
def __init__(self, name, com_port='', com_baud=9600, toextract='', poll_interval=30):
"""Initialize interfacer
com_port (string): path to COM port
"""
# Initialization
super().__init__(name)
# Open serial port
self._ser = self._open_serial_port(com_port, com_baud)
# Initialize RX buffer
self._rx_buf = ''
# VE Direct requirements
self.header1 = '\r'
self.header2 = '\n'
self.delimiter = '\t'
self.key = ''
self.value = ''
self.bytes_sum = 0
self.state = self.WAIT_HEADER
self.dict = {}
self.poll_interval = int(poll_interval)
self.last_read = time.time()
# Parser requirements
self._extract = toextract
#print "init system with to extract %s"%self._extract
def input(self, byte):
"""
Parse serial byte code from VE.Direct
"""
# FIXME This whole setup is just to parse 'key\tvalue\r\n' lines with 'Checksum' keys over serial
self.bytes_sum += ord(byte)
if self.state == self.WAIT_HEADER:
if byte == self.header1:
self.state = self.WAIT_HEADER
elif byte == self.header2:
self.state = self.IN_KEY
elif self.state == self.IN_KEY:
if byte == self.delimiter:
if self.key == 'Checksum':
self.state = self.IN_CHECKSUM
else:
self.state = self.IN_VALUE
else:
self.key += byte
elif self.state == self.IN_VALUE:
if byte == self.header1:
self.state = self.WAIT_HEADER
self.dict[self.key] = self.value
self.key = ''
self.value = ''
else:
self.value += byte
elif self.state == self.IN_CHECKSUM:
self.key = ''
self.value = ''
self.state = self.WAIT_HEADER
if self.bytes_sum % 256 == 0:
self.bytes_sum = 0
return self.dict
# FIXME if the checksum is wrong should we not throw away the dict?
self.bytes_sum = 0
else:
raise AssertionError()
def close(self):
"""Close serial port"""
# Close serial port
if self._ser is not None:
self._log.debug("Closing serial port")
self._ser.close()
def _open_serial_port(self, com_port, com_baud):
"""Open serial port
com_port (string): path to COM port
"""
#if not int(com_baud) in [75, 110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]:
# self._log.debug("Invalid 'com_baud': " + str(com_baud) + " | Default of 9600 used")
# com_baud = 9600
try:
s = serial.Serial(com_port, com_baud, timeout=10)
self._log.debug("Opening serial port: " + str(com_port) + " @ "+ str(com_baud) + " bits/s")
except serial.SerialException as e:
self._log.error(e)
s = False
# raise EmonHubInterfacerInitError('Could not open COM port %s' % com_port)
return s
def parse_package(self, data):
"""
Convert package from vedirect dictionary format to emonhub expected format
"""
clean_data = [str(self._settings['nodeoffset'])]
for key in self._extract:
if key in data:
# Emonhub doesn't like strings so we convert them to ints
tempval = 0
try:
tempval = float(data[key])
except Exception as e:
tempval = data[key]
if not isinstance(tempval, float):
if data[key] == "OFF":
data[key] = 0
else:
data[key] = 1
clean_data.append(str(data[key]))
return clean_data
def _read_serial(self):
self._log.debug(" Starting Serial read")
try:
while self._rx_buf == '':
byte = self._ser.read(1).decode()
packet = self.input(byte)
if packet is not None:
self._rx_buf = packet
except Exception as e:
self._log.error(e)
self._rx_buf = ""
def read(self):
"""Read data from serial port and process if complete line received.
Return data as a list: [NodeID, val1, val2]
"""
if not self._ser:
return False
# Read serial RX
now = time.time()
if now - self.last_read <= self.poll_interval:
#self._log.debug(" Waiting for %s seconds " % (str(now - self.last_read)))
# Wait to read based on poll_interval
return
# Read from serial
self._read_serial()
# Update last read time
self.last_read = now
# If line incomplete, exit
if self._rx_buf == '':
return
#Sample data looks like {'FW': '0307', 'SOC': '1000', 'Relay': 'OFF', 'PID': '0x203', 'H10': '6', 'BMV': '700', 'TTG': '-1', 'H12': '0', 'H18': '0', 'I': '0', 'H11': '0', 'Alarm': 'OFF', 'CE': '0', 'H17': '9', 'P': '0', 'AR': '0', 'V': '26719', 'H8': '29011', 'H9': '0', 'H2': '0', 'H3': '0', 'H1': '-1633', 'H6': '-5775', 'H7': '17453', 'H4': '0', 'H5': '0'}
# Create a Payload object
c = Cargo.new_cargo(rawdata=self._rx_buf)
f = self.parse_package(self._rx_buf)
# Reset buffer
self._rx_buf = ''
if f:
if int(self._settings['nodeoffset']):
c.nodeid = int(self._settings['nodeoffset'])
c.realdata = f[1:]
else:
self._log.error("nodeoffset needed in emonhub configuration, make sure it exists and is integer")
return c