#!/usr/bin/python import os, sys, socket, select, pygame from optparse import OptionParser parser= OptionParser() parser.add_option("--server", dest="server", default="leds", help="k74 server host name or ip address") parser.add_option("--port-watch", type="int", dest="pw", default="7299", help="base port number for k74 watch") parser.add_option("--port-set", type="int", dest="ps", default="7200", help="base port number for k74 set requests") parser.add_option("--port-clear", type="int", dest="pc", default="7300", help="base port number for k74 clear requests") parser.add_option("--label-font-size", type="int", dest="lfs", default="36", help="label font size") parser.add_option("--read-only", action="store_true", dest="ro", default=False, help="perform no state changes here") parser.add_option("--sound", action="store_true", dest="sound", default=False, help="enable sound effects") parser.add_option("--title", dest="title", default="k74 view", help="window title name") parser.add_option("--assets", dest="assets", default="/usr/share/k74", help="location of package imagery and sound assets") parser.add_option("--horizontal", action="store_true", dest="horizontal", default=False, help="layout horizontal") (opt, args) = parser.parse_args() class MultipleImageSprite(pygame.sprite.Sprite): """ a sprite class consisting of multiple images overlaid the images are blitted over each other in the order they are added """ def __init__(self): pygame.sprite.Sprite.__init__(self) def mi_begin(self): self.ml = [] self.mr = None def mi_add(self, image, rect): self.ml.append((image, rect)) if self.mr == None: self.mr = rect else: self.mr = pygame.Rect.union(self.mr, rect) def mi_add_image(self, image): rect = image.get_rect() self.mi_add(image, rect) def mi_commit(self): width = self.mr.width height = self.mr.height self.image = pygame.Surface((width, height), pygame.SRCALPHA, 32) for x in self.ml: (image, rect) = x rect.center = (width/2, height/2) self.image.blit(image, rect) self.rect = self.image.get_rect() class BitSprite(MultipleImageSprite): def __init__(self, x, y, label, name): MultipleImageSprite.__init__(self) self.mi_begin() try: g1 = pygame.image.load(name+'.png') except: g1 = pygame.image.load(os.path.join(opt.assets, name+'.png')) g1 = g1.convert_alpha() self.mi_add_image(g1) font = pygame.font.Font(None, opt.lfs) text = font.render(label, 1, (0, 0, 0)) self.mi_add_image(text) self.mi_commit() self.rect.center = (x, y) class Client: def __init__(self, port, reconnect=False): self.socket = None self.port = port self.reconnect = reconnect self.status = 0 self.connect() def connect(self): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect((opt.server, self.port)) # FIXME: can get socket.error:(111, 'Connection refused') if # the server is not yet running, we should probably show that # and try again. self.buffer = '' def close(self): self.socket.close() self.socket = None if self.reconnect: self.connect() def recv(self): while 1: is_readable = [self.socket] is_writable = [] is_error = [self.socket] r, w, e = select.select(is_readable, is_writable, is_error, 0.1) if e: self.close() return False if not r: return False try: byte = self.socket.recv(1) if len(byte) == 1: self.recv_byte(byte) return True else: self.recv_eof() return False except: self.recv_eof() return False def recv_byte(self, byte): if byte == '\n': self.status = int(self.buffer) self.buffer = '' else: self.buffer = self.buffer + byte def recv_eof(self): self.close() def led_set(led): if opt.ro: return request = Client(opt.ps + led) request.close() def led_clear(led): if opt.ro: return request = Client(opt.pc + led) request.close() def mb(event): if opt.horizontal: i = event.pos[0] / 100 + 1 else: i = event.pos[1] / 100 + 1 if event.button == 1: if opt.sound: sound_click.play() led_set(i) elif event.button == 3: if opt.sound: sound_click.play() led_clear(i) def kb(event): keys = { pygame.K_1: 1, pygame.K_2: 2, pygame.K_3: 3, pygame.K_4: 4, pygame.K_5: 5, pygame.K_6: 6, pygame.K_7: 7, pygame.K_8: 8, pygame.K_KP1: 1, pygame.K_KP2: 2, pygame.K_KP3: 3, pygame.K_KP4: 4, pygame.K_KP5: 5, pygame.K_KP6: 6, pygame.K_KP7: 7, pygame.K_KP8: 8 } if keys.has_key(event.key): n = keys[event.key] shift = (event.mod == pygame.KMOD_SHIFT or event.mod == pygame.KMOD_LSHIFT or event.mod == pygame.KMOD_RSHIFT) if shift: led_set(n) else: led_clear(n) def led_show_set(x): group.add(bit_sprites_1[x]) group.remove(bit_sprites_0[x]) def led_show_clear(x): group.remove(bit_sprites_1[x]) group.add(bit_sprites_0[x]) def led_flush(): group.clear(screen, background) group.update() dirty = group.draw(screen) pygame.display.update(dirty) def led_show_all(new): for x in range(8): if new & (1 << x): led_show_set(x) else: led_show_clear(x) led_flush() def led_change(xor, new): for x in range(8): if xor & (1 << x): if new & (1 << x): """ a led turned on """ led_show_set(x) if opt.sound: sound_on.play() else: """ a led turned off """ led_show_clear(x) if opt.sound: sound_off.play() led_flush() #! @bug: full screen black flash, due this? if opt.sound: pygame.mixer.pre_init(44100) pygame.init() pygame.display.set_caption(opt.title) if opt.horizontal: size = width, height = 800, 100 else: size = width, height = 100, 800 screen = pygame.display.set_mode(size) def load_sound(name): try: sound = pygame.mixer.Sound(name) except: sound = pygame.mixer.Sound(os.path.join(opt.assets, name)) return sound if opt.sound: sound_on = load_sound('on.ogg') sound_off = load_sound('off.ogg') sound_click = load_sound('click.ogg') try: background = pygame.image.load("background.png") except: background = pygame.image.load(os.path.join(opt.assets, "background.png")) background = background.convert_alpha() bh = background.get_height() bw = background.get_width() for y in range(screen.get_height() / bh + 1): for x in range(screen.get_width() / bw + 1): screen.blit(background, (x*bw, y*bh)) background = screen.copy() group = pygame.sprite.OrderedUpdates(()) bit_sprites_1 = {} bit_sprites_0 = {} if opt.horizontal: mx = 100 my = 0 else: mx = 0 my = 100 for i in range(8): x = i * mx + 50 y = i * my + 50 l = str(i+1) bit_sprites_0[i] = BitSprite(x, y, l, 'led-0-100x100') bit_sprites_1[i] = BitSprite(x, y, l, 'led-1-100x100') pygame.display.flip() watch = Client(opt.pw, reconnect=True) watch.recv() """ display the current status """ new = watch.status led_show_all(new) old = new while 1: if watch.recv(): pygame.time.set_timer(pygame.USEREVENT, 10100) new = watch.status xor = new ^ old; if xor != 0: led_change(xor, new) old = new for event in pygame.event.get(): if event.type == pygame.MOUSEBUTTONDOWN: mb(event) elif event.type == pygame.KEYDOWN: kb(event) elif event.type == pygame.QUIT: sys.exit(0) elif event.type == pygame.USEREVENT: print "k74-view: inactivity timeout, network socket, retrying" watch.close() # FIXME: event log view # FIXME: add explanation for each bit # FIXME: full screen mode on xo # FIXME: if no activity for ten seconds on socket, close and retry # FIXME: if system suspended and then resumed, socket may be left # open, but never receive any further data.