def persistent(self, interval): timer = Timer(interval, single(), persist=True) timer.register(self) yield sleep(interval * 10) timer.unregister()
def test_persistentTimer(app): app.timestamps.append(time.time()) timer = Timer(0.2, test(), "timer", persist=True) timer.register(app) wait_res = pytest.wait_for(app, "count", 2) assert app.count >= 2 assert wait_res delta = app.timestamps[1] - app.timestamps[0] # Should be 0.1, but varies depending on timer precision and load assert delta >= 0.08 and delta < 0.5 delta = app.timestamps[2] - app.timestamps[1] assert delta >= 0.08 and delta < 0.5 app.reset() timer.unregister()
class CVCamera(AVIOComponent): def __init__(self, device_id=0, targetresolution=(40, 16), *args, **kwargs): super(CVCamera, self).__init__(*args, **kwargs) self.targetresolution = targetresolution self.cam = cv2.VideoCapture(device_id) self.cam.set(3, 320) self.cam.set(4, 240) self.framerate = 20 / 1000.0 self.recording = True self.worker = Worker(process=False, workers=1, channel="camera").register(self) self.cam_timer = Timer(2, Event.create('takepicture')).register( self) self.surface = pygame.Surface(targetresolution) self.log('Camera started') def takepicture(self): if self.recording: self.log('Taking a pic') self.fireEvent(task(take_picture, self.cam), 'camera') @handler('task_success', channel='camera') def task_success(self, event, call, result): frame = result if frame is None: self.log("Oh, no camera attached! Change this and rerun!") self.cam_timer.unregister() return newframe = numpy.rot90(frame) #newframe = cv2.resize(frame, self.targetresolution) surfarr = pygame.surfarray.make_surface(newframe) srf = pygame.transform.scale(surfarr, self.targetresolution) self.fireEvent(cameraframe(srf)) self.fireEvent(Event.create('takepicture'))
class GIFPlayer(LoggingComponent): """GIF Player with configurable output""" def __init__(self, settings): super(GIFPlayer, self).__init__() self.log('Player initializing') pygame.display.init() self.config = settings self.factor = 1 self.delta = 0.0 self.frames = [] self.durations = [] self.clients = [] self.timer = None self.worker = Worker(process=False, workers=2, channel="gifimport_" + self.uniquename).register(self) # if transparency < 255: # self.log('Setting alpha to %i' % transparency) # for frame in self.frames: # frame[0].set_alpha(transparency) self.cur = 0 self.ptime = 0 self.playing = False self.breakpoint = 0 self.startpoint = 0 self.direction = True self.fireEvent(cli_register_event("test_gifplayer", cli_test_gifplayer)) if self.config['immediately']: self.get_frames() def cli_test_gifplayer(self, *args): if 'stop' in args: self.log("Stopping test video") self.stop() else: self.log('Running test video') self.config['filename'] = os.path.abspath( os.path.join(__file__, "../../test.gif")) self.get_frames() self.timer = Timer(self.config['delay'] / 1000.0, render(), persist=True).register(self) def update(self, settings): self.log('Updating', self.playing, self.config['playing']) if self.config['delay'] != settings['delay']: self.set_speed(settings['delay']) if self.config['bounds'] != settings['bounds']: self.set_bounds(*settings['bounds']) self.config = settings if self.config['playing'] is True: if self.playing is False: self.log('Beginning playback') self.play() else: if self.playing is True: self.log('Beginning playback') self.stop() def started(self, event, thing): self.log('Converting image') self.get_frames() def get_frames(self): self.log('Getting frames') if self.config['filename'] in (None, ""): self.log('No filename, cannot load gif') return try: # frames, durations, log = \ scale = (self.config['scale']['height'], self.config['scale']['width']) data = self.config['filename'], self.config[ 'ignore_timings'], scale self.fireEvent(task(load_image, data), "gifimport_" + self.uniquename) self.log('Worker started', lvl=verbose) except Exception as e: self.log("[GIF_WORKERS]", e, type(e), exc=True) def task_success(self, event, call, result): self.log("Worker finished:", event.channels[0], pretty=True, lvl=verbose) if event.channels[0] != "gifimport_" + self.uniquename: self.log("Not for us.", lvl=verbose) return if len(result) == 3: frames, durations, log = result else: self.log("Unexpected result:", result, pretty=True) return if len(frames) > 0: self.frames = frames self.durations = durations self.cur = 0 self.breakpoint = len(self.frames) - 1 self.startpoint = 0 else: self.log("No frames extracted:", log, lvl=warn) def render(self): # pos = self.x, self.y # self.log('Rendering %s' % self.config['filename'], lvl=verbose) if self.playing: self.delta += time.time() - self.ptime if self.delta > self.frames[self.cur][1]: # TODO: Rebuild this without loop, i.e. calculate the distance to jump while self.delta > self.frames[self.cur][1]: self.delta -= self.frames[self.cur][1] if self.config['reverse'] or (self.config['bounce'] and self.direction == -1): self.cur -= 1 if self.cur < self.startpoint: self.cur = self.breakpoint if not self.config['loop']: self.stop() if self.config['bounce']: self.direction = +1 else: self.cur += 1 if self.cur > self.breakpoint: self.cur = self.startpoint if not self.config['loop']: self.stop() if self.config['bounce']: self.direction = -1 if self.frames[self.cur][1] == 0: break self.ptime = time.time() try: frame = self.frames[self.cur][0] # self.log('Firing event', frame) if len(self.clients) > 0: self._broadcast(frame) self.fireEvent(mix_image(self.config['channel'], frame), "AVIO") except IndexError: pass def _broadcast(self, frame): # TODO: Maybe only transmit necessary data, not statics like length or always # the whole config message = { 'component': 'avio.gifplayer', 'action': 'frame_update', 'data': { 'channel': self.config['channel'], 'config': self.config, 'current': self.cur, 'length': self.breakpoint - self.startpoint, 'frame': frame.tolist() } } self.fireEvent(broadcast("clientgroup", message, group=self.clients), "isomer-web") def set_speed(self, factor): self.log('Setting new speed: %f' % factor) for i, duration in enumerate(self.durations): self.frames[i][1] = duration * factor def seek(self, num): self.cur = num if self.cur < 0: self.cur = 0 if self.cur >= len(self.frames): self.cur = len(self.frames) - 1 def set_bounds(self, start, end): length = len(self.frames) # TODO: I think, the outer min/max operations can be safely omitted self.startpoint = max(0, int(length * (max(0, start) / 100.0))) self.breakpoint = min(int(length * (min(100, end) / 100)), length) - 1 def stop(self): self.log('Stop!', lvl=debug) self.playing = False if self.timer is not None: self.timer.stop() self.timer.unregister() self.timer = None self._broadcast(self.frames[self.cur][0]) def play(self): self.log('Play!', lvl=debug) self.playing = True if self.timer is None: self.ptime = time.time() self.timer = Timer(self.config['delay'] / 1000.0, render(), persist=True).register(self) def rewind(self): self.seek(0) def fastforward(self): self.seek(self.length() - 1) def get_height(self): return self.image.size[1] def get_width(self): return self.image.size[0] def get_size(self): return self.image.size def length(self): return len(self.frames) def reverse(self): self.reversed = not self.reversed def reset(self): self.cur = 0 self.ptime = time.time() self.reversed = False
class IsomerClient(LoggingComponent): def __init__(self, no_stdin=False, no_stdout=False, loop_function=None, loop_frequency=60, *args, **kwargs): super(IsomerClient, self).__init__(*args, **kwargs) self.url = '{protocol}://{host}:{port}/{url}'.format(**kwargs) if no_stdout: set_verbosity(100) if not no_stdin: self.log("Connecting stdin") stdin.register(self) self.log("Connecting to isomer instance at", self.url) self.username = kwargs.get('username') self.password = kwargs.get('password') self.max_length = 100 self._request_id = 0 self.messages = [] self.hooks = {} self.client = WebSocketClient(self.url).register(self) if loop_function is not None: self.log('Registering external loop function') self.loop_function = loop_function self.loop_timer = Timer(1 / loop_frequency, call_loop(), persist=True).register(self) self.log("Ready") @handler("call_loop") def call_loop(self, event): """Runs a given client loop for interactive processes""" self.log('Running external loop') try: self.loop_function() except Exception: self.loop_timer.unregister() Timer(2, Event.create("quit")).register(self) @handler("quit") def quit(self): self.log("Quitting") sys.exit() @handler("registered") def registered(self, event, *args): if 'ws' not in event.channels: self.log("Hello", event) return self.log('Transmitting login') self.fireEvent(write(""), "ws") packet = { 'component': 'auth', 'action': 'login', 'data': { 'username': self.username, 'password': self.password } } self._transmit(packet) def _transmit(self, packet): self.log(packet) unicode = dumps(packet).encode('utf-8') self.log(unicode, type(unicode)) self.fireEvent(write(bytes(unicode)), 'ws') @handler("read") def read(self, *args): self.log("Reading") msg = args[0] self._handle_message(msg) self.messages.append(msg) if len(msg) > self.max_length: msg = str(msg)[:self.max_length] + " ..." self.log("Response [%i]: %s" % (len(self.messages), msg)) def _handle_message(self, msg): try: decoded = loads(msg) except Exception: self.log("Couldn't decode message!") return if decoded['component'] == 'isomer.auth' and decoded[ 'action'] == 'fail': self.log("Login failed. Check credentials and url!", lvl=error) sys.exit() @handler("read", channel="stdin") def stdin_read(self, data): """read Event (on channel ``stdin``) This is the event handler for ``read`` events specifically from the ``stdin`` channel. This is triggered each time stdin has data that it has read. """ data = data.strip().decode("utf-8") self.log("Incoming:", data, lvl=verbose) if len(data) == 0: self.log('Use /help to get a list of client commands') return if data[0] == "/": cmd = data[1:] args = [] if ' ' in cmd: cmd, args = cmd.split(' ', maxsplit=1) args = args.split(' ') if cmd in self.hooks: self.log('Firing hooked event:', cmd, args, lvl=debug) self.fireEvent(self.hooks[cmd](*args)) if cmd.lower() in ('send', 's'): data = " ".join(args) json = loads(" ".join(args)) self.log("Transmitting:", data, pretty=True, lvl=debug) self.fireEvent(write(data), 'ws') if cmd.lower() in ('history', 'h'): position = int(args[0]) pretty = '-p' in args self.log('Response [%i]: %s' % (position, self.messages[position]), pretty=pretty) if cmd.lower() == "test": self.fireEvent(get_data('user', {'name': 'riot'})) if cmd.lower() in ('quit', 'q'): self.log('Initiating system exit in 3 seconds on request.', lvl=verbose) Timer(3, Event.create("quit")).register(self) def get_data(self, event: get_data): """Request data from Isomer server""" request = { 'component': 'isomer.events.objectmanager', 'action': 'search', 'data': { 'schema': event.schema, 'search': event.search_filter, 'req': self._request_id } } self._transmit(request) self._request_id += 1
class PancakesBot(Component): channel = "pancakesbot" def init(self, **kwargs): # Default Settings prop_defaults = { "nick": "pancakesbot", "network": "irc.slashnet.org", "port": 6667, "channels": ["#bots"], "plugins": ["admin"], "plugins_path": "plugins", "command_prefix": "~", "storage_path": "storage", } self.__dict__.update(prop_defaults) # Overwrite defaults with kwargs if kwargs: self.__dict__.update(kwargs) self.terminate = False self.storage_path = os.path.abspath(self.storage_path) # Add a logger self.logger = logging.getLogger(__name__) # Message Queue used by plugins, as to avoid kicks for flooding. self.msg_queue = [] # Global mesasge queue timer, only activated when there are messages # to process. self.queue_timer = None # Add TCPClient and IRC to the system. TCPClient(channel=self.channel).register(self) IRC(channel=self.channel).register(self) # Creates an instance of UserManager self.logger.debug("Initializing user manager.") user_db_file = os.path.join(self.storage_path, "users-{}.db".format(self.network)) if not os.path.exists(self.storage_path): os.makedirs(self.storage_path) self.user_mngr = UserManager(self, user_db_file) self.user_mngr.register(self) # Add plugins directory to path self.logger.debug("Initializing plugin manager.") self.plugins_path = os.path.abspath(self.plugins_path) if not os.path.exists(self.plugins_path): os.makedirs(self.plugins_path) sys.path.append(self.plugins_path) # Keeps track of plugins and commands self.plugin_mngr = PluginManager(self, self.command_prefix, os.path.basename(self.plugins_path)).register(self) for plugin in self.plugins: self.plugin_mngr.load(plugin) # Send Keepalive PING every 5 minutes Timer(300.0, Event.create("keepalive"), persist=True).register(self) def enqueue_msg(self, target, message): if self.queue_timer: self.msg_queue.append((target, message)) else: self.fire(PRIVMSG(target, message)) self.queue_timer = Timer(1.0, Event.create("process_queue"), persist=True).register(self) def process_queue(self): if self.msg_queue: msg = self.msg_queue[0] self.fire(PRIVMSG(msg[0], msg[1])) self.msg_queue.pop(0) if not self.msg_queue: self.queue_timer.unregister() self.queue_timer = None def keepalive(self): timestamp = int(time() * 1000) self.logger.debug("PING: {}".format(timestamp)) self.fire(request(Message("PING", "LAG{0}".format(timestamp)))) @handler("signal", channels="*") def signal(self, signo, stack): if signo in (SIGINT, SIGTERM): self.fire(QUIT("Received SIGTERM, terminating...")) self.fire(events.terminate()) ####################################################### # Event Handlers # # These handle events and fire the pancakesbot events # ####################################################### @handler("privmsg") def _on_text(self, user, target, message): user += (self.user_mngr.get_user_id(user),) if (message.startswith("\x01ACTION ")) and (message[-1] == "\x01"): message = message[7:] self.logger.debug("ACTION: {}({})@{}: {}".format(user[0], user[3], target, message)) self.fire(events.on_action(user, target, message), "plugins") else: self.logger.debug("MSG: {}({})@{}: {}".format(user[0], user[3], target, message)) self.fire(events.on_text(user, target, message), "plugins") @handler("ready") def _on_ready(self, component): self.logger.info("Ready. " "Connecting to {0.network}:{0.port}".format(self)) self.fire(connect(self.network, self.port)) @handler("connected") def _on_connected(self, network, port): self.logger.info("Connected. Signing in as {0.nick}".format(self)) self.fire(NICK(self.nick)) self.fire(USER("pancakes", "pancakes", network, "robot")) self.fire(events.on_logon(network, port), "plugins") @handler("disconnected") def _on_disconnected(self): self.logger.info("Disconnected.") self.fire(events.on_disconnect(), "plugins") if self.terminate: raise SystemExit(0) else: self.logger.info("Reconnecting.") self.fire(events.on_reconnect(), "plugins") self.fire(connect(self.network, self.port)) @handler("terminate") def _on_terminate(self): self.terminate = True self.fire(events.on_exit(), "plugins") self.logger.info("Terminating.") raise SystemExit(0) @handler("join") def _on_join(self, user, channel): # Send a who for channel on join if user[0] == self.nick: self.fire(WHO(channel)) user += (self.user_mngr.get_user_id(user),) self.fire(events.on_join(user, channel), "plugins") self.logger.info("JOIN: {} ({}) to {}".format(user[0], user[3], channel)) @handler("part") def _on_part(self, user, channel, *args): if args: message = args[0] else: message = "" user += (self.user_mngr.get_user_id(user),) self.fire(events.on_part(user, channel, message), "plugins") self.logger.debug("PART: {} ({}) from {} ({})".format(user[0], user[3], channel, message)) @handler("quit") def _on_quit(self, user, *args): if args: message = args[0] else: message = "" user += (self.user_mngr.get_user_id(user),) self.fire(events.on_quit(user, message), "plugins") self.logger.debug("QUIT: {} ({})".format(user[0], user[3], message)) @handler("notice") def _on_notice(self, user, target, message): user += (self.user_mngr.get_user_id(user),) self.fire(events.on_notice(user, target, message), "plugins") self.logger.debug("NOTICE: {}@{}: {}".format(user[0], target, message)) @handler("invite") def _on_invite(self, nickname, channel): user = self.user_mngr.get_full_user(nick=nickname) self.fire(events.on_invite(user, channel), "plugins") self.logger.info("INVITE: {} ({}) invited us to {}.".format(user[0], user[3], channel)) @handler("kick") def _on_kick(self, user, channel, target, message): user += (self.user_mngr.get_user_id(user),) target = self.user_mngr.get_full_user(nick=target) self.fire(events.on_kick(user, target, channel, message), "plugins") self.logger.debug( "KICK: {} ({}) kicked {} ({}) from {} ({})".format(user[0], user[3], target[0], target[3], channel, message) ) @handler("mode") def _on_mode(self, user, target, mode, *args): if mode == "+b": user += (self.user_mngr.get_user_id(user),) self.fire(events.on_ban(user, args[0], target), "plugins") self.logger.debug("BAN: {} banned {} from {}".format(user[0], args[0], target)) elif mode == "-b": user += (self.user_mngr.get_user_id(user),) self.fire(events.on_unban(user, args[0], target), "plugins") self.logger.debug("UNBAN: {} unbanned {} from {}".format(user[0], args[0], target)) elif mode == "+v": user += (self.user_mngr.get_user_id(user),) t_user = self.user_mngr.get_full_user(nick=args[0]) self.fire(events.on_voice(user, t_user, target), "plugins") self.logger.debug("VOICE: {} gave voice " "to {} in {}".format(user[0], t_user[0], target)) elif mode == "-v": user += (self.user_mngr.get_user_id(user),) t_user = self.user_mngr.get_full_user(nick=args[0]) self.fire(events.on_devoice(user, t_user, target), "plugins") self.logger.debug("DEVOICE: {} removed voice " "from {} in {}".format(user[0], t_user[0], target)) elif mode == "+o": user += (self.user_mngr.get_user_id(user),) t_user = self.user_mngr.get_full_user(nick=args[0]) self.fire(events.on_op(user, t_user, target), "plugins") self.logger.debug("OP: {} gave operator " "to {} in {}".format(user[0], t_user[0], target)) elif mode == "-o": user += (self.user_mngr.get_user_id(user),) t_user = self.user_mngr.get_full_user(nick=args[0]) self.fire(events.on_deop(user, t_user, target), "plugins") self.logger.debug("DEOP: {} removed operator " "from {} in {}".format(user[0], t_user[0], target)) elif mode == "+q": user += (self.user_mngr.get_user_id(user),) t_user = self.user_mngr.get_full_user(nick=args[0]) self.fire(events.on_owner(user, t_user, target), "plugins") self.logger.debug("OWNER: {} gave owner " "to {} in {}".format(user[0], t_user[0], target)) elif mode == "-q": user += (self.user_mngr.get_user_id(user),) t_user = self.user_mngr.get_full_user(nick=args[0]) self.fire(events.on_deowner(user, t_user, target), "plugins") self.logger.debug("DEOWNER: {} removed owner " "from {} in {}".format(user[0], t_user[0], target)) elif target.startswith("#"): user += (self.user_mngr.get_user_id(user),) self.fire(events.on_mode(user, mode, target), "plugins") self.logger.debug("MODE: {} set {} in {} ({})".format(user[0], mode, target, args)) @handler("nick") def _on_nick(self, user, new_nick): self.user_mngr.changed_nick(user, new_nick) user += (self.user_mngr.get_user_id(user),) self.fire(events.on_nick(user, new_nick), "plugins") self.logger.debug("NICK: {} ({}) changed nick to {}".format(user[0], user[3], new_nick)) @handler("topic") def _on_topic(self, user, channel, topic): user += (self.user_mngr.get_user_id(user),) self.logger.debug('TOPIC: {} ({}) changed topic of {} to "{}"'.format(user[0], user[3], channel, topic)) @handler("numeric") def _on_numeric(self, source, numeric, *args): self.logger.debug("NUMERIC: {} - {}".format(numeric, ", ".join(args))) if numeric == ERR_NICKNAMEINUSE: # Change Nick if in use newnick = "{0:s}_".format(args[1]) self.nick = newnick self.fire(NICK(newnick)) if numeric == RPL_WHOREPLY: print(args) user = (args[5], args[2], args[3]) user += (self.user_mngr.get_user_id(user),) print(user) elif numeric in (RPL_ENDOFMOTD, ERR_NOMOTD): self.fire(events.on_connect(self.network, self.port), "plugins") for chan in self.channels: self.fire(JOIN(chan)) # Used to test handlers to determine arguments @handler("test") def _gen_handler(self, *args, **kwargs): if args: for arg in args: self.logger.info("from args: {}".format(arg)) if kwargs: for key, value in kwargs: self.logger.info("from kwargs: {} == {}".format(key, value))
class YoutubeDownloader(Component): channel = 'youtube' EXPECTED_MAX_SIZE = 15 def init(self, quality=3, rate_limit=None): self.manager = Manager() script_dir = get_script_dir() save_dir = os.path.join(script_dir, 'songs') if not os.path.exists(save_dir): os.mkdir(save_dir) print('Script Dir: {}'.format(script_dir)) print('Saving to: {}'.format(save_dir)) self.save_dir = save_dir self.options = [ '--extract-audio', '--prefer-ffmpeg', '--audio-format mp3', '--ffmpeg {}'.format(script_dir), '--audio-quality {}'.format(quality) ] if rate_limit: try: rate_limit = float(rate_limit) except ValueError as e: raise ValueError('rate_limit should be a float.') self.options.append( '--limit-rate {}'.format(str(rate_limit) + 'M')) else: rate_limit = 1 # Make timeout to cancel download relative to rate limiting self.timeout = 2 * self.EXPECTED_MAX_SIZE / rate_limit self.queue = deque() self.downloading = None self.process = None self.yt_dl = 'youtube-dl' if sys.platform.startswith('win'): self.yt_dl += '.exe' self.timer = None self.start_time = None @handler('youtube_download') def download(self, url): self.queue.append(url) self.fire(process_download(), 'youtube') @handler('process_download') def _process_download(self): if not self.downloading and self.queue: if not self.process: self.downloading = self.queue.popleft() flags = ' '.join(self.options) # Still not sure why I have to give a stock Manager() rather than self. self.process = Process( self.yt_dl + ' ' + flags + ' ' + self.downloading, cwd=self.save_dir).register(self.manager) self.process.start() self.start_time = time.clock() if not self.timer: self.timer = Timer(1, poll_process(), persist=True).register(self) def _shutdown_process(self): self.process.kill() self.process = None self.timer.stop() self.timer.unregister() self.timer = None @handler('poll_process') def _poll_process(self): status = self.process.status if (time.clock() - self.start_time) > self.timeout: self._shutdown_process() self.fire( youtube_error('Timeout while downloading {}'.format( self.downloading)), '*') self.downloading = None self.fire(process_download()) return if status is not None: self._shutdown_process() if status == 0: self.fire(youtube_download_complete(self.downloading), '*') else: self.fire(youtube_error(self.downloading), '*') self.downloading = None self.fire(process_download())
class Matelight(ConfigurableComponent): """Matelight connector with some minimal extra facilities""" channel = "matelight" configprops = { 'host': {'type': 'string', 'default': 'matelight'}, 'port': {'type': 'integer', 'default': 1337}, 'gamma': {'type': 'number', 'default': 1.1}, 'size': { 'type': 'object', 'properties': { 'width': {'type': 'integer', 'default': 40}, 'height': {'type': 'integer', 'default': 16} }, 'default': { 'width': 40, 'height': 16 } } } def __init__(self, *args, **kwargs): super(Matelight, self).__init__('MATELIGHT', *args, **kwargs) self.log("Initializing matelight output") self.size = (self.config.size['width'], self.config.size['height']) self.gamma = self.config.gamma self.fading = None self.auto_restart = True self.output_broken = False self.last_frame = np.zeros(self.size, np.uint8) self.fade_timer = None self.init_timer = Timer(5, fade_out_ml()).register(self) self.refresh_timer = None self.fireEvent(cli_register_event("test_matelight", cli_test_matelight)) self.cli_test_matelight(None) def started(self, *args): self.log("Starting matelight output on device %s:%i" % (self.host, self.port)) def cli_test_matelight(self, event): self.log('Displaying test image') import os, cv2 as cv path = os.path.abspath(os.path.join(__file__, "../../testscreen.png")) self.log("Path of image:", path, lvl=verbose) test_image = cv.imread(path) test_image = cv.cvtColor(test_image, cv.COLOR_BGR2RGB) self._transmit(test_image) def fade_out_ml(self, event): if self.fading is None: self.fading = 20 self.fade_timer = Timer(1 / 60.0, fade_out_ml(), persist=True).register( self) elif self.fading > 0: new_frame = (self.last_frame * 0.9).astype(np.uint8) self._transmit(new_frame) self.fading -= 1 elif self.fading <= 0: self._clear() self.fade_timer.unregister() self.fading = None def _clear(self): self.log('Clearing') img = np.zeros((self.config.size['width'], self.config.size['height'], 3), np.uint8) self._transmit(img) def clear_ml(self, event): self._clear() @handler('refresh_ml') def refresh_ml(self, event): self._transmit(self.last_frame) self.refresh_timer = Timer(1, refresh_ml()).register(self) def _transmit(self, image): self.log("New transmission request", lvl=verbose) if self.output_broken and not self.auto_restart: return self.log('Transmitting image, shape:', image.shape, lvl=verbose) self.last_frame = image if self.gamma != 1: image = (image * self.gamma).astype(np.uint8) ml_data = bytearray(image) + b"\00\00\00\00" try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(ml_data, (self.config.host, self.config.port)) except Exception as e: self.log("Error during matelight transmission: ", e) self.output_broken = True if self.refresh_timer is not None: self.refresh_timer.unregister() self.refresh_timer = Timer(1, refresh_ml()).register(self) @handler("transmit_ml") @handler(transmit_ml) def transmit_ml(self, event): if self.fade_timer is not None: self.fade_timer.unregister() self.fade_timer = None self._transmit(event.frame)
class TextRender(ConfigurableComponent): """Text Renderer""" channel = "AVIO" configprops = { 'channel': { 'type': 'integer' }, 'playing': { 'type': 'boolean', 'default': False }, 'movement': { 'type': 'object', 'properties': { 'speed': { 'type': 'number', 'default': 1.0 }, 'direction': { 'type': 'number', 'default': 90 } }, }, 'loop': { 'type': 'boolean', 'default': True }, 'bounce': { 'type': 'boolean', 'default': False }, 'font': { 'type': 'string', 'default': '' } } def __init__(self): super(TextRender, self).__init__("GIFMASTER") self.timer = None @handler(subscribe, channel="isomer-web") def subscribe(self, event): self.log("Subscription Event:", event.client) if event.client.uuid not in self.players[event.data].clients: self.players[event.data].clients.append(event.client.uuid) @handler(unsubscribe, channel="isomer-web") def unsubscribe(self, event): self.log("Unsubscription Event:", event.client) if event.client.uuid in self.players[event.data].clients: self.players[event.data].clients.remove(event.client.uuid) @handler("userlogout", channel="isomer-web") def userlogout(self, event): self.stop_client(event) @handler("clientdisconnect", channel="isomer-web") def clientdisconnect(self, event): """Handler to deal with a possibly disconnected simulation frontend :param event: ClientDisconnect Event """ self.stop_client(event) def stop_client(self, event): try: for player in self.players: if event.clientuuid in player.clients: player.clients.remove(event.clientuuid) self.log("Remote simulator disconnected") else: self.log("Client not subscribed") except Exception as e: self.log("Strange thing while client disconnected", e, type(e)) def cli_test_text(self, *args): if 'stop' in args: self.log("Stopping test video") self.stop() else: self.log('Running test video') self.config['filename'] = os.path.abspath( os.path.join(__file__, "../../test.gif")) self.get_frames() self.timer = Timer(self.config['delay'] / 1000.0, render(), persist=True).register(self) def update(self): self.log('Updating', self.playing, self.config['playing']) if self.config['playing'] is True: if self.playing is False: self.log('Beginning playback') self.play() else: if self.playing is True: self.log('Beginning playback') self.stop() def started(self, event, thing): self.log('Converting image') def task_success(self, event, call, result): self.log("Worker finished:", event.channels[0], pretty=True, lvl=verbose) if event.channels[0] != "gifimport_" + self.uniquename: self.log("Not for us.", lvl=verbose) return if len(result) == 3: frames, durations, log = result else: self.log("Unexpected result:", result, pretty=True) return if len(frames) > 0: self.frames = frames self.durations = durations self.cur = 0 self.breakpoint = len(self.frames) - 1 self.startpoint = 0 self.reversed = False else: self.log("No frames extracted:", log, lvl=warn) def render(self): # pos = self.x, self.y # self.log('Rendering %s' % self.config['filename'], lvl=verbose) if self.playing: self.delta += time.time() - self.ptime if self.delta > self.frames[self.cur][1]: while self.delta > self.frames[self.cur][1]: self.delta -= self.frames[self.cur][1] if self.reversed: self.cur -= 1 if self.cur < self.startpoint: self.cur = self.breakpoint else: self.cur += 1 if self.cur > self.breakpoint: self.cur = self.startpoint if self.frames[self.cur][1] == 0: break self.ptime = time.time() try: frame = self.frames[self.cur][0] # self.log('Firing event', frame) if len(self.clients) > 0: self._broadcast(frame) self.fireEvent(mix_image(self.config['channel'], frame), "AVIO") except IndexError: pass def _broadcast(self, frame): message = { 'component': 'avio.gifplayer', 'action': 'frame_update', 'data': { 'channel': self.config['channel'], 'frame': frame.tolist() } } self.fireEvent(broadcast("clientgroup", message, group=self.clients), "isomer-web") def set_speed(self, factor): self.log('Setting new speed: %f' % factor) for i, duration in enumerate(self.durations): self.frames[i][1] = duration * factor def seek(self, num): self.cur = num if self.cur < 0: self.cur = 0 if self.cur >= len(self.frames): self.cur = len(self.frames) - 1 def set_bounds(self, start, end): if start < 0: start = 0 if start >= len(self.frames): start = len(self.frames) - 1 if end < 0: end = 0 if end >= len(self.frames): end = len(self.frames) - 1 if end < start: end = start self.startpoint = start self.breakpoint = end def stop(self): self.log('Stop!', lvl=debug) self.playing = False if self.timer is not None: self.timer.stop() self.timer.unregister() self.timer = None def play(self): self.log('Play!', lvl=debug) self.playing = True if self.timer is None: self.ptime = time.time() self.timer = Timer(self.config['delay'] / 1000.0, render(), persist=True).register(self) def rewind(self): self.seek(0) def fastforward(self): self.seek(self.length() - 1) def get_height(self): return self.image.size[1] def get_width(self): return self.image.size[0] def get_size(self): return self.image.size def length(self): return len(self.frames) def reverse(self): self.reversed = not self.reversed def reset(self): self.cur = 0 self.ptime = time.time() self.reversed = False
class Matelight(AVIOComponent): """Matelight connector with some minimal extra facilities""" channel = "matelight" def __init__(self, host="matelight", port=1337, *args): super(Matelight, self).__init__(*args) self.log("Initializing matelight output") self.host = host self.port = port self.size = (40, 16) self.gamma = 0.5 self.fading = None self.auto_restart = True self.output_broken = False self.last_frame = np.zeros(self.size, np.uint8) path = os.path.abspath("./images/startscreen_matelight.png") boot_image = cv.imread(path) self.boot_image = cv.cvtColor(boot_image, cv.COLOR_BGR2RGB) self._transmit(self.boot_image) self.fade_timer = None self.init_timer = Timer(5, fade_out_ml()).register(self) self.refresh_timer = Timer(1, refresh_ml(), persist=True).register(self) def started(self, *args): self.log("Starting matelight output on device %s:%i" % (self.host, self.port)) def fade_out_ml(self, event): if self.fading is None: self.fading = 20 self.fade_timer = Timer(1 / 60.0, fade_out_ml(), persist=True).register(self) elif self.fading > 0: new_frame = (self.last_frame * 0.9).astype(np.uint8) self._transmit(new_frame) self.fading -= 1 elif self.fading <= 0: self._clear() self.fade_timer.unregister() self.fading = None def _clear(self): self.log('Clearing') img = np.zeros((40, 16, 3), np.uint8) self._transmit(img) def clear_ml(self, event): self._clear() @handler('refresh_ml') def refresh_ml(self, event): self._transmit(self.last_frame) def _transmit(self, image): if self.output_broken and not self.auto_restart: return self.log('Transmitting image, shape:', image.shape, lvl=verbose) self.last_frame = image if self.gamma != 1: image = (image * self.gamma).astype(np.uint8) ml_data = bytearray(image) + b"\00\00\00\00" try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(ml_data, (self.host, self.port)) except Exception as e: self.log("Error during matelight transmission: ", e) self.output_broken = True def transmit_ml(self, event): if self.fade_timer is not None: self.fade_timer.unregister() self.fade_timer = None self._transmit(event.frame) @handler('keypress') def keypress(self, event): if event.ev.key == 223 and event.ev.mod == 1: self._transmit(self.boot_image) Timer(2, fade_out_ml()).register(self) if event.ev.mod & pygame.KMOD_LCTRL and event.ev.mod & pygame.KMOD_LSHIFT: if event.ev.key == pygame.K_MINUS: self.gamma = max(0.1, self.gamma - 0.1) elif event.ev.key == pygame.K_PLUS: self.gamma = min(1, self.gamma + 0.1) self.log(self.gamma)