Beispiel #1
0
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)
Beispiel #4
0
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)
Beispiel #5
0
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)
Beispiel #6
0
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()
Beispiel #7
0
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)
Beispiel #9
0
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)
Beispiel #10
0
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)
Beispiel #11
0
class Coordinate(PropertyHolder):
    latitude = FloatProperty(title='Latitude', default=0.00)
    longitude = FloatProperty(title='Longitude', default=0.00)
Beispiel #12
0
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
Beispiel #16
0
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
Beispiel #17
0
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)