XAI / oocsi_source.py
Anniek's picture
Update oocsi_source.py
a8d5902
# Copyright (c) 2017-2022 Mathias Funk
# This software is released under the MIT License.
# http://opensource.org/licenses/mit-license.php
import json
import socket
import threading
import atexit
import time
import uuid
from math import fsum
class OOCSI:
def __init__(self, handle=None, host='localhost', port=8080, callback=None, logger=None, maxReconnectionAttempts=100000):
if handle is None or len(handle.strip()) == 0:
self.handle = "OOCSIClient_" + uuid.uuid4().__str__().replace('-', '')[0:15];
else:
self.handle = handle
self.receivers = {self.handle: [callback]}
self.calls = {}
self.services = {}
self.reconnect = True
self.maxReconnects = maxReconnectionAttempts
self.connected = False
if logger is not None:
self.log = logger
# Connect the socket to the port where the server is listening
self.server_address = (host, port)
self.log('connecting to %s port %s' % self.server_address)
# start the connection
# (block till we are connected or connection error/timeout)
if not self.connect():
self.log('Initial OOCSI connection failed')
raise OOCSIDisconnect('OOCSI has not been found')
# start the connection thread
self.runtime = OOCSIThread(self)
self.runtime.start()
def connect(self):
connectionSuccessful = False
try:
# Create a TCP/IP socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect(self.server_address)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
try:
# Send data
message = self.handle + '(JSON)'
self.internalSend(message)
data = self.sock.recv(1024).decode()
if data.startswith('{'):
connectionSuccessful = True
self.log('connection established')
# re-subscribe for channels
for channelName in self.receivers:
self.internalSend('subscribe {0}'.format(channelName))
self.connected = True
elif data.startswith('error'):
self.log(data)
self.reconnect = False
finally:
pass
except:
pass
return connectionSuccessful
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.stop()
def log(self, message):
print('[{0}]: {1}'.format(self.handle, message))
def internalSend(self, msg):
try:
self.sock.sendall((msg + '\n').encode())
except:
self.connected = False
def loop(self):
try:
data = self.sock.recv(4 * 1024 * 1024).decode()
lines = data.split("\n")
for line in lines:
if len(data) == 0:
self.sock.close()
self.connected = False
elif line.startswith('ping') or line.startswith('.'):
self.internalSend('.')
elif line.startswith('{'):
self.receive(json.loads(line))
except:
pass
def receive(self, event):
sender = event['sender']
recipient = event['recipient']
# clean up the event
del event['recipient']
del event['sender']
del event['timestamp']
if 'data' in event:
del event['data']
if '_MESSAGE_HANDLE' in event and event['_MESSAGE_HANDLE'] in self.services:
service = self.services[event['_MESSAGE_HANDLE']]
del event['_MESSAGE_HANDLE']
service(event)
self.send(sender, event)
self.receiveChannelEvent(sender, recipient, event)
else:
if '_MESSAGE_ID' in event:
myCall = self.calls[event['_MESSAGE_ID']]
if myCall['expiration'] > time.time():
response = self.calls[event['_MESSAGE_ID']]
response['response'] = event
del response['expiration']
del response['_MESSAGE_ID']
del response['response']['_MESSAGE_ID']
else:
del self.calls[event['_MESSAGE_ID']]
else:
self.receiveChannelEvent(sender, recipient, event)
def receiveChannelEvent(self, sender, recipient, event):
if recipient in self.receivers and self.receivers[recipient] != None:
for x in self.receivers[recipient]:
x(sender, recipient, event)
def send(self, channelName, data):
self.internalSend('sendraw {0} {1}'.format(channelName, json.dumps(data)))
def call(self, channelName, callName, data, timeout = 1):
data['_MESSAGE_HANDLE'] = callName
data['_MESSAGE_ID'] = uuid.uuid4().__str__()
self.calls[data['_MESSAGE_ID']] = {'_MESSAGE_HANDLE': callName, '_MESSAGE_ID': data['_MESSAGE_ID'], 'expiration': time.time() + timeout}
self.send(channelName, data)
return self.calls[data['_MESSAGE_ID']]
def callAndWait(self, channelName, callName, data, timeout = 1):
call = self.call(channelName, callName, data, timeout)
expiration = time.time() + timeout
while time.time() < expiration:
time.sleep(0.1)
if 'response' in call:
break;
# self.calls.append
return call
def register(self, channelName, callName, callback):
self.services[callName] = callback
self.internalSend('subscribe {0}'.format(channelName))
self.log('registered responder on {0} for {1}'.format(channelName, callName))
def subscribe(self, channelName, f):
if channelName in self.receivers:
self.receivers[channelName].append(f)
else:
self.receivers[channelName] = [f]
self.internalSend('subscribe {0}'.format(channelName))
self.log('subscribed to {0}'.format(channelName))
def unsubscribe(self, channelName):
del self.receivers[channelName]
self.internalSend('unsubscribe {0}'.format(channelName))
self.log('unsubscribed from {0}'.format(channelName))
def variable(self, channelName, key):
return OOCSIVariable(self, channelName, key)
def stop(self):
self.reconnect = False
self.internalSend('quit')
self.sock.close()
self.connected = False
def handleEvent(self, sender, receiver, message):
{}
def returnHandle(self):
return self.handle
def heyOOCSI(self, custom_name=None):
if custom_name is None:
return (OOCSIDevice(self, self.handle))
else:
return (OOCSIDevice(self, custom_name))
class OOCSIThread(threading.Thread):
def __init__(self, parent=None):
self.parent = parent
super(OOCSIThread, self).__init__()
def run(self):
# register exit handler
atexit.register(self._stop)
# run the established connection
while self.parent.connected:
self.parent.loop()
# reconnect
if self.parent.reconnect:
failedConnectionAttempts = 0
while self.parent.reconnect:
self.parent.log('re-connecting to OOCSI')
if self.parent.connect():
failedConnectionAttempts = 0
while self.parent.connected:
self.parent.loop()
else:
failedConnectionAttempts += 1
time.sleep(5)
# raise exception after unsuccessful connection attempts
if failedConnectionAttempts == self.parent.maxReconnects:
self.parent.log('OOCSI connection failed after 10 attempts')
raise OOCSIDisconnect('OOCSI has not been found')
self.parent.log('closing connection to OOCSI')
def _stop(self):
self.parent.stop()
return threading.Thread._stop(self)
class OOCSIDisconnect(Exception):
pass
class OOCSICall:
def __init__(self, parent=None):
self.uuid = uuid.uuid4()
self.expiration = time.time()
class OOCSIVariable(object):
def __init__(self, oocsi, channelName, key):
self.key = key
self.channel = channelName
oocsi.subscribe(channelName, self.internalReceiveValue)
self.oocsi = oocsi
self.value = None
self.windowLength = 0
self.values = []
self.minvalue = None
self.maxvalue = None
self.sigma = None
def get(self):
if self.windowLength > 0 and len(self.values) > 0:
return fsum(self.values)/float(len(self.values))
else:
return self.value
def set(self, value):
tempvalue = value
if not self.minvalue is None and tempvalue < self.minvalue:
tempvalue = self.minvalue
elif not self.maxvalue is None and tempvalue > self.maxvalue:
tempvalue = self.maxvalue
elif not self.sigma is None:
mean = self.get()
if not mean is None:
if abs(mean - tempvalue) > self.sigma:
if mean - tempvalue > 0:
tempvalue = mean - self.sigma/float(len(self.values))
else:
tempvalue = mean + self.sigma/float(len(self.values))
if self.windowLength > 0:
self.values.append(tempvalue)
self.values = self.values[-self.windowLength:]
else:
self.value = tempvalue
self.oocsi.send(self.channel, {self.key: value})
def internalReceiveValue(self, sender, recipient, data):
if self.key in data:
tempvalue = data[self.key]
if not self.minvalue is None and tempvalue < self.minvalue:
tempvalue = self.minvalue
elif not self.maxvalue is None and tempvalue > self.maxvalue:
tempvalue = self.maxvalue
elif not self.sigma is None:
mean = self.get()
if not mean is None:
if abs(mean - tempvalue) > self.sigma:
if mean - tempvalue > 0:
tempvalue = mean - self.sigma/float(len(self.values))
else:
tempvalue = mean + self.sigma/float(len(self.values))
if self.windowLength > 0:
self.values.append(tempvalue)
self.values = self.values[-self.windowLength:]
else:
self.value = tempvalue
def min(self, minvalue):
self.minvalue = minvalue
if self.value < self.minvalue:
self.value = self.minvalue
return self
def max(self, maxvalue):
self.maxvalue = maxvalue
if self.value > self.maxvalue:
self.value = self.maxvalue
return self
def smooth(self, windowLength, sigma=None):
self.windowLength = windowLength
self.sigma = sigma
return self
class OOCSIDevice():
def __init__(self, OOCSI, device_name:str) -> None:
self._device_name = device_name
self._device = {self._device_name:{}}
self._device[self._device_name]["properties"] = {}
self._device[self._device_name]["properties"]["device_id"] = OOCSI.returnHandle()
self._device[self._device_name]["components"] = {}
self._device[self._device_name]["location"] = {}
self._components = self._device[self._device_name]["components"]
self._oocsi=OOCSI
self._oocsi.log(f'Created device {self._device_name}.')
def addProperty(self, properties:str, propertyValue):
self._device[self._device_name]["properties"][properties] = propertyValue
self._oocsi.log(f'Added {properties} to the properties list of device {self._device_name}.')
return self
def addLocation(self, location_name:str, latitude:float = 0, longitude:float = 0):
self._device[self._device_name]["location"][location_name] = [latitude, longitude]
self._oocsi.log(f'Added {location_name} to the locations list of device {self._device_name}.')
return self
def addSensor(self, sensor_name:str, sensor_channel:str, sensor_type:str, sensor_unit:str, sensor_default:float, mode:str = "auto", step:float = None, icon:str = None):
self._components[sensor_name]={}
self._components[sensor_name]["channel_name"] = sensor_channel
self._components[sensor_name]["type"] = "sensor"
self._components[sensor_name]["sensor_type"] = sensor_type
self._components[sensor_name]["unit"] = sensor_unit
self._components[sensor_name]["value"] = sensor_default
self._components[sensor_name]["mode"] = mode
self._components[sensor_name]["step"] = step
self._components[sensor_name]["icon"] = icon
self._device[self._device_name]["components"][sensor_name] = self._components[sensor_name]
self._oocsi.log(f'Added {sensor_name} to the components list of device {self._device_name}.')
return self
def addNumber(self, number_name:str, number_channel:str, number_min_max, number_unit:str, number_default:float, icon:str = None):
self._components[number_name]={}
self._components[number_name]["channel_name"] = number_channel
self._components[number_name]["min_max"]= number_min_max
self._components[number_name]["type"] = "number"
self._components[number_name]["unit"] = number_unit
self._components[number_name]["value"] = number_default
self._components[number_name]["icon"] = icon
self._device[self._device_name]["components"][number_name] = self._components[number_name]
self._oocsi.log(f'Added {number_name} to the components list of device {self._device_name}.')
return self
def addBinarySensor(self, sensor_name:str, sensor_channel:str, sensor_type:str, sensor_default:bool = False, icon:str = None):
self._components[sensor_name]={}
self._components[sensor_name]["channel_name"] = sensor_channel
self._components[sensor_name]["type"] = "binary_sensor"
self._components[sensor_name]["sensor_type"] = sensor_type
self._components[sensor_name]["state"] = sensor_default
self._components[sensor_name]["icon"] = icon
self._device[self._device_name]["components"][sensor_name] = self._components[sensor_name]
self._oocsi.log(f'Added {sensor_name} to the components list of device {self._device_name}.')
return self
def addSwitch(self, switch_name:str, switch_channel:str, switch_default:bool = False, icon:str = None):
self._components[switch_name]={}
self._components[switch_name]["channel_name"] = switch_channel
self._components[switch_name]["type"] = "switch"
self._components[switch_name]["state"] = switch_default
self._components[switch_name]["icon"] = icon
self._device[self._device_name]["components"][switch_name] = self._components[switch_name]
self._oocsi.log(f'Added {switch_name} to the components list of device {self._device_name}.')
return self
def addLight(self, light_name:str, light_channel:str, led_type:str, spectrum, light_default_state:bool = False, light_default_brightness:int = 0, mired_min_max = None, icon:str = None):
SPECTRUM = ["WHITE","CCT","RGB"]
LEDTYPE = ["RGB","RGBW","RGBWW","CCT","DIMMABLE","ONOFF"]
self._components[light_name]={}
if led_type in LEDTYPE:
if spectrum in SPECTRUM:
self._components[light_name]["spectrum"] = spectrum
else:
self._oocsi.log(f'error, {light_name} spectrum does not exist.')
pass
else:
self._oocsi.log(f'error, {light_name} ledtype does not exist.')
pass
self._components[light_name]["channel_name"] = light_channel
self._components[light_name]["type"] = "light"
self._components[light_name]["ledType"] = led_type
self._components[light_name]["spectrum"] = spectrum
self._components[light_name]["min_max"]= mired_min_max
self._components[light_name]["state"] = light_default_state
self._components[light_name]["brightness"] = light_default_brightness
self._components[light_name]["icon"] = icon
self._device[self._device_name]["components"][light_name] = self._components[light_name]
self._oocsi.log(f'Added {light_name} to the components list of device {self._device_name}.')
return self
def submit(self):
data = self._device
self._oocsi.internalSend('sendraw {0} {1}'.format("heyOOCSI!", json.dumps(data)))
self._oocsi.log(f'Sent heyOOCSI! message for device {self._device_name}.')
def sayHi(self):
self.submit()