Hi Mirko, schade...
Wäre toll wenn du dann mit ein bisschen Ruhe draufschauen würdest - denke eine Version fürs GIT wäre schön.
Habe noch mal nachgelegt: STX/ETX Auswertung, die Leseschleifen werden vorzeitig verlassen wenn alles da (keine Timeouts notwendig, evtl. toleranter für langsame Zähler und dennoch schnell), Überprüfung der Checksumme (damit keine falschen Daten übernommen werden), Entfernung von Echo-Zeichen (zumindest mein Lesekopf braucht das).
Bin jetzt runter auf 1,7s (von über 14s am Anfang!)
Noch schneller ginge es wohl, wenn man eine dedizierte Routine für die Baudratenerkennung baut und dann die "Updates" in einem Schreibvorgang ohne Abwarten der Antwort abhandelt.
Wäre toll wenn du dann mit ein bisschen Ruhe draufschauen würdest - denke eine Version fürs GIT wäre schön.
Habe noch mal nachgelegt: STX/ETX Auswertung, die Leseschleifen werden vorzeitig verlassen wenn alles da (keine Timeouts notwendig, evtl. toleranter für langsame Zähler und dennoch schnell), Überprüfung der Checksumme (damit keine falschen Daten übernommen werden), Entfernung von Echo-Zeichen (zumindest mein Lesekopf braucht das).
Bin jetzt runter auf 1,7s (von über 14s am Anfang!)
Noch schneller ginge es wohl, wenn man eine dedizierte Routine für die Baudratenerkennung baut und dann die "Updates" in einem Schreibvorgang ohne Abwarten der Antwort abhandelt.
PHP-Code:
#!/usr/local/bin/python
import logging
import time
import serial
import re
logger = logging.getLogger('DLMS')
class DLMS():
def __init__(self, smarthome, serialport, baudrate = "300", update_cycle = "20"):
self._sh = smarthome
self._update_cycle = int(update_cycle)
self._obis_codes = {}
self._serial = serial.Serial(serialport, int(baudrate), bytesize = serial.SEVENBITS, parity = serial.PARITY_EVEN, timeout = 2)
self._request = bytearray('\x06000\r\n', 'ascii')
def run(self):
self.alive = True
self._sh.scheduler.add('DLMS', self._update_values, prio = 5, cycle = self._update_cycle)
def stop(self):
self.alive = False
self._serial.close()
self._sh.scheduler.remove('DLMS')
def _update_values(self):
logger.debug("dlms: update")
start = time.time()
init_seq = bytes('/?!\r\n', 'ascii')
self._serial.flushInput()
self._serial.write(init_seq)
response = bytes()
prev_length = 0
try:
while self.alive:
response += self._serial.read()
length = len(response)
if (length == prev_length) or ((length > len(init_seq)) and (response[-1] == 0x0a)): # break if timeout or newline-character
break
prev_length = length
except Exception as e:
logger.warning("dlms: {0}".format(e))
# remove echoed chars if present
if (init_seq == response[:len(init_seq)]):
response = response[len(init_seq):]
if (len(response) >= 5) and ((response[4] - 0x30) in range(6)):
baud_capable = 300 * (1 << (response[4] - 0x30))
if baud_capable > self._serial.baudrate:
try:
logger.debug("dlms: meter returned capability for higher baudrate {}".format(baud_capable))
self._request[2] = response[4] # change request to set higher baudrate
self._serial.write(self._request)
logger.debug("dlms: trying to switch baudrate")
switch_start = time.time()
# Alt1:
#self._serial.baudrate = baud_capable
# Alt2:
#settings = self._serial.getSettingsDict()
#settings['baudrate'] = baud_capable
#self._serial.applySettingsDict(settings)
# Alt3:
port = self._serial.port
self._serial.close()
del self._serial
logger.debug("dlms: socket closed - creating new one")
self._serial = serial.Serial(port, baud_capable, bytesize = serial.SEVENBITS, parity = serial.PARITY_EVEN, timeout = 2)
logger.debug("dlms: Switching took: {:.2f}s".format(time.time() - switch_start))
logger.debug("dlms: switch done")
except Exception as e:
logger.warning("dlms: {0}".format(e))
return
else:
self._serial.write(self._request)
response = bytes()
prev_length = 0
try:
while self.alive:
response += self._serial.read()
length = len(response)
if (length == prev_length) or ((length >= 2) and (response[-2] == 0x03)): # break if timeout or "ETX"
break
prev_length = length
except Exception as e:
logger.warning("dlms: {0}".format(e))
logger.debug("dlms: Reading took: {:.2f}s".format(time.time() - start))
# remove echoed chars if present
if (self._request == response[:len(self._request)]):
response = response[len(self._request):]
# perform checks (start with STX, end with ETX, checksum match)
checksum = 0
for i in response[1:]:
checksum ^= i
if (len(response) < 5) or (response[0] != 0x02) or (response[-2] != 0x03) or (checksum != 0x00):
logger.warning("dlms: checksum/protocol error: response={} checksum={}".format(' '.join(hex(i) for i in response), checksum))
return
#print(str(response[1:-4], 'ascii'))
for line in re.split('\r\n', str(response[1:-4], 'ascii')):
#if re.match('[0-9]+\.[0-9]\.[0-9](.+)', line): # allows only x.y.z(foo)
if re.match('[0-9]+\.[0-9].+(.+)', line): # allows also x.y(foo)
try:
data = re.split(r'[(*)]+', line)
logger.debug("dlms: {} = {} {}".format(data[0], data[1], data[2]))
if data[0] in self._obis_codes:
for item in self._obis_codes[data[0]]['items']:
item(data[1], 'DLMS', 'OBIS {}'.format(data[0]))
except Exception as e:
logger.warning("dlms: line={} exception={}".format(line, e))
def parse_item(self, item):
if 'dlms_obis_code' in item.conf:
logger.debug("parse item: {0}".format(item))
obis_code = item.conf['dlms_obis_code']
if not obis_code in self._obis_codes:
self._obis_codes[obis_code] = {'items': [item], 'logics': []}
else:
self._obis_codes[obis_code]['items'].append(item)
return None
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
myplugin = Plugin('DLMS')
myplugin.run()
Kommentar