Browse code

init witj jeedom template

louis.jonget authored on17/01/2023 08:47:47
Showing1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,349 @@
1
+# This file is part of Jeedom.
2
+#
3
+# Jeedom is free software: you can redistribute it and/or modify
4
+# it under the terms of the GNU General Public License as published by
5
+# the Free Software Foundation, either version 3 of the License, or
6
+# (at your option) any later version.
7
+#
8
+# Jeedom is distributed in the hope that it will be useful,
9
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+# GNU General Public License for more details.
12
+#
13
+# You should have received a copy of the GNU General Public License
14
+# along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
15
+#
16
+
17
+import time
18
+import logging
19
+import threading
20
+import requests
21
+import datetime
22
+try:
23
+    from collections.abc import Mapping
24
+except ImportError:
25
+    from collections import Mapping
26
+import serial
27
+import os
28
+from os.path import join
29
+import socket
30
+from queue import Queue
31
+import socketserver
32
+from socketserver import (TCPServer, StreamRequestHandler)
33
+import signal
34
+import unicodedata
35
+import pyudev
36
+
37
+# ------------------------------------------------------------------------------
38
+
39
+class jeedom_com():
40
+	def __init__(self,apikey = '',url = '',cycle = 0.5,retry = 3):
41
+		self.apikey = apikey
42
+		self.url = url
43
+		self.cycle = cycle
44
+		self.retry = retry
45
+		self.changes = {}
46
+		if cycle > 0 :
47
+			self.send_changes_async()
48
+		logging.info('Init request module v%s' % (str(requests.__version__),))
49
+
50
+	def send_changes_async(self):
51
+		try:
52
+			if len(self.changes) == 0:
53
+				resend_changes = threading.Timer(self.cycle, self.send_changes_async)
54
+				resend_changes.start()
55
+				return
56
+			start_time = datetime.datetime.now()
57
+			changes = self.changes
58
+			self.changes = {}
59
+			logging.info('Send to jeedom : '+str(changes))
60
+			i=0
61
+			while i < self.retry:
62
+				try:
63
+					r = requests.post(self.url + '?apikey=' + self.apikey, json=changes, timeout=(0.5, 120), verify=False)
64
+					if r.status_code == requests.codes.ok:
65
+						break
66
+				except Exception as error:
67
+					logging.error('Error on send request to jeedom ' + str(error)+' retry : '+str(i)+'/'+str(self.retry))
68
+				i = i + 1
69
+			if r.status_code != requests.codes.ok:
70
+				logging.error('Error on send request to jeedom, return code %s' % (str(r.status_code),))
71
+			dt = datetime.datetime.now() - start_time
72
+			ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0
73
+			timer_duration = self.cycle - ms
74
+			if timer_duration < 0.1 :
75
+				timer_duration = 0.1
76
+			if timer_duration > self.cycle:
77
+				timer_duration = self.cycle
78
+			resend_changes = threading.Timer(timer_duration, self.send_changes_async)
79
+			resend_changes.start()
80
+		except Exception as error:
81
+			logging.error('Critical error on  send_changes_async %s' % (str(error),))
82
+			resend_changes = threading.Timer(self.cycle, self.send_changes_async)
83
+			resend_changes.start()
84
+
85
+	def add_changes(self,key,value):
86
+		if key.find('::') != -1:
87
+			tmp_changes = {}
88
+			changes = value
89
+			for k in reversed(key.split('::')):
90
+				if k not in tmp_changes:
91
+					tmp_changes[k] = {}
92
+				tmp_changes[k] = changes
93
+				changes = tmp_changes
94
+				tmp_changes = {}
95
+			if self.cycle <= 0:
96
+				self.send_change_immediate(changes)
97
+			else:
98
+				self.merge_dict(self.changes,changes)
99
+		else:
100
+			if self.cycle <= 0:
101
+				self.send_change_immediate({key:value})
102
+			else:
103
+				self.changes[key] = value
104
+
105
+	def send_change_immediate(self,change):
106
+		threading.Thread( target=self.thread_change,args=(change,)).start()
107
+
108
+	def thread_change(self,change):
109
+		logging.info('Send to jeedom :  %s' % (str(change),))
110
+		i=0
111
+		while i < self.retry:
112
+			try:
113
+				r = requests.post(self.url + '?apikey=' + self.apikey, json=change, timeout=(0.5, 120), verify=False)
114
+				if r.status_code == requests.codes.ok:
115
+					break
116
+			except Exception as error:
117
+				logging.error('Error on send request to jeedom ' + str(error)+' retry : '+str(i)+'/'+str(self.retry))
118
+			i = i + 1
119
+
120
+	def set_change(self,changes):
121
+		self.changes = changes
122
+
123
+	def get_change(self):
124
+		return self.changes
125
+
126
+	def merge_dict(self,d1, d2):
127
+		for k,v2 in d2.items():
128
+			v1 = d1.get(k) # returns None if v1 has no value for this key
129
+			if isinstance(v1, Mapping) and isinstance(v2, Mapping):
130
+				self.merge_dict(v1, v2)
131
+			else:
132
+				d1[k] = v2
133
+
134
+	def test(self):
135
+		try:
136
+			response = requests.get(self.url + '?apikey=' + self.apikey, verify=False)
137
+			if response.status_code != requests.codes.ok:
138
+				logging.error('Callback error: %s %s. Please check your network configuration page'% (response.status.code, response.status.message,))
139
+				return False
140
+		except Exception as e:
141
+			logging.error('Callback result as a unknown error: %s. Please check your network configuration page'% (e.message,))
142
+			return False
143
+		return True
144
+
145
+# ------------------------------------------------------------------------------
146
+
147
+class jeedom_utils():
148
+
149
+	@staticmethod
150
+	def convert_log_level(level = 'error'):
151
+		LEVELS = {'debug': logging.DEBUG,
152
+		  'info': logging.INFO,
153
+		  'notice': logging.WARNING,
154
+		  'warning': logging.WARNING,
155
+		  'error': logging.ERROR,
156
+		  'critical': logging.CRITICAL,
157
+		  'none': logging.CRITICAL}
158
+		return LEVELS.get(level, logging.CRITICAL)
159
+
160
+	@staticmethod
161
+	def set_log_level(level = 'error'):
162
+		FORMAT = '[%(asctime)-15s][%(levelname)s] : %(message)s'
163
+		logging.basicConfig(level=jeedom_utils.convert_log_level(level),format=FORMAT, datefmt="%Y-%m-%d %H:%M:%S")
164
+
165
+	@staticmethod
166
+	def find_tty_usb(idVendor, idProduct, product = None):
167
+		context = pyudev.Context()
168
+		for device in context.list_devices(subsystem='tty'):
169
+			if 'ID_VENDOR' not in device:
170
+				continue
171
+			if device['ID_VENDOR_ID'] != idVendor:
172
+				continue
173
+			if device['ID_MODEL_ID'] != idProduct:
174
+				continue
175
+			if product is not None:
176
+				if 'ID_VENDOR' not in device or device['ID_VENDOR'].lower().find(product.lower()) == -1 :
177
+					continue
178
+			return str(device.device_node)
179
+		return None
180
+
181
+	@staticmethod
182
+	def stripped(str):
183
+		return "".join([i for i in str if i in range(32, 127)])
184
+
185
+	@staticmethod
186
+	def ByteToHex( byteStr ):
187
+		return byteStr.hex()
188
+
189
+	@staticmethod
190
+	def dec2bin(x, width=8):
191
+		return ''.join(str((x>>i)&1) for i in xrange(width-1,-1,-1))
192
+
193
+	@staticmethod
194
+	def dec2hex(dec):
195
+		if dec is None:
196
+			return '0x00'
197
+		return "0x{:02X}".format(dec)
198
+
199
+	@staticmethod
200
+	def testBit(int_type, offset):
201
+		mask = 1 << offset
202
+		return(int_type & mask)
203
+
204
+	@staticmethod
205
+	def clearBit(int_type, offset):
206
+		mask = ~(1 << offset)
207
+		return(int_type & mask)
208
+
209
+	@staticmethod
210
+	def split_len(seq, length):
211
+		return [seq[i:i+length] for i in range(0, len(seq), length)]
212
+
213
+	@staticmethod
214
+	def write_pid(path):
215
+		pid = str(os.getpid())
216
+		logging.info("Writing PID " + pid + " to " + str(path))
217
+		open(path, 'w').write("%s\n" % pid)
218
+
219
+	@staticmethod
220
+	def remove_accents(input_str):
221
+		nkfd_form = unicodedata.normalize('NFKD', unicode(input_str))
222
+		return u"".join([c for c in nkfd_form if not unicodedata.combining(c)])
223
+
224
+	@staticmethod
225
+	def printHex(hex):
226
+		return ' '.join([hex[i:i + 2] for i in range(0, len(hex), 2)])
227
+
228
+# ------------------------------------------------------------------------------
229
+
230
+class jeedom_serial():
231
+
232
+	def __init__(self,device = '',rate = '',timeout = 9,rtscts = True,xonxoff=False):
233
+		self.device = device
234
+		self.rate = rate
235
+		self.timeout = timeout
236
+		self.port = None
237
+		self.rtscts = rtscts
238
+		self.xonxoff = xonxoff
239
+		logging.info('Init serial module v%s' % (str(serial.VERSION),))
240
+
241
+	def open(self):
242
+		if self.device:
243
+			logging.info("Open serial port on device: " + str(self.device)+', rate '+str(self.rate)+', timeout : '+str(self.timeout))
244
+		else:
245
+			logging.error("Device name missing.")
246
+			return False
247
+		logging.info("Open Serialport")
248
+		try:
249
+			self.port = serial.Serial(
250
+			self.device,
251
+			self.rate,
252
+			timeout=self.timeout,
253
+			rtscts=self.rtscts,
254
+			xonxoff=self.xonxoff,
255
+			parity=serial.PARITY_NONE,
256
+			stopbits=serial.STOPBITS_ONE
257
+			)
258
+		except serial.SerialException as e:
259
+			logging.error("Error: Failed to connect on device " + self.device + " Details : " + str(e))
260
+			return False
261
+		if not self.port.isOpen():
262
+			self.port.open()
263
+		self.flushOutput()
264
+		self.flushInput()
265
+		return True
266
+
267
+	def close(self):
268
+		logging.info("Close serial port")
269
+		try:
270
+			self.port.close()
271
+			logging.info("Serial port closed")
272
+			return True
273
+		except:
274
+			logging.error("Failed to close the serial port (" + self.device + ")")
275
+			return False
276
+
277
+	def write(self,data):
278
+		logging.info("Write data to serial port : "+str(jeedom_utils.ByteToHex(data)))
279
+		self.port.write(data)
280
+
281
+	def flushOutput(self,):
282
+		logging.info("flushOutput serial port ")
283
+		self.port.flushOutput()
284
+
285
+	def flushInput(self):
286
+		logging.info("flushInput serial port ")
287
+		self.port.flushInput()
288
+
289
+	def read(self):
290
+		if self.port.inWaiting() != 0:
291
+			return self.port.read()
292
+		return None
293
+
294
+	def readbytes(self,number):
295
+		buf = b''
296
+		for i in range(number):
297
+			try:
298
+				byte = self.port.read()
299
+			except IOError as e:
300
+				logging.error("Error: " + str(e))
301
+			except OSError as e:
302
+				logging.error("Error: " + str(e))
303
+			buf += byte
304
+		return buf
305
+
306
+# ------------------------------------------------------------------------------
307
+
308
+JEEDOM_SOCKET_MESSAGE = Queue()
309
+
310
+class jeedom_socket_handler(StreamRequestHandler):
311
+	def handle(self):
312
+		global JEEDOM_SOCKET_MESSAGE
313
+		logging.info("Client connected to [%s:%d]" % self.client_address)
314
+		lg = self.rfile.readline()
315
+		JEEDOM_SOCKET_MESSAGE.put(lg)
316
+		logging.info("Message read from socket: " + str(lg.strip()))
317
+		self.netAdapterClientConnected = False
318
+		logging.info("Client disconnected from [%s:%d]" % self.client_address)
319
+
320
+class jeedom_socket():
321
+
322
+	def __init__(self,address='localhost', port=55000):
323
+		self.address = address
324
+		self.port = port
325
+		socketserver.TCPServer.allow_reuse_address = True
326
+
327
+	def open(self):
328
+		self.netAdapter = TCPServer((self.address, self.port), jeedom_socket_handler)
329
+		if self.netAdapter:
330
+			logging.info("Socket interface started")
331
+			threading.Thread(target=self.loopNetServer, args=()).start()
332
+		else:
333
+			logging.info("Cannot start socket interface")
334
+
335
+	def loopNetServer(self):
336
+		logging.info("LoopNetServer Thread started")
337
+		logging.info("Listening on: [%s:%d]" % (self.address, self.port))
338
+		self.netAdapter.serve_forever()
339
+		logging.info("LoopNetServer Thread stopped")
340
+
341
+	def close(self):
342
+		self.netAdapter.shutdown()
343
+
344
+	def getMessage(self):
345
+		return self.message
346
+
347
+# ------------------------------------------------------------------------------
348
+# END
349
+# ------------------------------------------------------------------------------