class VideoConvert(Block): ''' Input raw CV frame (numpy arrays) and output converted frame ''' extension = SelectProperty(Extension, title='Image Extension', default=Extension.jpg) version = VersionProperty('0.0.1') def process_signals(self, signals): for signal in signals: try: image = io.BytesIO() frameSignal = signal.to_dict() frame = frameSignal['frame'] ret, temp_image = cv2.imencode(self.extension().value, frame) image.write(temp_image.tobytes()) output_sig = { 'image': image, 'extension': self.extension().value } self.notify_signals(Signal(output_sig)) except: self.logger.exception("Failed to execute command")
class HTTPRequests(HTTPRequestsBase): """ A Block that makes HTTP Requests. Makes the configured request with the configured data parameters, evaluated in the context of incoming signals. Properties: url (str): URL to make request to. basic_auth_creds (obj): Basic Authentication credentials. http_method (select): HTTP method (ex. GET, POST, PUT, DELETE, etc). data (obj): URL Parameters. headers (list(dict)): Custom headers. """ version = VersionProperty("0.2.0") data = ObjectProperty(Data, title="Parameters", default=Data(), order=3) http_method = SelectProperty(HTTPMethod, default=HTTPMethod.GET, title='HTTP Method', order=0) def _create_payload(self, signal): payload = {} for param in self.data().params(): param_key = param.key(signal) param_value = param.value(signal) payload[param_key] = param_value if payload and not self.data().form_encode_data(): payload = json.dumps(payload) return payload
class Unpickle(Block): version = VersionProperty("1.0.1") def process_signals(self, signals): if len(signals) > 1: raise RuntimeError("Should only have a single pickled signal") if not hasattr(signals[0], 'pickled_data'): raise RuntimeError( "Pickled signal should have the pickled_data field" ) try: decoded_data = b64decode(signals[0].pickled_data) except TypeError: self.logger.exception("Unable to decode pickled_data") try: signals = pickle.loads(decoded_data) except pickle.UnpicklingError: self.logger.exception("Pickling based unpickle error") except: self.logger.exception("Error unpickling data") self.notify_signals(signals)
class AppendState(StateBase): """ Merge the *setter* state into *getter* signals. Maintains a *state* and merges that state (with name **state_name**) with signals that are input through the *getter* input. """ state_name = StringProperty(default='state', title="State Name", order=2) version = VersionProperty("0.2.0") def _process_group(self, signals, group, input_id, signals_to_notify): if input_id == 'setter': return self._process_setter_group(signals, group) else: return self._process_getter_group(signals, group) def _process_getter_group(self, signals, group): signals_to_notify = [] for signal in signals: existing_state = self.get_state(group) self.logger.debug( "Assigning state {} to signal".format(existing_state)) setattr(signal, self.state_name(), existing_state) signals_to_notify.append(signal) return signals_to_notify def _process_setter_group(self, signals, group): for signal in signals: self.logger.debug("Attempting to set state") self._process_state(signal, group) return []
class Replicator(Block): """Each incoming signal is replicated x times, where x is the length of list. Each output signal with have a new attribute, title, with the value of the list. """ version = VersionProperty("1.1.0") list = Property(title='List', default='', order=0) title = StringProperty(title='Attribute Title', default='', order=1) def process_signals(self, signals): return_signals = [] for signal in signals: try: values = self.list(signal) except Exception: values = [None] self.logger.exception("Failed to evaluate list") values = [None] if not values else values for value in values: sig = Signal(signal.to_dict()) setattr(sig, self.title(), value) return_signals.append(sig) if return_signals: self.notify_signals(return_signals)
class RethinkDBBase(LimitLock, Retry, Block): """ A block for communicating with a RethinkDB server. Properties: host (str): server host to connect to port (int): port on the server host, default rethink port is 28015 database_name (str): database name to access connect_timeout (interval): time to wait for a successful connection """ version = VersionProperty('1.0.0') host = StringProperty(title='Host', default='[[RETHINKDB_HOST]]') port = IntProperty(title='Port', default='[[RETHINKDB_PORT]]') database_name = StringProperty(title='DB name', default='test') connect_timeout = TimeDeltaProperty(title="Connect timeout", default={"seconds": 20}, visible=False) def process_signals(self, signals): self.execute_with_lock(self._locked_process_signals, 10, signals=signals) def _locked_process_signals(self, signals): pass
class GPIOWrite(Block): pin = IntProperty(default=0, title="Pin Number") value = Property(title='Write Value', default="{{ False }}") version = VersionProperty("0.1.1") def __init__(self): super().__init__() self._gpio = None def configure(self, context): super().configure(context) self._gpio = GPIODevice(self.logger) def stop(self): self._gpio.close() super().stop() def process_signals(self, signals): for signal in signals: self._write_gpio_pin(self.pin(signal), self.value(signal)) self.notify_signals(signals) def _write_gpio_pin(self, pin, value): try: return self._gpio.write(pin, value) except: self.logger.warning( "Failed to write value {} to gpio pin: {}".format(value, pin), exc_info=True)
class AndroidThingsButton(GeneratorBlock): version = VersionProperty('0.1.0') button_selected = SelectProperty( ButtonSelector, title='Button', order=0, default=ButtonSelector.A ) def configure(self, context): super().configure(context) button = self.button_selected().name if button == 'A': rh.touch.A.press(self.pressed) rh.touch.A.release(self.released) elif button == 'B': rh.touch.B.press(self.pressed) rh.touch.B.release(self.released) elif button == 'C': rh.touch.C.press(self.pressed) rh.touch.C.release(self.released) def pressed(self, channel): self.notify_signals(Signal(), 'pressed') def released(self, channel): self.notify_signals(Signal(), 'released')
class When(Block): subject = Property(default=None, title='Subject', allow_none=True, order=0) cases = ListProperty(Case, title='Cases', default=[], order=1) version = VersionProperty('0.1.0') def process_signals(self, in_sigs): then_signals = [] else_signals = [] for signal in in_sigs: subject = self.subject(signal) for case in self.cases(): if subject != case.when(signal): continue sig = Signal() if case.exclude(signal) else signal for attr in case.attributes(): title = attr.title(signal) value = attr.formula(signal) setattr(sig, title, value) then_signals.append(sig) break
class NCS_Inference(Block): version = VersionProperty('0.1.0') model = StringProperty(title='Model Path') def __init__(self): super().__init__() self.device = None self.graph = None def configure(self, context): super().configure(context) self.device = ncs.Device(ncs.EnumerateDevices()[0]) self.device.OpenDevice() self.graph = self.device.AllocateGraph(self.model()) def process_signals(self, signals): outgoing_signals = [] for signal in signals: if self.graph.LoadTensor(signal.batch, 'userObject'): output, _ = graph.GetResult() outgoing_signals.append(Signal({'prediction': output})) self.notify_signals(outgoing_signals) def stop(self): self.graph.DeallocateGraph() self.device.CloseDevice()
class HarperDBBulkData(HarperDBBase, Block, EnrichSignals): version = VersionProperty('0.1.0') operation = SelectProperty(Operation, title='Data Source', default=Operation.DATA, order=2) schema = StringProperty(title='Schema', default='dev', order=3) table = StringProperty(title='Table', default='dog', order=4) data = Property(title='Data (escaped string, file, or url)', default='{{ $data }}', order=5) def process_signals(self, signals): out_sigs = [] for signal in signals: payload = { 'schema': self.schema(), 'table': self.table(), 'action': 'insert', 'operation': self.operation().value, } if self.operation() is Operation.DATA: payload['data'] = self.data(signal).replace('\\n', '\n') if self.operation() is Operation.FILE: payload['file_path'] = self.data(signal) if self.operation() is Operation.URL: payload['csv_url'] = self.data(signal) result = self.sendQuery(payload) job_result = self.get_job_result(result["message"].replace( "Starting job with id ", "")) out_sigs.append(self.get_output_signal(job_result, signal)) self.notify_signals(out_sigs)
class TuYaInsight(TuYaBase, EnrichSignals): version = VersionProperty("0.1.1") def execute_tuya_command(self, signal): return_signal = {} if not self._updating: self.logger.debug('Reading values from {} {}...'.format( self.ip, self.mac)) self._updating = True try: if self.device: self._updating = False return_signal = self.device.status() #self._updating = False except Exception as e: # raises when update_insight_params has given up retrying self.logger.error( 'Unable to connect to TuYa, dropping signal {}'.format( signal)) self.device = None self._updating = False return return_signal else: # drop new signals while retrying self.logger.error( 'Another thread is waiting for param update, ' 'dropping signal {}'.format(signal)) return return_signal def is_valid_device(self, device): return super().is_valid_device(device)
class GetEncodingFromFile(Block): image_paths = ListProperty(StringType, title='Image Path', default=[]) uid = StringProperty(title='User ID', defult='') sname = StringProperty(title='Save Name', default='') version = VersionProperty("2.1.0") def save_encoding(self, file_path, save_name, user_id): serialized_encoding = [] for f in file_path: image = face_recognition.load_image_file(f) face_encoding = face_recognition.face_encodings(image)[0] serialized_encoding.append( base64.b64encode(pickle.dumps(face_encoding)).decode()) entry = { 'user_id': user_id, 'name': save_name, 'encoding': serialized_encoding } return entry def process_signals(self, signals): add_face_signals = [] for signal in signals: confirmation = self.save_encoding(self.image_paths(signal), self.sname(signal), self.uid(signal)) add_face_signals.append(Signal(confirmation)) self.notify_signals(add_face_signals)
class IRTherm(I2CBase): """ Read temparature from an ir-thermometer sensor chip """ version = VersionProperty('0.1.0') def process_signals(self, signals): signals_to_notify = [] for signal in signals: signals_to_notify.append(self._read_htu(signal)) self.notify_signals(signals_to_notify) def _read_htu(self, signal): temperature = self._read_temperature() self.logger.debug("Temperature: {}".format(temperature)) return self.get_output_signal({"temperature": temperature}, signal) def get_output_signal(self, value, signal): #TODO: move to mixin return Signal(value) def _read_temperature(self): try: temp = self._read_sensor(0x07) except: # Catch _read_sensor exeptions amd whem it returns None self.logger.warning("Failed to read temperature", exc_info=True) return return temp def _read_sensor(self, write_register): self._i2c.write_list(write_register, []) sleep(0.05) response = self._i2c.read_bytes(1) return response[0]
class GPIOInterrupts(GeneratorBlock): pin = IntProperty(default=0, title="Pin Number") version = VersionProperty("0.1.1") pull_up_down = ObjectProperty(PullUpDown, title="Pull Resistor Up/Down", default=PullUpDown()) def __init__(self): super().__init__() self._gpio = None def start(self): # TODO: allow more than one pin to be configured per block self._gpio.interrupt(self._callback, self.pin(), self.pull_up_down().default().value) super().start() def configure(self, context): super().configure(context) self._gpio = GPIODevice(self.logger) def stop(self): self._gpio.close() super().stop() def process_signals(self, signals): pass def _callback(self, channel): self.logger.debug( "Interrupt callback invoked by pin: {}".format(channel)) self.notify_signals(Signal({"pin": channel}))
class AndroidThingsRGB(TerminatorBlock): version = VersionProperty('0.1.0') red = Property(title='Red', default=False, order=0) green = Property(title='Green', default=False, order=1) blue = Property(title='Blue', default=False, order=2) def _all_off(self, signal): prop_array = \ [self.red(signal), self.blue(signal), self.green(signal)] if True in prop_array: return False else: return True def process_signals(self, signals): for signal in signals: if self._all_off(signal): rh.lights.rgb(0, 0, 0) else: if self.red(signal): rh.lights.red.on() if self.green(signal): rh.lights.green.on() if self.blue(signal): rh.lights.blue.on()
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 MongoDBBulkInsert(MongoDBInsert): """ The same as the Mongo Block except that it won't evaluate the collection property on each signal. It will also save signals as a list of signals, rather than one-by-one. Use this block for better performance on large volume inserts. """ collection = StringProperty(title='Collection Name', default="signals") version = VersionProperty('3.0.1') def configure(self, context): super().configure(context) # After super configuring (this will connect, we can get our collection if self._db: self._collection = self._get_sub_collection( self._db, self.collection()) else: self._collection = None def process_signals(self, signals): try: self.logger.debug("Inserting {} signals".format(len(signals))) self._collection.insert( [s.to_dict(self.with_type()) for s in signals], continue_on_error=True) except DuplicateKeyError as e: self.logger.warning('{}: {}'.format(type(e).__name__, e)) except Exception as e: self.logger.error("Collection insert failed: {0}: {1}".format( type(e).__name__, e)) def _bulk_generator(self, signals): for s in signals: yield s.to_dict(self.with_type)
class Switch(StateBase): """ Passthrough *getter* signals if the state is True. *getter* signals pass through to the *true* output if the last *setter* signal set the state to True. Else, the signals to *getter* pass through to the *false* output. """ version = VersionProperty("0.2.0") def _process_group(self, signals, group, input_id, signals_to_notify): if input_id == 'setter': return self._process_setter_group(signals, group) else: return self._process_getter_group(signals, group, signals_to_notify) def _process_getter_group(self, signals, group, signals_to_notify): for signal in signals: if self.get_state(group): self.logger.debug("State is True") signals_to_notify['true'].append(signal) else: self.logger.debug("State is False") signals_to_notify['false'].append(signal) def _process_setter_group(self, signals, group): """ Process the signals from the setter input for a group. Add any signals that should be passed through to the to_notify list """ for signal in signals: self.logger.debug("Attempting to set state") self._process_state(signal, group) return []
class SenseHAT(Block, EnrichSignals): imu = ObjectProperty(IMUsensor, title='IMU Sensor') version = VersionProperty('0.1.0') def __init__(self): super().__init__() self.hat = None def configure(self, context): super().configure(context) self.hat = SenseHat() self.hat.set_imu_config( self.imu().accel(), self.imu().compass(), self.imu().gyro()) def process_signals(self, signals): data = {} if self.imu().accel(): data['accelerometer'] = self.hat.get_accelerometer_raw() if self.imu().compass(): data['compass'] = self.hat.get_compass_raw() if self.imu().gyro(): data['gyroscope'] = self.hat.get_gyroscope_raw() outgoing_signals = [] for signal in signals: outgoing_signals.append(self.get_output_signal(data, signal)) self.notify_signals(outgoing_signals)
class MojioVehicles(MojioBase): """ Notify details of connected moj.io vehicles """ version = VersionProperty("2.0.2") def _get_url_endpoint(self): return 'vehicles'
class Lifx(Block): version = VersionProperty('0.1.0') mac = StringProperty(title='MAC address', default='[[LIFX_MAC]]') ip = StringProperty(title='IP Address', default='[[LIFX_IP]]') power = IntProperty(title='1 for on 0 for off', default=0) hue = IntProperty(title='Hue (0-65535)', default=0) sat = IntProperty(title='Saturation (0-65535)', default=0) bri = IntProperty(title='Brightness (0-65535)', default=65535) kelvin = IntProperty(title='Kelvin (2500-9000)', default=3500) kill_switch = BoolProperty(title='Turn off Light at Service Stop?', default=True, advanced=True) def configure(self, context): super().configure(context) self.bulb = Light(self.mac(), self.ip()) def process_signals(self, signals): for signal in signals: if self.power(signal) == 0: brightness = 0 else: brightness = self.bri(signal) self.bulb.set_power(True) self.bulb.set_color([self.hue(signal), self.sat(signal), brightness, self.kelvin(signal)]) pass self.notify_signals(signals) def stop(self): if self.kill_switch(): self.bulb.set_power(False) super().stop()
class Picamera(Block): version = VersionProperty('0.1.0') file_name = StringProperty(title='Image Name', default='image') file_type = SelectProperty(Filetypes, title='File Type', default=Filetypes.JPEG) preview = BoolProperty(title='Open Preview Window', default=False) count = 0 def configure(self, context): super().configure(context) self.camera = PiCamera() if self.preview(): self.camera.start_preview() sleep(2) def process_signals(self, signals): for signal in signals: image_name = '{}_{}.{}'.format(self.file_name(), self.count, self.file_type().value) self.camera.capture('{}'.format(image_name), format=self.file_type().value) self.count += 1 self.notify_signals(signals) def stop(self): if self.preview: self.camera.stop_preview() self.camera.close() super().stop()
class AzureIoTSendEvent(AzureIoTBase, TerminatorBlock): """A block to send events to Azure cloud. """ version = VersionProperty("1.0.0") event_to_send = Property(title="Event to Send", default="{{ $.to_dict() }}", order=2) def process_signals(self, signals): for signal in signals: data = self.event_to_send(signal) if not isinstance(data, dict): self.logger.error("Data: {} rejected, a dict is expected". format(data)) return self.logger.info("Sending: {}".format(data)) self._client.send_event(data) def get_callbacks(self): return {"send_event_callback": self._send_event_callback} def _send_event_callback(self, result, message): self.logger.info( "Confirmation for event sent received with result: {} " "and message: {}".format(result, message))
class JWTRefresh(EnrichSignals, JWTBase): version = VersionProperty('0.1.0') input = StringProperty( title='Token Value', default='{{ $headers.get(\'Authorization\').split()[1] }}', order=3) exp_minutes = Property(title='Valid For Minutes (exp claim)', order=4, allow_none=True) def process_signal(self, signal, input_id=None): _token = self.input(signal) _key = self.key(signal) _algorithm = self.algorithm(signal) _exp_minutes = self.exp_minutes(signal) try: _claims = jwt.decode(_token, _key, algorithms=[_algorithm.value]) if isinstance(_exp_minutes, int): _claims['exp'] = self.set_new_exp_time(_exp_minutes) else: try: del _claims['exp'] except KeyError: pass _token = jwt.encode(_claims, _key, algorithm=_algorithm.value) return self.get_output_signal({'token': _token.decode('UTF-8')}, signal) except PyJWTError as e: self.notify_signals( self.get_output_signal({'message': e.args[0]}, signal), 'error')
class LowPowerSleepMode(Block): rtcdevice = StringProperty(title='RTC Device', default='1') sleeptime = IntProperty(title='Sleep Time (in seconds)', default=10) version = VersionProperty('0.0.1') """Upon receipt of a signal, sleep""" def process_signals(self, signals): t = time.time() try: rtc_device = self.rtcdevice().strip('rtc') call([ 'rtcwake', '-m', 'mem', '-d', 'rtc' + rtc_device, '-s', str(self.sleeptime()) ]) try: check_call(['hwclock', '--hctosys']) except CalledProcessError as err: self.logger.warning( "An error occured while resetting the clock: {}".format( err)) except: self.logger.exception( "An error occurred while trying to sleep: {}".format( sys.exc_info()[0])) t = time.time() - t self.notify_signals([Signal({'sleeptime': t})])
class TCPClient(Block): host = StringProperty(title='IP Address', default='127.0.0.1') message = StringProperty(title='Message', default='GET / HTTP/1.1\n') port = IntProperty(title='Port', default=50001) expect_response = BoolProperty(title='Expect response?', default=True) version = VersionProperty('0.0.1') def process_signals(self, signals): for signal in signals: message = self.message(signal).encode('utf-8') response = self.send_message(message) if response: signal.response = response self.notify_signals(signals) def send_message(self, message): response = None buffer_size = 8192 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((self.host(), self.port())) s.send(message) if self.expect_response(): response = s.recv(buffer_size) s.shutdown(2) s.close() return response
class GPIORead(Block): pin = IntProperty(default=0, title="Pin Number") version = VersionProperty('0.1.0') def __init__(self): super().__init__() self._gpio = None def configure(self, context): super().configure(context) self._gpio = GPIODevice(self.logger) def stop(self): self._gpio.close() super().stop() def process_signals(self, signals): for signal in signals: signal.value = self._read_gpio_pin(self.pin(signal)) self.notify_signals(signals) def _read_gpio_pin(self, pin): try: return self._gpio.read(pin) except: self.logger.warning("Failed to read gpio pin: {}".format(pin), exc_info=True)
class Stagger(Block): version = VersionProperty("1.0.1") period = TimeDeltaProperty(title='Period', default={"seconds": 1}) min_interval = TimeDeltaProperty(title='Minimum Interval', advanced=True, default={"microseconds": 100000}) def process_signals(self, signals, input_id=None): stagger_period = self._get_stagger_period(len(signals)) self.logger.debug("{} signals received, notifying every {}".format( len(signals), stagger_period)) # Launch the notification mechanism in a new thread so that it can # sleep between notifications stagger_data = StaggerData( stagger_period, math.ceil(self.period() / stagger_period), signals, self.notify_signals, self.logger, ) stagger_data.start_notify() def _get_stagger_period(self, num_signals): """ Returns the stagger period based on a number of signals """ return max(self.period() / num_signals, self.min_interval())
class JWTCreate(EnrichSignals, JWTBase): version = VersionProperty('0.1.0') exp_minutes = Property(title='Valid For Minutes (blank for no exp claim)', order=3, allow_none=True) claims = ListProperty(ClaimField, title='Claims', order=4, allow_none=True) def process_signal(self, signal, input_id=None): _key = self.key(signal) _algorithm = self.algorithm(signal) _exp_minutes = self.exp_minutes(signal) _claims = self.claims(signal) _newclaims = {} try: if isinstance(_exp_minutes, int): _newclaims['exp'] = self.set_new_exp_time(_exp_minutes) for claim in _claims: if claim.name(signal) is not 'exp': _newclaims[claim.name(signal)] = claim.value(signal) _token = jwt.encode(_newclaims, _key, algorithm=_algorithm.value).decode('UTF-8') return self.get_output_signal({'token': _token}, signal) # jwt.encode throws ValueError if key is in wrong format except (PyJWTError, ValueError) as e: self.notify_signals( self.get_output_signal({'message': e.args[0]}, signal), 'error')