class HelperDict(dict): """ Diese Klasse hält die Configwerte und verhält sich dabei weitestgehend wie ein dict. Beim versuch auf einen nicht vorhandenen Key zuzugreifen wird eine Warning und ein stacktrace ausgegeben, und 0 zurückgeben um den Programmablauf möglichst nicht zu stören """ def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) # Wir setzen einen Scope, wird meist ,it set_debug überschrieben self.debug = Scope("HelperDict") def set_debug(self, debug): """ Setzt den debugscope Das ist dafür gedacht damit die Warnings möglichst aus dem scope der Config kommen. Wenn das nicht gemacht wird, kommen die ausgaben vom Scope "HelperDict" """ self.debug = debug def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError as e: self.debug.error(e, "Config key %s not found." % key) return 0
def __start(self, debugname): """ Interner Starthelfer für den Subprozess """ # Reinitialisieren diverser Problematischer Bereiche # da sie sonst im Subprozess broken sind reload(bitbots.util) reload(bitbots.debug) reload(bitbots.debug.debug) reload(bitbots.debug) self.__debug = Scope("Multiprocessing.%s" % self.__class__.__name__) self.__debug("Subprocess gestartet") self.debug = Scope(debugname) self.__client_con_reader() #initialisieren aller felder in __data_client die es noch nicht #gibt welche aber provides werden, damit beim senden schon #alle namen vorhanden sind, und das nicht immer getesten werden #muss for name in self.__provides: if name not in self.__data_client: self.__data_client[name] = None self.__client_con.send((name, None)) while True: try: self.run() except Exception as e: if isinstance(e, KeyboardInterrupt): # Keyboard interrupt wollen wir nicht fangen! raise try: self.__debug.error(e, "Subprozess has terminated, restart. ") time.sleep(1) except Exception as e: print(e)
def test_create_sub_Scope(self): test = Scope('Test') test_sub1 = test.sub('sub1') self.assertEquals(test_sub1.get_name(), 'Test.sub1') test_sub2 = test.sub('SUB2') self.assertEquals(test_sub2.get_name(), 'Test.SUB2') test_sub3 = test_sub1.sub('bla') self.assertEquals(test_sub3.get_name(), 'Test.sub1.bla')
def test_net_send_string_log(self): self.dbg.reset() debug = Scope('Test') debug.log("Name", "Toller Name mit Umlauten: öä") gevent.sleep(1) msg = self.dbg.get_debug_data(HOST + "::Test.Name") self.assertTrue(msg) # es ist keine nachricht angekommen self.assertEquals(msg[0], "string") self.assertEquals(msg[1], "Toller Name mit Umlauten: öä")
def yaml_include(loader, node): """Include another YAML file.""" debug = Scope("Util.Config.yaml_loader") filename = loader.construct_scalar(node) filename = find_resource(filename + '.yaml') with open(filename) as include_fp: data = yaml.load(include_fp) debug.log("Include-Config '%s' geladen" % filename) return data
def test_net_error_prefix(self): self.dbg.reset() debug = Scope('Test') try: raise Exception("This is a Test Exception") except Exception as e: debug.error(e, "Testprefix: ") gevent.sleep(1) msg = self.dbg.get_debug_data(HOST + "::Test.warning") self.assertTrue(msg) # es ist keine nachricht angekommen self.assertEquals(msg[0], "warning") # wir können hier so nur auf die 2. nachricht zugreifen (momentan traceback) self.assertTrue("This is a Test Exception" in msg[1]) # traceback im msg
def __init__(self): self.glog = Scope("bin.Record", console_out=False) # global logger super(DarwinDebugHandler, self).__init__(self) self.setLevel(logging.DEBUG) self.glog.log("Logging attached to Record-Script") if not __debug__: self.glog.warning("__debug__ is false, some messages might be optimised into nirvana!")
def test_net_console_log(self): self.dbg.reset() debug = Scope('Test') debug.log('Testnachricht') gevent.sleep(1) msg = self.dbg.get_debug_data(HOST + "::Test.log") self.assertTrue(msg) # es ist keine nachricht angekommen self.assertEquals(msg[0], "log") self.assertEquals(msg[1], "Testnachricht") debug.log('Dies ist eine längere Nachricht') gevent.sleep(1) msg = self.dbg.get_debug_data(HOST + "::Test.log") self.assertTrue(msg) # es ist keine nachricht angekommen self.assertEquals(msg[0], "log") self.assertEquals(msg[1], "Dies ist eine längere Nachricht")
def test_create_Scope(self): test = Scope('Test') self.assertEquals(test.get_name(), 'Test') self.assertTrue(test.get_console_out()) test2 = Scope('Test', False) self.assertEquals(test2.get_name(), 'Test') self.assertFalse(test2.get_console_out()) test3 = Scope('Test.hui') self.assertEquals(test3.get_name(), 'Test.hui')
class DarwinDebugHandler(logging.StreamHandler): """ Debug-Handler that passes Messages to the darwin debugging """ def __init__(self): self.glog = Scope("bin.Record", console_out=False) # global logger super(DarwinDebugHandler, self).__init__(self) self.setLevel(logging.DEBUG) self.glog.log("Logging attached to Record-Script") if not __debug__: self.glog.warning("__debug__ is false, some messages might be optimised into nirvana!") def write(self, message): """ Called by logger Implements an function required by Objects attached to a :class:`StreamHandler` :param message: Incoming Logging Message :type message: Should be a String :return: Nothing """ self.glog.log(message) def flush(self): """ Called by logger Implements an function required by Objects attached to a :class:`StreamHandler` (This one actually does nothing, it is only here for the looks) :return: Nothing """ pass
def __init__(self, debug=None): if not debug: self.debug = Scope("Nice") else: self.debug = debug.sub("Nice") config = get_config() if config['nice']: try: self.nice_ok = True self.level = self.get_nice() self.__test_nice() except: # behandlung um intern consistent zu bleiben self.nice_ok = False raise # fehler weiterwerfen else: self.debug.log("Nice ist in der Config deaktiviert") self.nice_ok = False
def __init__(self, requires=(), provides=()): """ Initialisiert die internen Datenstrukturen :param requires: eine Liste der Datenfelder welche dieses Modul benötigt :type requires: list of str :param provides: eine Liste der Datenfelder welche dieses Modul breitstellt :type provides: list of str """ self.__data_server = {} self.__data_client = {} self.__requires = requires self.__provides = provides self.__con, self.__client_con = multiprocessing.Pipe() self.__internal = {} self.__started = False self.__debug = Scope("Multiprocessing.%s" % self.__class__.__name__)
class AbstractProcessModule(AbstractModule): """ Dieses Modul sorgt dafür das erbende Module in einem eigenen Prozess ausgeführt werden. Dies hat einige Vor-, und Nachteile. Damit das Modul trotzdem mit den aktuellen Daten aus dem data dictonarry versorgt werden kann, wird Interprozesscommunikation verwendet. Dabei ist immer daran zu denken das dies durchaus etwas Zeit braucht bis die aktuellen Werte da sind. Außerdem sollte man nicht zu viel Daten versenden. Das Modul achtet selbstständig darauf das zu einem Schlüssel nur Wertänderungen übertragen werden. Damit das Modul weiß welche Datenfelder zu übertragen sind müssen diese im Konstruktor dieser Klasse mit angegeben werden (beim Super- aufruf bei der vererbung). .. warning:: Datenfelder welche nicht in den requires bez. provides stehen werden *niemals* übertragen. Beim versuch etwas zu providen was nicht vereinbart ist kommt es sogar zu einem Fehler .. hint:: Es sollte vermieden werden die gleichen namen zu requiren und providen da durch die relativ langsame übertragung sonst mit hoher warschienlichkeit immer der alte und neue Wert abwechselnd ankommen, und unendlich lange im kreis gesendet werden Die Daten werden wenn das Modul dran ist im :func:`update` ausgelesen und dann bei wertänderungen an den Subprosess gesendet. An gleicher stelle werden auch die vom Subprozess empfangenen Daten in das data dictonary geschrieben. .. warning:: Bei einigen in C geschriebenen Klassen funktioniert das Senden nicht immer, diese können nicht als require oder provide genutz werden. (Objekte müssen Pickabel sein) Bei Fragen: Nils fragen. Im Subprocess können die daten mit :func:`get` ausgelesen und mit :func:`set` gesetzt werden. .. warning:: Die Funktionen :func:`get` und :func:`set` nur im subprozess benutzen (alles was in :func:`run` ist oder davon aufgeruffen wird) sonst kommt es zu einer Datenkorruption. Siehe: :func:`set_init` Die Methode :func:`run` wird als eingener Prozess gestartet, sollte also überschrieben werden wenn man ein Modul von diesem erben lässt. """ def __init__(self, requires=(), provides=()): """ Initialisiert die internen Datenstrukturen :param requires: eine Liste der Datenfelder welche dieses Modul benötigt :type requires: list of str :param provides: eine Liste der Datenfelder welche dieses Modul breitstellt :type provides: list of str """ self.__data_server = {} self.__data_client = {} self.__requires = requires self.__provides = provides self.__con, self.__client_con = multiprocessing.Pipe() self.__internal = {} self.__started = False self.__debug = Scope("Multiprocessing.%s" % self.__class__.__name__) def run(self): """ Dies ist die Methode welche in einem eigenen Process ausgeführt wird """ raise NotImplementedError def set(self, name, value): """ Setzt einen Wert im data dict :param name: Der name des Datenfeldes :param value: der zu übertragen value zu dem Namen. Achtung: dieser Wert muss mit pickle zu packen sein. (ist fast alles). Es wird darauf geachtet nur Wert änderungen zu Senden, um das datenvolum gering zu halten .. warning:: Diese Funktion darf *nur* innerhalb des Subprozesses benutzt werden, sonst kommt es in der interprozesskommunikation zu Problemen (korrupte Daten im schlimmsten fall, mindestens aber kommen die werte nicht da an wo man dachte) Siehe :func:`set_init` .. warning:: Namen die nicht im provide stehen werfen einen KeyError """ #minimierung des Datenaufkommens, wenn der wert schon so ist, #muss er nicht neu gesendet werden if self.__data_client[name] != value: self.__data_client[name] = value self.__client_con.send((name, value)) def get(self, name, default=None): """ Hohlt einen Wert vom data dict :param name: Der name des Datenfeldes :param default: Der wert der zurückgegeben wird wenn noch kein Wert für den Namen übertragen wurde. :return: den Wert der unter name liegt, oder default default kommt normalerweise in drei Fällen zum einsatz: Wenn der wert für den namen durch die langsame Interprozesskommunikation am anfang noch nicht übertragen wurde oder wenn der name nicht in den requires war also niemals übertragen wird, oder der Wert vom sendenen Modul einfach noch nicht gesetzt wurde. Default ist standartmäßig None .. warning:: Diese Funktion darf *nur* innerhalb des Subprozesses benutzt werden, sonst kommt es in der interprozesskommunikation zu Problemen (korrupte Daten im schlimmsten fall, mindestens aber kommen die werte nicht da an wo man dachte) Siehe :func:`set_init` """ self.__client_con_reader() if name in self.__data_client: return self.__data_client[name] return default def set_init(self, name, value, data=None): """ Setzt einen Wert im data dict. Diese Methode ist dafür da um Werte vor dem Starten vom Subprozess zu setzen. Die Werte stehen damit sofort sowohl im globalen data dict als auch im Subprozess (über:func:`get`) zur verfügung, es muss nicht die wartezeit der Kommunikation in kauf genommen werden. .. warning:: Nur in :func:`__init__` benutzen. Alles andere würde die Daten nicht da abliefern wo sie hingehören Wenn ein data dict angegeben wir, dann wird der Wert auch sofort in das dict geschrieben :param name: Name des Keys :type name: String :param value: Der Wert :param data: Das globale Datadictonary (aus start(), optional) :type data: dict """ self.__data_server[name] = value self.__data_client[name] = value if data: data[name] = value def __start(self, debugname): """ Interner Starthelfer für den Subprozess """ # Reinitialisieren diverser Problematischer Bereiche # da sie sonst im Subprozess broken sind reload(bitbots.util) reload(bitbots.debug) reload(bitbots.debug.debug) reload(bitbots.debug) self.__debug = Scope("Multiprocessing.%s" % self.__class__.__name__) self.__debug("Subprocess gestartet") self.debug = Scope(debugname) self.__client_con_reader() #initialisieren aller felder in __data_client die es noch nicht #gibt welche aber provides werden, damit beim senden schon #alle namen vorhanden sind, und das nicht immer getesten werden #muss for name in self.__provides: if name not in self.__data_client: self.__data_client[name] = None self.__client_con.send((name, None)) while True: try: self.run() except Exception as e: if isinstance(e, KeyboardInterrupt): # Keyboard interrupt wollen wir nicht fangen! raise try: self.__debug.error(e, "Subprozess has terminated, restart. ") time.sleep(1) except Exception as e: print(e) # TODO: vernünftige fehlerbehandlung!!! def __client_con_reader(self): """ Ließt alle daten die in der Clientseitigen Pipe warten und pflegt diese in das interne data dict ein, damit sie mit :func:`get` geholt werden können. Diese Funktion muss nicht explizit aufgeruffen werden, das geht automatisch """ try: while self.__client_con.poll(): data = self.__client_con.recv() #print "Client: Recived: ",data self.__data_client[data[0]] = data[1] except EOFError: self.__debug.warning("Clientseitige Pipe ist korrupt") raise SystemExit() def __server_con_reader(self): """ Ließt alle Daten die in der Serverseitigen Pipe warten und pflegt diese in das interne data dict ein, damit sie im :func:`update` zurückgeschrieben werden können. Diese Funktion muss nicht explizit aufgerufen werden, :func:`update` macht das """ try: while self.__con.poll(): data = self.__con.recv() #skyprint "Server: Recived: ",data self.__data_server[data[0]] = data[1] except EOFError: self.__debug.warning("Serverseitige Pipe ist korrupt") raise SystemExit() def __server_con_sender(self): """ Sendet alle geänderten Daten an den Subprozess """ for key in self.__internal: # zur vermeidung unnotig gesendeter daten if key not in self.__data_server or \ self.__internal[key] != self.__data_server[key]: self.__con.send((key, self.__internal[key])) self.__data_server[key] = self.__internal[key] def update(self, data): """ Update der normalen Updatekette der Modul-Architektur. Hier werden die Daten für den Subprozess eingesamelt und das versenden gestartet, ebenso werden die empfangenen Daten zurück ins data dict geschrieben """ self.__server_con_reader() for name in self.__requires: if name in data: self.__internal[name] = data[name] self.__server_con_sender() if not self.__started: self.__debug("Starte Subprocess") process = multiprocessing.Process(target=self.__start, name=self.__class__.__name__, args=(self.debug.get_name(),)) process.deamon = True process.start() self.__started = True for name in self.__provides: data[name] = self.__data_server.get(name, None)
def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) # Wir setzen einen Scope, wird meist ,it set_debug überschrieben self.debug = Scope("HelperDict")
def __init__(self): self.debug = Scope('MotorConnectionAnalyser') self.cables = {} self.load_connection_file()
class MotorConnectionAnalyser: """ """ def __init__(self): self.debug = Scope('MotorConnectionAnalyser') self.cables = {} self.load_connection_file() def load_connection_file(self): """ Lädt im idealfall die datei mit den Motorverbindungen """ try: path = find_resource('cables.yaml') with open(path, 'r') as f: self.cables = yaml.load(f) except IOError: self.debug.warning('Konnte die Resource cables.yaml nicht finden, abbruch!') except yaml.YAMLError as e: self.debug.warning("Fehler in der cables.yaml: \n %s" % e) def find_connection_error(self, motors): """Sucht Verbindungsfehler anhand einer Liste nicht gefundener Motoren. :param motors: Liste nicht gefundener Motoren als int :return: Liste mit Motoren die wahrscheinlich Ausgangspunkt einer fehlerhaften verbindung sind """ connected = self.cables.keys() connected = [motor for motor in connected if motor not in motors] errors = [] try: for motor in motors: if self.cables[motor]['to'] in connected: errors.append(motor) except KeyError as e: self.debug.warning("I don't know this 'motor' %s" % e) return 'fail' return errors def get_error_message(self, motors): """ Generiert einen Fehlerbericht in natürlicher Sprache als string, anhand einer Liste von nicht gefundenen Motoren durch verwendung von :py:func:`find_connection_error` """ if not motors: return "I assume the error is in the caller of my motor-connection analysis, there was no faulty motor provided" if len(motors) == 1 : if motors[0] not in [20, 17, 18, 6, 5]: return "Actually, I think this is not a connection error. I am not qualified for that analysis, but I think Motor %s might have the wrong ID" errors = self.find_connection_error(motors) if errors == 'fail': return "Error while analysing the motor-problem, check log-file!" l = len(errors) if l is 0: message = "I did not find any connection-error" elif l is 1: motor1 = errors[0] motor2 = self.cables[motor1]['to'] #cable_id = self.cables[motor1]['name'] message = "I assume the error is the cable between Motor %s and %s" message = message % (motor1, motor2) elif set(errors) == set([1, 2, 7, 8, 19]): message = "Sorry dude, it seems my cm-730 board is not working." else: message = "I assume the following %s connection errors:\n" % len(errors) for motor in errors: motor2 = self.cables[motor]['to'] #cable_id = self.cables[motor]['name'] message += ("Cable between Motor %s and %s . \n" % (motor, motor2)) return message def say_error_message(self, motors): """ Liest einen mit :py:func:`get_error_message` generierten String vor """ message = self.get_error_message(motors) say(message)
#!/usr/bin/env python #-*- coding:utf-8 -*- from bitbots.debug import Scope from bitbots.util.resource_manager import find_animation from bitbots.util.config import get_config import json import threading CONFIG = get_config() debug = Scope("Util.Scope") try: import bitbots.motion.animation as animation __anmimation_module = True except ImportError: # für entwickler welche sachen ohne virtualenv (meist windows) testen __anmimation_module = False debug.warning( "Animation Framework NICHT gefunden (bitbots.motion.animation)") try: from bitbots.ipc import STATE_ANIMATION_RUNNING except ImportError: # für entwickler welche sachen ohne virtualenv (meist windows) testen STATE_ANIMATION_RUNNING = 1 debug.warning("IPC Implementation nicht gefunden") def __play_animation(name, ipc, callback=None): """ Spielt eine Animation ab
class Nice(object): """ Diese Klasse stellt methoden bereit, um die relevanz des aktuellen Prozesses zu verändern. Wenn das runtersetzen des Nicelevels (hochsetzen der Priorität) nicht möglich ist wird aus gründen der sicherheit nichts getan. """ def __init__(self, debug=None): if not debug: self.debug = Scope("Nice") else: self.debug = debug.sub("Nice") config = get_config() if config['nice']: try: self.nice_ok = True self.level = self.get_nice() self.__test_nice() except: # behandlung um intern consistent zu bleiben self.nice_ok = False raise # fehler weiterwerfen else: self.debug.log("Nice ist in der Config deaktiviert") self.nice_ok = False def __test_nice(self): """ Testet ob das runtersetzen des nicelevels möglich ist """ try: # wenn wir hier unterbrochen werden passieren dumme dinge try: self.set_nice(-1, True) self.set_nice(1, True) self.nice_ok = 1 except OSError: # dann der lange weg try: os.popen("sudo -n renice -n %d -p %d 2> /dev/null" % (-1, multiprocessing.current_process().pid)) except OSError: #irgentwass schiefgegangen, der rest fängt es ab... pass time.sleep(1) if not (self.get_nice() == -1): # wir dürfen nicht.... :( self.nice_ok = False self.debug.warning( "Nicelevel darf nicht reduziert werden, disable Nice") else: self.nice_ok = 2 self.set_normal(True) except: # dann gehts nicht # wenn wir hier landen wird es hoffentlich meist ein # KeybordInterrupt sein, wenn wir es nicht auf false setzen # passieren unter umständen komische dinge self.nice_ok = False raise # weitergeben des fehlers... def get_active(self): """ :return: Ob das Modul aktiv ist. :return type: boolean """ return self.nice_ok def get_nice(self): """ :return: Das aktuelle Nicelevel :return type: int """ self.level = os.nice(0) self.debug.log("niceines", self.level) return self.level def set_nice(self, change, silence=False): """ Verändert das Nicellevel um change :param change: die Änderung des Nicelevels :type change: int :param silence: Supress debugmessages :type silence: boolean :return: True wenn erfolgt :return type: boolean .. hint:: Wenn :func:`get_nice` == False wird ''nichts'' getan (außer einer debug warning) """ if self.nice_ok: if self.nice_ok == 1: self.level = os.nice(change) else: os.popen("sudo -n renice -n %d -p %d 2> /dev/null" % (self.level + change, multiprocessing.current_process().pid)) time.sleep(1) self.get_nice() if not silence: self.debug("Set Nicelevel to %d" % self.level) return True else: self.debug.warning("Setzen von Nice nicht möglich") return False def set_realtime(self): """ Setzt die Priorität auf "Realtime" """ if self.nice_ok: return self.set_level(-20) else: self.debug.warning("Set (Soft-) Realtime Priorität nicht möglich!") return False def set_normal(self, silence=False): """ Setzt die Prioritöt auf Normal """ return self.set_level(0, silence) def set_level(self, level, silence=False): """ Setzt das nice level auf level :param level: das Level auf das die Priorität gesetzt werden soll :type level: int """ return self.set_nice((self.level - level) * (-1), silence)