Download oocsi_source.py from Anniek/XAI: direct link, hf CLI and curl.
- Browser
- Download file 17.4 kB
-
https://huggingface.co/spaces/Anniek/XAI/resolve/main/oocsi_source.py
- Command line
-
hf download hf://spaces/Anniek/XAI/oocsi_source.py
-
curl -L -o oocsi_source.py https://huggingface.co/spaces/Anniek/XAI/resolve/main/oocsi_source.py
17.4 kB
| # 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() | |