Skip to content

Commit

Permalink
Merge branch 'vedirect-fixes' of https://github.com/bwduncan/emonhub
Browse files Browse the repository at this point in the history
…into bwduncan-vedirect-fixes
  • Loading branch information
Trystan Lea committed Sep 22, 2020
2 parents c125b3c + fe443b6 commit 04dd7a3
Showing 1 changed file with 61 additions and 90 deletions.
151 changes: 61 additions & 90 deletions src/interfacers/EmonHubVEDirectInterfacer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import Cargo
from emonhub_interfacer import EmonHubInterfacer

"""class EmonhubSerialInterfacer
class EmonHubVEDirectInterfacer(EmonHubInterfacer):
"""class EmonhubSerialInterfacer
Monitors the serial port for data
Monitors the serial port for data
"""
"""

class EmonHubVEDirectInterfacer(EmonHubInterfacer):

WAIT_HEADER, IN_KEY, IN_VALUE, IN_CHECKSUM = range(4)

Expand All @@ -26,31 +26,30 @@ def __init__(self, name, com_port='', com_baud=9600, toextract='', poll_interval
# 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 = ''
# VE Direct state machine requirements
self.header1 = b'\r'
self.header2 = b'\n'
self.delimiter = b'\t'
self.key = bytearray()
self.value = bytearray()
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", self._extract)
self.poll_interval = float(poll_interval)

# Polling timer
self.last_read = 0.0

def input(self, byte):
"""
Parse serial byte code from VE.Direct
Return None if more data is needed, return an empty dict if the
checksum was wrong, return a dict of valid data otherwise.
"""
# 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:
Expand All @@ -59,7 +58,7 @@ def input(self, byte):
self.state = self.IN_KEY
elif self.state == self.IN_KEY:
if byte == self.delimiter:
if self.key == 'Checksum':
if self.key.decode() == 'Checksum':
self.state = self.IN_CHECKSUM
else:
self.state = self.IN_VALUE
Expand All @@ -68,22 +67,25 @@ def input(self, 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 = ''
self.dict[self.key.decode()] = self.value.decode()
self.key = bytearray()
self.value = bytearray()
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.key = bytearray()
self.value = bytearray()
try:
if self.bytes_sum % 256 == 0:
return self.dict
self._log.error("Invalid checksum, discarding data")
return {}
finally:
self.dict = {}
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()
raise RuntimeError("Impossible state")

def close(self):
"""Close serial port"""
Expand All @@ -97,97 +99,66 @@ def _open_serial_port(self, com_port, com_baud):
"""Open serial port
com_port (string): path to COM port
com_baud (int): baud rate
"""

#if not int(com_baud) in [75, 110, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]:
# self._log.debug("Invalid 'com_baud': %d | Default of 9600 used", com_baud)
# com_baud = 9600

try:
s = serial.Serial(com_port, com_baud, timeout=10)
self._log.debug("Opening serial port: %s @ %d bits/s", com_port, com_baud)
except serial.SerialException as e:
self._log.error(e)
s = False
# raise EmonHubInterfacerInitError('Could not open COM port %s' % com_port)
return s
self._log.debug("Opening serial port: %s @ %s bits/s", com_port, com_baud)
return serial.Serial(com_port, com_baud, timeout=10)
except serial.SerialException:
self._log.exception("Open error")

def parse_package(self, data):
"""
Convert package from vedirect dictionary format to emonhub expected format
"""
clean_data = [str(self._settings['nodeoffset'])]
clean_data = []
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]))
# Emonhub doesn't like strings so we convert them to floats
try:
clean_data.append(float(data[key]))
except KeyError:
self._log.warning("Requested key %s missing from received data", key)
continue
except ValueError:
# VEDirect values which aren't numerical are either "OFF" or... something else.
clean_data.append(0.0 if data[key] == 'OFF' else 1.0)
return clean_data

def _read_serial(self):
self._log.debug(" Starting Serial read")
self._log.debug("Starting Serial read from %s", self._ser)
try:
while self._rx_buf == '':
byte = self._ser.read(1).decode()
packet = self.input(byte)
while True:
# Read one byte at a time from the serial port and pass it into
# the input FSM until we have a complete packet, then return it
packet = self.input(self._ser.read())
if packet is not None:
self._rx_buf = packet

except Exception as e:
self._log.error(e)
self._rx_buf = ""
return packet
except Exception: # FIXME Too general Exception. Maybe SerialException?
self._log.exception("Read error")

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 %d seconds ", now - self.last_read)
# Wait to read based on poll_interval
return

# Read from serial
self._read_serial()
# Update last read time
rx_buf = self._read_serial()
self.last_read = now
# If line incomplete, exit
if self._rx_buf == '':
# If _read_serial raised an exception or returned empty dict, exit
if not 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")
if 'nodeoffset' not in self._settings:
self._log.error("nodeoffset needed in emonhub configuration, make sure it exists and is integer")

return c
return Cargo.new_cargo(rawdata=rx_buf,
realdata=self.parse_package(rx_buf),
nodeid=int(self._settings['nodeoffset']))

0 comments on commit 04dd7a3

Please sign in to comment.