1
1
import logging
2
- import os
3
2
import re
4
- import serial
5
- import socket
6
3
import time
4
+ from random import randrange
7
5
8
6
from .becker_helper import finalize_code
9
7
from .becker_helper import generate_code
10
- from .becker_helper import BeckerConnectionError
8
+ from .becker_helper import BeckerCommunicator
11
9
from .database import Database
12
10
13
11
COMMAND_UP = 0x20
31
29
COMMAND_CLEARPOS3 = 0x92
32
30
COMMAND_CLEARPOS4 = 0x93
33
31
34
- DEFAULT_DEVICE_NAME = '/dev/serial/by-id/usb-BECKER-ANTRIEBE_GmbH_CDC_RS232_v125_Centronic-if00'
32
+ # DEFAULT_DEVICE_NAME moved to becker_helper
35
33
36
34
logging .basicConfig ()
37
35
_LOGGER = logging .getLogger (__name__ )
@@ -45,7 +43,7 @@ class Becker:
45
43
Use this class to perform operations on your Becker Shutter using a centronic USB Stick
46
44
This class will as well maintain a call increment in an internal database
47
45
"""
48
- def __init__ (self , device_name = DEFAULT_DEVICE_NAME , init_dummy = False , db_filename = None ):
46
+ def __init__ (self , device_name = None , init_dummy = False , db_filename = None , callback = None ):
49
47
"""
50
48
Create a new instance of the Becker controller
51
49
@@ -54,53 +52,30 @@ def __init__(self, device_name=DEFAULT_DEVICE_NAME, init_dummy=False, db_filenam
54
52
:type device_name: str
55
53
:type init_dummy: bool
56
54
"""
57
- self .is_serial = "/" in device_name
58
- if self .is_serial and not os .path .exists (device_name ):
59
- raise BeckerConnectionError (device_name + " is not existing" )
60
- self .device = device_name
55
+ self .communicator = BeckerCommunicator (device_name , callback )
61
56
self .db = Database (db_filename )
62
57
63
58
# If no unit is defined create a dummy one
64
59
units = self .db .get_all_units ()
65
60
if not units and init_dummy :
66
61
self .db .init_dummy ()
67
62
68
- try :
69
- self ._connect ()
70
- except serial .SerialException :
71
- raise BeckerConnectionError ("Error when trying to establish connection using " + device_name )
63
+ # Start communicator thread
64
+ self .communicator .start ()
72
65
73
- def _connect (self ):
74
- if self .is_serial :
75
- self .s = serial .Serial (self .device , 115200 , timeout = 1 )
76
- self .write_function = self .s .write
77
- else :
78
- if ':' in self .device :
79
- host , port = self .device .split (':' , 1 )
80
- else :
81
- host = self .device
82
- port = '5000'
83
- self .s = socket .create_connection ((host , port ))
84
- self .write_function = self ._reconnecting_sendall
85
-
86
- def _reconnecting_sendall (self , * args , ** kwargs ):
87
- """Wrapper for socker.sendall that reconnects (once) on failure"""
88
-
89
- try :
90
- return self .s .sendall (* args , ** kwargs )
91
- except OSError :
92
- # Assume the connection failed, and connect again
93
- self ._connect ()
94
- return self .s .sendall (* args , ** kwargs )
66
+ def close (self ):
67
+ """Stop communicator thread, close device and database"""
68
+ self .communicator .close ()
69
+ self .db .conn .close ()
95
70
96
71
async def write (self , codes ):
97
72
for code in codes :
98
- self .write_function (finalize_code (code ))
99
- time . sleep ( 0.1 )
73
+ self .communicator . send (finalize_code (code ))
74
+ # Sleep implemented in BeckerCommunicator
100
75
101
76
async def run_codes (self , channel , unit , cmd , test ):
102
77
if unit [2 ] == 0 and cmd != "TRAIN" :
103
- _LOGGER .error ("The unit %s is not configured" % (unit [0 ]))
78
+ _LOGGER .error ("The unit %s is not configured" , (unit [0 ]))
104
79
return
105
80
106
81
# move up/down dependent on given time
@@ -171,21 +146,14 @@ async def run_codes(self, channel, unit, cmd, test):
171
146
self .db .set_unit (unit , test )
172
147
173
148
async def send (self , channel , cmd , test = False ):
174
- b = channel .split (':' )
175
- if len (b ) > 1 :
176
- ch = int (b [1 ])
177
- un = int (b [0 ])
178
- else :
179
- ch = int (channel )
180
- un = 1
149
+
150
+ un , ch = self ._split_channel (channel )
181
151
182
152
if not 1 <= ch <= 7 and ch != 15 :
183
153
_LOGGER .error ("Channel must be in range of 1-7 or 15" )
184
154
return
185
155
186
- if not self .device :
187
- _LOGGER .error ("No device defined" )
188
- return
156
+ # device check implemented in BeckerCommunicator
189
157
190
158
if un > 0 :
191
159
unit = self .db .get_unit (un )
@@ -255,3 +223,39 @@ async def list_units(self):
255
223
"""
256
224
257
225
return self .db .get_all_units ()
226
+
227
+ @staticmethod
228
+ def _split_channel (channel ):
229
+ b = channel .split (':' )
230
+ if len (b ) > 1 :
231
+ ch = int (b [1 ])
232
+ un = int (b [0 ])
233
+ else :
234
+ ch = int (channel )
235
+ un = 1
236
+ return un , ch
237
+
238
+ async def init_unconfigured_unit (self , channel , name = None ):
239
+ """Init unconfigured units in database and send init call"""
240
+ # check if unit is configured
241
+ un , ch = self ._split_channel (channel ) # pylint: disable=unused-variable
242
+ unit = self .db .get_unit (un )
243
+ if unit [2 ] == 0 :
244
+ _LOGGER .warning (
245
+ "Unit %s%s with channel %s not registered in database file %s!" ,
246
+ un ,
247
+ " of " + name if name is not None else "" ,
248
+ channel ,
249
+ self .db .filename ,
250
+ )
251
+ # set the unit as configured
252
+ unit [1 ] = randrange (10 , 40 , 1 )
253
+ unit [2 ] = 1
254
+ self .db .set_unit (unit )
255
+ # send init call to sync with database (5 required for my Roto cover)
256
+ for init_call_count in range (1 ,6 ):
257
+ _LOGGER .debug (
258
+ "Init call to %s:%s #%d" , un , 1 , init_call_count )
259
+ await self .stop (':' .join ((str (un ), '1' )))
260
+ # 0.5 to 0.9 seconds (works with my Roto cover)
261
+ time .sleep (randrange (5 , 10 , 1 ) / 10 )
0 commit comments