from __future__ import unicode_literals import logging import serial import re import threading import time # from pypca.exceptions import PCAException import pypca.constants as CONST _LOGGER = logging.getLogger(__name__) # get ready-> get last line not second or something, or flush output after ready class PCA: _serial = None _stopevent = None _thread = None _re_reading = re.compile( r"OK 24 (\d+) 4 (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)" ) _re_devices = re.compile( r"L 24 (\d+) (\d+) : (\d+) 4 (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)" ) def __init__(self, port, timeout=2): """Initialize the pca device.""" self._devices = {} self._port = port self._baud = 57600 self._timeout = timeout self._serial = serial.Serial(timeout=timeout) def open(self): """Open the device.""" self._serial.port = self._port self._serial.baudrate = self._baud self._serial.timeout = self._timeout self._serial.open() self._serial.flushInput() self._serial.flushOutput() self.get_ready() def close(self): """Close the device.""" self._stop_worker() self._serial.close() def get_ready(self): """Wait til the device is ready""" line = self._serial.readline().decode("utf-8") start = time.time() timeout = 5 while self._re_reading.match(line) is None and time.time() - start < timeout: line = self._serial.readline().decode("utf-8") return True def get_devices(self): """Get all the devices with the help of the l switch""" self._write_cmd("l") line = self._serial.readline().decode("utf-8") while self._re_devices.match(line) is not None: # add the line to devices dict line = line.split(" ") deviceId = ( str(line[7]).zfill(3) + str(line[8]).zfill(3) + str(line[9]).zfill(3) ) self._devices[deviceId] = {} self._devices[deviceId]["state"] = line[10] self._devices[deviceId]["power"] = ( int(line[11]) * 256 + int(line[12]) ) / 10.0 self._devices[deviceId]["consumption"] = ( int(line[13]) * 256 + int(line[14]) ) / 100.0 line = self._serial.readline().decode("utf-8") time.sleep( 0.05 ) # sleep here, otherwise the loop will lock the serial interface return self._devices def get_current_power(self, deviceId): """Get current power usage of given DeviceID.""" return self._devices[deviceId]["power"] def get_total_consumption(self, deviceId): """Get total power consumption of given DeviceID in KWh.""" return self._devices[deviceId]["consumption"] def get_state(self, deviceId): """Get state of given DeviceID.""" return self._devices[deviceId]["state"] def _stop_worker(self): if self._stopevent is not None: self._stopevent.set() if self._thread is not None: self._thread.join() def start_scan(self): """Start scan task in background.""" self.get_devices() self._start_worker() def _write_cmd(self, cmd): """Write a cmd.""" self._serial.write(cmd.encode()) def _start_worker(self): """Start the scan worker.""" if self._thread is not None: return self._stopevent = threading.Event() self._thread = threading.Thread(target=self._refresh, args=()) self._thread.daemon = True self._thread.start() def turn_off(self, deviceId): """Turn off given DeviceID.""" address = re.findall("...", deviceId) offCommand = "1,5,{},{},{},{},255,255,255,255{}".format( address[0].lstrip("0"), address[1].lstrip("0"), address[2].lstrip("0"), "0", CONST.SEND_SUFFIX, ) self._write_cmd(offCommand) self._devices[deviceId]["state"] = 0 return True def turn_on(self, deviceId): """Turn on given DeviceID.""" address = re.findall("...", deviceId) onCommand = "1,5,{},{},{},{},255,255,255,255{}".format( address[0].lstrip("0"), address[1].lstrip("0"), address[2].lstrip("0"), "1", CONST.SEND_SUFFIX, ) self._write_cmd(onCommand) self._devices[deviceId]["state"] = 1 return True def _refresh(self): """Background refreshing thread.""" while not self._stopevent.isSet(): line = self._serial.readline() # this is for python2/python3 compatibility. Is there a better way? try: line = line.encode().decode("utf-8") except AttributeError: line = line.decode("utf-8") if self._re_reading.match(line): line = line.split(" ") deviceId = ( str(line[4]).zfill(3) + str(line[5]).zfill(3) + str(line[6]).zfill(3) ) self._devices[deviceId]["power"] = ( int(line[8]) * 256 + int(line[9]) ) / 10.0 self._devices[deviceId]["state"] = int(line[7]) self._devices[deviceId]["consumption"] = ( int(line[10]) * 256 + int(line[11]) ) / 100.0