class Motorhat(Block): version = VersionProperty('0.1.0') motor1_speed = FloatProperty(title='DC Motor 1 Speed', default=0) motor2_speed = FloatProperty(title='DC Motor 2 Speed', default=0) motor3_speed = FloatProperty(title='DC Motor 3 Speed', default=0) motor4_speed = FloatProperty(title='DC Motor 4 Speed', default=0) def configure(self, context): super().configure(context) self.MotorHAT = Adafruit_MotorHAT(addr=0x60) def process_signals(self, signals): for signal in signals: for r in range(1, 5): speed = getattr(self, 'motor{}_speed'.format(r))(signal) direction = Adafruit_MotorHAT.FORWARD if speed >= 0 \ else Adafruit_MotorHAT.BACKWARD self.MotorHAT.getMotor(r).run(direction) self.MotorHAT.getMotor(r).setSpeed(abs(int(speed))) def stop(self): for r in range(1, 5): direction = Adafruit_MotorHAT.FORWARD self.MotorHAT.getMotor(r).run(direction) self.MotorHAT.getMotor(r).setSpeed(0) super().stop()
class NetworkConfig(PropertyHolder): input_dim = ListProperty(Dimensions, title='Input Tensor Shape', default=[{ 'value': -1 }, { 'value': 28 }, { 'value': 28 }, { 'value': 1 }]) learning_rate = FloatProperty(title='Learning Rate', default=0.01) loss = SelectProperty(LossFunctions, title='Loss Function', default=LossFunctions.cross_entropy) optimizer = SelectProperty(Optimizers, title="Optimizer", default=Optimizers.GradientDescentOptimizer) dropout = FloatProperty(title='Dropout Percentage During Training', default=0) random_seed = Property(title="Random Seed", default=None, allow_none=True, visible=False)
class LineItems(PropertyHolder): description = StringProperty(title='Line Item Description', default='Invoice Description') quantity = IntProperty(title='Quantity', default=1) unit_amount = FloatProperty(title='Unit Amount', default='{{ $amount }}') tax_amount = FloatProperty(title='Tax Amount', default='{{ $sales_tax }}') invoice_type = StringProperty(title='Invoice Type', default='ACCREC', allow_none=False) invoice_account_code = IntProperty(title='Invoice Account Code', default=100)
class Buzzer(Block): version = VersionProperty("0.1.0") frequency = FloatProperty(title='Note Freuquency (Hz)', default=261) duration = FloatProperty(title='Note Duration (s)', default=1) def process_signals(self, signals): for signal in signals: rh.buzzer.note(self.frequency(signal), self.duration(signal)) self.notify_signals(signals)
class NetworkConfig(PropertyHolder): input_dim = IntProperty(title='Number of Inputs', default=784) learning_rate = FloatProperty(title='Learning Rate', default=0.01) loss = SelectProperty(LossFunctions, title='Loss Function', default=LossFunctions.cross_entropy) optimizer = SelectProperty(Optimizers, title="Optimizer", default=Optimizers.GradientDescentOptimizer) dropout = FloatProperty(title='Dropout Percentage During Training', default=0) random_seed = IntProperty(title="Random Seed", default=0, visible=False)
class Speak(TerminatorBlock): message = StringProperty(title="Message to speak", default="{{ $message }}") rate = IntProperty(title="Words per Minute (1-200)", default=200, advanced=True) volume = FloatProperty(title="Volume (0-1.0)", default=1.0, advanced=True) version = VersionProperty('0.2.0') def __init__(self): super().__init__() self.engine = None def configure(self, context): super().configure(context) self.engine = pyttsx3.init() self.engine.setProperty("rate", self.rate()) self.engine.setProperty("volume", self.volume()) def process_signals(self, signals): for signal in signals: msg = self.message(signal) self.engine.say(msg) self.engine.runAndWait() def stop(self): self.engine.stop() super().stop()
class SimplifyPolyline(GroupBy, Block): version = VersionProperty('0.1.0') high_quality = BoolProperty(default=True, title='High Quality') tolerance = FloatProperty(default=0.1, title='Tolerance') x_attr = Property(default='{{ $x }}', title='X Attribute') y_attr = Property(default='{{ $y }}', title='Y Attribute') def process_group_signals(self, signals, group, input_id=None): points = [] for signal in signals: point = {'x': self.x_attr(signal), 'y': self.y_attr(signal)} points.append(point) simplified = simplify(points, self.tolerance(), self.high_quality()) outgoing_signals = [] for result in simplified: signal_dict = {'x': result['x'], 'y': result['y'], 'group': group} outgoing_signals.append(Signal(signal_dict)) return outgoing_signals
class XeroUpdateInvoice(Block): version = VersionProperty("0.1.3") consumer_key = StringProperty(title='Xero Consumer Key', default='[[XERO_CONSUMER_KEY]]', allow_none=False) contact_name = StringProperty(title='Contact Name (Stripe customerID)', default='{{ $customer }}') payment_amount = FloatProperty(title='Amount Paid', default='{{ $amount }}') invoice_account_code = IntProperty(title='Invoice Account Code', default=310) def __init__(self): self.xero = None self.credentials = None super().__init__() def configure(self, context): super().configure(context) con_key = self.consumer_key() with open('blocks/xero/keys/privatekey.pem') as keyfile: rsa_private_key = keyfile.read() self.credentials = PrivateCredentials(con_key, rsa_private_key) self.xero = Xero(self.credentials) def start(self): super().start() def process_signals(self, signals): response_signal = [] for signal in signals: invoice_resp_signal = self.xero.invoices.filter( Contact_Name=self.contact_name(signal), Status='AUTHORISED', order='UpdatedDateUTC DESC')[0] invoice_id = invoice_resp_signal['InvoiceID'] response_signal.append( Signal( self.xero.payments.put({ 'Invoice': { 'InvoiceID': invoice_id }, 'Account': { 'Code': self.invoice_account_code() }, 'Amount': self.payment_amount(signal) })[0])) self.notify_signals(response_signal)
class Ping(EnrichSignals, Block): version = VersionProperty('0.1.0') hostname = StringProperty(title='Hostname', default='127.0.0.1') timeout = FloatProperty( title='Timeout (seconds)', default=0.0, advanced=True, ) def process_signals(self, signals): outgoing_signals = [] for signal in signals: timeout = self.timeout(signal) if timeout > 0: timeout_str = "-W {} ".format(timeout) else: timeout_str = "" command = 'ping -c 1 {timeout_str}{host}'.format( timeout_str=timeout_str, host=self.hostname(signal), ) start_time = time.monotonic() exit_code = subprocess.call( command, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) stop_time = time.monotonic() round_trip = None if not exit_code: round_trip = stop_time - start_time round_trip = round(round_trip * 1000, 1) signal_dict = { 'ping_response': not exit_code, 'ping_time_ms': round_trip, } new_signal = self.get_output_signal(signal_dict, signal) outgoing_signals.append(new_signal) self.notify_signals(outgoing_signals)
class Series(PropertyHolder): y_axis = FloatProperty( title='Dependent Variable', default='{{ $y_data }}', allow_none=False) name = StringProperty( title='Series Name', default='default name', allow_none=False)
class Coordinate(PropertyHolder): latitude = FloatProperty(title='Latitude', default=0.00) longitude = FloatProperty(title='Longitude', default=0.00)
class IdentifyFace(EnrichSignals, Block): version = VersionProperty("2.1.1") accuracy = FloatProperty(title='Comparison Accuracy', default=0.6) location = BoolProperty(title='Output Face Location', default=False) capture = Property(title='Image', default='{{ $frame }}') def __init__(self): super().__init__() self.ref_names = [] self.ref_encodings = [] def process_signals(self, signals, input_id): if input_id == 'known': for signal in signals: self.ref_names = [] self.ref_encodings = [] for face in signal.faces: name = face['name'] for encoding in face['encoding']: self.ref_names.append(name) self.ref_encodings.append( pickle.loads(base64.b64decode(encoding))) if input_id == 'unknown': new_signals = [] for signal in signals: frame = numpy.array(self.capture(signal)) face_locations = face_recognition.face_locations(frame) face_encodings = face_recognition.face_encodings( frame, face_locations) # Set a default signal if no faces are found if self.location(): out = self.get_output_signal( {'found': ['None'], 'location': [[0, 0, 0, 0]]}, signal) else: out = self.get_output_signal({'found': ['None']}, signal) names = [] locations = [] if len(face_encodings) > 0: for e in range(len(face_encodings)): # Compare unknown face with all known face encodings match = face_recognition.compare_faces( self.ref_encodings, face_encodings[e], self.accuracy() ) name = 'Unknown' # Grab the name of the matched face for i in range(len(match)): if match[i]: name = self.ref_names[i] names.append(name) # Get the location and format it nicely location = [ face_locations[e][0], face_locations[e][1], face_locations[e][2], face_locations[e][3] ] locations.append(location) # Add list of found names (and locations) to output signal if self.location(): out = self.get_output_signal( {'found': names, 'location': locations}, signal) else: out = self.get_output_signal({'found': names}, signal) new_signals.append(out) self.notify_signals(new_signals)
class FindFace(Block): version = VersionProperty('2.0.0') accuracy = FloatProperty(title='Comparison Accuracy', default=0.6) camera = IntProperty(title='Camera Index', default=0) frame_size = FloatProperty(title='Frame Size', default=1.0) image = BoolProperty(title='Input Image', default=False) ipcam = BoolProperty(title='IP Camera', default=False) ipcam_address = StringProperty(title='IP Camera Address', default='') location = BoolProperty(title='Output Face Location', default=False) def __init__(self): super().__init__() self.video_capture = None self.ref_names = [] self.ref_encodings = [] def start(self): if (not self.image() and not self.ipcam()): # Establish connection with usb camera if it's used self.video_capture = cv2.VideoCapture(self.camera()) def process_signals(self, signals, input_id): for signal in signals: if input_id == 'known': self.ref_names = [] self.ref_encodings = [] for face in signal.faces: name = face['name'] for encoding in face['encoding']: self.ref_names.append(name) self.ref_encodings.append( pickle.loads(base64.b64decode(encoding))) if input_id == 'unknown': if self.image(): # Load in an image frame try: frame = pickle.loads(signal.capture) except TypeError: frame = pickle.loads(base64.b64decode(signal.capture)) elif self.ipcam(): # Download a jpeg frame from the camera done = False try: stream = urllib.request.urlopen(self.ipcam_address()) except: break ipbytes = bytes() while not done: ipbytes += stream.read(1024) a = ipbytes.find(b'\xff\xd8') b = ipbytes.find(b'\xff\xd9') if a != -1 and b != -1: done = True jpg = ipbytes[a:b + 2] ipbytes = ipbytes[b + 2:] frame = cv2.imdecode( numpy.fromstring(jpg, dtype=numpy.uint8), cv2.IMREAD_UNCHANGED) else: # Grab a single frame from the webcam try: ret, frame = self.video_capture.read() except: break if (not ret): break # Resize frame to specified size frame = cv2.resize(frame, (0, 0), fx=self.frame_size(), fy=self.frame_size()) # Find all the faces and face encodings in the current frame of video face_locations = face_recognition.face_locations(frame) face_encodings = face_recognition.face_encodings( frame, face_locations) # Set a default signal if no faces are found if self.location(): signal = Signal({ "found": ["None"], "location": [[0, 0, 0, 0]] }) else: signal = Signal({"found": ["None"]}) names = [] locations = [] if len(face_encodings) > 0: for e in range(len(face_encodings)): # Compare unknown face with all known face encodings match = face_recognition.compare_faces( self.ref_encodings, face_encodings[e], self.accuracy()) name = "Unknown" # Grab the name of the matched face for i in range(len(match)): if match[i]: name = self.ref_names[i] names.append(name) # Get the location and format it nicely location = [ face_locations[e][0], face_locations[e][1], face_locations[e][2], face_locations[e][3] ] locations.append(location) # Add list of found names (and locations) to the output signal if self.location(): signal = Signal({ "found": names, "location": locations }) else: signal = Signal({"found": names}) self.notify_signals([signal])
class EcobeeThermostat(Retry, EnrichSignals, Persistence, Block): app_key = StringProperty(title='App Key', default='[[ECOBEE_APP_KEY]]') refresh_token = StringProperty(title='Initial Refresh Token', default='[[ECOBEE_REFRESH_TOKEN]]') desired_temp = FloatProperty(title='Desired Temperature', default='{{ $temp }}') version = VersionProperty('0.0.2') def __init__(self): super().__init__() self._auth_token = None self._refresh_token = None self._refresh_job = None def configure(self, context): super().configure(context) # Try to use the persisted refresh token, otherwise use the block # configuration's initial refresh token if self._refresh_token is None: self._refresh_token = self.refresh_token() self.refresh_auth_token() def start(self): super().start() self._refresh_job = Job(self.refresh_auth_token, timedelta(seconds=3000), True) def stop(self): if self._refresh_job: self._refresh_job.cancel() super().stop() def persisted_values(self): return ["_refresh_token"] def refresh_auth_token(self): self.logger.info("Fetching access token...") self.logger.debug("Using refresh token {}".format(self._refresh_token)) token_body = requests.post( 'https://api.ecobee.com/token', params={ 'grant_type': 'refresh_token', 'code': self._refresh_token, 'client_id': self.app_key(), }, ).json() self._auth_token = token_body['access_token'] self._refresh_token = token_body['refresh_token'] self.logger.info("Fetched access token : {}".format(token_body)) def before_retry(self, *args, **kwargs): super().before_retry(*args, **kwargs) self.refresh_auth_token() def process_signals(self, signals, input_id='read'): out_signals = [] output_id = None for signal in signals: if input_id == 'read': output_id = 'temps' out_sig = self.get_output_signal( self.fetch_thermostats(signal), signal) elif input_id == 'set': output_id = 'set_status' desired_temp = self.desired_temp(signal) out_sig = self.get_output_signal(self.set_temp(desired_temp), signal) if out_sig: out_signals.append(out_sig) self.notify_signals(out_signals, output_id=output_id) def fetch_thermostats(self, signal=None): therms = self.execute_with_retry(self._make_ecobee_request, 'thermostat', method='get', body={ 'selection': { 'selectionType': 'registered', 'selectionMatch': '', 'includeRuntime': True, 'includeSettings': True, } }) return therms.json() def set_temp(self, temp): self.logger.info("Setting thermostat to {} degrees".format(temp)) result = self.execute_with_retry(self._make_ecobee_request, 'thermostat', method='post', body={ "selection": { "selectionType": "registered", "selectionMatch": "" }, "functions": [{ "type": "setHold", "params": { "holdType": "nextTransition", "heatHoldTemp": int(temp * 10), "coolHoldTemp": int(temp * 10) } }] }) return result.json() def _make_ecobee_request(self, endpoint, method='get', body={}): headers = { 'Authorization': 'Bearer {}'.format(self._auth_token), } params = { 'format': 'json', } if method == 'get': params['body'] = json.dumps(body) post_body = None else: post_body = body resp = getattr(requests, method)( 'https://api.ecobee.com/1/{}'.format(endpoint), headers=headers, params=params, json=post_body, ) resp.raise_for_status() return resp
class DymoScale(GeneratorBlock): read_interval = FloatProperty( title='Read Interval', default=1.0, advanced=True) reconnect_interval = FloatProperty( title='Reconnect Interval', default=5.0, advanced=True) version = VersionProperty('0.2.0') manufacturer_id = 0x0922 product_id = 0x8003 device_interface = 0 def __init__(self): super().__init__() self.device = None self._kill = False self._thread = None self._address = None self._packet_size = None def start(self): super().start() spawn(self._connect) def stop(self): self._disconnect() super().stop() def _connect(self): self.logger.debug('Connecting to scale device...') self._kill = False while not self.device and not self._kill: try: self.device = usb.core.find( idVendor=self.manufacturer_id, idProduct=self.product_id) if self.device is None: msg = 'Scale not found, trying again in {} seconds' if not self.status.is_set(RunnerStatus.warning): self.set_status('warning') self.logger.error( msg.format(self.reconnect_interval())) else: self.logger.warning( msg.format(self.reconnect_interval())) sleep(self.reconnect_interval()) continue self.logger.debug('Device discovered') self.device.reset() self.logger.debug('Device reset') if self.device.is_kernel_driver_active(self.device_interface): self.device.detach_kernel_driver(self.device_interface) self._detached = True self.logger.debug('Detached kernel driver') else: self.logger.debug('No active kernel driver found') self.device.set_configuration() self.logger.debug('Device Configured') endpoint = self.device[self.device_interface][(0, 0)][0] self._address = endpoint.bEndpointAddress self._packet_size = endpoint.wMaxPacketSize except: self.device = None if not self.status.is_set(RunnerStatus.warning): self.set_status('warning') msg = 'Unable to connect to scale, trying again in {} seconds' self.logger.exception(msg.format(self.reconnect_interval())) sleep(self.reconnect_interval()) self.set_status('ok') self._thread = spawn(self._reader) def _disconnect(self): self.logger.debug('Halting read operations') self._kill = True #self._thread.join() usb.util.dispose_resources(self.device) self.device = None def _reader(self): thread_id = current_thread().name self.logger.debug('Reader thread {} spawned'.format(thread_id)) while not self._kill: try: data = self.device.read(self._address, self._packet_size) except: if not self.status.is_set(RunnerStatus.warning): self.set_status('warning') self.logger.exception('Read operation from scale failed') self._disconnect() sleep(self.reconnect_interval()) self._connect() break units, weight = self._parse_weight(data) signal_dict = { 'units': units, 'weight': weight, } self.notify_signals([Signal(signal_dict)]) sleep(self.read_interval()) self.logger.debug('Reader thread {} completed'.format(thread_id)) def _parse_weight(self, data): # battery = data[0] if data[1] == 5: # scale value is negative sign = -1 else: sign = 1 if data[2] == 2: units = 'g' else: units = 'oz' if data[3] == 255: # values are multiplied by 10 factor = 10 else: factor = 1 weight_hi = data[4] weight_lo = data[5] raw_weight = unpack('<H', bytes([weight_hi, weight_lo]))[0] weight = raw_weight / factor * sign return units, weight
class ModbusTCP(LimitLock, EnrichSignals, Retry, Block): """ Communicate with a device using Modbus over TCP. Parameters: host (str): The host to connect to. port (int): The modbus port to connect to. timeout (float): Seconds to wait for a response before failing. """ version = VersionProperty('1.0.0', order=100) host = Property(title='Host', default='127.0.0.1', order=10) port = IntProperty(title='Port', default=502, order=11) function_name = SelectProperty(FunctionName, title='Function Name', default=FunctionName.read_coils, order=13) address = IntProperty(title='Starting Address', default=0, order=14) value = Property(title='Write Value(s)', default='{{ True }}', order=16) count = IntProperty(title='Number of coils/registers to read', default=1, order=15) unit_id = IntProperty(title='Unit ID', default=1, order=12) timeout = FloatProperty(title='Timeout', default=1, advanced=True) def __init__(self): super().__init__() self._clients = {} def configure(self, context): super().configure(context) # We don't need pymodbus to log for us. The block will handle that. logging.getLogger('pymodbus').setLevel(logging.CRITICAL) # Make sure host is able to evaluate without a signal before connecting try: host = self.host() port = self.port() except: # host uses an expression so don't connect yet self.logger.debug( "Host is an expression that uses a signal so don't connect") host = None port = 0 self._connect(host, port) def process_signals(self, signals): try: self.execute_with_lock( self._locked_process_signals, 5, signals=signals ) except: # a warning has already been logged by LimitLock mixin pass def _locked_process_signals(self, signals): output = [] for signal in signals: output_signal = self._process_signal(signal) if output_signal: output.append(output_signal) if output: self.notify_signals(output) def _process_signal(self, signal): modbus_function = self.function_name(signal).value params = self._prepare_params(modbus_function, signal) params['address'] = self.address(signal) params['unit'] = self.unit_id(signal) if modbus_function is None or \ self.address(signal) is None or \ params is None: # A warning method has already been logged if we get here return try: return self.execute_with_retry( self._execute, signal=signal, modbus_function=modbus_function, params=params) except: self.logger.exception( 'Failed to execute on host: {}'.format(self.host(signal))) return self.get_output_signal({}, signal) def stop(self): for client in self._clients: self._clients[client].close() super().stop() def _connect(self, host=None, port=502): # If host is specifed connect to that, else reconnect to existing hosts if host: self._connect_to_host(host, port) else: for client in self._clients: host, port = client.split(":") self._connect_to_host(host, int(port)) def _connect_to_host(self, host, port): self.logger.debug('Connecting to modbus host: {}'.format(host)) client = pymodbus.client.sync.ModbusTcpClient(host, port=port, timeout=self.timeout()) self._clients['{}:{}'.format(host,port)] = client self.logger.debug( 'Succesfully connected to modbus host: {}'.format(host)) def _client(self, host, port): if '{}:{}'.format(host,port) not in self._clients: self._connect(host, port) return self._clients['{}:{}'.format(host,port)] def _execute(self, signal, modbus_function, params): self.logger.debug( "Execute Modbus function '{}' with params: {}".format( modbus_function, params)) result = getattr(self._client(self.host(signal), self.port(signal)), modbus_function)(**params) self.logger.debug('Modbus function returned: {}'.format(result)) if result: results = result.__dict__ results["params"] = params signal = self.get_output_signal(results, signal) self._check_exceptions(signal) return signal def _prepare_params(self, modbus_function, signal): try: if modbus_function in ['write_coil', 'write_register']: return {'value': self.value(signal)} elif modbus_function in ['write_coils', 'write_registers']: return {'values': self.value(signal)} elif modbus_function.startswith('read'): return {'count': self.count(signal)} else: return {} except: self.logger.warning('Failed to prepare function params', exc_info=True) def before_retry(self, *args, **kwargs): ''' Reconnect before making retry query. ''' self._connect() def _check_exceptions(self, signal): ''' Add exception details if the response has an exception code ''' code = getattr(signal, 'exception_code', None) desc = None if code and isinstance(code, int): if code == 1: desc = 'Function code received in the query is not ' \ 'recognized or allowed by slave' elif code == 2: desc = 'Data address of some or all the required entities ' \ 'are not allowed or do not exist in slave' elif code == 3: desc = 'Value is not accepted by slave' elif code == 4: desc = 'Unrecoverable error occurred while slave was ' \ 'attempting to perform requested action' elif code == 5: desc = 'Slave has accepted request and is processing it, ' \ 'but a long duration of time is required. ' \ 'This response is returned to prevent a ' \ 'timeout error from occurring in the master. ' \ 'Master can next issue a Poll Program Complete ' \ 'message to determine if processing is completed' elif code == 6: desc = 'Slave engaged in processing a long-duration command. '\ 'Master should retry later' elif code == 7: desc = 'Slave cannot perform the programming functions. ' \ 'Master should request diagnostic ' \ 'or error information from slave' elif code == 8: desc = 'Slave detected a parity error in memory. ' \ 'Master can retry the request, ' \ 'but service may be required on the slave device' elif code == 10: desc = 'Specialized for Modbus gateways. ' \ 'Indicates a misconfigured gateway' elif code == 11: desc = 'Specialized for Modbus gateways. ' \ 'Sent when slave fails to respond' if desc: signal.exception_details = desc
class ModbusRTU(Retry, Block): """ Communicate with a device using Modbus over RTU. Parameters: slave_address (str): Slave address of modbus device. port (str): Serial port modbus device is connected to. timeout (float): Seconds to wait for a response before failing. """ version = VersionProperty('1.0.0', order=100) slave_address = IntProperty(title='Slave Address', default=1, order=10) function_name = SelectProperty(FunctionName, title='Function Name', default=FunctionName.read_input_registers, order=11) address = Property(title='Starting Address', default='0', order=12) count = IntProperty(title='Number of coils/registers to read', default=1, order=13) value = Property(title='Write Value(s)', default='{{ True }}', order=14) port_config = ObjectProperty(PortConfig, title="Serial Port Setup", default=PortConfig(), advanced=True) timeout = FloatProperty(title='Timeout', default='0.05', advanced=True) def __init__(self): super().__init__() self._client = None self._process_lock = Lock() self._modbus_function = None self._num_locks = 0 self._max_locks = 5 def configure(self, context): super().configure(context) self._connect() self._modbus_function = \ self._function_name_from_code(self.function_name().value) def process_signals(self, signals, input_id='default'): output = [] for signal in signals: if self._num_locks >= self._max_locks: self.logger.debug( "Skipping signal; max numbers of signals waiting") continue self._num_locks += 1 with self._process_lock: output_signal = self._process_signal(signal) if output_signal: output.append(output_signal) self._num_locks -= 1 if output: self.notify_signals(output) def _process_signal(self, signal): params = self._prepare_params(signal) return self.execute_with_retry(self._execute, params=params) def _connect(self): self.logger.debug('Connecting to modbus') minimalmodbus.BAUDRATE = self.port_config().baudrate() minimalmodbus.PARITY = self.port_config().parity() minimalmodbus.BYTESIZE = self.port_config().bytesize() minimalmodbus.STOPBITS = self.port_config().stopbits() minimalmodbus.TIMEOUT = self.timeout() self._client = minimalmodbus.Instrument(self.port_config().port(), self.slave_address()) self.logger.debug(self._client) self.logger.debug('Succesfully connected to modbus') def _execute(self, params, retry=False): self.logger.debug('Executing Modbus function \'{}\' with params: {}, ' 'is_retry: {}'.format(self._modbus_function, params, retry)) response = getattr(self._client, self._modbus_function)(**params) self.logger.debug('Modbus function returned: {}'.format(response)) return self._process_response(response, params) def _function_name_from_code(self, code): return { 1: 'read_bit', 2: 'read_bit', 5: 'write_bit', 15: 'write_bit', 3: 'read_registers', 4: 'read_registers', 6: 'write_register', 16: 'write_registers' }.get(code) def _prepare_params(self, signal): params = {} params['functioncode'] = self.function_name().value params['registeraddress'] = self._address(signal) if self.function_name().value in [3, 4]: params['numberOfRegisters'] = self.count() elif self.function_name().value in [5, 6, 15, 16]: try: params['value'] = self.value(signal) except: raise Exception('Invalid configuration of `value` property') return params def _process_response(self, response, params): if not response: return signal = Signal({'values': response, 'params': params}) return signal def _address(self, signal): try: return int(self.address(signal)) except: self.logger.warning('Address needs to evaluate to an integer', exc_info=True) def before_retry(self, *args, **kwargs): """ Reconnect before making retry query. """ self._close() self._connect() def _close(self): """minimalmodbus needs some help re-connecting""" try: # Try to manually close the serial connection self._client.serial.close() except: self.logger.warning("Failed to manually close serial connection", exc_info=True)