class MessageHandler(): ''' This handler just displays IFS IR events as they arrive. ''' def __init__(self, level): super().__init__() self._counter = itertools.count() self._log = Logger("queue", Level.INFO) self._ir = ['PSID', 'PORT', 'CNTR', 'STBD', 'SSID'] self._log.info('ready.') # ...................................................... def handle(self, message): self._log.debug('handle message #{}: priority {}: {}'.format( message.number, message.priority, message.description)) _event = message.event self._ir[ 0] = Fore.RED + Style.BRIGHT + 'PSID' if _event is Event.INFRARED_PORT_SIDE else Fore.BLACK + Style.DIM + '....' self._ir[ 1] = Fore.RED + Style.NORMAL + 'PORT' if _event is Event.INFRARED_PORT else Fore.BLACK + Style.DIM + '....' self._ir[ 2] = Fore.BLUE + Style.BRIGHT + 'CNTR' if _event is Event.INFRARED_CNTR else Fore.BLACK + Style.DIM + '....' self._ir[ 3] = Fore.GREEN + Style.NORMAL + 'STBD' if _event is Event.INFRARED_STBD else Fore.BLACK + Style.DIM + '....' self._ir[ 4] = Fore.GREEN + Style.BRIGHT + 'SSID' if _event is Event.INFRARED_STBD_SIDE else Fore.BLACK + Style.DIM + '....' print('{} '.format(self._ir[0]) \ + '{} '.format(self._ir[1]) \ + '{} '.format(self._ir[2]) \ + '{} '.format(self._ir[3]) \ + '{} '.format(self._ir[4]) + Style.RESET_ALL, end='\n')
class MessageBus(): ''' Message Bus description. ''' def __init__(self, level=Level.INFO): self._log = Logger('bus', level) self._log.debug('initialised...') self._subscriptions = set() self._log.debug('ready.') # .......................................................................... @property def subscriptions(self): ''' Return the current set of Subscriptions. ''' return self._subscriptions # .......................................................................... async def publish_to_bus(self, message: Message): ''' Publishes the Message to all Subscribers. ''' self._log.info(Style.BRIGHT + 'publish message: eid: {}; event: {}'.format(message.eid, message.event.name)) for queue in self._subscriptions: queue.put_nowait(message)
class Toggle(object): ''' Returns a boolean value indicating the state of a toggle switch connected to the specified GPIO pin. The pin is pulled high in software by default ("off") and returns False, and connected to ground when switched "on", returning True. :param pin: the BCM pin to which the switch is connected :param level: log level ''' def __init__(self, pin, level): self._log = Logger("toggle", level) self._log.info('configuring toggle on pin {}'.format(pin)) self._pin = pin # The GPIO pin is set up as an input, pulled up to avoid false # detection. The pin is wired to connect to GND on button press. GPIO.setmode(GPIO.BCM) GPIO.setup(self._pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) self._log.info('ready.') # ...................................................... @property def state(self): ''' Returns True when the toggle switch is turned on (connected to ground). ''' _state = False if GPIO.input(self._pin) else True self._log.debug('state on pin {}: {}'.format(self._pin, _state)) return _state # ...................................................... def close(self): GPIO.cleanup() # clean up GPIO on normal exit self._log.info('closed.')
class GamepadScan(object): ''' Returns the device with the most recently changed status from /dev/input/event{n} This can help you figure out which device is your gamepad, if if was connected after everything else in the system had settled. ''' def __init__(self, config, level): self._log = Logger("gamepad-scan", level) if config is None: raise ValueError("no configuration provided.") _config = config['ros'].get('gamepad') self._device_path = _config.get('device_path') self._log.debug('device path: {}'.format(self._device_path)) self._log.info('ready') # .......................................................................... def _get_ctime(self, path): try: _device_stat = os.stat(path) return _device_stat.st_ctime except OSError: return -1.0 # .......................................................................... def get_latest_device(self): ''' Build a dictionary of available devices, return the one with the most recent status change. ''' _dict = {} for i in range(10): _path = '/dev/input/event{}'.format(i) try: _device_stat = os.stat(_path) _ctime = _device_stat.st_ctime except OSError: break self._log.debug('device: {}'.format(_path) + Fore.BLUE + Style.NORMAL + '\tstatus changed: {}'.format(dt.datetime.fromtimestamp(_ctime))) _dict[_path] = _ctime # find most recent by sorting the dictionary on ctime _sorted = sorted(_dict.items(), key=lambda x:x[1]) _latest_devices = _sorted[len(_sorted)-1] _latest_device = _latest_devices[0] self._log.info('device path: {}'.format(self._device_path)) self._log.info('most recent device: {}'.format(_latest_device)) return _latest_device # .......................................................................... def check_gamepad_device(self): ''' Checks that the configured device matches the device with the most recently changed status, returning True if matched. ''' _latest_device = self.get_latest_device() if self._device_path == _latest_device: self._log.info(Style.BRIGHT + 'matches: {}'.format(self._device_path)) return True else: self._log.info(Style.BRIGHT + 'does not match: {}'.format(_latest_device)) return False
class ConfigLoader(): ''' Has just one method: configure() reads a YAML file. ''' def __init__(self, level): self._log = Logger('configloader', level) self._log.info('ready.') # .......................................................................... def configure(self, filename='config.yaml'): ''' Read and return configuration from the specified YAML file. Pretty-prints the configuration object if the log level is set to DEBUG. ''' self._log.info( 'reading from yaml configuration file {}...'.format(filename)) _config = yaml.safe_load(open(filename, 'r')) if self._log.level == Level.DEBUG: self._log.debug('YAML configuration as read:') print(Fore.BLUE) pp = pprint.PrettyPrinter(width=80, indent=2) pp.pprint(_config) print(Style.RESET_ALL) self._log.info('configuration read.') return _config
class FlaskWrapperService(threading.Thread): def __init__(self): global g_queue super().__init__() self._log = Logger('flask.wrapper', Level.INFO) g_queue = MockMessageQueue() threading.Thread.__init__(self) self._thread = None self._log.debug('ready') # .......................................................................... def run(self): global app self._log.info('starting flask wrapper...') # suppress console log messages (this doesn't actually work) _flask_log = logging.getLogger('werkzeug') _flask_log.setLevel(logging.ERROR) _flask_log.disabled = True # socketio.run(app) self._thread = threading.Thread(target=app.run( host='0.0.0.0', port=8085, debug=False, use_reloader=False)) self._thread.start() # app.run(host='0.0.0.0', port=8085, debug=False, use_reloader=False) self._log.info('ended flask thread.') # .......................................................................... def close(self): self._log.info('closing flask.') if self._thread != None: self._thread.join(timeout=1.0) self._log.info('flask app thread joined.') self._log.info('closed flask.')
class I2CScanner(): ''' Scans the I2C bus returning a list of addresses. ''' def __init__(self, level): super().__init__() self._log = Logger('i2cscan', level) self._log.debug('initialising...') self.hex_list = [] try: import smbus bus_number = 1 # 1 indicates /dev/i2c-1 self._bus = smbus.SMBus(bus_number) self._log.info('ready.') except ImportError: self._log.warning('initialised. This script requires smbus. Will operate without but return an empty result.') self._bus = None def getHexAddresses(self): ''' Returns a hexadecimal version of the list. This is only populated after calling getAddresses(). ''' return self.hex_list def getIntAddresses(self): ''' Returns an integer version of the list. This is only populated after calling getAddresses(). ''' return self._int_list def getAddresses(self): self._int_list = [] device_count = 0 if self._bus is not None: for address in range(3, 128): try: self._bus.write_byte(address, 0) self._log.debug('found {0}'.format(hex(address))) self._int_list.append(address) self.hex_list.append(hex(address)) device_count = device_count + 1 except IOError as e: if e.errno != errno.EREMOTEIO: self._log.error('Error: {0} on address {1}'.format(e, hex(address))) except Exception as e: # exception if read_byte fails self._log.error('Error unk: {0} on address {1}'.format(e, hex(address))) self._bus.close() self._bus = None self._log.info("found {0} device(s)".format(device_count)) else: self._log.info("found no devices (no smbus available)") return self._int_list
def main(): signal.signal(signal.SIGINT, signal_handler) _log = Logger("scanner", Level.INFO) _log.info(Fore.CYAN + Style.BRIGHT + ' INFO : starting test...') _log.info(Fore.YELLOW + Style.BRIGHT + ' INFO : Press Ctrl+C to exit.') try: _i2c_scanner = I2CScanner(Level.WARN) _addresses = _i2c_scanner.getAddresses() hexAddresses = _i2c_scanner.getHexAddresses() _addrDict = dict( list(map(lambda x, y: (x, y), _addresses, hexAddresses))) for i in range(len(_addresses)): _log.debug(Fore.BLACK + Style.DIM + 'found device at address: {}'.format(hexAddresses[i])) vl53l1x_available = (0x29 in _addresses) ultraborg_available = (0x36 in _addresses) if not vl53l1x_available: raise OSError('VL53L1X hardware dependency not available.') elif not ultraborg_available: raise OSError('UltraBorg hardware dependency not available.') _log.info('starting scan...') # _player = Player(Level.INFO) _loader = ConfigLoader(Level.INFO) filename = 'config.yaml' _config = _loader.configure(filename) _lidar = Lidar(_config, Level.INFO) _lidar.enable() values = _lidar.scan() _angle_at_min = values[0] _min_mm = values[1] _angle_at_max = values[2] _max_mm = values[3] _log.info( Fore.CYAN + Style.BRIGHT + 'min. distance at {:>5.2f}°:\t{}mm'.format(_angle_at_min, _min_mm)) _log.info( Fore.CYAN + Style.BRIGHT + 'max. distance at {:>5.2f}°:\t{}mm'.format(_angle_at_max, _max_mm)) time.sleep(1.0) _lidar.close() _log.info(Fore.CYAN + Style.BRIGHT + 'test complete.') except Exception: _log.info(traceback.format_exc()) sys.exit(1)
def main(): _log = Logger("cat-scan test", Level.INFO) _log.info('starting test...') _log.info(Fore.YELLOW + Style.BRIGHT + ' INFO : Press Ctrl+C to exit.') try: _i2c_scanner = I2CScanner(Level.WARN) _addresses = _i2c_scanner.getAddresses() hexAddresses = _i2c_scanner.getHexAddresses() _addrDict = dict(list(map(lambda x, y:(x,y), _addresses, hexAddresses))) for i in range(len(_addresses)): _log.debug(Fore.BLACK + Style.DIM + 'found device at address: {}'.format(hexAddresses[i]) + Style.RESET_ALL) ultraborg_available = ( 0x36 in _addresses ) if not ultraborg_available: raise OSError('UltraBorg hardware dependency not available.') _log.info('starting cat scan...') _loader = ConfigLoader(Level.INFO) filename = 'config.yaml' _config = _loader.configure(filename) _queue = MockMessageQueue(Level.INFO) _catscan = CatScan(_config, _queue, Level.INFO) _catscan.enable() _catscan.set_mode(True) _count = 0 while not _queue.is_complete(): if _count % 5 == 0: _log.info('waiting for cat... ' + Fore.BLACK + Style.DIM + '({:d} sec)'.format(_count)) time.sleep(1.0) _count += 1 time.sleep(9.0) _catscan.close() _log.info('test complete.') except KeyboardInterrupt: if _catscan: _catscan.close() except Exception: _log.info(traceback.format_exc()) finally: if _catscan: _catscan.close()
def main(): signal.signal(signal.SIGINT, signal_handler) print('follower test :' + Fore.CYAN + Style.BRIGHT + ' INFO : starting test...' + Style.RESET_ALL) print('follower test :' + Fore.YELLOW + Style.BRIGHT + ' INFO : Press Ctrl+C to exit.' + Style.RESET_ALL) _log = Logger("follower", Level.INFO) try: _i2c_scanner = I2CScanner(Level.WARN) _addresses = _i2c_scanner.getAddresses() hexAddresses = _i2c_scanner.getHexAddresses() _addrDict = dict(list(map(lambda x, y:(x,y), _addresses, hexAddresses))) for i in range(len(_addresses)): _log.debug(Fore.BLACK + Style.DIM + 'found device at address: {}'.format(hexAddresses[i]) + Style.RESET_ALL) vl53l1x_available = ( 0x29 in _addresses ) ultraborg_available = ( 0x36 in _addresses ) if not vl53l1x_available: raise OSError('VL53L1X hardware dependency not available.') elif not ultraborg_available: raise OSError('UltraBorg hardware dependency not available.') _log.info('starting wall follower...') _loader = ConfigLoader(Level.INFO) filename = 'config.yaml' _config = _loader.configure(filename) _follower = WallFollower(_config, Level.INFO) _follower.enable() _follower.set_position(Orientation.PORT) time.sleep(1.5) _follower.set_position(Orientation.STBD) time.sleep(1.5) # for i in range(10): # mm = _follower.scan() # time.sleep(1.0) _follower.close() print('follower test :' + Fore.CYAN + Style.BRIGHT + ' INFO : test complete.' + Style.RESET_ALL) except Exception: _log.info(traceback.format_exc()) sys.exit(1)
class OutputSplitter(object): ''' An output (as far as picamera is concerned), is just a filename or an object which implements a write() method (and optionally the flush() and close() methods) If the filename parameter is None no file is written. ''' def __init__(self, filename): self.frame = None self.buffer = io.BytesIO() self._log = Logger('output', Level.INFO) self._filename = filename if self._filename: self._output_file = io.open(filename, 'wb') self._log.info(Fore.MAGENTA + 'output file: {}'.format(filename)) else: self._output_file = None self._log.info(Fore.MAGENTA + 'no output file generated from video.') self._condition = Condition() self._log.info('ready.') def get_filename(self): return self._filename def write(self, buf): if buf.startswith(b'\xff\xd8'): # new frame, copy existing buffer's content and notify all clients it's available self.buffer.truncate() with self._condition: self.frame = self.buffer.getvalue() self._condition.notify_all() self.buffer.seek(0) if self._output_file and not self._output_file.closed: self._output_file.write(buf) return self.buffer.write(buf) def flush(self): self._log.debug('flushing...') if self._output_file and not self._output_file.closed: self._output_file.flush() if not self.buffer.closed: self.buffer.flush() self._log.debug('flushed.') def close(self): self._log.info('closing...') if self._output_file and not self._output_file.closed: self._output_file.close() if not self.buffer.closed: self.buffer.close() self._log.info('closed.')
def event_url(self, params): (url, irc) = params; Logger.info("RepostinatorPlugin: Got url '%s' from urlcatcher" % params[0]) if irc.server.get_current_nick() == irc.message.destination: Logger.debug("Got url in priv ignoring") return key = "%s_%s_%s" % (irc.message.destination, irc.get_ircnet_name(), url) data = self.store.get(key); if(data): irc.reply('This url was first posted in this channel by %s @ %s' % (data[0], data[2])) else: data = [irc.message.source.nick, irc.message.destination, time.ctime()] self.store.put(key, data);
class MockMessageBus(): ''' This message queue just displays filtered, clock-related events as they arrive. ''' def __init__(self, level): super().__init__() self._counter = itertools.count() self._log = Logger("queue", Level.INFO) self._start_time = dt.now() self._last_time = self._start_time self._log.info('ready.') # ...................................................... def set_clock(self, clock): self._clock = clock # ...................................................... def add(self, message): global tock_count message.number = next(self._counter) self._log.debug('added message {:06d}: priority {}: {}'.format( message.number, message.priority, message.description)) _event = message.event _value = message.value _now = dt.now() _delta = _now - self._start_time _elapsed_ms = (_now - self._last_time).total_seconds() * 1000.0 _process_delta = _now - message.timestamp _elapsed_loop_ms = _delta.total_seconds() * 1000.0 _process_ms = _process_delta.total_seconds() * 1000.0 _total_ms = _elapsed_loop_ms + _process_ms if SHOW_STATS: if _event is Event.CLOCK_TICK: # self._log.info(Fore.YELLOW + Style.NORMAL + 'CLOCK_TICK: {}; value: {}'.format(_event.description, _value)) self._log.info(Fore.BLACK + Style.DIM + 'event: {}; proc time: {:8.5f}ms; tick loop: {:5.2f}ms; total: {:5.2f}ms; '.format(\ _event.description, _process_ms, _elapsed_loop_ms, _total_ms) + Fore.WHITE + Style.NORMAL \ + ' elapsed: {:6.3f}ms; trim: {:7.4f}'.format(_elapsed_ms, self._clock.trim)) elif _event is Event.CLOCK_TOCK: self._log.info(Fore.YELLOW + Style.DIM + 'event: {}; proc time: {:8.5f}ms; tock loop: {:5.2f}ms; total: {:6.3f}ms; '.format(\ _event.description, _process_ms, _elapsed_loop_ms, _total_ms) + Fore.WHITE + Style.NORMAL \ + ' elapsed: {:6.3f}ms; trim: {:7.4f}'.format(_elapsed_ms, self._clock.trim)) self._start_time = _now tock_count += 1 else: self._log.info(Fore.BLACK + Style.BRIGHT + 'other event: {}'.format(_event.description)) self._last_time = _now
class Lux(): ''' Uses the Pimoroni LTR-559 Light and Proximity Sensor to return a lux value. ''' def __init__(self, level): self._log = Logger("lux", level) self._ltr559 = LTR559() self._log.info('ready.') def get_value(self): self._ltr559.update_sensor() _lux = self._ltr559.get_lux() self._log.debug("lux: {:06.2f}".format(_lux)) return _lux
class TimeOfFlight(): ''' This is based on Pimoroni's VL53L1X Time of Flight (ToF) Breakaway Garden board, which has a 4-400cm range (27° field of view), up to 50Hz ranging frequency, and an accuracy of +/-25mm (+/-20mm in the dark). ''' def __init__(self, range_value, level): self._log = Logger("tof", level) self._range = range_value # self._tof = VL53L1X.VL53L1X(i2c_bus=1, i2c_address=0x29) self._tof = VL53L1X.VL53L1X() self._tof.open() # Optionally set an explicit timing budget. These values are measurement time # in microseconds, and inter-measurement time in milliseconds. If you uncomment # the line below to set a budget you should use `self._tof.start_ranging(0)` # self._tof.set_timing(66000, 70) # lower timing budgets allow for faster updates, but sacrifice accuracy if self._range is Range.PERFORMANCE: self._tof.set_timing(UPDATE_TIME_MICROS, INTER_MEASUREMENT_PERIOD_MILLIS) self._log.info('ready.') # .......................................................................... def read_distance(self): ''' Return the range in mm. This function will block until a reading is returned. ''' distance_in_mm = self._tof.get_distance() self._log.debug('distance: {:d}mm'.format(distance_in_mm)) return distance_in_mm # .......................................................................... def enable(self): if self._range is Range.PERFORMANCE: self._tof.start_ranging(Range.UNCHANGED.value) self._log.info('enabled with high performance.') else: self._tof.start_ranging(self._range.value) self._log.info('enabled with {} range.'.format(self._range.name)) # .......................................................................... def disable(self): self._tof.stop_ranging() self._log.info('disabled.')
def load_plugin(self, name, search_dir = None): name = name.strip() import_name = None if self._loaded_plugins.has_key(name): raise PluginLoadError("Plugin is already loaded") import_name = self.find_plugin(name, search_dir) if not import_name: raise PluginLoadError("No such plugin") basename = import_name.rpartition(".")[2] try: mod = __import__(import_name) cls = getattr(mod, basename) except Exception as e: # Remove the system-entry if import_name and sys.modules.has_key(import_name): del sys.modules[import_name] Logger.log_traceback(self) raise PluginLoadError("Failed to load " + import_name) # Find the plugin entry point for objname in dir(cls): obj = getattr(cls, objname) if objname != 'Plugin' and type(obj) == types.ClassType and issubclass(obj, Plugin): Logger.debug("Plugin entry is '%s'" % objname) instance = new.instance(obj) # Initialize plugin instance instance.store = DatastoreController().get_store(basename) instance.event = self._eventcontroller instance.config = self._config instance.nets = IRCNetsController() instance.plugin = self instance.__init__() self._loaded_plugins[basename.lower()] = (basename, instance, import_name) return True del sys.modules[import_name] raise PluginLoadError("Unable to find entry point")
def main(): signal.signal(signal.SIGINT, signal_handler) print('uscanner_test :' + Fore.CYAN + Style.BRIGHT + ' INFO : starting test...' + Style.RESET_ALL) print('uscanner_test :' + Fore.YELLOW + Style.BRIGHT + ' INFO : Press Ctrl+C to exit.' + Style.RESET_ALL) _log = Logger("uscanner", Level.INFO) try: _scanner = I2CScanner(Level.WARN) _int_addresses = _scanner.get_int_addresses() _hex_addresses = _scanner.get_hex_addresses() _addrDict = dict( list(map(lambda x, y: (x, y), _int_addresses, _hex_addresses))) for i in range(len(_int_addresses)): _log.debug( Fore.BLACK + Style.DIM + 'found device at address: {}'.format(_hex_addresses[i]) + Style.RESET_ALL) ultraborg_available = (0x36 in _int_addresses) if ultraborg_available: _log.info('starting ultrasonic scan...') _loader = ConfigLoader(Level.INFO) filename = 'config.yaml' _config = _loader.configure(filename) _scanner = UltrasonicScanner(_config, Level.INFO) _scanner.enable() values = _scanner.scan() time.sleep(1.0) _scanner.close() else: _log.error('required hardware dependencies not available.') print('uscanner_test :' + Fore.CYAN + Style.BRIGHT + ' INFO : test complete.' + Style.RESET_ALL) except Exception: _log.info(traceback.format_exc()) sys.exit(1)
class MockMessageQueue(): ''' This message queue waits for one CAT_SCAN event. ''' def __init__(self, level): super().__init__() self._log = Logger("mock-queue", Level.INFO) self._counter = itertools.count() self._experienced_event = False # ...................................................... def add(self, message): message.set_number(next(self._counter)) self._log.debug('added message #{}: priority {}: {}'.format( message.get_number(), message.get_priority(), message.get_description())) event = message.get_event() if event is Event.INFRARED_STBD_SIDE: self._log.info(Fore.GREEN + Style.BRIGHT + 'event: {};\tvalue: {:d}'.format( event.description, message.get_value())) elif event is Event.INFRARED_STBD: self._log.info(Fore.GREEN + Style.BRIGHT + 'event: {};\tvalue: {:d}'.format( event.description, message.get_value())) elif event is Event.INFRARED_CENTER: self._log.info(Fore.BLUE + Style.BRIGHT + 'event: {};\tvalue: {:d}'.format( event.description, message.get_value())) elif event is Event.INFRARED_PORT: self._log.info(Fore.RED + Style.BRIGHT + 'event: {};\tvalue: {:d}'.format( event.description, message.get_value())) elif event is Event.INFRARED_PORT_SIDE: self._log.info(Fore.RED + Style.BRIGHT + 'event: {};\tvalue: {:d}'.format( event.description, message.get_value())) else: self._log.info(Fore.RED + 'other event: {}'.format(event.description)) # ...................................................... def is_complete(self): return self._experienced_event
class TickHandler(object): def __init__(self, ifs, level): super().__init__() self._ifs = ifs self._log = Logger('tick-hand', level) self._log.info('ready.') # .......................................................................... def tick(self, message): # self._log.info(Fore.GREEN + 'th.rx tick: {}::{}'.format(message.description, message.value)) # self._ifs.add(message) pass # .......................................................................... def tock(self, message): self._log.debug( Fore.GREEN + Style.BRIGHT + 'th.rx tock: {}::{}'.format(message.description, message.value)) pass
def test_moth(): ''' Test the basic functionality of the Moth sensor's various outputs. The test is entirely visual, i.e., something to be looked at, tested with a bright light source. ''' _log = Logger("test", Level.INFO) # read YAML configuration _loader = ConfigLoader(Level.INFO) filename = 'config.yaml' _config = _loader.configure(filename) _moth = Moth(_config, None, Level.INFO) _rgbmatrix = RgbMatrix(Level.INFO) _rgbmatrix.set_display_type(DisplayType.SOLID) # for i in range(50): while True: # orientation and bias ............................. _orientation = _moth.get_orientation() _bias = _moth.get_bias() _log.debug('bias: {}; orientation: {}'.format(type(_bias), _orientation)) if _orientation is Orientation.PORT: _rgbmatrix.show_color(Color.BLACK, Orientation.STBD) _rgbmatrix.show_color(Color.RED, Orientation.PORT) _log.info(Fore.RED + Style.BRIGHT + '{}\t {:<6.3f}'.format(_orientation.name, _bias[0])) elif _orientation is Orientation.STBD: _rgbmatrix.show_color(Color.BLACK, Orientation.PORT) _rgbmatrix.show_color(Color.GREEN, Orientation.STBD) _log.info(Fore.GREEN + Style.BRIGHT + '{}\t {:<6.3f}'.format(_orientation.name, _bias[1])) else: _rgbmatrix.show_color(Color.YELLOW, Orientation.PORT) _rgbmatrix.show_color(Color.YELLOW, Orientation.STBD) _log.info(Fore.YELLOW + '{}\t {:6.3f}|{:6.3f}'.format(_orientation.name, _bias[0], _bias[1])) # int values ....................................... _int_values = _moth.get_int_values() _log.info(Fore.RED + '{:d}\t'.format(_int_values[0]) + Fore.GREEN + '{:d}\t'.format(_int_values[1]) ) # float values ..................................... _values = _moth.get_values() _log.info(Fore.RED + '{:<6.3f}\t'.format(_values[0]) + Fore.GREEN + '{:<6.3f}\t'.format(_values[1]) ) # time.sleep(1.0) time.sleep(0.1)
class Publisher(ABC): ''' Abstract publisher, subclassed by any classes that publish to a MessageBus. ''' def __init__(self, message_factory, message_bus, level=Level.INFO): self._log = Logger('pub', level) self._log.info(Fore.MAGENTA + 'Publisher: create.') self._message_factory = message_factory self._message_bus = message_bus self._counter = itertools.count() self._log.debug('ready.') # .......................................................................... def get_message_of_type(self, event, value): ''' Provided an Event type and a message value, returns a Message generated from the MessageFactory. ''' return self._message_factory.get_message(event, value) def get_random_event_type(self): types = [ Event.STOP, Event.INFRARED_PORT, Event.INFRARED_STBD, Event.FULL_AHEAD, Event.ROAM, Event.EVENT_R1 ] return types[random.randint(0, len(types)-1)] # .......................................................................... @abstractmethod async def publish(self, iterations): ''' DESCRIPTION. ''' self._log.info(Fore.MAGENTA + Style.BRIGHT + 'Publish called.') for x in range(iterations): self._log.info(Fore.MAGENTA + 'Publisher: I have {} subscribers now'.format(len(self._message_bus.subscriptions))) _uuid = str(uuid.uuid4()) _message = self.get_message_of_type(self.get_random_event_type(), 'msg_{:d}-{}'.format(x, _uuid)) _message.number = next(self._counter) self._message_bus.publish(_message) await asyncio.sleep(1) _shutdown_message = self.get_message_of_type(Event.SHUTDOWN, 'shutdown') self._message_bus.publish(_shutdown_message)
class ConfigLoader(): ''' Has just one method: configure() reads a YAML file. ''' def __init__(self, level): self._log = Logger('configloader', level) self._log.info('ready.') # .......................................................................... def configure(self, filename): ''' Read and return configuration from the specified YAML file. ''' self._log.info( 'reading from yaml configuration file {}...'.format(filename)) _config = yaml.safe_load(open(filename, 'r')) for key, value in _config.items(): self._log.debug('config key: {}; value: {}'.format( key, str(value))) self._log.info('configuration read.') return _config
def send(self, data): params = data.split() if len(params) == 0: return cmd = params[0] if cmd == "JOIN": self.server_user_response("JOIN", ":" + params[1]) self.server_user_response(ircdef.RPL_ENDOFNAMES, "%s :/End of /NAMES list." % params[1]) elif cmd == "WHO": self.server_response(ircdef.RPL_WHOREPLY, "%s %s %s %s %s %s H :0 %s" % ( self.bot_nick, params[1], self.bot_ident, self.bot_host, self.server_name, self.bot_nick, "The NeuBot")) self.server_response(ircdef.RPL_ENDOFWHO, "%s %s :End of /WHO list." % (self.bot_nick, params[1],)) elif cmd == "PRIVMSG": match = re.match("PRIVMSG (.*?) :(.*)", data) channel, message = match.groups() print "\033[1;37m[%s:PRIVMSG %s]\033[0m %s" % (self.bot_nick, channel, message) elif cmd == "NOTICE": match = re.match("NOTICE (.*?) :(.*)", data) channel, message = match.groups() print "\033[1;35m<%s:NOTICE %s>\033[0m %s" % (self.bot_nick, channel, message) elif cmd == "PING": match = re.match("PING (.*)", data) self.server_response("PONG", match.group(1)) else: Logger.debug("Unhandled: " + data.strip())
class Selector(object): def __init__(self, config, level): if config is None: raise ValueError('no configuration provided.') self._log = Logger("selector", level) self._rot = RotaryEncoder(config, i2c_address=0x0F, level=Level.WARN) self._rot.increment = 8 self._last_value = 0 self._colors = [ Color.RED, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA ] self._log.info('ready.') # .......................................................................... def read(self): _value = self._rot.read(False) % 360 if _value != self._last_value: _color = self.get_color_for_heading(_value) self._rot.set_led((int(_color.red), int(_color.green), int(_color.blue))) self._log.info('returned value: {:d}; color: {}'.format(_value, _color)) self._last_value = _value return _value # .......................................................................... def get_color_for_heading(self, degrees): ''' Provided a heading in degrees return a color. ''' _value = round(((degrees-30.0) / 60.0) + 0.5) _index = (_value % 6) _color = self._colors[_index] self._log.debug('{}°; value: {}; index: {}; color: {}'.format(degrees, _value, _index, _color)) return _color # .......................................................................... def reset(self): _color = Color.BLACK self._rot.set_led((int(_color.red), int(_color.green), int(_color.blue))) self._rot.reset() self._log.info('reset.')
class MockMessageQueue(): ''' This message queue just displays filtered, clock-related events as they arrive. ''' def __init__(self, level): super().__init__() self._counter = itertools.count() self._log = Logger("queue", Level.INFO) self._start_time = dt.now() self._log.info('ready.') # ...................................................... def add(self, message): message.number = next(self._counter) self._log.debug('added message {:06d}: priority {}: {}'.format( message.number, message.priority, message.description)) _event = message.event _value = message.value _delta = dt.now() - self._start_time _elapsed_ms = _delta.total_seconds() * 1000 _process_delta = dt.now() - message.timestamp _process_ms = _process_delta.total_seconds() * 1000 _total_ms = _elapsed_ms + _process_ms if _event is Event.CLOCK_TICK: self._log.info( Fore.YELLOW + Style.NORMAL + 'CLOCK_TICK: {}; value: {}'.format(_event.description, _value)) elif _event is Event.CLOCK_TOCK: self._log.info(Fore.GREEN + Style.NORMAL + 'event: {}; value: {}; processing time: {:6.3f}ms; loop: {:6.3f}ms elapsed; total: {:6.3f}ms'.format(\ _event.description, _value, _process_ms, _elapsed_ms, _total_ms)) self._start_time = dt.now() else: self._log.info(Fore.BLACK + Style.BRIGHT + 'other event: {}'.format(_event.description))
class Subscription(): ''' A subscription on the MessageBus. ''' def __init__(self, message_bus, level=Level.WARN): self._log = Logger('subscription', level) self._log.debug('__init__') self._message_bus = message_bus self.queue = asyncio.Queue() def __enter__(self): self._log.debug('__enter__') self._message_bus._subscriptions.add(self.queue) return self.queue def __exit__(self, type, value, traceback): self._log.debug('__exit__') self._message_bus._subscriptions.remove(self.queue)
class I2cMaster(): ''' A Raspberry Pi Master for a corresponding Arduino Slave. Parameters: device_id: the I²C address over which the master and slave communicate level: the log level, e.g., Level.INFO ''' def __init__(self, config, level): super().__init__() if config is None: raise ValueError('no configuration provided.') self._config = config['ros'].get('i2c_master') self._device_id = self._config.get( 'device_id' ) # i2c hex address of slave device, must match Arduino's SLAVE_I2C_ADDRESS self._channel = self._config.get('channel') self._log = Logger('i²cmaster-0x{:02x}'.format(self._device_id), level) self._log.info('initialising...') self._counter = itertools.count() self._loop_count = 0 # currently only used in testing self._thread = None self._enabled = False self._closed = False self._i2c = SMBus(self._channel) self._log.info( 'ready: imported smbus2 and obtained I²C bus at address 0x{:02X}...' .format(self._device_id)) # .......................................................................... def enable(self): self._enabled = True self._log.info('enabled.') # .......................................................................... def disable(self): self._enabled = False self._log.info('disabled.') # .............................................................................. def read_i2c_data(self): ''' Read a byte from the I²C device at the specified handle, returning the value. ## Examples: int.from_bytes(b'\x00\x01', "big") # 1 int.from_bytes(b'\x00\x01', "little") # 256 ''' with SMBus(self._channel) as bus: _byte = bus.read_byte_data(self._device_id, 0) time.sleep(0.01) return _byte # .......................................................................... def write_i2c_data(self, data): ''' Write a byte to the I²C device at the specified handle. ''' with SMBus(self._channel) as bus: bus.write_byte(self._device_id, data) time.sleep(0.01) # .......................................................................... def get_input_from_pin(self, pin): ''' Sends a message to the pin, returning the result as a byte. ''' self.write_i2c_data(pin) # time.sleep(0.2) _received_data = self.read_i2c_data() # time.sleep(0.2) self._log.debug('received response from pin {:d} of {:08b}.'.format( pin, _received_data)) return _received_data # .......................................................................... def in_loop(self): ''' Returns true if the main loop is active (the thread is alive). ''' return self._thread != None and self._thread.is_alive() # .......................................................................... def _front_sensor_loop(self, loop_delay_sec, callback): self._log.info('starting event loop...\n') while self._enabled: _count = next(self._counter) print(CLEAR_SCREEN) # pin 1: analog infrared sensor ................ _received_data = self.get_input_from_pin(1) # self._log.info('[{:04d}] ANALOG IR ({:d}): \t'.format(_count, 1) + ( Fore.RED if ( _received_data > 100.0 ) else Fore.YELLOW ) \ # + Style.BRIGHT + '{:d}'.format(_received_data) + Style.DIM + '\t(analog value 0-255)') callback(1, PinType.ANALOG_INPUT, _received_data) # pin 2: analog infrared sensor ................ _received_data = self.get_input_from_pin(2) # self._log.info('[{:04d}] ANALOG IR ({:d}): \t'.format(_count, 2) + ( Fore.RED if ( _received_data > 100.0 ) else Fore.YELLOW ) \ # + Style.BRIGHT + '{:d}'.format(_received_data) + Style.DIM + '\t(analog value 0-255)') callback(2, PinType.ANALOG_INPUT, _received_data) # pin 3: analog infrared sensor ................ _received_data = self.get_input_from_pin(3) # self._log.info('[{:04d}] ANALOG IR ({:d}): \t'.format(_count, 3) + ( Fore.RED if ( _received_data > 100.0 ) else Fore.YELLOW ) \ # + Style.BRIGHT + '{:d}'.format(_received_data) + Style.DIM + '\t(analog value 0-255)') callback(3, PinType.ANALOG_INPUT, _received_data) # pin 4: analog infrared sensor ................ _received_data = self.get_input_from_pin(4) # self._log.info('[{:04d}] ANALOG IR ({:d}): \t'.format(_count, 4) + ( Fore.RED if ( _received_data > 100.0 ) else Fore.YELLOW ) \ # + Style.BRIGHT + '{:d}'.format(_received_data) + Style.DIM + '\t(analog value 0-255)') callback(4, PinType.ANALOG_INPUT, _received_data) # pin 5: analog infrared sensor ................ _received_data = self.get_input_from_pin(5) # self._log.info('[{:04d}] ANALOG IR ({:d}): \t'.format(_count, 5) + ( Fore.RED if ( _received_data > 100.0 ) else Fore.YELLOW ) \ # + Style.BRIGHT + '{:d}'.format(_received_data) + Style.DIM + '\t(analog value 0-255)') callback(5, PinType.ANALOG_INPUT, _received_data) # pin 9: digital bumper sensor ................. _received_data = self.get_input_from_pin(9) # self._log.info('[{:04d}] DIGITAL IR ({:d}): \t'.format(_count, 9) + Fore.GREEN + Style.BRIGHT + '{:d}'.format(_received_data) \ # + Style.DIM + '\t(displays digital pup value 0|1)') callback(9, PinType.DIGITAL_INPUT_PULLUP, _received_data) # pin 10: digital bumper sensor ................ _received_data = self.get_input_from_pin(10) # self._log.info('[{:04d}] DIGITAL IR ({:d}): \t'.format(_count, 10) + Fore.GREEN + Style.BRIGHT + '{:d}'.format(_received_data) \ # + Style.DIM + '\t(displays digital pup value 0|1)') callback(10, PinType.DIGITAL_INPUT_PULLUP, _received_data) # pin 11: digital bumper sensor ................ _received_data = self.get_input_from_pin(11) # self._log.info('[{:04d}] DIGITAL IR ({:d}): \t'.format(_count, 11) + Fore.GREEN + Style.BRIGHT + '{:d}'.format(_received_data) \ # + Style.DIM + '\t(displays digital pup value 0|1)') callback(11, PinType.DIGITAL_INPUT_PULLUP, _received_data) time.sleep(loop_delay_sec) # we never get here if using 'while True:' self._log.info('exited event loop.') # .......................................................................... def start_front_sensor_loop(self, loop_delay_sec, callback): ''' This is the method to call to actually start the loop. ''' if not self._enabled: raise Exception( 'attempt to start front sensor event loop while disabled.') elif not self._closed: if self._thread is None: enabled = True self._thread = threading.Thread( target=I2cMaster._front_sensor_loop, args=[self, loop_delay_sec, callback]) self._thread.start() self._log.info('started.') else: self._log.warning('cannot enable: process already running.') else: self._log.warning('cannot enable: already closed.') # .......................................................................... def close(self): ''' Close the instance. ''' self._log.debug('closing I²C master.') if not self._closed: try: if self._thread != None: self._thread.join(timeout=1.0) self._log.debug('front sensor loop thread joined.') self._thread = None self._closed = True # self._pi.i2c_close(self._handle) # close device self._log.debug('I²C master closed.') except Exception as e: self._log.error('error closing master: {}'.format(e)) else: self._log.debug('I²C master already closed.')
class Servo(): ''' Provided with configuration this permits setting of the center position offset. The UltraBorg supports four servos, numbered 1-4. ''' def __init__(self, config, number, level): self._number = number self._log = Logger("servo-{:d}".format(number), level) self._config = config if self._config: self._log.info('configuration provided.') _config = self._config['ros'].get('servo{:d}'.format(number)) self._center_offset = _config.get('center_offset') else: self._log.warning('no configuration provided.') self._center_offset = 0.0 self._log.info('center offset: {:>3.1f}'.format(self._center_offset)) self._UB = UltraBorg(level) # create a new UltraBorg object self._UB.Init() # initialise the board (checks it is connected) # settings ....................................................................... self._min_angle = -90.1 # Smallest position to use (n/90) self._max_angle = +90.1 # Largest position to use (n/90) self._startup_delay = 0.5 # Delay before making the initial move self._step_delay = 0.05 # Delay between steps self._rate_start = 0.05 # Step distance for all servos during initial move self._step_distance = 1.0 # Step distance for servo #1 self._enabled = False self._closed = False self._log.info('ready.') # .......................................................................... def get_min_angle(self): ''' Return the configured minimum servo angle. ''' return self._min_angle # .......................................................................... def get_max_angle(self): ''' Return the configured maximum servo angle. ''' return self._max_angle # .......................................................................... def is_in_range(self, degrees): ''' A convenience method that returns true if the argument is within the minimum and maximum range (inclusive of endpoints) of the servo. ''' return degrees >= self._min_angle and degrees <= self._max_angle # .......................................................................... def set_position(self, position): ''' Set the position of the servo to a value between -90.0 and 90.0 degrees. Values outside this range set to their respective min/max. This adjusts the argument by the value of the center offset. ''' self._log.debug('set servo position to: {:+5.2f}° (center offset {:+5.2f}°)'.format(position, self._center_offset)) position += self._center_offset if position < self._min_angle: self._log.warning('cannot set position {:+5.2f}° less than minimum {:+5.2f}°'.format(position, self._min_angle)) position = self._min_angle if position > self._max_angle: self._log.warning('cannot set position {:+5.2f}° greater than maximum {:+5.2f}°'.format(position, self._max_angle)) position = self._max_angle # values for servo range from -1.0 to 1.0 _value = position / 90.0 if self._number == 1: self._UB.SetServoPosition1(_value) elif self._number == 2: self._UB.SetServoPosition2(_value) elif self._number == 3: self._UB.SetServoPosition3(_value) elif self._number == 4: self._UB.SetServoPosition4(_value) # .......................................................................... def get_position(self, defaultValue): ''' Get the position of the servo. If unavailable return the default. ''' if self._number == 1: _position = self._UB.GetServoPosition1() elif self._number == 2: _position = self._UB.GetServoPosition2() elif self._number == 3: _position = self._UB.GetServoPosition3() elif self._number == 4: _position = self._UB.GetServoPosition4() if _position is None: return defaultValue else: return ( _position * 90.0 ) + self._center_offset # .......................................................................... def get_distance(self, retry_count, useRawDistance): ''' Gets the filtered distance for the ultrasonic module in millimeters. Parameters: retry_count: how many times to attempt to read from the sensor before giving up (this value is ignored when useRawDistance is True) useRawDistance: if you need a faster response use this instead (no filtering) e.g.: 0 -> No object in range 25 -> Object 25 mm away 1000 -> Object 1000 mm (1 m) away 3500 -> Object 3500 mm (3.5 m) away Returns 0 for no object detected or no ultrasonic module attached. This admittedly is not a servo function but since we have an UltraBorg supporting this servo, this becomes a convenience method. The ultrasonic sensor should be connected to the same number as the servo. ''' if self._number == 1: if useRawDistance: return self._UB.GetRawDistance1() else: return self._UB.GetWithRetry(self._UB.GetDistance1, retry_count) elif self._number == 2: if useRawDistance: return self._UB.GetRawDistance2() else: return self._UB.GetWithRetry(self._UB.GetDistance2, retry_count) elif self._number == 3: if useRawDistance: return self._UB.GetRawDistance3() else: return self._UB.GetWithRetry(self._UB.GetDistance3, retry_count) elif self._number == 4: if useRawDistance: return self._UB.GetRawDistance4() else: return self._UB.GetWithRetry(self._UB.GetDistance4, retry_count) # .......................................................................... def sweep(self): ''' Repeatedly sweeps between minimum and maximum until disabled. ''' self._log.info('press CTRL+C to quit.') try: self._log.info('move to center position.') position = 0.0 self.set_position(position) # wait to be sure the servo has caught up time.sleep(self._startup_delay) self._log.info('sweep to start position.') while position > self._min_angle: position -= self._rate_start if position < self._min_angle: position = self._min_angle self.set_position(position) time.sleep(self._step_delay) self._log.info('sweep through the range.') while self._enabled: position += self._step_distance # check if too large, if so wrap to the over end if position > self._max_angle: position -= (self._max_angle - self._min_angle) self.set_position(position) time.sleep(self._step_delay) except KeyboardInterrupt: self.close() self._log.info('done.') # .......................................................................... def get_ultraborg(self): ''' Return the UltraBorg supporting this Servo. ''' return self._UB # .......................................................................... def enable(self): self._enabled = True self._log.info('enabled.') # .......................................................................... def disable(self): self._enabled = False self._log.info('disabled.') # .......................................................................... def reset(self): if self._number == 1: self._UB.SetServoPosition1(0.0) elif self._number == 2: self._UB.SetServoPosition2(0.0) elif self._number == 3: self._UB.SetServoPosition3(0.0) elif self._number == 4: self._UB.SetServoPosition4(0.0) self._log.info('reset position.') # .......................................................................... def close(self): ''' Close the servo. If it has not already been disabled its position will reset to zero. ''' if self._enabled: self.reset() if not self._closed: self._closed = True self.disable() self._log.info('closed.')
class I2CScanner(): ''' Scans the I²C bus, returning a list of devices. ''' def __init__(self, level): super().__init__() self._log = Logger('i2cscan', level) self._log.debug('initialising...') self._int_list = [] self._hex_list = [] try: import smbus bus_number = 1 # 1 indicates /dev/i2c-1 self._bus = smbus.SMBus(bus_number) self._log.info('ready.') except ImportError: self._log.warning('initialised. This script requires smbus. Will operate without but return an empty result.') self._bus = None # .......................................................................... def get_hex_addresses(self): ''' Returns a hexadecimal version of the list. ''' self._scan_addresses() return self._hex_list # .......................................................................... def get_int_addresses(self): ''' Returns an integer version of the list. ''' self._scan_addresses() return self._int_list # .......................................................................... def has_address(self, addresses): ''' Performs the address scan (if necessary) and returns true if a device is available at any of the specified addresses. ''' self._scan_addresses() for address in addresses: if address in self._int_list: return True return False # .......................................................................... def _scan_addresses(self): ''' Scans the bus and returns the available device addresses. After being called and populating the int and hex lists, this closes the connection to smbus. ''' if len(self._int_list) == 0: device_count = 0 if self._bus is not None: for address in range(3, 128): try: self._bus.write_byte(address, 0) self._log.debug('found I²C device at 0x{:02X}'.format(address)) self._int_list.append(address) self._hex_list.append(hex(address)) device_count = device_count + 1 except IOError as e: if e.errno != errno.EREMOTEIO: self._log.error('Error: {0} on address {1}'.format(e, hex(address))) except Exception as e: # exception if read_byte fails self._log.error('Error unk: {0} on address {1}'.format(e, hex(address))) self._bus.close() self._bus = None self._log.info("found {:d} device(s).".format(device_count)) else: self._log.info("found no devices (no smbus available).") return self._int_list
class RgbMatrix(Feature): ''' This class provides access to a pair of Pimoroni 5x5 RGB LED Matrix displays, labeled port and starboard. It also includes several canned demonstrations, which can be used to indicate behaviours in progress. ''' def __init__(self, level): global enabled self._log = Logger("rgbmatrix", level) self._rgbmatrix5x5_PORT = RGBMatrix5x5(address=0x77) self._log.info('port rgbmatrix at 0x77.') self._rgbmatrix5x5_PORT.set_brightness(0.8) self._rgbmatrix5x5_PORT.set_clear_on_exit() self._rgbmatrix5x5_STBD = RGBMatrix5x5(address=0x74) self._log.info('starboard rgbmatrix at 0x74.') self._rgbmatrix5x5_STBD.set_brightness(0.8) self._rgbmatrix5x5_STBD.set_clear_on_exit() self._height = self._rgbmatrix5x5_PORT.height self._width = self._rgbmatrix5x5_PORT.width self._thread_PORT = None self._thread_STBD = None enabled = False self._closing = False self._closed = False self._display_type = DisplayType.DARK # default self._log.info('ready.') # .......................................................................... def name(self): return 'RgbMatrix' # .......................................................................... def _get_target(self): if self._display_type is DisplayType.RAINBOW: return RgbMatrix._rainbow elif self._display_type is DisplayType.BLINKY: return RgbMatrix._blinky elif self._display_type is DisplayType.RANDOM: return RgbMatrix._random elif self._display_type is DisplayType.SCAN: return RgbMatrix._scan elif self._display_type is DisplayType.SWORL: return RgbMatrix._sworl elif self._display_type is DisplayType.DARK: return RgbMatrix._dark # .......................................................................... def enable(self): global enabled if not self._closed and not self._closing: if self._thread_PORT is None and self._thread_STBD is None: enabled = True self._thread_PORT = threading.Thread( target=self._get_target(), args=[self, self._rgbmatrix5x5_PORT]) self._thread_PORT.start() self._thread_STBD = threading.Thread( target=self._get_target(), args=[self, self._rgbmatrix5x5_STBD]) self._thread_STBD.start() self._log.debug('enabled.') else: self._log.warning('cannot enable: process already running.') else: self._log.warning('cannot enable: already closed.') # .......................................................................... def clear(self): self._clear(self._rgbmatrix5x5_PORT) self._clear(self._rgbmatrix5x5_STBD) # .......................................................................... def is_disabled(self): global enabled return not enabled # .......................................................................... def disable(self): global enabled self._log.debug('disabling...') enabled = False self._clear(self._rgbmatrix5x5_PORT) self._clear(self._rgbmatrix5x5_STBD) if self._thread_PORT != None: self._thread_PORT.join(timeout=1.0) self._log.debug('port rgbmatrix thread joined.') self._thread_PORT = None if self._thread_STBD != None: self._thread_STBD.join(timeout=1.0) self._log.debug('starboard rgbmatrix thread joined.') self._thread_STBD = None self._log.debug('disabled.') # .......................................................................... def _rainbow(self, rgbmatrix5x5): ''' Display a rainbow pattern. ''' global enabled self._log.info('starting rainbow...') _spacing = 360.0 / 5.0 _hue = 0 while enabled: for x in range(self._width): for y in range(self._height): _hue = int(time.time() * 100) % 360 offset = (x * y) / 25.0 * _spacing h = ((_hue + offset) % 360) / 360.0 r, g, b = [ int(c * 255) for c in colorsys.hsv_to_rgb(h, 1.0, 1.0) ] rgbmatrix5x5.set_pixel(x, y, r, g, b) # r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(h + 0.5, 1.0, 1.0)] # rainbow2.set_pixel(x, y, r, g, b) if not enabled: break rgbmatrix5x5.show() # rainbow2.show() time.sleep(0.0001) self._clear(rgbmatrix5x5) self._log.info('rainbow ended.') # .......................................................................... def _sworl(self, rgbmatrix5x5): ''' Display a sworl pattern, whatever that is. ''' global enabled self._log.info('starting sworl...') target = Color.LIGHT_BLUE self.set_color(target) # for r in numpy.arange(0.0, target.red): # for g in numpy.arange(0.0, target.green): # for b in numpy.arange(0.0, target.blue): # rgbmatrix5x5.set_all(r, g, b) # rgbmatrix5x5.show() # time.sleep(0.01) # if not enabled: # break # for r in numpy.arange(target.red, 0.0, -1.0): # for g in numpy.arange(target.green, 0.0, -1.0): # for b in numpy.arange(target.blue, 0.0, -1.0): # rgbmatrix5x5.set_all(r, g, b) # rgbmatrix5x5.show() # time.sleep(0.01) # if not enabled: # break self._clear(rgbmatrix5x5) self._log.info('sworl ended.') # .......................................................................... def _dark(self, rgbmatrix5x5): ''' Display a dark static color. ''' global enabled self._log.info('starting dark...') self.set_color(Color.BLACK) while enabled: time.sleep(0.2) # .......................................................................... @staticmethod def make_gaussian(fwhm): x = numpy.arange(0, 5, 1, float) y = x[:, numpy.newaxis] x0, y0 = 2, 2 fwhm = fwhm gauss = numpy.exp(-4 * numpy.log(2) * ((x - x0)**2 + (y - y0)**2) / fwhm**2) return gauss # .......................................................................... def _blinky(self, rgbmatrix5x5): ''' Display a pair of blinky spots. ''' global enabled self._log.info('starting blinky...') if self._height == self._width: _delta = 0 else: _delta = 2 while enabled: for i in range(3): for z in list(range(1, 10)[::-1]) + list(range(1, 10)): fwhm = 5.0 / z gauss = RgbMatrix.make_gaussian(fwhm) start = time.time() for y in range(self._height): for x in range(self._width): h = 0.5 s = 0.8 if self._height <= self._width: v = gauss[x, y] # v = gauss[x, y + _delta] else: v = gauss[x, y] # v = gauss[x + _delta, y] rgb = colorsys.hsv_to_rgb(h, s, v) r = int(rgb[0] * 255.0) g = int(rgb[1] * 255.0) b = int(rgb[2] * 255.0) rgbmatrix5x5.set_pixel(x, y, r, g, b) rgbmatrix5x5.show() end = time.time() t = end - start if t < 0.04: time.sleep(0.04 - t) pass if not enabled: break self._clear(rgbmatrix5x5) self._log.info('blinky ended.') # .......................................................................... def _scan(self, rgbmatrix5x5): ''' KITT- or Cylon-like eyeball scanning. ''' global enabled self._log.info('starting scan...') r = int(255.0) g = int(64.0) b = int(0.0) # start = time.time() # for x in range(self._width): x = 2 _delay = 0.25 while enabled: # for i in range(count): for y in range(0, self._height): rgbmatrix5x5.clear() rgbmatrix5x5.set_pixel(x, y, r, g, b) rgbmatrix5x5.show() time.sleep(_delay) for y in range(self._height - 1, 0, -1): rgbmatrix5x5.clear() rgbmatrix5x5.set_pixel(x, y, r, g, b) rgbmatrix5x5.show() time.sleep(_delay) if not enabled: break self._clear(rgbmatrix5x5) self._log.debug('scan ended.') # .......................................................................... def _random(self, rgbmatrix5x5): ''' Display an ever-changing random pattern. ''' global enabled self._log.info('starting random...') count = 0 while enabled: rand_hue = numpy.random.uniform(0.1, 0.9) rand_mat = numpy.random.rand(self._width, self._height) for y in range(self._height): for x in range(self._width): # h = 0.1 * rand_mat[x, y] h = rand_hue * rand_mat[x, y] s = 0.8 v = rand_mat[x, y] rgb = colorsys.hsv_to_rgb(h, s, v) r = int(rgb[0] * 255.0) g = int(rgb[1] * 255.0) b = int(rgb[2] * 255.0) rgbmatrix5x5.set_pixel(x, y, r, g, b) if not enabled: break rgbmatrix5x5.show() time.sleep(0.01) self._clear(rgbmatrix5x5) self._log.info('random ended.') # .......................................................................... def set_color(self, color): ''' Set the color of both RGB Matrix displays. ''' self._set_color(self._rgbmatrix5x5_PORT, color) self._set_color(self._rgbmatrix5x5_STBD, color) # .......................................................................... def _set_color(self, rgbmatrix5x5, color): ''' Set the color of the RGB Matrix. ''' rgbmatrix5x5.set_all(color.red, color.green, color.blue) rgbmatrix5x5.show() # .......................................................................... def _clear(self, rgbmatrix5x5): ''' Clears the RGB Matrix by setting its color to black. ''' self._set_color(rgbmatrix5x5, Color.BLACK) # .......................................................................... def set_display_type(self, display_type): self._display_type = display_type # .......................................................................... def close(self): if self._closing: self._log.warning('already closing.') return self._closing = True self.disable() self._closed = True self._log.info('closed.')
class ThunderBorg: """ This module is designed to communicate with the ThunderBorg busNumber I²C bus on which the ThunderBorg is attached (Rev 1 is bus 0, Rev 2 is bus 1) bus the smbus object used to talk to the I²C bus i2cAddress The I²C address of the ThunderBorg chip to control foundChip True if the ThunderBorg chip can be seen, False otherwise printFunction Function reference to call when printing text, if None "print" is used """ # Shared values used by this class busNumber = 1 # Check here for Rev 1 vs Rev 2 and select the correct bus i2cAddress = I2C_ID_THUNDERBORG # I²C address, override for a different address foundChip = False printFunction = None i2cWrite = None i2cRead = None def __init__(self, level): super().__init__() self._log = Logger('thunderborg', level) self._log.info('ready.') def RawWrite(self, command, data): """ RawWrite(command, data) Sends a raw command on the I2C bus to the ThunderBorg Command codes can be found at the top of ThunderBorg.py, data is a list of 0 or more byte values Under most circumstances you should use the appropriate function instead of RawWrite """ rawOutput = [command] rawOutput.extend(data) rawOutput = bytes(rawOutput) self.i2cWrite.write(rawOutput) def RawRead(self, command, length, retryCount=3): """ RawRead(command, length, [retryCount]) Reads data back from the ThunderBorg after sending a GET command Command codes can be found at the top of ThunderBorg.py, length is the number of bytes to read back The function checks that the first byte read back matches the requested command If it does not it will retry the request until retryCount is exhausted (default is 3 times) Under most circumstances you should use the appropriate function instead of RawRead """ while retryCount > 0: self.RawWrite(command, []) rawReply = self.i2cRead.read(length) reply = [] for singleByte in rawReply: reply.append(singleByte) if command == reply[0]: break else: retryCount -= 1 if retryCount > 0: return reply else: raise IOError('I2C read for command %d failed' % (command)) def InitBusOnly(self, busNumber, address): """ InitBusOnly(busNumber, address) Prepare the I2C driver for talking to a ThunderBorg on the specified bus and I2C address This call does not check the board is present or working, under most circumstances use Init() instead """ self.busNumber = busNumber self.i2cAddress = address self.i2cRead = io.open("/dev/i2c-" + str(self.busNumber), "rb", buffering=0) fcntl.ioctl(self.i2cRead, I2C_SLAVE, self.i2cAddress) self.i2cWrite = io.open("/dev/i2c-" + str(self.busNumber), "wb", buffering=0) fcntl.ioctl(self.i2cWrite, I2C_SLAVE, self.i2cAddress) def Print(self, message): """ Print(message) Wrapper used by the ThunderBorg instance to print(messages, will call printFunction if set, print otherwise) """ self._log.info(message) # if self.printFunction == None: # print(message) # else: # self.printFunction(message) def NoPrint(self, message): """ NoPrint(message) Does nothing, intended for disabling diagnostic printout by using: TB = ThunderBorg.ThunderBorg() TB.printFunction = TB.NoPrint """ pass def Init(self, tryOtherBus=False): """ Init([tryOtherBus]) Prepare the I2C driver for talking to the ThunderBorg If tryOtherBus is True, this function will attempt to use the other bus if the ThunderBorg devices can not be found on the current busNumber This is only really useful for early Raspberry Pi models! """ self.Print('Loading ThunderBorg on bus %d, address %02X' % (self.busNumber, self.i2cAddress)) # Open the bus self.i2cRead = io.open("/dev/i2c-" + str(self.busNumber), "rb", buffering=0) fcntl.ioctl(self.i2cRead, I2C_SLAVE, self.i2cAddress) self.i2cWrite = io.open("/dev/i2c-" + str(self.busNumber), "wb", buffering=0) fcntl.ioctl(self.i2cWrite, I2C_SLAVE, self.i2cAddress) # Check for ThunderBorg try: i2cRecv = self.RawRead(COMMAND_GET_ID, I2C_MAX_LEN) if len(i2cRecv) == I2C_MAX_LEN: if i2cRecv[1] == I2C_ID_THUNDERBORG: self.foundChip = True self.Print('Found ThunderBorg at %02X' % (self.i2cAddress)) else: self.foundChip = False self.Print( 'Found a device at %02X, but it is not a ThunderBorg (ID %02X instead of %02X)' % (self.i2cAddress, i2cRecv[1], I2C_ID_THUNDERBORG)) else: self.foundChip = False self.Print('Missing ThunderBorg at %02X' % (self.i2cAddress)) except KeyboardInterrupt: raise except: self.foundChip = False self.Print('Missing ThunderBorg at %02X' % (self.i2cAddress)) # See if we are missing chips if not self.foundChip: self.Print('ThunderBorg was not found') if tryOtherBus: if self.busNumber == 1: self.busNumber = 0 else: self.busNumber = 1 self.Print('Trying bus %d instead' % (self.busNumber)) self.Init(False) else: self.Print( 'Are you sure your ThunderBorg is properly attached, the correct address is used, and the I2C drivers are running?' ) self.bus = None else: self.Print('ThunderBorg loaded on bus %d' % (self.busNumber)) def SetMotor2(self, power): """ SetMotor2(power) Sets the drive level for motor 2, from +1 to -1. e.g. SetMotor2(0) -> motor 2 is stopped SetMotor2(0.75) -> motor 2 moving forward at 75% power SetMotor2(-0.5) -> motor 2 moving reverse at 50% power SetMotor2(1) -> motor 2 moving forward at 100% power """ if power < 0: # Reverse command = COMMAND_SET_B_REV pwm = -int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX else: # Forward / stopped command = COMMAND_SET_B_FWD pwm = int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX try: self.RawWrite(command, [pwm]) except KeyboardInterrupt: raise except: self.Print('Failed sending motor 2 drive level!') def GetMotor2(self): """ power = GetMotor2() Gets the drive level for motor 2, from +1 to -1. e.g. 0 -> motor 2 is stopped 0.75 -> motor 2 moving forward at 75% power -0.5 -> motor 2 moving reverse at 50% power 1 -> motor 2 moving forward at 100% power """ try: i2cRecv = self.RawRead(COMMAND_GET_B, I2C_MAX_LEN) except KeyboardInterrupt: raise except: # self.Print('Failed reading motor 2 drive level!') self._log.debug('failed reading motor 2 drive level.') return None power = float(i2cRecv[2]) / float(PWM_MAX) if i2cRecv[1] == COMMAND_VALUE_FWD: return power elif i2cRecv[1] == COMMAND_VALUE_REV: return -power else: return def SetMotor1(self, power): """ SetMotor1(power) Sets the drive level for motor 1, from +1 to -1. e.g. SetMotor1(0) -> motor 1 is stopped SetMotor1(0.75) -> motor 1 moving forward at 75% power SetMotor1(-0.5) -> motor 1 moving reverse at 50% power SetMotor1(1) -> motor 1 moving forward at 100% power """ if power < 0: # Reverse command = COMMAND_SET_A_REV pwm = -int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX else: # Forward / stopped command = COMMAND_SET_A_FWD pwm = int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX try: self.RawWrite(command, [pwm]) except KeyboardInterrupt: raise except: self.Print('Failed sending motor 1 drive level!') def GetMotor1(self): """ power = GetMotor1() Gets the drive level for motor 1, from +1 to -1. e.g. 0 -> motor 1 is stopped 0.75 -> motor 1 moving forward at 75% power -0.5 -> motor 1 moving reverse at 50% power 1 -> motor 1 moving forward at 100% power """ try: i2cRecv = self.RawRead(COMMAND_GET_A, I2C_MAX_LEN) except KeyboardInterrupt: raise except: # self.Print('Failed reading motor 1 drive level!') self._log.debug('failed reading motor 1 drive level.') return None power = float(i2cRecv[2]) / float(PWM_MAX) if i2cRecv[1] == COMMAND_VALUE_FWD: return power elif i2cRecv[1] == COMMAND_VALUE_REV: return -power else: return def SetMotors(self, power): """ SetMotors(power) Sets the drive level for all motors, from +1 to -1. e.g. SetMotors(0) -> all motors are stopped SetMotors(0.75) -> all motors are moving forward at 75% power SetMotors(-0.5) -> all motors are moving reverse at 50% power SetMotors(1) -> all motors are moving forward at 100% power """ if power < 0: # Reverse command = COMMAND_SET_ALL_REV pwm = -int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX else: # Forward / stopped command = COMMAND_SET_ALL_FWD pwm = int(PWM_MAX * power) if pwm > PWM_MAX: pwm = PWM_MAX try: self.RawWrite(command, [pwm]) except KeyboardInterrupt: raise except: self.Print('Failed sending all motors drive level!') def MotorsOff(self): """ MotorsOff() Sets all motors to stopped, useful when ending a program """ try: self.RawWrite(COMMAND_ALL_OFF, [0]) except KeyboardInterrupt: raise except: self.Print('Failed sending motors off command!') def SetLed1(self, r, g, b): """ SetLed1(r, g, b) Sets the current colour of the ThunderBorg LED. r, g, b may each be between 0 and 1 e.g. SetLed1(0, 0, 0) -> ThunderBorg LED off SetLed1(1, 1, 1) -> ThunderBorg LED full white SetLed1(1.0, 0.5, 0.0) -> ThunderBorg LED bright orange SetLed1(0.2, 0.0, 0.2) -> ThunderBorg LED dull purple """ levelR = max(0, min(PWM_MAX, int(r * PWM_MAX))) levelG = max(0, min(PWM_MAX, int(g * PWM_MAX))) levelB = max(0, min(PWM_MAX, int(b * PWM_MAX))) try: self.RawWrite(COMMAND_SET_LED1, [levelR, levelG, levelB]) except KeyboardInterrupt: raise except: self.Print('Failed sending colour for the ThunderBorg LED!') def GetLed1(self): """ r, g, b = GetLed1() Gets the current colour of the ThunderBorg LED. r, g, b may each be between 0 and 1 e.g. 0, 0, 0 -> ThunderBorg LED off 1, 1, 1 -> ThunderBorg LED full white 1.0, 0.5, 0.0 -> ThunderBorg LED bright orange 0.2, 0.0, 0.2 -> ThunderBorg LED dull purple """ try: i2cRecv = self.RawRead(COMMAND_GET_LED1, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading ThunderBorg LED colour!') return r = i2cRecv[1] / float(PWM_MAX) g = i2cRecv[2] / float(PWM_MAX) b = i2cRecv[3] / float(PWM_MAX) return r, g, b def SetLed2(self, r, g, b): """ SetLed2(r, g, b) Sets the current colour of the ThunderBorg Lid LED. r, g, b may each be between 0 and 1 e.g. SetLed2(0, 0, 0) -> ThunderBorg Lid LED off SetLed2(1, 1, 1) -> ThunderBorg Lid LED full white SetLed2(1.0, 0.5, 0.0) -> ThunderBorg Lid LED bright orange SetLed2(0.2, 0.0, 0.2) -> ThunderBorg Lid LED dull purple """ levelR = max(0, min(PWM_MAX, int(r * PWM_MAX))) levelG = max(0, min(PWM_MAX, int(g * PWM_MAX))) levelB = max(0, min(PWM_MAX, int(b * PWM_MAX))) try: self.RawWrite(COMMAND_SET_LED2, [levelR, levelG, levelB]) except KeyboardInterrupt: raise except: self.Print('Failed sending colour for the ThunderBorg Lid LED!') def GetLed2(self): """ r, g, b = GetLed2() Gets the current colour of the ThunderBorg Lid LED. r, g, b may each be between 0 and 1 e.g. 0, 0, 0 -> ThunderBorg Lid LED off 1, 1, 1 -> ThunderBorg Lid LED full white 1.0, 0.5, 0.0 -> ThunderBorg Lid LED bright orange 0.2, 0.0, 0.2 -> ThunderBorg Lid LED dull purple """ try: i2cRecv = self.RawRead(COMMAND_GET_LED2, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading ThunderBorg Lid LED colour!') return r = i2cRecv[1] / float(PWM_MAX) g = i2cRecv[2] / float(PWM_MAX) b = i2cRecv[3] / float(PWM_MAX) return r, g, b def SetLeds(self, r, g, b): """ SetLeds(r, g, b) Sets the current colour of both LEDs. r, g, b may each be between 0 and 1 e.g. SetLeds(0, 0, 0) -> Both LEDs off SetLeds(1, 1, 1) -> Both LEDs full white SetLeds(1.0, 0.5, 0.0) -> Both LEDs bright orange SetLeds(0.2, 0.0, 0.2) -> Both LEDs dull purple """ levelR = max(0, min(PWM_MAX, int(r * PWM_MAX))) levelG = max(0, min(PWM_MAX, int(g * PWM_MAX))) levelB = max(0, min(PWM_MAX, int(b * PWM_MAX))) try: self.RawWrite(COMMAND_SET_LEDS, [levelR, levelG, levelB]) except KeyboardInterrupt: raise except: self.Print('Failed sending colour for both LEDs!') def SetLedShowBattery(self, state): """ SetLedShowBattery(state) Sets the system to enable or disable the LEDs showing the current battery level If enabled the LED colours will be ignored and will use the current battery reading instead This sweeps from fully green for maximum voltage (35 V) to fully red for minimum voltage (7 V) """ if state: level = COMMAND_VALUE_ON else: level = COMMAND_VALUE_OFF try: self.RawWrite(COMMAND_SET_LED_BATT_MON, [level]) except KeyboardInterrupt: raise except: self.Print('Failed sending LED battery monitoring state!') def GetLedShowBattery(self): """ state = GetLedShowBattery() Gets if the system is using the LEDs to show the current battery level, true for enabled, false for disabled If enabled the LED colours will be ignored and will use the current battery reading instead This sweeps from fully green for maximum voltage (35 V) to fully red for minimum voltage (7 V) """ try: i2cRecv = self.RawRead(COMMAND_GET_LED_BATT_MON, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading LED battery monitoring state!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def SetCommsFailsafe(self, state): """ SetCommsFailsafe(state) Sets the system to enable or disable the communications failsafe The failsafe will turn the motors off unless it is commanded at least once every 1/4 of a second Set to True to enable this failsafe, set to False to disable this failsafe The failsafe is disabled at power on """ if state: level = COMMAND_VALUE_ON else: level = COMMAND_VALUE_OFF try: self.RawWrite(COMMAND_SET_FAILSAFE, [level]) except KeyboardInterrupt: raise except: self.Print('Failed sending communications failsafe state!') def GetCommsFailsafe(self): """ state = GetCommsFailsafe() Read the current system state of the communications failsafe, True for enabled, False for disabled The failsafe will turn the motors off unless it is commanded at least once every 1/4 of a second """ try: i2cRecv = self.RawRead(COMMAND_GET_FAILSAFE, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading communications failsafe state!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def GetDriveFault1(self): """ state = GetDriveFault1() Reads the motor drive fault state for motor #1, False for no problems, True for a fault has been detected Faults may indicate power problems, such as under-voltage (not enough power), and may be cleared by setting a lower drive power If a fault is persistent, it repeatably occurs when trying to control the board, this may indicate a wiring problem such as: * The supply is not powerful enough for the motors The board has a bare minimum requirement of 6V to operate correctly A recommended minimum supply of 7.2V should be sufficient for smaller motors * The + and - connections for motor #1 are connected to each other * Either + or - is connected to ground (GND, also known as 0V or earth) * Either + or - is connected to the power supply (V+, directly to the battery or power pack) * One of the motors may be damaged Faults will self-clear, they do not need to be reset, however some faults require both motors to be moving at less than 100% to clear The easiest way to check is to put both motors at a low power setting which is high enough for them to rotate easily, such as 30% Note that the fault state may be true at power up, this is normal and should clear when both motors have been driven For more details check the website at www.piborg.org/thunderborg and double check the wiring instructions """ try: i2cRecv = self.RawRead(COMMAND_GET_DRIVE_A_FAULT, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading the drive fault state for motor #1!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def GetDriveFault2(self): """ state = GetDriveFault2() Reads the motor drive fault state for motor #2, False for no problems, True for a fault has been detected Faults may indicate power problems, such as under-voltage (not enough power), and may be cleared by setting a lower drive power If a fault is persistent, it repeatably occurs when trying to control the board, this may indicate a wiring problem such as: * The supply is not powerful enough for the motors The board has a bare minimum requirement of 6V to operate correctly A recommended minimum supply of 7.2V should be sufficient for smaller motors * The + and - connections for motor #2 are connected to each other * Either + or - is connected to ground (GND, also known as 0V or earth) * Either + or - is connected to the power supply (V+, directly to the battery or power pack) * One of the motors may be damaged Faults will self-clear, they do not need to be reset, however some faults require both motors to be moving at less than 100% to clear The easiest way to check is to put both motors at a low power setting which is high enough for them to rotate easily, such as 30% Note that the fault state may be true at power up, this is normal and should clear when both motors have been driven For more details check the website at www.piborg.org/thunderborg and double check the wiring instructions """ try: i2cRecv = self.RawRead(COMMAND_GET_DRIVE_B_FAULT, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading the drive fault state for motor #2!') return if i2cRecv[1] == COMMAND_VALUE_OFF: return False else: return True def GetBatteryReading(self): """ voltage = GetBatteryReading() Reads the current battery level from the main input. Returns the value as a voltage based on the 3.3 V rail as a reference. """ try: i2cRecv = self.RawRead(COMMAND_GET_BATT_VOLT, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading battery level!') return raw = (i2cRecv[1] << 8) + i2cRecv[2] level = float(raw) / float(COMMAND_ANALOG_MAX) level *= VOLTAGE_PIN_MAX return level + VOLTAGE_PIN_CORRECTION def SetBatteryMonitoringLimits(self, minimum, maximum): """ SetBatteryMonitoringLimits(minimum, maximum) Sets the battery monitoring limits used for setting the LED colour. The values are between 0 and 36.3 V. The colours shown range from full red at minimum or below, yellow half way, and full green at maximum or higher. These values are stored in EEPROM and reloaded when the board is powered. """ levelMin = minimum / float(VOLTAGE_PIN_MAX) levelMax = maximum / float(VOLTAGE_PIN_MAX) levelMin = max(0, min(0xFF, int(levelMin * 0xFF))) levelMax = max(0, min(0xFF, int(levelMax * 0xFF))) try: self.RawWrite(COMMAND_SET_BATT_LIMITS, [levelMin, levelMax]) time.sleep(0.2) # Wait for EEPROM write to complete except KeyboardInterrupt: raise except: self.Print('Failed sending battery monitoring limits!') def GetBatteryMonitoringLimits(self): """ minimum, maximum = GetBatteryMonitoringLimits() Reads the current battery monitoring limits used for setting the LED colour. The values are between 0 and 36.3 V. The colours shown range from full red at minimum or below, yellow half way, and full green at maximum or higher. """ try: i2cRecv = self.RawRead(COMMAND_GET_BATT_LIMITS, I2C_MAX_LEN) except KeyboardInterrupt: raise except: self.Print('Failed reading battery monitoring limits!') return rawMin = i2cRecv[1] rawMax = i2cRecv[2] levelMin = float(rawMin) / float(0xFF) levelMax = float(rawMax) / float(0xFF) levelMin *= VOLTAGE_PIN_MAX levelMax *= VOLTAGE_PIN_MAX return levelMin, levelMax def WriteExternalLedWord(self, b0, b1, b2, b3): """ WriteExternalLedWord(b0, b1, b2, b3) Low level serial LED word writing. Bytes are written MSB first starting from b0 e.g. WriteExtermnalLedWord(255, 64, 1, 0) will write out: 11111111 01000000 00000001 00000000 to the LEDs. """ b0 = max(0, min(PWM_MAX, int(b0))) b1 = max(0, min(PWM_MAX, int(b1))) b2 = max(0, min(PWM_MAX, int(b2))) b3 = max(0, min(PWM_MAX, int(b3))) try: self.RawWrite(COMMAND_WRITE_EXTERNAL_LED, [b0, b1, b2, b3]) except KeyboardInterrupt: raise except: self.Print('Failed sending word for the external LEDs!') def SetExternalLedColours(self, colours): """ SetExternalLedColours([[r, g, b], ..., [r, g, b]]) Takes a set of RGB triples to set each LED to. Each call will set all of the LEDs e.g. SetExternalLedColours([[1.0, 1.0, 0.0]]) will set a single LED to full yellow. SetExternalLedColours([[1.0, 0.0, 0.0], [0.5, 0.0, 0.0], [0.0, 0.0, 0.0]]) will set LED 1 to full red, LED 2 to half red, and LED 3 to off. """ # Send the start marker self.WriteExternalLedWord(0, 0, 0, 0) # Send each colour in turn for r, g, b in colours: self.WriteExternalLedWord(255, 255 * b, 255 * g, 255 * r) def Help(self): """ Help() Displays the names and descriptions of the various functions and settings provided """ funcList = [ ThunderBorg.__dict__.get(a) for a in dir(ThunderBorg) if isinstance(ThunderBorg.__dict__.get(a), types.FunctionType) ] funcListSorted = sorted(funcList, key=lambda x: x.func_code.co_firstlineno) print(self.__doc__) print for func in funcListSorted: print('=== %s === %s' % (func.func_name, func.func_doc))
class RgbMatrix(Feature): ''' This class provides access to a pair of Pimoroni 5x5 RGB LED Matrix displays, labeled port and starboard. It also includes several canned demonstrations, which can be used to indicate behaviours in progress. The port side display requires cutting of the ADDR trace on the board to alter its I2C address to 0x77, so that two boards can be used. The starboard display is at the default address of 0x74 and hence is required, the port optional. ''' def __init__(self, level): global enabled self._log = Logger("rgbmatrix", level) _i2c_scanner = I2CScanner(Level.WARN) _addresses = _i2c_scanner.get_int_addresses() self._rgbmatrix5x5_PORT = RGBMatrix5x5( address=0x77) if (0x77 in _addresses) else None # self._rgbmatrix5x5_PORT = RGBMatrix5x5(address=0x77) if self._rgbmatrix5x5_PORT: self._log.info('port rgbmatrix at 0x77.') self._rgbmatrix5x5_PORT.set_brightness(0.8) self._rgbmatrix5x5_PORT.set_clear_on_exit() else: self._log.info('no port rgbmatrix found.') self._rgbmatrix5x5_STBD = RGBMatrix5x5(address=0x74) self._log.info('starboard rgbmatrix at 0x74.') self._rgbmatrix5x5_STBD.set_brightness(0.8) self._rgbmatrix5x5_STBD.set_clear_on_exit() self._height = self._rgbmatrix5x5_STBD.height self._width = self._rgbmatrix5x5_STBD.width self._log.info('rgbmatrix width,height: {},{}'.format( self._width, self._height)) self._thread_PORT = None self._thread_STBD = None self._color = Color.RED # used by _solid enabled = False self._closing = False self._closed = False self._display_type = DisplayType.DARK # default # color used by wipe display self._wipe_color = Color.WHITE # default # used by _cpu: self._max_value = 0.0 # TEMP self._buf = numpy.zeros( (self._rgbmatrix5x5_STBD._width, self._rgbmatrix5x5_STBD._height)) self._colors = [ Color.GREEN, Color.YELLOW_GREEN, Color.YELLOW, Color.ORANGE, Color.RED ] self._log.info('ready.') # .......................................................................... def name(self): return 'RgbMatrix' # .......................................................................... def _get_target(self): if self._display_type is DisplayType.BLINKY: return [RgbMatrix._blinky, None] elif self._display_type is DisplayType.CPU: return [RgbMatrix._cpu, None] elif self._display_type is DisplayType.DARK: return [RgbMatrix._dark, None] elif self._display_type is DisplayType.RAINBOW: return [RgbMatrix._rainbow, None] elif self._display_type is DisplayType.RANDOM: return [RgbMatrix._random, None] elif self._display_type is DisplayType.SCAN: return [RgbMatrix._scan, None] elif self._display_type is DisplayType.SOLID: return [RgbMatrix._solid, None] elif self._display_type is DisplayType.SWORL: return [RgbMatrix._sworl, None] elif self._display_type is DisplayType.WIPE_LEFT: return [RgbMatrix._wipe, WipeDirection.LEFT] # .......................................................................... def enable(self): global enabled if not self._closed and not self._closing: if self._thread_PORT is None and self._thread_STBD is None: enabled = True _target = self._get_target() if self._rgbmatrix5x5_PORT: self._thread_PORT = Thread( name='rgb-port', target=_target[0], args=[self, self._rgbmatrix5x5_PORT, _target[1]]) self._thread_PORT.start() self._thread_STBD = Thread( name='rgb-stbd', target=_target[0], args=[self, self._rgbmatrix5x5_STBD, _target[1]]) self._thread_STBD.start() self._log.debug('enabled.') else: self._log.warning('cannot enable: process already running.') else: self._log.warning('cannot enable: already closed.') # .......................................................................... def clear(self): if self._rgbmatrix5x5_PORT: self._clear(self._rgbmatrix5x5_PORT) self._clear(self._rgbmatrix5x5_STBD) # .......................................................................... def is_disabled(self): global enabled return not enabled # .......................................................................... def disable(self): global enabled self._log.debug('disabling...') enabled = False if self._rgbmatrix5x5_PORT: self._clear(self._rgbmatrix5x5_PORT) self._clear(self._rgbmatrix5x5_STBD) if self._thread_PORT != None: self._thread_PORT.join(timeout=1.0) self._log.debug('port rgbmatrix thread joined.') self._thread_PORT = None if self._thread_STBD != None: self._thread_STBD.join(timeout=1.0) self._log.debug('starboard rgbmatrix thread joined.') self._thread_STBD = None self._log.debug('disabled.') # .......................................................................... def _cpu(self, rgbmatrix5x5, arg): ''' A port of the CPU example from the Matrix 11x7. For some reasoon the output needs to be rotated 90 degrees to work properly. ''' self._log.info('starting cpu...') i = 0 cpu_values = [0] * self._width while enabled: try: cpu_values.pop(0) cpu_values.append(psutil.cpu_percent()) # # display cpu_values and max (turns out to be 50.0) # for i in range(0, len(cpu_values)-1): # self._max_value = max(self._max_value, cpu_values[i]) # self._log.info(Fore.BLUE + 'cpu_values: {}, {}, {}, {}, {}; '.format(cpu_values[0], cpu_values[1], cpu_values[2], cpu_values[3], cpu_values[4]) \ # + Style.BRIGHT + '\tmax: {:5.2f}'.format(self._max_value)) self._set_graph(rgbmatrix5x5, cpu_values, low=0.0, high=50.0) # high was 25 rgbmatrix5x5.show() time.sleep(0.2) except KeyboardInterrupt: self._clear(rgbmatrix5x5) self._log.info('cpu ended.') sys.exit(0) # ...................................................... def _set_graph(self, rgbmatrix5x5, values, low=None, high=None, x=0, y=0): ''' Plot a series of values into the display buffer. ''' global enabled # def set_graph(self, values, low=None, high=None, brightness=1.0, x=0, y=0, width=None, height=None): # x=0 # y=0 _width = self._width - 1 _height = self._height + 0 if low is None: low = min(values) if high is None: high = max(values) self._buf = self._grow_buffer(x + _width, y + _height) span = high - low for p_y in range(0, _height): try: _value = values[p_y] _value -= low _value /= float(span) _value *= _width * 10.0 _value = min(_value, _height * 12.0) _value = max(_value, 0.0) if _value > 5.0: _value = 50.0 # self._log.info(Fore.MAGENTA + 'p_y={}; _value: {}'.format(p_y, _value) + Style.RESET_ALL) for p_x in range(0, _width): _r = self._colors[p_x].red _g = self._colors[p_x].green _b = self._colors[p_x].blue if _value <= 10.0: _r = (_value / 10.0) * _r _g = (_value / 10.0) * _g _b = (_value / 10.0) * _b _x = x + (_width - p_x) _y = y + p_y self._log.info(Fore.YELLOW + 'setting pixel x={}/{}, y={}/{};'.format( _x, _width, _y, _height) + Fore.MAGENTA + '\tvalue: {:>5.2f}'.format(_value) + Style.RESET_ALL) rgbmatrix5x5.set_pixel(_x, _y, _r, _g, _b) _value -= 10.0 if _value < 0.0: _value = 0.0 print('') except IndexError: return # ...................................................... def _grow_buffer(self, x, y): ''' Grows a copy of the buffer until the new shape fits inside it. :param x: Minimum x size :param y: Minimum y size ''' x_pad = max(0, x - self._buf.shape[0]) y_pad = max(0, y - self._buf.shape[1]) return numpy.pad(self._buf, ((0, x_pad), (0, y_pad)), 'constant') # .......................................................................... def _rainbow(self, rgbmatrix5x5, arg): ''' Display a rainbow pattern. ''' global enabled self._log.info('starting rainbow...') _spacing = 360.0 / 5.0 _hue = 0 while enabled: for x in range(self._width): for y in range(self._height): _hue = int(time.time() * 100) % 360 offset = (x * y) / 25.0 * _spacing h = ((_hue + offset) % 360) / 360.0 r, g, b = [ int(c * 255) for c in colorsys.hsv_to_rgb(h, 1.0, 1.0) ] rgbmatrix5x5.set_pixel(x, y, r, g, b) # r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(h + 0.5, 1.0, 1.0)] # rainbow2.set_pixel(x, y, r, g, b) if not enabled: break rgbmatrix5x5.show() # rainbow2.show() time.sleep(0.0001) self._clear(rgbmatrix5x5) self._log.info('rainbow ended.') # .......................................................................... def _sworl(self, rgbmatrix5x5, arg): ''' Display a sworl pattern, whatever that is. ''' global enabled self._log.info('starting sworl...') try: for r in range(0, 10, 1): rgbmatrix5x5.set_all(r, 0, 0) rgbmatrix5x5.show() time.sleep(0.003) for i in range(0, 5): for r in range(10, 250, 10): _blue = r - 128 if r > 128 else 0 rgbmatrix5x5.set_all(r, _blue, 0) rgbmatrix5x5.show() time.sleep(0.01) if not enabled: break for r in range(250, 10, -10): _blue = r - 128 if r > 128 else 0 rgbmatrix5x5.set_all(r, _blue, 0) rgbmatrix5x5.show() time.sleep(0.01) if not enabled: break self._log.info('sworl ended.') except KeyboardInterrupt: self._log.info('sworl interrupted.') finally: self.set_color(Color.BLACK) # .......................................................................... def _wipe(self, rgbmatrix5x5, direction): ''' Display a wipe in the specified direction. ''' if direction is WipeDirection.LEFT or direction is WipeDirection.RIGHT: self._wipe_horizontal(rgbmatrix5x5, direction) if direction is WipeDirection.UP or direction is WipeDirection.DOWN: self._wipe_vertical(rgbmatrix5x5, direction) # .......................................................................... def _wipe_horizontal(self, rgbmatrix5x5, direction): ''' Note: not implemented yet. ''' global enabled raise NotImplementedError() # .......................................................................... def set_wipe_color(self, color): self._wipe_color = color # .......................................................................... def _wipe_vertical(self, rgbmatrix, direction): ''' Note: UP has not been implemented yet. ''' global enabled if not rgbmatrix: self._log.debug('null RGB matrix argument.') return if direction is WipeDirection.DOWN: self._log.info('starting wipe DOWN...') elif direction is WipeDirection.UP: raise NotImplementedError('wipe UP not implemented.') else: raise ValueError('unrecognised direction argument.') _delay = 0.05 self.set_color(Color.BLACK) # self._set_color(rgbmatrix, Color.BLACK) time.sleep(0.1) xra = [[0, self._width, 1], [self._width - 1, -1, -1]] yra = [[0, self._height, 1], [self._height - 1, -1, -1]] colors = [self._wipe_color, Color.BLACK] try: for i in range(0, 2): xr = xra[i] yr = yra[i] r, g, b = colors[i].rgb for x in range(xr[0], xr[1], xr[2]): for y in range(yr[0], yr[1], yr[2]): rgbmatrix.set_pixel(x, y, r, g, b) rgbmatrix.show() time.sleep(_delay) self._log.info('wipe ended.') except KeyboardInterrupt: self._log.info('wipe interrupted.') finally: self.set_color(Color.BLACK) # .......................................................................... def set_solid_color(self, color): self._color = color # .......................................................................... def show_color(self, color, orientation): self.set_solid_color(color) if orientation is Orientation.PORT or orientation is Orientation.BOTH and self._rgbmatrix5x5_PORT: self._set_color(self._rgbmatrix5x5_PORT, self._color) if orientation is Orientation.STBD or orientation is Orientation.BOTH: self._set_color(self._rgbmatrix5x5_STBD, self._color) # .......................................................................... def get_rgbmatrix(self, orientation): ''' Return the port or starboard RGB matrix. ''' if orientation is Orientation.PORT: return self._rgbmatrix5x5_PORT if orientation is Orientation.STBD: return self._rgbmatrix5x5_STBD else: return None # .......................................................................... def show_hue(self, hue, orientation): ''' Set the color of the display to the hue specified as a value from 0.0 - 1.0. Not sure this works, abandoned for now. ''' rgb = colorsys.hsv_to_rgb(abs(hue), 1.0, 1.0) r = int(rgb[0] * 255.0) g = int(rgb[1] * 255.0) b = int(rgb[2] * 255.0) if orientation is Orientation.PORT or orientation is Orientation.BOTH and self._rgbmatrix5x5_PORT: self._rgbmatrix5x5_PORT.set_all(r, g, b) self._rgbmatrix5x5_PORT.show() if orientation is Orientation.STBD or orientation is Orientation.BOTH: self._rgbmatrix5x5_STBD.set_all(r, g, b) self._rgbmatrix5x5_STBD.show() # .......................................................................... def _solid(self, rgbmatrix5x5, arg): ''' Display a specified static, solid color on only the starboard display. ''' global enabled # self.set_color(self._color) # self._set_color(self._rgbmatrix5x5_PORT, self._color) self._set_color(self._rgbmatrix5x5_STBD, self._color) self._log.info('starting solid color to {}...'.format( str.lower(self._color.name))) while enabled: time.sleep(0.2) # .......................................................................... def _dark(self, rgbmatrix5x5, arg): ''' Display a dark static color. ''' global enabled self._log.info('starting dark...') self.set_color(Color.BLACK) while enabled: time.sleep(0.2) # .......................................................................... @staticmethod def make_gaussian(fwhm): x = numpy.arange(0, 5, 1, float) y = x[:, numpy.newaxis] x0, y0 = 2, 2 fwhm = fwhm gauss = numpy.exp(-4 * numpy.log(2) * ((x - x0)**2 + (y - y0)**2) / fwhm**2) return gauss # .......................................................................... def _blinky(self, rgbmatrix5x5, arg): ''' Display a pair of blinky spots. ''' global enabled self._log.info('starting blinky...') if self._height == self._width: _delta = 0 else: _delta = 2 while enabled: for i in range(3): for z in list(range(1, 10)[::-1]) + list(range(1, 10)): fwhm = 5.0 / z gauss = RgbMatrix.make_gaussian(fwhm) start = time.time() for y in range(self._height): for x in range(self._width): h = 0.5 s = 0.8 if self._height <= self._width: v = gauss[x, y] # v = gauss[x, y + _delta] else: v = gauss[x, y] # v = gauss[x + _delta, y] rgb = colorsys.hsv_to_rgb(h, s, v) r = int(rgb[0] * 255.0) g = int(rgb[1] * 255.0) b = int(rgb[2] * 255.0) rgbmatrix5x5.set_pixel(x, y, r, g, b) rgbmatrix5x5.show() end = time.time() t = end - start if t < 0.04: time.sleep(0.04 - t) pass if not enabled: break self._clear(rgbmatrix5x5) self._log.info('blinky ended.') # .......................................................................... def _scan(self, rgbmatrix5x5, arg): ''' KITT- or Cylon-like eyeball scanning. ''' global enabled self._log.info('starting scan...') r = int(255.0) g = int(64.0) b = int(0.0) # start = time.time() # for x in range(self._width): x = 2 _delay = 0.25 while enabled: # for i in range(count): for y in range(0, self._height): rgbmatrix5x5.clear() rgbmatrix5x5.set_pixel(x, y, r, g, b) rgbmatrix5x5.show() time.sleep(_delay) for y in range(self._height - 1, 0, -1): rgbmatrix5x5.clear() rgbmatrix5x5.set_pixel(x, y, r, g, b) rgbmatrix5x5.show() time.sleep(_delay) if not enabled: break self._clear(rgbmatrix5x5) self._log.debug('scan ended.') # .......................................................................... def _random(self, rgbmatrix5x5, arg): ''' Display an ever-changing random pattern. ''' global enabled self._log.info('starting random...') count = 0 while enabled: rand_hue = numpy.random.uniform(0.1, 0.9) rand_mat = numpy.random.rand(self._width, self._height) for y in range(self._height): for x in range(self._width): # h = 0.1 * rand_mat[x, y] h = rand_hue * rand_mat[x, y] s = 0.8 v = rand_mat[x, y] rgb = colorsys.hsv_to_rgb(h, s, v) r = int(rgb[0] * 255.0) g = int(rgb[1] * 255.0) b = int(rgb[2] * 255.0) rgbmatrix5x5.set_pixel(x, y, r, g, b) if not enabled: break rgbmatrix5x5.show() time.sleep(0.01) self._clear(rgbmatrix5x5) self._log.info('random ended.') # .......................................................................... def set_color(self, color): ''' Set the color of both RGB Matrix displays. ''' if self._rgbmatrix5x5_PORT: self._set_color(self._rgbmatrix5x5_PORT, color) self._set_color(self._rgbmatrix5x5_STBD, color) # .......................................................................... def _set_color(self, rgbmatrix5x5, color): ''' Set the color of the RGB Matrix. ''' rgbmatrix5x5.set_all(color.red, color.green, color.blue) rgbmatrix5x5.show() # .......................................................................... def _clear(self, rgbmatrix5x5): ''' Clears the RGB Matrix by setting its color to black. ''' self._set_color(rgbmatrix5x5, Color.BLACK) # .......................................................................... def set_display_type(self, display_type): self._display_type = display_type # .......................................................................... def close(self): if self._closing: self._log.warning('already closing.') return self.set_color(Color.BLACK) self._closing = True self.disable() self._closed = True self._log.info('closed.')
class JobQueueManager(): """ Handles the monitoring of the JobQueue and runs jobs """ def __init__(self, config, verbose, daemon=True): """ Parse config file and setup the logging """ self.config = config self.verbose = verbose self.daemon = daemon self.running = True self.logger = Logger(self.config.DAEMON.log_name, self.config.DAEMON.log_dir).get_logger() self.pidfile = self.config.DAEMON.pid_file self.api_manager = FrontendApiManager(self.config.API, logger=self.logger) self.sync_manager = SyncManager(self.api_manager, logger=self.logger) def daemonize(self): """ Turn this running process into a deamon """ # Perform first fork try: pid = os.fork() if pid > 0: self.logger.error('First fork returned but was >0, exiting') sys.exit(1) self.logger.debug('First fork worked') except OSError as e: self.logger.error('First fork failed ({0})'.format(e)) raise Exception(e) # Escape out of where we started os.chdir(self.config.DAEMON.working_dir) os.umask(self.config.DAEMON.umask) os.setsid() # Perform second fork try: pid = os.fork() if pid > 0: self.logger.error('Second fork returned but was >0, exiting') sys.exit(1) except OSError as e: self.logger.error('Second fork failed ({0})'.format(e)) raise Exception(e) # Close off the stdXXX self.logger.debug('Closing file descriptors') sys.stdout.flush() sys.stderr.flush() si = open(os.devnull, 'r') so = open(os.devnull, 'a+') se = open(os.devnull, 'a+') os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # Register the pid file deletion atexit.register(self.on_exit) # Write the PID to file pid = str(os.getpid()) with open(self.pidfile, 'w+') as f: f.write(pid + '\n') self.logger.debug("Written PID of '{0}' into file '{1}'".format(pid, self.pidfile)) def on_exit(self): """ Delete the PID file """ os.remove(self.pidfile) self.logger.debug('Removed the pid file ({0})'.format(self.pidfile)) def run(self): """ Main worker loop """ while self.running: # Loop over the job queue and handle any jobs that we are not processing yet job_queue = self.api_manager.get_job_queue() if not job_queue: self.logger.info('Job queue empty') for job in job_queue: try: self.sync_manager.handle(job) self.logger.info('Starting job {0}'.format(job['name'])) except self.sync_manager.AlreadyWorkingOnException: self.logger.debug('Already working on job {0}'.format(job['name'])) except self.sync_manager.ActionAlreadyWorkingOnException: self.logger.debug("action='{0}' job='{1}' message='action already working on'".format( job['action'], job['name'])) # Go over all queued jobs and complete any finished ones, report on what jobs we finished off for job in self.sync_manager.complete_jobs(): self.logger.info('Removed finished job {0}'.format(job)) # Sleep for a set time before checking the queue again sleep_time = float(self.config.DAEMON.sleep) self.logger.debug('Sleeping for {0} seconds'.format(sleep_time)) time.sleep(sleep_time) if not self.running: # Good-ish place to be in self.logger.warning('Main loop was stopped (running = False)') return True else: # Bad place to be in self.logger.warning('Main loop broke without us saying (running = True)') return False def start(self): """ Start the daemon """ # Check for a pidfile to see if the daemon already runs try: with open(self.pidfile, 'r') as pf: pid = int(pf.read().strip()) except IOError: if not os.path.isdir(os.path.dirname(self.pidfile)): print('ERROR: PID folder does not exist: {0}'.format(os.path.dirname(self.pidfile))) sys.exit(1) pid = None if pid: # pidfile exists, bail message = 'pidfile {0} already exists. Daemon already running?' self.logger.error(message.format(self.pidfile)) sys.exit(1) # Turn into a daemon if we are told to if self.daemon: self.daemonize() message = 'We are now a daemon, all stdout/stderr redirected' self.logger.debug(message) else: print('INFO: Skipping daemon mode') print('INFO: Log file: {0}'.format(self.logger.log_file)) # Work our magic self.run() # Finishing up properly self.logger.info('Finished successfully, bye bye!') return True def stop(self): """ Stop the daemon, kill off all jobs """ # Check for a pidfile to see if the daemon already runs try: with open(self.pidfile, 'r') as pf: pid = int(pf.read().strip()) except IOError: if not os.path.isdir(os.path.dirname(self.pidfile)): message = 'ERROR: PID folder does not exist: {0}' print(message.format(os.path.dirname(self.pidfile))) sys.exit(1) pid = None if not pid: message = 'pidfile {0} does not exist. Daemon not running?' self.logger.error(message.format(self.pidfile)) sys.exit(1) # Go over all queued jobs and close off any finished ones, report on the number we finished off for job in self.sync_manager.complete_jobs(): self.logger.info('Removed finished job {0}'.format(job)) # Go over all currently running jobs, report on them and then kill them. for process in self.sync_manager.processing_queue: self.logger.info('Still processing job {0}'.format(process.name)) # We now kill off the process, upon restart the job should restart again self.logger.warning('Killing job {0}'.format(process.name)) process.terminate() self.running = False
def find_plugin(self, name, search_dir = None): for path, candidate_name in self.plugin_candidates(search_dir): if candidate_name.lower() == name.lower(): module_name = path + "." + candidate_name Logger.debug("Candidate plugin '%s'" % module_name) return module_name
class JsonNdParser(object): def __init__(self, delim, level, include_datatype=True): self._log = Logger("jnp", level) self._delim = delim self._log.info('delimiter: \'{}\''.format(delim)) self._include_datatype = include_datatype self._log.info('include datatype: {}'.format(self._include_datatype)) csv.register_dialect("csv-vbar", delimiter=self._delim) self._log.info('ready.') #........................................................................... def parse_file(self, filename): ''' Parse the CSV file to a List of JSON strings. ''' _list = [] try: with open(filename) as csv_file: _header = [] csv_reader = csv.reader(csv_file, delimiter='|') _line_count = 0 for _row_data in csv_reader: if _line_count == 0: # then reada header self._log.debug('raw column names are: {}'.format( ', '.join(_row_data))) for i in range(len(_row_data) - 1): if i == 0: _header.append('@timestamp') elif i == 1: _header.append('row') else: _value = _row_data[i].strip() _header.append(_value) self._log.info('header field names:\n' + Fore.YELLOW + '{}'.format(', '.join(_header))) _line_count += 1 else: _json_row = self._parse_row(_header, _row_data, _line_count) _list.append(_json_row) _line_count += 1 self._log.info('processed {} lines.'.format(_line_count)) except Exception as e: raise JsonNdParserException( 'error parsing CSV to JSON: {}\n{}'.format( e, traceback.format_exc())) self._log.info('returning list of {} elements.'.format(len(_list))) return _list #........................................................................... def _parse_row(self, header, row, line_count): ''' Parse the row, returning it as a JSON string. This uses the header to provide field names. ''' _row_dict = OrderedDict() self._log.debug('processing row {}...'.format(line_count)) for i in range(len(row) - 1): _value = row[i].strip() if i == 0: _datatype = 'date' elif i == 1: _datatype = 'integer' _value = line_count elif '.' in _value: _datatype = 'float' _value = float(_value) else: _datatype = 'integer' _value = int(_value) if self._include_datatype: self._log.debug(Fore.MAGENTA + '[{}] datatype: {}; value: \'{}\''.format( i, _datatype, _value)) _row_dict['{}:{}'.format(header[i], _datatype)] = _value else: _row_dict['{}'.format(header[i])] = _value _json_row = json.dumps(_row_dict) self._log.debug(Fore.BLUE + 'JSON:\n{}'.format(_json_row) + Style.RESET_ALL) self._log.info('processed row {}.'.format(line_count)) return _json_row
import paramiko else: from ftplib import FTP, FTP_TLS from storebase import * from lib import utils from lib import const # Do this last! from lib.logger import Logger log = Logger("io") log.debug("Use Paramiko: ", "yes" if use_paramiko else "no") class FTPStreamer(Thread): def __init__(self, ftp, path, mode): Thread.__init__(self, name="FTPWorker") log.trace("FTPStreamer init", ftp, path, mode) self.ftp = ftp self.path = path self.mode = mode self.lock = RLock() self.buffer = [] self.bufsize = 0 self.done = False self.error = None