A-Jansen commited on
Commit
4181d9c
·
1 Parent(s): 9b346a6
Files changed (2) hide show
  1. home.py +141 -0
  2. oocsi_source.py +438 -0
home.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ These are the meta data and instructions
3
+ Author: Anniek Jansen
4
+ Course: Human AI interaction
5
+
6
+ Instructions:
7
+ (For Anniek: conda activate py (activate local environment where the right packages are installed))
8
+ 1. Pip install streamlit, oocsi in your python environment
9
+ 2. Save this file somewhere on your computer
10
+ 3. In the command line, cd to where your file is: "cd/...../folder
11
+
12
+ 4. To run it: streamlit run home.py
13
+ 5. Click on the link it provides you
14
+ 6. You need to click sometimes rerun in the website
15
+
16
+
17
+ To hide menu: copy paste this in config.toml
18
+ [ui]
19
+ hideSidebarNav = true
20
+
21
+ """
22
+
23
+
24
+ import streamlit as st
25
+ import pandas as pd
26
+ # from oocsi import __init__ as OOCSI
27
+ from oocsi_source import OOCSI
28
+ from uuid import uuid4
29
+ from streamlit_extras.switch_page_button import switch_page
30
+ import requests
31
+ import json
32
+
33
+
34
+
35
+
36
+ # Check if 'participantID' already exists in session_state
37
+ # If not, then initialize it
38
+ if 'participantID' not in st.session_state:
39
+ st.session_state.participantID= "P" + uuid4().__str__().replace('-', '')[0:10]
40
+ st.session_state.pages =['SHAP', 'DecisionTree', 'counterfactual', 'visualMap']
41
+ st.session_state.profileIndices=[25,112]
42
+
43
+ if 'oocsi' not in st.session_state:
44
+ st.session_state.oocsi = OOCSI('','oocsi.id.tue.nl')
45
+
46
+
47
+ # st.write(st.session_state.participantID)
48
+
49
+ st.set_page_config(page_title="XAI research", layout="wide")
50
+
51
+ header1, header2, header3 = st.columns([1,2,1])
52
+ consent_form1, consent_form2, consent_form3 = st.columns([1,4,1])
53
+
54
+ # with header2:
55
+ # st.title("Consent form")
56
+ # st.write("For debugging:")
57
+ # st.write(st.session_state.participantID)
58
+
59
+ with consent_form2:
60
+ st.header('Information form for participants')
61
+ st.write('''Hello and thank you for taking the time to participate in this survey.''')
62
+ st.write('''This document gives you information about the study Comparing Explainable AI (XAI) methods. Before the study begins, it is important that you learn about the procedure followed in this study and that you give your informed consent for voluntary participation. Please read this document carefully. ''')
63
+
64
+ st.subheader('Aim and benefit of the study')
65
+ st.write('''The aim of this study is to measure the satisfaction of users with different types of XAI methods.
66
+ This information is used to have better understandable/ more satisfying type of explanations in future application. ''')
67
+ st.write('''This study is performed by Rachel Wang, François Leborgne and Anniek Jansen, all EngD trainees of the Designing Human-System Interaction program and for this course under the supervision of Chao Zhang of the Human-Technology Interaction group.''')
68
+
69
+ st.subheader('Procedure')
70
+ st.markdown('''During this project we ask you to:
71
+ - Look at different predictions from an AI model (predicting the survival of passengers of the titanic)
72
+ - Complete a survey at the start of the study with demographic information
73
+ - Complete a short survey (8 questions) for four types of explanation method
74
+ - Complete a survey in the end to compare the explanation methods and explain why certain methods had your preference.
75
+ ''')
76
+
77
+ st.subheader('Risks')
78
+ st.markdown("The study does not involve any risks, detrimental side effects, or cause discomfort.")
79
+
80
+ st.subheader("Duration")
81
+ st.markdown("The instructions, measurements and debriefing will take approximately 15 minutes.")
82
+
83
+ st.subheader("Voluntary")
84
+ st.markdown('''Your participation is completely voluntary. You can refuse to participate without giving any reasons and you can stop your participation at any time during the study. You can also withdraw your permission to use your data immediately after completing the study. None of this will have any negative consequences for you whatsoever.''')
85
+
86
+ st.subheader("Confidentiality and use, storage, and sharing of data")
87
+ st.markdown('''
88
+ All research conducted at the Human-Technology Interaction Group adheres to the Code of Ethics of the NIP (Nederlands Instituut voor Psychologen – Dutch Institute for Psychologists), and this study has been approved by the Ethical Review Board of the department.
89
+
90
+ In this study demographic data (gender, age, education level, highest level of education, data literacy, AI expertise), experimental data (response to questionnaires and duration of experiment) will be recorded, analyzed, and stored. The goal of collecting, analyzing, and storing this data is to answer the research question and publish the results in the scientific literature. To protect your privacy, all data that can be used to personally identify you will be stored on an encrypted server of the Human Technology Interaction group for at least 10 years that is only accessible by selected HTI staff members. No information that can be used to personally identify you will be shared with others.
91
+
92
+ During the study, the data will be stored on encrypted laptops and DataFoundry - a platform developed by the department of Industrial Design at TU/e and is GDPR compliant. After the analyses, the data will also be made available on OSF (open science framework, a place to share research data open source).
93
+
94
+ The data collected in this study might also be of relevance for future research projects within the Human Technology Interaction group as well as for other researchers. The aim of those studies might be unrelated to the goals of this study.
95
+ The collected data will therefore also be made available to the general public in an online data repository.
96
+ The coded data collected in this study and that will be released to the public will (to the best of our knowledge and ability) not contain information that can identify you. It will include all answers you provide during the study, including demographic variables (e.g., age and gender) if you choose to provide these during the study.
97
+
98
+ At the bottom of this consent form, you can indicate whether or not you agree with participation in this study. You can also indicate whether you agree with the distribution of your data by means of a secured online data repository with open access for the general public and the distribution of your data by means of a secured online data repository with open access for the general public. You are not obliged to letting us use and share your data. If you are not willing to share your data in this way, you can still participate in this study. Your data will be used in the scientific article but not shared with other researchers.
99
+
100
+ No video or audio recordings are made that could identify you.
101
+
102
+ ''')
103
+
104
+ st.subheader("Further information")
105
+ st.markdown('''If you want more information about this study, the study design, or the results, you can contact Anniek Jansen (contact email: [email protected] ).
106
+ If you have any complaints about this study, please contact the supervisor, Chao Zhang ([email protected]) You can report irregularities related to scientific integrity to confidential advisors of the TU/e.
107
+ ''')
108
+
109
+ st.subheader("Informed consent form")
110
+ st.markdown('''
111
+ - I am 18 years or older
112
+ - I have read and understood the information of the corresponding information form for participants.
113
+ - I have been given the opportunity to ask questions. My questions are sufficiently answered, and I had sufficient time to decide whether I participate.
114
+ - I know that my participation is completely voluntary. I know that I can refuse to participate and that I can stop my participation at any time during the study, without giving any reasons. I know that I can withdraw permission to use my data directly after the experiment.
115
+ - I agree to voluntarily participate in this study carried out by the research group Human Technology Interaction and Industrial Design of the Eindhoven University of Technology.
116
+ - I know that no information that can be used to personally identify me or my responses in this study will be shared with anyone outside of the research team.
117
+ ''')
118
+
119
+ OSF= st.radio("I ... (please select below) give permission to make my anonymized recorded data available to others in a public online data repository, and allow others to use this data for future research projects unrelated to this study.",
120
+ ('do',
121
+ 'do not'))
122
+
123
+ st.subheader("Consent")
124
+ agree = st.checkbox('I consent to processing my personal data gathered during the research in the way described in the information sheet.')
125
+
126
+ consentforOSF =""
127
+ if OSF =='do':
128
+ consentforOSF='yes'
129
+ else:
130
+ consentforOSF='no'
131
+
132
+ if agree:
133
+
134
+ st.write('Thank you! Please continue to the next page to start the experiment')
135
+ if st.button("Next page"):
136
+ st.session_state.oocsi.send('EngD_HAII_consent', {
137
+ 'participant_ID': st.session_state.participantID,
138
+ 'consent': 'yes',
139
+ 'consentForOSF': consentforOSF
140
+ })
141
+ switch_page("explanationpage")
oocsi_source.py ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2017-2022 Mathias Funk
2
+ # This software is released under the MIT License.
3
+ # http://opensource.org/licenses/mit-license.php
4
+
5
+ import json
6
+ import socket
7
+ import threading
8
+ import atexit
9
+ import time
10
+ import uuid
11
+ from math import fsum
12
+
13
+ class OOCSI:
14
+ def __init__(self, handle=None, host='localhost', port=4444, callback=None, logger=None, maxReconnectionAttempts=100000):
15
+ if handle is None or len(handle.strip()) == 0:
16
+ self.handle = "OOCSIClient_" + uuid.uuid4().__str__().replace('-', '')[0:15];
17
+ else:
18
+ self.handle = handle
19
+
20
+ self.receivers = {self.handle: [callback]}
21
+ self.calls = {}
22
+ self.services = {}
23
+ self.reconnect = True
24
+ self.maxReconnects = maxReconnectionAttempts
25
+ self.connected = False
26
+ if logger is not None:
27
+ self.log = logger
28
+
29
+ # Connect the socket to the port where the server is listening
30
+ self.server_address = (host, port)
31
+ self.log('connecting to %s port %s' % self.server_address)
32
+
33
+ # start the connection
34
+ # (block till we are connected or connection error/timeout)
35
+ if not self.connect():
36
+ self.log('Initial OOCSI connection failed')
37
+ raise OOCSIDisconnect('OOCSI has not been found')
38
+
39
+ # start the connection thread
40
+ self.runtime = OOCSIThread(self)
41
+ self.runtime.start()
42
+
43
+ def connect(self):
44
+ connectionSuccessful = False
45
+ try:
46
+ # Create a TCP/IP socket
47
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
48
+ self.sock.connect(self.server_address)
49
+ self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
50
+
51
+ try:
52
+ # Send data
53
+ message = self.handle + '(JSON)'
54
+ self.internalSend(message)
55
+
56
+ data = self.sock.recv(1024).decode()
57
+ if data.startswith('{'):
58
+ connectionSuccessful = True
59
+ self.log('connection established')
60
+ # re-subscribe for channels
61
+ for channelName in self.receivers:
62
+ self.internalSend('subscribe {0}'.format(channelName))
63
+ self.connected = True
64
+ elif data.startswith('error'):
65
+ self.log(data)
66
+ self.reconnect = False
67
+ finally:
68
+ pass
69
+ except:
70
+ pass
71
+ return connectionSuccessful
72
+
73
+ def __enter__(self):
74
+ return self
75
+
76
+ def __exit__(self, exc_type, exc_value, traceback):
77
+ self.stop()
78
+
79
+ def log(self, message):
80
+ print('[{0}]: {1}'.format(self.handle, message))
81
+
82
+ def internalSend(self, msg):
83
+ try:
84
+ self.sock.sendall((msg + '\n').encode())
85
+ except:
86
+ self.connected = False
87
+
88
+ def loop(self):
89
+ try:
90
+ data = self.sock.recv(4 * 1024 * 1024).decode()
91
+ lines = data.split("\n")
92
+ for line in lines:
93
+ if len(data) == 0:
94
+ self.sock.close()
95
+ self.connected = False
96
+ elif line.startswith('ping') or line.startswith('.'):
97
+ self.internalSend('.')
98
+ elif line.startswith('{'):
99
+ self.receive(json.loads(line))
100
+ except:
101
+ pass
102
+
103
+ def receive(self, event):
104
+ sender = event['sender']
105
+ recipient = event['recipient']
106
+
107
+ # clean up the event
108
+ del event['recipient']
109
+ del event['sender']
110
+ del event['timestamp']
111
+ if 'data' in event:
112
+ del event['data']
113
+
114
+ if '_MESSAGE_HANDLE' in event and event['_MESSAGE_HANDLE'] in self.services:
115
+ service = self.services[event['_MESSAGE_HANDLE']]
116
+ del event['_MESSAGE_HANDLE']
117
+ service(event)
118
+ self.send(sender, event)
119
+ self.receiveChannelEvent(sender, recipient, event)
120
+
121
+ else:
122
+ if '_MESSAGE_ID' in event:
123
+ myCall = self.calls[event['_MESSAGE_ID']]
124
+ if myCall['expiration'] > time.time():
125
+ response = self.calls[event['_MESSAGE_ID']]
126
+ response['response'] = event
127
+ del response['expiration']
128
+ del response['_MESSAGE_ID']
129
+ del response['response']['_MESSAGE_ID']
130
+ else:
131
+ del self.calls[event['_MESSAGE_ID']]
132
+
133
+ else:
134
+ self.receiveChannelEvent(sender, recipient, event)
135
+
136
+ def receiveChannelEvent(self, sender, recipient, event):
137
+ if recipient in self.receivers and self.receivers[recipient] != None:
138
+ for x in self.receivers[recipient]:
139
+ x(sender, recipient, event)
140
+
141
+ def send(self, channelName, data):
142
+ self.internalSend('sendraw {0} {1}'.format(channelName, json.dumps(data)))
143
+
144
+ def call(self, channelName, callName, data, timeout = 1):
145
+ data['_MESSAGE_HANDLE'] = callName
146
+ data['_MESSAGE_ID'] = uuid.uuid4().__str__()
147
+ self.calls[data['_MESSAGE_ID']] = {'_MESSAGE_HANDLE': callName, '_MESSAGE_ID': data['_MESSAGE_ID'], 'expiration': time.time() + timeout}
148
+ self.send(channelName, data)
149
+ return self.calls[data['_MESSAGE_ID']]
150
+
151
+
152
+ def callAndWait(self, channelName, callName, data, timeout = 1):
153
+ call = self.call(channelName, callName, data, timeout)
154
+ expiration = time.time() + timeout
155
+ while time.time() < expiration:
156
+ time.sleep(0.1)
157
+ if 'response' in call:
158
+ break;
159
+ # self.calls.append
160
+ return call
161
+
162
+ def register(self, channelName, callName, callback):
163
+ self.services[callName] = callback
164
+ self.internalSend('subscribe {0}'.format(channelName))
165
+ self.log('registered responder on {0} for {1}'.format(channelName, callName))
166
+
167
+ def subscribe(self, channelName, f):
168
+ if channelName in self.receivers:
169
+ self.receivers[channelName].append(f)
170
+ else:
171
+ self.receivers[channelName] = [f]
172
+ self.internalSend('subscribe {0}'.format(channelName))
173
+ self.log('subscribed to {0}'.format(channelName))
174
+
175
+ def unsubscribe(self, channelName):
176
+ del self.receivers[channelName]
177
+ self.internalSend('unsubscribe {0}'.format(channelName))
178
+ self.log('unsubscribed from {0}'.format(channelName))
179
+
180
+ def variable(self, channelName, key):
181
+ return OOCSIVariable(self, channelName, key)
182
+
183
+ def stop(self):
184
+ self.reconnect = False
185
+ self.internalSend('quit')
186
+ self.sock.close()
187
+ self.connected = False
188
+
189
+ def handleEvent(self, sender, receiver, message):
190
+ {}
191
+
192
+ def returnHandle(self):
193
+ return self.handle
194
+
195
+ def heyOOCSI(self, custom_name=None):
196
+ if custom_name is None:
197
+ return (OOCSIDevice(self, self.handle))
198
+ else:
199
+ return (OOCSIDevice(self, custom_name))
200
+
201
+
202
+ class OOCSIThread(threading.Thread):
203
+ def __init__(self, parent=None):
204
+ self.parent = parent
205
+ super(OOCSIThread, self).__init__()
206
+
207
+ def run(self):
208
+
209
+ # register exit handler
210
+ atexit.register(self._stop)
211
+
212
+ # run the established connection
213
+ while self.parent.connected:
214
+ self.parent.loop()
215
+
216
+ # reconnect
217
+ if self.parent.reconnect:
218
+ failedConnectionAttempts = 0
219
+ while self.parent.reconnect:
220
+ self.parent.log('re-connecting to OOCSI')
221
+ if self.parent.connect():
222
+ failedConnectionAttempts = 0
223
+ while self.parent.connected:
224
+ self.parent.loop()
225
+ else:
226
+ failedConnectionAttempts += 1
227
+ time.sleep(5)
228
+ # raise exception after unsuccessful connection attempts
229
+ if failedConnectionAttempts == self.parent.maxReconnects:
230
+ self.parent.log('OOCSI connection failed after 10 attempts')
231
+ raise OOCSIDisconnect('OOCSI has not been found')
232
+
233
+ self.parent.log('closing connection to OOCSI')
234
+
235
+ def _stop(self):
236
+ self.parent.stop()
237
+ return threading.Thread._stop(self)
238
+
239
+
240
+
241
+ class OOCSIDisconnect(Exception):
242
+ pass
243
+
244
+
245
+
246
+ class OOCSICall:
247
+ def __init__(self, parent=None):
248
+ self.uuid = uuid.uuid4()
249
+ self.expiration = time.time()
250
+
251
+
252
+
253
+ class OOCSIVariable(object):
254
+ def __init__(self, oocsi, channelName, key):
255
+ self.key = key
256
+ self.channel = channelName
257
+ oocsi.subscribe(channelName, self.internalReceiveValue)
258
+ self.oocsi = oocsi
259
+ self.value = None
260
+ self.windowLength = 0
261
+ self.values = []
262
+ self.minvalue = None
263
+ self.maxvalue = None
264
+ self.sigma = None
265
+
266
+ def get(self):
267
+ if self.windowLength > 0 and len(self.values) > 0:
268
+ return fsum(self.values)/float(len(self.values))
269
+ else:
270
+ return self.value
271
+
272
+ def set(self, value):
273
+ tempvalue = value
274
+ if not self.minvalue is None and tempvalue < self.minvalue:
275
+ tempvalue = self.minvalue
276
+ elif not self.maxvalue is None and tempvalue > self.maxvalue:
277
+ tempvalue = self.maxvalue
278
+ elif not self.sigma is None:
279
+ mean = self.get()
280
+ if not mean is None:
281
+ if abs(mean - tempvalue) > self.sigma:
282
+ if mean - tempvalue > 0:
283
+ tempvalue = mean - self.sigma/float(len(self.values))
284
+ else:
285
+ tempvalue = mean + self.sigma/float(len(self.values))
286
+
287
+ if self.windowLength > 0:
288
+ self.values.append(tempvalue)
289
+ self.values = self.values[-self.windowLength:]
290
+ else:
291
+ self.value = tempvalue
292
+ self.oocsi.send(self.channel, {self.key: value})
293
+
294
+ def internalReceiveValue(self, sender, recipient, data):
295
+ if self.key in data:
296
+ tempvalue = data[self.key]
297
+ if not self.minvalue is None and tempvalue < self.minvalue:
298
+ tempvalue = self.minvalue
299
+ elif not self.maxvalue is None and tempvalue > self.maxvalue:
300
+ tempvalue = self.maxvalue
301
+ elif not self.sigma is None:
302
+ mean = self.get()
303
+ if not mean is None:
304
+ if abs(mean - tempvalue) > self.sigma:
305
+ if mean - tempvalue > 0:
306
+ tempvalue = mean - self.sigma/float(len(self.values))
307
+ else:
308
+ tempvalue = mean + self.sigma/float(len(self.values))
309
+
310
+ if self.windowLength > 0:
311
+ self.values.append(tempvalue)
312
+ self.values = self.values[-self.windowLength:]
313
+ else:
314
+ self.value = tempvalue
315
+
316
+ def min(self, minvalue):
317
+ self.minvalue = minvalue
318
+ if self.value < self.minvalue:
319
+ self.value = self.minvalue
320
+ return self
321
+
322
+ def max(self, maxvalue):
323
+ self.maxvalue = maxvalue
324
+ if self.value > self.maxvalue:
325
+ self.value = self.maxvalue
326
+ return self
327
+
328
+ def smooth(self, windowLength, sigma=None):
329
+ self.windowLength = windowLength
330
+ self.sigma = sigma
331
+ return self
332
+
333
+
334
+
335
+ class OOCSIDevice():
336
+ def __init__(self, OOCSI, device_name:str) -> None:
337
+ self._device_name = device_name
338
+ self._device = {self._device_name:{}}
339
+ self._device[self._device_name]["properties"] = {}
340
+ self._device[self._device_name]["properties"]["device_id"] = OOCSI.returnHandle()
341
+ self._device[self._device_name]["components"] = {}
342
+ self._device[self._device_name]["location"] = {}
343
+ self._components = self._device[self._device_name]["components"]
344
+ self._oocsi=OOCSI
345
+ self._oocsi.log(f'Created device {self._device_name}.')
346
+
347
+ def addProperty(self, properties:str, propertyValue):
348
+ self._device[self._device_name]["properties"][properties] = propertyValue
349
+ self._oocsi.log(f'Added {properties} to the properties list of device {self._device_name}.')
350
+ return self
351
+
352
+ def addLocation(self, location_name:str, latitude:float = 0, longitude:float = 0):
353
+ self._device[self._device_name]["location"][location_name] = [latitude, longitude]
354
+ self._oocsi.log(f'Added {location_name} to the locations list of device {self._device_name}.')
355
+ return self
356
+
357
+ 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):
358
+ self._components[sensor_name]={}
359
+ self._components[sensor_name]["channel_name"] = sensor_channel
360
+ self._components[sensor_name]["type"] = "sensor"
361
+ self._components[sensor_name]["sensor_type"] = sensor_type
362
+ self._components[sensor_name]["unit"] = sensor_unit
363
+ self._components[sensor_name]["value"] = sensor_default
364
+ self._components[sensor_name]["mode"] = mode
365
+ self._components[sensor_name]["step"] = step
366
+ self._components[sensor_name]["icon"] = icon
367
+ self._device[self._device_name]["components"][sensor_name] = self._components[sensor_name]
368
+ self._oocsi.log(f'Added {sensor_name} to the components list of device {self._device_name}.')
369
+ return self
370
+
371
+ def addNumber(self, number_name:str, number_channel:str, number_min_max, number_unit:str, number_default:float, icon:str = None):
372
+ self._components[number_name]={}
373
+ self._components[number_name]["channel_name"] = number_channel
374
+ self._components[number_name]["min_max"]= number_min_max
375
+ self._components[number_name]["type"] = "number"
376
+ self._components[number_name]["unit"] = number_unit
377
+ self._components[number_name]["value"] = number_default
378
+ self._components[number_name]["icon"] = icon
379
+ self._device[self._device_name]["components"][number_name] = self._components[number_name]
380
+ self._oocsi.log(f'Added {number_name} to the components list of device {self._device_name}.')
381
+ return self
382
+
383
+ def addBinarySensor(self, sensor_name:str, sensor_channel:str, sensor_type:str, sensor_default:bool = False, icon:str = None):
384
+ self._components[sensor_name]={}
385
+ self._components[sensor_name]["channel_name"] = sensor_channel
386
+ self._components[sensor_name]["type"] = "binary_sensor"
387
+ self._components[sensor_name]["sensor_type"] = sensor_type
388
+ self._components[sensor_name]["state"] = sensor_default
389
+ self._components[sensor_name]["icon"] = icon
390
+ self._device[self._device_name]["components"][sensor_name] = self._components[sensor_name]
391
+ self._oocsi.log(f'Added {sensor_name} to the components list of device {self._device_name}.')
392
+ return self
393
+
394
+ def addSwitch(self, switch_name:str, switch_channel:str, switch_default:bool = False, icon:str = None):
395
+ self._components[switch_name]={}
396
+ self._components[switch_name]["channel_name"] = switch_channel
397
+ self._components[switch_name]["type"] = "switch"
398
+ self._components[switch_name]["state"] = switch_default
399
+ self._components[switch_name]["icon"] = icon
400
+ self._device[self._device_name]["components"][switch_name] = self._components[switch_name]
401
+ self._oocsi.log(f'Added {switch_name} to the components list of device {self._device_name}.')
402
+ return self
403
+
404
+ 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):
405
+ SPECTRUM = ["WHITE","CCT","RGB"]
406
+ LEDTYPE = ["RGB","RGBW","RGBWW","CCT","DIMMABLE","ONOFF"]
407
+
408
+ self._components[light_name]={}
409
+ if led_type in LEDTYPE:
410
+ if spectrum in SPECTRUM:
411
+ self._components[light_name]["spectrum"] = spectrum
412
+ else:
413
+ self._oocsi.log(f'error, {light_name} spectrum does not exist.')
414
+ pass
415
+ else:
416
+ self._oocsi.log(f'error, {light_name} ledtype does not exist.')
417
+ pass
418
+
419
+ self._components[light_name]["channel_name"] = light_channel
420
+ self._components[light_name]["type"] = "light"
421
+ self._components[light_name]["ledType"] = led_type
422
+ self._components[light_name]["spectrum"] = spectrum
423
+ self._components[light_name]["min_max"]= mired_min_max
424
+ self._components[light_name]["state"] = light_default_state
425
+ self._components[light_name]["brightness"] = light_default_brightness
426
+ self._components[light_name]["icon"] = icon
427
+ self._device[self._device_name]["components"][light_name] = self._components[light_name]
428
+ self._oocsi.log(f'Added {light_name} to the components list of device {self._device_name}.')
429
+ return self
430
+
431
+ def submit(self):
432
+ data = self._device
433
+ self._oocsi.internalSend('sendraw {0} {1}'.format("heyOOCSI!", json.dumps(data)))
434
+ self._oocsi.log(f'Sent heyOOCSI! message for device {self._device_name}.')
435
+
436
+ def sayHi(self):
437
+ self.submit()
438
+