diff -r 313aad7435a8 -r bb150048d329 slotUI/SlotCli.py --- a/slotUI/SlotCli.py Sun Dec 02 18:03:06 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,651 +0,0 @@ -#!/usr/bin/env python -""" -Freeslot project -Command line interface -""" - -from freeslot import Blackbox, LOGLEVEL -from optparse import OptionParser -from operator import itemgetter -from subprocess import Popen, PIPE -import sys, os -from copy import copy -import curses -from time import sleep - -import SimpleXMLRPCServer -import xmlrpclib -import threading - - -VERSION = "1.7" -MAXSLOTS = 6 -TERM = { - "caption": "\033[1;37m\033[1;44m", - "text": "\033[1;30m", - } - -# disable debug log output -LOGLEVEL = 10 - -SOUNDPREFIX = "quake-" -EVENTPREFIX = "event/" -SOUNDS = { - "countdown_start": os.path.abspath(SOUNDPREFIX + "sound/countdown.mp3"), - "race_start": os.path.abspath(SOUNDPREFIX + "sound/racestart.mp3"), - "race_prepare": os.path.abspath(SOUNDPREFIX + "sound/prepare.mp3"), - "lap_record": os.path.abspath(SOUNDPREFIX + "sound/laprecord.mp3"), - "fuel_warning1": os.path.abspath(SOUNDPREFIX + "sound/fuel1.mp3"), - "fuel_warning2": os.path.abspath(SOUNDPREFIX + "sound/fuel2.mp3"), - "fuel_full": os.path.abspath(SOUNDPREFIX + "sound/fuel_full.mp3"), - "pitlane_enter": os.path.abspath(SOUNDPREFIX + "sound/pitlane_enter.mp3"), - "pitlane_exit": os.path.abspath(SOUNDPREFIX + "sound/pitlane_exit.mp3"), - "data_error": os.path.abspath(SOUNDPREFIX + "sound/data_error.mp3"), - "panic": os.path.abspath(SOUNDPREFIX + "sound/panic.mp3"), - "panic_shortcut": os.path.abspath(SOUNDPREFIX + "sound/panic_shortcut.mp3"), - "resume": os.path.abspath(SOUNDPREFIX + "sound/resume.mp3"), - "win": os.path.abspath(SOUNDPREFIX + "sound/win.mp3"), - - } - -def trigger_sound(what): - if what in SOUNDS: - Popen(["/usr/bin/mpg123", "-q", SOUNDS[what]]) - #os.spawnlp(os.P_NOWAIT, "/usr/bin/mpg123", "mpg123", SOUNDS[what]) - #Popen(["/usr/bin/mpg123", SOUNDS[what]]).pid - -def trigger_event(what, slot = 0): - trigger_sound(what) - Popen(["/bin/sh", os.path.abspath(EVENTPREFIX + what), str(slot)]) - -class SlotServer(threading.Thread): - def __init__(self, blackbox): - threading.Thread.__init__(self) - self.server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8000)) - self.server.register_instance(blackbox) - #self.server.register_function(lambda astr: '_' + astr, '_string') - self.daemon = True - - def run(self): - self.server.serve_forever() - -class SlotClient(): - def __init__(self, url): - self.box = xmlrpclib.Server(url) - -class SlotCli(): - def __init__(self, test = None, dev=""): - self.box = Blackbox() - if (not test): - self.box.connect(dev) - self.rpcserver = SlotServer(self.box) - self.rpcserver.start() - self.slot_dummy = { - "name": "Unnamed", - "laps": 0, - "laps_last": 0, - "last": 0.00, - "best": 0.00, - "fuel": 0, - "fuel_last": 0, - "position": 0, - "drive": 0, - "status": "Idle", - "clk": 0, - "car": 0, - "limit": 15, - } - - self.slot = [ - copy(self.slot_dummy), copy(self.slot_dummy), - copy(self.slot_dummy), copy(self.slot_dummy), - copy(self.slot_dummy), copy(self.slot_dummy), - ] - self.reset_slots() - self.sysclk = 0.00 - self.sysclk_last = 0.00 - self.bestlap = 9999999.00 - self.test = test - self.laplimit = 999; - self.timelimit = 0; - - def reset_slots(self): - idx = 0 - for slt in self.slot: - slt["laps"] = 0 - slt["laps_last"] = 0 - slt["last"] = 0.00 - slt["best"] = 0.00 - slt["fuel"] = 100 - slt["fuel_last"] = 0 - slt["position"] = idx - slt["car"] = idx # used for sort order calculation - slt["status"] = self.slot_dummy["status"] - slt["clk"] = 0 - slt["limit"] = 15 - idx += 1 - self.bestlap = 99999.00 - self.raceactive = False - - def update_positions(self): - order1 = sorted(self.slot, key=itemgetter( - "clk")) - order2 = sorted(self.slot, key=itemgetter( - "laps"), reverse=True) - idx = 1 - for tst in order2: - self.slot[tst["car"]]["position"] = idx - idx += 1 - - def render_slots(self): - self.update_positions() - self.scr.addstr(3,0, - #"Pos | #/Name | Laps | Best | Last | Fuel | Status ", - "Pos | #/Name ", - curses.color_pair(2)) - self.scr.addstr(4,4, - " Laps | Best | Last | Fuel | Status ", - curses.color_pair(2)) - for idx in range(MAXSLOTS): - """ - self.scr.addstr((3 + (self.slot[idx]["position"] * 2)), 0, - "%3i | %i %15s | %4i | %7.2fs | %7.2fs | %3i%% | %10s" % ( - self.slot[idx]["position"], - self.slot[idx]["car"] + 1, self.slot[idx]["name"], - self.slot[idx]["laps"], - self.slot[idx]["best"], - self.slot[idx]["last"], - self.slot[idx]["fuel"], - self.slot[idx]["status"], - ), - curses.color_pair(11 + idx) ) - """ - if idx > 3: - namesuffix = " (%i)" % self.slot[idx]["drive"] - else: - namesuffix = "" - - self.scr.addstr((3 + (self.slot[idx]["position"] * 2)), 0, - "%3i | %i %15s %48s" % ( - self.slot[idx]["position"], - self.slot[idx]["car"] + 1, (self.slot[idx]["name"] + namesuffix), - "", - ), - curses.color_pair(11 + idx) ) - self.scr.addstr((4 + (self.slot[idx]["position"] * 2)), 4, - " %4i | %7.2fs | %7.2fs | %3i%% | %10s | %2i% 15s" % ( - self.slot[idx]["laps"], - self.slot[idx]["best"], - self.slot[idx]["last"], - self.slot[idx]["fuel"], - self.slot[idx]["status"], - self.slot[idx]["limit"], - "" - ), - curses.color_pair(11 + idx) ) - - def cleartop(self): - self.scr.addstr(0,0, "%60s" % "Race Limits: %i Laps / %i Minutes" % (self.laplimit, self.timelimit)) - self.scr.addstr(1,0, "%80s" % " ") - - def readName(self, slot): - self.scr.nodelay(0) # enable delay on readkey - curses.echo() - self.scr.addstr(0,0, "Enter Name for Controller %i [%s]:" % ( - slot + 1, - self.slot[slot]["name"]), - curses.color_pair(1)) - self.scr.refresh() - name = self.scr.getstr(1,0, 15) - if name != "": - self.slot[slot]["name"] = name - self.cleartop() - self.scr.refresh() - curses.noecho() - self.scr.nodelay(1) # disable delay on readkey - - def readLimit(self, slot): - limit = self.readInt("SPEEDLIMIT for %s (%i)" % ( - self.slot[slot]["name"], - slot + 1), - self.slot[slot]["limit"], 15) - if limit: - self.slot[slot]["limit"] = limit - self.cleartop() - self.box.speedlimit(slot, limit) - - - def readInt(self, msg, default, maximum = 999999): - self.scr.nodelay(0) # enable delay on readkey - curses.echo() - self.scr.addstr(0,0, "%s [%i]:" % ( - msg, - default), - curses.color_pair(1)) - self.scr.refresh() - inp = self.scr.getstr(1,0, 4) - if inp != "": - try: - inp = int(inp) - if inp > maximum: inp = maximum - except Exception: - inp = None - else: - inp = None - self.cleartop() - self.scr.refresh() - curses.noecho() - self.scr.nodelay(1) # disable delay on readkey - return inp - - - def monitor_init(self, live = 1): - """ - Send initializing commands for live monitoring - """ - self.box.query("F1\n") # set fuel logic enabled - self.box.query("*%i\n" % live) # set live fuel info - - def monitor_learn(self, slot): - # clear garbage in UART rx buffer - self.box.query("*0\n") # set live fuel info - self.box.query("*0\n") # set live fuel info - while self.box.readline() != "": pass - - trk = False - spd = 0 - trk_old = False - spd_old = 0 - clock = -1 - - self.monitor_init(slot + 2) - while 1: - #key = self.scr.getch() - #if key == ord('c'): break - - # is there something in the rx buffer? - rx = self.box.readline() - if (rx != ""): - try: - data = rx.split(":") - if rx[:3] == "LN:": - if clock >= 0: - clock += 1 - spd = int(data[1], 16) - trk = (data[2] != 'X') - if (spd != spd_old) or (trk != trk_old): - if clock < 0: - clock = 0 - print "%i,%i,%s" % (clock, spd, trk) - trk_old = trk - spd_old = spd * 1 - if rx[:2] == "L:": - # update lap time info - l = int(data[2], 16) - s = int(data[3]) - 1 - t = int(data[4], 16) / 2000.00 - if (slot == s): - print "# lap %i complete: %3.2f seconds" % (l, t) - clock = 0 - print "%i,%i,%s" % (clock, spd, trk) - except: - print "RX ERROR: " % rx - - def monitor_playback(self, slot, filename): - # clear garbage in UART rx buffer - self.box.query("*0\n") # set live fuel info - self.box.query("*0\n") # set live fuel info - sleep(1) - cli.box.speedminimum(slot, 0 ) - while self.box.readline() != "": pass - - clock = -5 - trkfile = open(filename, "r").readlines() - print "Loading %s..." % filename - - while 1: - try: - for l in trkfile: - l = l.strip() - if (l != "") and (l[:1] != "#"): - print "Line: %s" % repr(l) - data = l.split(",") - speed = int(data[1]) - while (clock < int(data[0]) and (int(data[0]) > 0)): - clock += 1 - sleep(0.07) - print "CLK %i/%i -> set: %i" % (clock, int(data[0]), speed) - cli.box.speedminimum(slot, speed ) - # now wait for lap sync :) - while self.box.readline() != "": pass - rx = "" - while rx[:2] != "L:": - rx = self.box.readline() - data = rx.split(":") - l = int(data[2], 16) - s = int(data[3]) - 1 - t = int(data[4], 16) / 2000.00 - print "# lap %i complete: %3.2f seconds" % (l, t) - clock = -3 - except Exception, e: - print repr(e) - sys.exit(1) - except KeyboardInterrupt: - print "resetting" - cli.box.speedminimum(slot, 0 ) - cli.box.speedminimum(slot, 0 ) - sys.exit(0) - - - - def monitor(self): - """ - Live Monitor on the console - Keyboard loop to control it??? - """ - # clear garbage in UART rx buffer - while self.box.readline() != "": pass - - self.monitor_init() - self.scr = curses.initscr() - curses.start_color() - curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) # standard text - curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLUE) # label - curses.init_pair(11, curses.COLOR_BLACK, curses.COLOR_YELLOW) # player 1 slot - curses.init_pair(12, curses.COLOR_BLACK, curses.COLOR_GREEN) # player 2 slot - curses.init_pair(13, curses.COLOR_BLACK, curses.COLOR_RED) # player 3 slot - curses.init_pair(14, curses.COLOR_BLACK, curses.COLOR_MAGENTA) # player 4 slot - curses.init_pair(15, curses.COLOR_WHITE, curses.COLOR_BLACK) # player 5 slot - curses.init_pair(16, curses.COLOR_WHITE, curses.COLOR_BLACK) # player 6 slot - curses.noecho() # disable key echo - curses.cbreak() # do not buffer keypresses - self.scr.keypad(1) # enable special keys - self.scr.nodelay(1) # disable delay on readkey - - self.cleartop() - self.render_slots() - self.scr.refresh() - - - while 1: - key = self.scr.getch() - if key == ord('c'): break - elif key == ord(' '): self.box.query("+") # panic / resume - elif key == 10: self.box.query("#") # remote start button press - elif key == ord('1'): self.readName(0) - elif key == ord('2'): self.readName(1) - elif key == ord('3'): self.readName(2) - elif key == ord('4'): self.readName(3) - elif key == ord('5'): self.readName(4) - elif key == ord('6'): self.readName(5) - elif key == ord('q'): self.readLimit(0) - elif key == ord('w'): self.readLimit(1) - elif key == ord('e'): self.readLimit(2) - elif key == ord('r'): self.readLimit(3) - elif key == ord('t'): self.readLimit(4) - elif key == ord('z'): self.readLimit(5) - elif key == ord('a'): - if self.slot[4]["drive"] > 0: self.slot[4]["drive"] -= 1 - cli.box.speedminimum(4, self.slot[4]["drive"]) - elif key == ord('s'): - if self.slot[4]["drive"] < 16: self.slot[4]["drive"] += 1 - cli.box.speedminimum(4, self.slot[4]["drive"]) - elif key == ord('y'): - if self.slot[5]["drive"] > 0: self.slot[5]["drive"] -= 1 - cli.box.speedminimum(5, self.slot[5]["drive"]) - elif key == ord('x'): - if self.slot[5]["drive"] < 16: self.slot[5]["drive"] += 1 - cli.box.speedminimum(5, self.slot[5]["drive"]) - elif key == ord('t'): - tmp = self.readInt("Set new Race TIME limit", self.timelimit) - if tmp: self.timelimit = tmp - elif key == ord('l'): - tmp = self.readInt("Set new Race LAP limit", self.laplimit) - if tmp: self.laplimit = tmp - - - # is there something in the rx buffer? - rx = self.box.readline() - if (rx != "") or self.test: - self.scr.addstr(17,0, - "Last RX: %19s" % rx, curses.color_pair(2)) - self.scr.redrawwin() - self.scr.refresh() - # we have received something - try: - data = rx.split(":") - if rx[:2] == "L:": - # update lap time info - l = int(data[2], 16) - slot = int(data[3]) - 1 - t = int(data[4], 16) / 2000.00 - self.sysclk = int(data[5], 16) / 2000.00 - self.slot[slot]["laps_last"] = self.slot[slot]["laps"] - self.slot[slot]["laps"] = l - self.slot[slot]["last"] = t - self.slot[slot]["clk"] = self.sysclk - if (self.slot[slot]["best"] > t) or (self.slot[slot]["best"] == 0): - self.slot[slot]["best"] = t - if self.bestlap > t: - trigger_event("lap_record", slot + 1) - self.bestlap = t - - self.slot[slot]["status"] = "IN-RACE" - if (self.slot[slot]["laps_last"] != l) and (l == self.laplimit): - # we have lap limit reached! - trigger_event("win", slot + 1) - self.raceactive = False - self.slot[slot]["status"] = "WINNER!" - self.box.query("+") # stop race - - self.render_slots() - - if rx[:2] == "F:": - # update fuel level - slot = int(data[1]) - f = int(data[2], 16) - f = f / 100 # fuel in percent - self.sysclk = int(data[3], 16) / 2000.00 - self.slot[slot]["fuel_last"] = self.slot[slot]["fuel"] - self.slot[slot]["fuel"] = f - if self.slot[slot]["fuel_last"] != f: - if (self.slot[slot]["fuel_last"] == 16) and (f == 15): - # 15 percent fuel, set speed limit for car to 8 - # warning sound - trigger_event("fuel_warning1", slot + 1) - cli.box.speedlimit(slot, 8) - if (self.slot[slot]["fuel_last"] == 6) and (f == 5): - # 5 percent, set speed limit for car to 6 - # warning sound - trigger_event("fuel_warning2", slot + 1) - cli.box.speedlimit(slot, 6) - if (self.slot[slot]["fuel_last"] == 1) and (f == 0): - # fuel empty - # set speedlimit to 4 - cli.box.speedlimit(slot, 4) - if (self.slot[slot]["fuel_last"] < f) and (f >= 11) and (f < 20): - cli.box.speedlimit(slot, 15) - if (self.slot[slot]["fuel_last"] < f) and (f == 100): - trigger_event("fuel_full", slot + 1) - self.render_slots() - - if rx[:1] == "~": - # jumpstart occured - slot = int(rx[1:2]) - t = int(data[1], 16) / 2000.00 - self.slot[slot]["jumpstart"] = t - self.slot[slot]["status"] = "Jumpstart!" - - if rx[:3] == "RW:": - # ResponseWire packet, do nothing at the moment, just decode - slot = int(data[1]) - devtype = int(data[2]) - sender = int(data[3], 16) - status = int(data[4], 16) - self.sysclk = int(data[5], 16) - if (devtype == 4): - # pitlane sent something - if (status == 5): - self.slot[slot]["status"] = "PITLANE" - trigger_event("pitlane_enter", slot + 1) - if (status == 7): - self.slot[slot]["status"] = "IN-RACE" - trigger_event("pitlane_exit", slot + 1) - - self.render_slots() - - if rx == "!RACE PREPARE": - # reset current race status - # and display PREPARE PHASE - self.reset_slots() - for slot in range(MAXSLOTS): - self.slot[slot]["status"] = "Prepare" - trigger_event("race_prepare") - - if rx == "!RACE START": - for slot in range(MAXSLOTS): - if self.slot[slot]["status"] == "~~~~~~~~~~": - self.slot[slot]["status"] = "Idle" - trigger_event("race_start") - self.raceactive = True - - if rx == "!COUNTDOWN": - # countdown initiated - for slot in range(MAXSLOTS): - self.slot[slot]["status"] = "~~~~~~~~~~" - trigger_event("countdown_start") - - if rx == "!PANIC": - # panic mode - trigger_event("panic") - - if rx == "!SHORTCUT": - # panic mode - trigger_event("panic_shortcut") - - if rx == "!RESUME": - # panic mode - trigger_event("resume") - - - if ((self.timelimit > 0) and (self.raceactive) and - (self.sysclk_last != self.sysclk) and - ((self.sysclk / 60) >= self.timelimit)): - self.sysclk_last = self.sysclk - self.raceactive = False - # we have time limit reached! - self.box.query("+") # stop race - trigger_event("win") - # get the one with position 1 - for slot in self.slots: - if slot["position"] == 1: - slot["status"] = "WINNER!" - self.render_slots() - self.sysclk_last = self.sysclk - - - - self.scr.addstr(17,31, - "Race Timer: %7.3f min" % (self.sysclk / 60), - curses.color_pair(2)) - - self.scr.refresh() - - except Exception: - trigger_event("data_error") - pass - - # terminate - curses.nocbreak() - self.scr.keypad(0) - curses.echo() - curses.endwin() - return None - - def cyclemode(self): - pass - -if __name__ == "__main__": - parser = OptionParser(version="%prog " + VERSION) - parser.add_option("--live", dest="live", action="store_true", default=False, - help="Run Live monitor on console", metavar="[0-5]") - parser.add_option("--learn", dest="learn", action="store_true", default=False, - help="Run Learning mode for [slot]", metavar="[0-5]") - parser.add_option("--teach", dest="playback", - help="Playback teach file", metavar="[filename]") - - parser.add_option("--slot", dest="carid", - help="Required for programming a car directly", metavar="[1-6]") - parser.add_option("--fuel", dest="fuel", - help="Set maximum CAR fuel level", metavar="[0-15]") - parser.add_option("--brake", dest="brake", - help="Set CAR brake strength", metavar="[0-15]") - parser.add_option("--accel", dest="accel", - help="Set CAR acceleration ", metavar="[6-15]") - parser.add_option("--blink", dest="blink", - help="Set car lights blinking state", metavar="[on|off]") - parser.add_option("--limit", dest="limit", - help="Controlled SPEED LIMIT (15 = no limit)", metavar="[0-15]") - parser.add_option("--drive", dest="drive", - help="Controlled SPEED MINIMUM (0 = disabled)", metavar="[0-15]") - parser.add_option("--test", dest="test", action="store_true", default=False, - help="", metavar="") - parser.add_option("--dev", dest="dev", default="/dev/ttyUSB0", - help="Communication port", metavar="[/dev/ttyUSB0]") - - (options, args) = parser.parse_args() - #if not options.dev: - # options.dev = "/dev/ttyUSB0" - - if options.live or options.learn or options.playback: - cli = SlotCli(options.test, options.dev) - else: - cli = SlotClient('http://localhost:8000') - # should a CLI function be started? - - if options.live: - # start the live monitor - cli.monitor() - sys.exit(0) - - # check commandline if we have to program something - if not options.carid: - print "Option --slot is required for all car programming commands!\nUse --help to get a list of available commands" - sys.exit(1) - else: - options.carid = int(options.carid) - 1 - if (options.carid < 0) or (options.carid > 6): - print "Error: Invalid slot selected" - sys.exit(1) - - if options.learn: - # start the learn monitor - cli.monitor_learn(options.carid) - sys.exit(0) - - if options.playback: - # start the playback monitor - cli.monitor_playback(options.carid, options.playback) - sys.exit(0) - - if options.fuel: - print "setFuel: " + cli.box.progcar(int(options.carid), "fuel", int(options.fuel)) - - if options.accel: - print "setAccel: " + cli.box.progcar(int(options.carid), "accel", int(options.accel)) - - if options.brake: - print "setBrake: " + cli.box.progcar(int(options.carid), "brake", int(options.brake)) - - if options.blink: - state = False - if options.blink == "on": - state = True - print "setBlink: " + cli.box.blinkcar(int(options.carid), state) - - if options.limit: - print "Change Speed Limit: " + cli.box.speedlimit(int(options.carid), int(options.limit)) - - if options.drive: - print "Change minimum Speed drive: " + cli.box.speedminimum(int(options.carid), int(options.drive)) -