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 DatabaseConnection(PropertyHolder): server = StringProperty(title='Server', default='localhost', order=0) port = IntProperty(title='Port', default=9925, order=1) ssl = BoolProperty(title='Use SSL', default=False, order=2) allow_redirects = BoolProperty(title='Allow Redirects', default=False, order=3) timeout = IntProperty(title='Timeout', default=15, order=4)
class ObjectPath(PropertyHolder): class_id = IntProperty(title='Class ID', default=1, order=0) instance_num = IntProperty(title='Instance', default=1, order=1) attribute_num = Property(title='Attribute', default=None, allow_none=True, order=2)
class PortConfig(PropertyHolder): baudrate = IntProperty(title='Baud Rate', default=19200, order=21) parity = StringProperty(title='Parity (N, E, O)', default='N', order=23) bytesize = IntProperty(title='Byte Size', default=8, order=22) stopbits = IntProperty(title='Stop Bits', default=1, order=24) port = StringProperty(title='Serial Port', default='/dev/ttyUSB0', order=20)
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 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 GPIOInterrupts(Block): pin = IntProperty(default=0, title="Pin Number") version = VersionProperty('0.1.0') interrupt_trigger = ObjectProperty(Trigger, title="Trigger on which edge:", default=Trigger()) def __init__(self): super().__init__() self._gpio = None def configure(self, context): super().configure(context) self._gpio = GPIODevice(self.logger) # TODO: allow more than one pin to be configured per block self._gpio.interrupt( self._callback, self.pin(), self.interrupt_trigger().default().value) 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 PlayAudioFile(LimitLock, Block): version = VersionProperty('0.1.0') audio_file = FileProperty(title='Audio File Location', default='audio.wav') max_locks = IntProperty(title='Audio Queue Size', default=5, advanced=True) def process_signal(self, signal, input_id=None): file_loc = self.audio_file(signal).value if not isfile(file_loc): self.logger.error( "{} is not a valid file location".format(file_loc)) return obj = simpleaudio.WaveObject.from_wave_file(file_loc) self.execute_with_lock(self._play_simpleaudio, self.max_locks(signal), obj) # Notify the incoming signal once the audio has completed playing return signal def _play_simpleaudio(self, audio_obj): """ Plays a simpleaudio object and waits for it to complete """ self.logger.info("Playing simpleaudio obj {}".format(audio_obj)) play = audio_obj.play() play.wait_done() def stop(self): simpleaudio.stop_all() super().stop()
class AdafruitPWMServo(Block): pulse_length = IntProperty(title="Pulse Length", default=0) frequency = IntProperty(title="Frequency", default=60) servo_channel = IntProperty(title="Servo Channel", defalut=0) def __init__(self): super().__init__() self.pwm = Adafruit_PCA9685.PCA9685() def start(self): self.pwm.set_pwm_freq(self.frequency()) def process_signals(self, signals): for signal in signals: self.pwm.set_pwm(self.servo_channel(), 0, self.pulse_length())
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 SQSSendMessage(SQSBase): """Send message over Amazon SQS User needs to specify a queue url and message body""" message_body = StringProperty( title="Message Body", default="Hello Mr. SQS", allow_none=False) delay_seconds = IntProperty( title="Delay sending message", default=0, allow_none=True) def process_signals(self, signals): new_signals = [] for signal in signals: try: self.logger.debug("Sending message via {} queue".format( self.queue_url(signal))) response = self.client.send_message(QueueUrl=self.queue_url(signal), DelaySeconds=self.delay_seconds(signal), MessageBody=self.message_body(signal)) new_signals.append(Signal(response)) except: self.logger.exception("Message failed to send") self.notify_signals(new_signals)
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 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 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 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 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 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 Limitable(): """ A Mongo block mixin that allows you to limit results """ limit = IntProperty(title='Limit', default=0) def query_args(self): existing_args = super().query_args() existing_args['limit'] = self.limit() return existing_args
class Layers(PropertyHolder): count = IntProperty(title='Number of Neurons', default=10) activation = SelectProperty(ActivationFunctions, title='Activation Function', default=ActivationFunctions.softmax) initial_weights = SelectProperty(InitialValues, title='Initial Weight Values', default=InitialValues.random) bias = BoolProperty(title='Add Bias Unit', default=True)
class GoogleIoTMQTTBase(object): """The base block for Google IoT. This block is responsible for connecting to the cloud broker via MQTT paho.""" project_id = StringProperty(title="Project Id", order=10) project_region = StringProperty(title="Project Region", order=11, default="") registry_id = StringProperty(title="Registry Id", order=12) device_id = StringProperty(title="Device Id", order=13) private_key_path = FileProperty( title="Private Key Path", order=14, default="[[PROJECT_ROOT]]/etc/google_iot_rsa_private.pem") cert_path = FileProperty( title="Certificate Path", order=15, default="[[PROJECT_ROOT]]/etc/google_iot_cert.pem") keep_alive = IntProperty(title="Keep Alive", default=10, advanced=True, order=21) def __init__(self): super().__init__() self._client = None def configure(self, context): """set up google client properties""" super().configure(context) self._client = IoTCoreClient( self.project_id(), self.project_region(), self.registry_id(), self.device_id(), self.private_key_path().value, self.cert_path().value, self.keep_alive(), self.logger, on_message=self.on_message ) self._client.connect() def stop(self): self.disconnect() super().stop() def connect(self): self.logger.debug("Connecting...") self.client.connect() def disconnect(self): self.logger.debug("Disconnecting...") self._client.disconnect() def on_message(self, client, user_data, message): self.logger.debug("on_message, client: {}, message: {}, user data: {}". format(client, message, user_data))
class SignalRepeater(GroupBy, Block): version = VersionProperty('0.1.1') max_repeats = IntProperty(title='Max Repeats', default=-1) interval = TimeDeltaProperty(title='Repeat Interval', default={'seconds': 10}) def configure(self, context): super().configure(context) self.notifications = defaultdict(dict) self._group_locks = defaultdict(Lock) def stop(self): for group in copy(self.notifications): self._cancel_group_job(group) super().stop() def _cancel_group_job(self, group): job = self.notifications[group].get('job') if job: self.logger.debug("Cancelling job for group {}".format(group)) job.cancel() del self.notifications[group] def process_group_signals(self, signals, group, input_id='repeat'): if input_id == 'cancel': self._cancel_group_job(group) return if len(signals) == 0: return signal = signals[-1] repeats_remaining = self.max_repeats(signal) with self._group_locks[group]: self._cancel_group_job(group) if repeats_remaining == 0: # They don't want to repeat, ignore return self.logger.debug("Setting up repeat for group {}".format(group)) self.notifications[group]['signal'] = signal self.notifications[group]['num_remaining'] = repeats_remaining self.notifications[group]['job'] = Job(target=self.notify_group, delta=self.interval(signal), repeatable=True, group=group) def notify_group(self, group): with self._group_locks[group]: notification = self.notifications[group] if notification.get('num_remaining', 0) != 0: notification['num_remaining'] -= 1 self.logger.debug( "Notifying signal for group {}, {} remaining".format( group, notification['num_remaining'])) self.notify_signals([notification['signal']]) else: self._cancel_group_job(group)
class PiCar(Block): forward_speed = IntProperty( title="Forward speed given to both motors, -255 - 255", default=0) right_speed = IntProperty( title="Additional Rotation of right motor, -255 - 255", default=0) left_speed = IntProperty( title="Additional Rotation of left motor, -255 - 255", default=0) version = VersionProperty('0.0.1') def __init__(self): super().__init__() self.MotorHAT = Adafruit_MotorHAT(addr=0x60) self._motor_left = self.MotorHAT.getMotor(1) self._motor_right = self.MotorHAT.getMotor(2) def process_signals(self, signals): for signal in signals: if (self.forward_speed(signal) >= 0): self._motor_right.run(Adafruit_MotorHAT.FORWARD) self._motor_left.run(Adafruit_MotorHAT.FORWARD) right_wheel_speed = self.forward_speed( signal) + self.right_speed(signal) left_wheel_speed = self.forward_speed( signal) + self.left_speed(signal) self._motor_right.setSpeed(right_wheel_speed) self._motor_left.setSpeed(left_wheel_speed) elif (self.forward_speed(signal) < 0): self._motor_right.run(Adafruit_MotorHAT.BACKWARD) self._motor_left.run(Adafruit_MotorHAT.BACKWARD) right_wheel_speed = abs( self.forward_speed(signal) + self.right_speed(signal)) left_wheel_speed = abs( self.forward_speed(signal) + self.left_speed(signal)) self._motor_right.setSpeed(right_wheel_speed) self._motor_left.setSpeed(left_wheel_speed) def stop(self): try: self.MotorHAT.getMotor(1).run(Adafruit_MotorHAT.RELEASE) self.MotorHAT.getMotor(2).run(Adafruit_MotorHAT.RELEASE) except: self.logger.exception('Exception while halting motors')
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 Connection(PropertyHolder): """ Mongo connection properties Properties: host (str): Database host port (int): Database port ssl (bool): Whether or not to use SSL when connecting (certs won't be validated) """ host = StringProperty(title='Mongo Host', default="127.0.0.1") port = IntProperty(title='Port', default=27017) ssl = BoolProperty(title='Use SSL', default=False)
class SQSReceiveMessage(SQSBase): """Receive message from Amazon SQS User needs to specify a queue url""" max_number_of_messages = IntProperty( title="Max Number Messages to Receive", default=1, allow_none=True) visibility_timeout = IntProperty(title="Visibility Timeout", default=0, allow_none=True) wait_time_seconds = IntProperty(title="Time to wait for messages", default=10, allow_none=True) receive_request_attempt_id = StringProperty(title="ID to attempt retry", default="", allow_none=True) def process_signals(self, signals): new_signals = [] for signal in signals: try: self.logger.debug("Receiving message from {} queue".format( self.queue_url(signal))) message = self.client.receive_message( QueueUrl=self.queue_url(signal), MaxNumberOfMessages=self.max_number_of_messages(signal), VisibilityTimeout=self.visibility_timeout(signal), WaitTimeSeconds=self.wait_time_seconds(signal), ReceiveRequestAttemptId=self.receive_request_attempt_id( signal)) new_signals.append(Signal(message)) except: self.logger.exception("Message failed to be received") self.notify_signals(new_signals)
class AIYNatureExplorer(InferenceBase): model = SelectProperty( Models, title='Classifier Model', default=Models.birds) top_k_predictions = IntProperty( title='Return Top k Predictions', default=5, advanced=True) version = VersionProperty('0.1.0') def start(self): self._running = True super().start() def stop(self): self._running = False self.logger.debug('killing child thread ...') super().stop() def run(self): while self._running: try: self.logger.debug( 'loading {} model ...'.format(self.model().name)) model = inaturalist_classification.model(self.model().value) with CameraInference(model) as inference: self.logger.debug('running inference ...') for result in inference.run(): predictions = inaturalist_classification.get_classes( result, top_k=self.top_k_predictions()) outgoing_signals = [] for label, score in predictions: signal_dict = { 'label': label, 'score': score, } outgoing_signal = Signal(signal_dict) outgoing_signals.append(outgoing_signal) if not self._running: break self.notify_signals(outgoing_signals) except: self.logger.exception('failed to get inference result!') self.reset_camera() self.release_camera()
class RollingWindow(Block): version = VersionProperty('0.1.0') chunk_size = IntProperty(title='Chunk Size', default=1) step_by = IntProperty(title='Step By', default=1) key = StringProperty(title='Key', default='') def process_signals(self, signals): out_sigs = [] for signal in signals: key = self.key(signal) chunk_size = self.chunk_size(signal) step_by = self.step_by(signal) if key in signal: data = signal[key] for i in range( math.ceil((len(data) - chunk_size + 1) / step_by)): chunk = [] for x in range(chunk_size): chunk.append(data[(i * step_by) + x]) out_sigs.append(chunk) print(out_sigs) self.notify_signals(out_sigs)
class SafeTrigger(): """ Guarantees notifying signals every interval, regardless of count """ interval = TimeDeltaProperty(title='Interval', default={'seconds': 1}, order=0) max_count = IntProperty(title='Max Count', default=1, order=1) def __init__(self): super().__init__() self._job = None self.stop_event = Event() self.signal_lock = Lock() def start(self): super().start() self._job = Job(self._emit, self.interval(), True) # Run an emit cycle immediately, but in a new thread since it # might take some time and we don't want it to hold up start spawn(self._emit) def stop(self): """ Stop the simulator thread and signal generation """ if self._job: self._job.cancel() self.stop_event.set() super().stop() def _emit(self): """ Called every *interval* to generate then notify the signals """ self.logger.debug("New generation cycle requested") count = 0 signals = [] # Stop any currently running simulator threads self.stop_event.set() # We only want one simulator thread simulating at a time with self.signal_lock: # Ok, we're running, so clear the event and wait self.stop_event.clear() self.logger.debug("Starting generation...") while count < self.max_count() and not self.stop_event.is_set(): signals.extend(self.generate_signals(1)) count += 1 self.logger.debug("Notifying {} signals".format(len(signals))) self.notify_signals(signals)
class Connection(PropertyHolder): server = StringProperty(title='Server', default='[[MSSQL_SERVER]]', order=1) port = IntProperty(title='Port', default='[[MSSQL_PORT]]', order=2) database = StringProperty(title='Database', default='[[MSSQL_DB]]', order=3) user_id = StringProperty(title='User ID', allow_none=True, default='[[MSSQL_USER]]', order=4) password = StringProperty(title='Password', allow_none=True, default='[[MSSQL_PWD]]', order=5)
class GoogleAnalytics(GoogleAnalyticsBase): lookback_days = IntProperty(title="Lookback Days (inclusive)", default=0) version = VersionProperty("1.0.1") def get_url_suffix(self): return 'analytics/v3/data/ga' def get_url_parameters(self): params = super().get_url_parameters() params["start-date"] = "{0}daysAgo".format(self.lookback_days()) params["end-date"] = "today" params["dimensions"] = "ga:userType" return params