Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly rewrite VEDirect interfacer for Python3. #121

Merged
merged 3 commits into from
Sep 23, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 62 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,31 @@ 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 %s"%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 +59,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 +68,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 +100,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': " + 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
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 %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
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']))