Beispiel #1
0
def outputD(c):
    '''output def(c = commander handle)'''
    uiq.put(('UI thread initialised', 'DEBUG'))
    while True:
        updateUItime(c)
        if uiq.empty():
            time.sleep(cfg.ui_timeupdate)
        else:
            item = uiq.get()
            if type(item) == str:
                item = (item, 'NORMAL')  # No message priority was specified
            #            if item[1] == 'DEBUG':
            #                try: item[2]
            #                except IndexError:
            #                    item = (*item, 1)                            # Default to debug level 1
            #                if type(item[2]) != int:
            #                    uiq("Invalid debug level encountered", 'ERR')
            #                elif cfg.ui_debug < item[2]:                        # Debug level is too low to show this item
            #                    continue
            if cfg.ui_ts:
                if cfg.ui_tscut > 0:
                    ts = datetime.now().strftime(
                        cfg.ui_tsfmt
                    )[:-cfg.ui_tscut] + " - "  # Format timestamp
                else:
                    ts = datetime.now().strftime(cfg.ui_tsfmt) + " - "
                item = (ts + item[0].replace('\n', '\n' + ' ' * len(ts)),
                        item[1]
                        )  # Add timestamp and spaces to align multiline output
            c.output(item[0], cfg.ui_colors[item[1]])
Beispiel #2
0
def debug(msg, level=1):
    '''debugging output, level = debug level (default 1)'''
    try:
        if cfg.ui_debuglevel[threading.current_thread().name] >= level:
            uiq.put((msg, 'DEBUG'))
    except KeyError:  # This is necessary for when environment.py is called directly
        pass
Beispiel #3
0
def pendulumTimeout():
    '''pendulumTimeout() is called when the pendulum doesn't arrive for cfg.p_timeout seconds
    '''
    global timeoutrpt, watchdog, pa, pd, prevArr, prevDep
    watchdog.reset()  # Need this to trigger again in cfg.p_timeout seconds
    timeoutrpt += 1
    uiq.put(('WARNING:  Beat timeout after {} seconds! ({}/{})'.format(
        cfg.p_timeout, timeoutrpt, cfg.p_timeoutrpt), 'WARN'))
    if timeoutrpt == cfg.p_timeoutrpt:  # We've reached the maximum number of misses
        timeoutrpt = 0  # Reset this for when the clock restarts
        uiq.put(('ERROR:  Too many missed beats.  Resetting monitor thread.',
                 'ERR'))
        globs.beatbanner = "[ Waiting for first beat ]"
        prevArr = 0
        prevDep = 0
        watchdog.stop()  # Kill the watchdog
        try:
            cfg.p_timeoutcmd
        except AttributeError:
            pass
        else:
            uiq.put(
                ('Executing timeout command \'{}\''.format(cfg.p_timeoutcmd),
                 'DEBUG'))
            try:
                tocmdout = subprocess.check_output([cfg.p_timeoutcmd])
                if len(tocmdout) > 0:
                    uiq.put((tocmdout.decode('ascii'), 'DEBUG'))
            except subprocess.CalledProcessError as err:
                uiq.put(('ERROR: {}'.format(err), 'ERR'))
Beispiel #4
0
def switchdebug(mname, mcode, mode=None):
    '''switch debug modes interactively'''
    if not mode:
        uiq.put(
            ('{} debug output level is {}'.format(mname,
                                                  cfg.ui_debuglevel[mcode])))
        #        if cfg.ui_debuglevel[mcode]: uiq.put(('{} debug output is on'.format(mname)))
        #        else: uiq.put(('{} debug output is off'.format(mname)))
        return
    else:
        try:
            newlevel = int(mode)
            if not 0 <= newlevel <= 3:
                raise ValueError
            cfg.ui_debuglevel[mcode] = newlevel
        except ValueError:
            uiq.put(
                ('ERROR: {} debug setting must be 0-3'.format(mname), 'ERR'))


#    elif mode.lower() == 'on':
#        cfg.ui_debugmod[mcode] = True
#        switchdebug(mname, mcode)
#        uiq.put(('{} debug output is on'.format(mname)))
#    elif mode.lower() == 'off':
#        cfg.ui_debugmod[mcode] = False
#        globals()[varname] = False
#        uiq.put(('{} debug output is off'.format(mname)))
#    else:
#        uiq.put(('ERROR: {} debug setting must be \'on\' or \'off\''.format(mname), 'ERR'))
    switchdebug(mname, mcode)
Beispiel #5
0
 def __call__(self,line):
     tokens=line.split()
     cmd=tokens[0].lower()
     args=tokens[1:]
     if cmd in self._quit_cmd:
         return Commander.Exit
     elif cmd in self._help_cmd:
         return self.help(args[0] if args else None)
     elif hasattr(self, 'do_'+cmd):
         return getattr(self, 'do_'+cmd)(*args)
     else:
         uiq.put(('Unknown command \'{}\''.format(line),'WARN'))
Beispiel #6
0
def envD(pig):
    '''environmental data thread (pig = pigpiod handle)'''
    debug('Environmental data thread initialising')

    if cfg.env_frequency - 3 * cfg.env_delay < 0:
        uiq.put(('ERROR: env_frequency too low (must be at least {})'.format(
            3 * cfg.env_delay), 'ERR'))
        return
    while True:
        globs.temperature, globs.humidity = temphumid(pig)
        debug(
            'Read temperature {} and humidity {}'.format(
                globs.temperature, globs.humidity), 2)
        time.sleep(cfg.env_frequency - 3 * cfg.env_delay)
Beispiel #7
0
def pendulumD(pig):
    '''pendulum monitoring thread
    '''
    pig.set_mode(cfg.p_gpio_irsense_pin, pigpio.INPUT)
    pig.set_glitch_filter(cfg.p_gpio_irsense_pin, cfg.p_glitchfilter)
    global prevArr, prevDep, watchdog, pa, pd
    prevArr = 0
    prevDep = 0

    # Set a callback for every pendulum cross
    pig.set_pull_up_down(cfg.p_gpio_irsense_pin, pigpio.PUD_OFF)
    pd = pig.callback(cfg.p_gpio_irsense_pin, pigpio.RISING_EDGE,
                      pendulumDepart)
    pa = pig.callback(cfg.p_gpio_irsense_pin, pigpio.FALLING_EDGE,
                      pendulumArrive)

    uiq.put(('pendulum monitor thread initialised', 'DEBUG'))
Beispiel #8
0
    def do_env(self, *args):
        '''set/display environmental sensor parameters
Usage: env [cmd] [args]
where cmd/args are:
    (nothing)   display environmental sensor status
    debug       display/set environmental sensor debug output
        (none)  display environmental sensor debug output status
        [0-3]   set environmental sensor debug output level
'''
        if len(args) == 0:
            if cfg.env_engine:
                uiq.put(
                    ('Current temperature is {} C and humidity is {}%'.format(
                        globs.temperature, globs.humidity)))
            else:
                uiq.put(('Environmental sensors disabled'))
        else:
            cmd = args[0].lower()
            if cmd == 'debug':
                if len(args) == 1: switchdebug('Environmental sensors', 'env')
                else: switchdebug('Environmental sensors', 'env', args[1])
Beispiel #9
0
def dbopen():
    '''open the database
    '''
    global dbx
    dbx = None
    debug('Opening database', 2)
    #    if cfg.ui_debugmod['db']: uiq.put(('Opening database', 'DEBUG', 2))
    try:
        dbx = sqlite3.connect(cfg.db_file)
        #        dbx.isolation_level = 'EXCLUSIVE'                   # FIXME - this seems not to lock against other processes
        #        dbx.execute('BEGIN EXCLUSIVE')
        sql = "CREATE TABLE IF NOT EXISTS beats ({});".format(globs.sqltable)
        cur = dbx.cursor()
        try:
            cur.execute(sql)
            dbx.commit()
        except Error as dberr:
            uiq.put(('Database table create error: {}'.format(dberr), 'ERR'))
    except Error as dberr:
        uiq.put(('Database open error: {}'.format(dberr), 'ERR'))
    return dbx
Beispiel #10
0
    def do_db(self, *args):
        '''set/display database parameters
Usage: db [cmd] [args]
    where cmd/args are:
        (nothing)   display database engine status
        debug       display/set database debug output
            (none)  display database debug output status
            [0-3]   set database debug output level
'''
        if len(args) == 0:
            if cfg.db_engine: uiq.put(('Database storage is on'))
            else: uiq.put(('Database storage is off'))
        else:
            cmd = args[0].lower()
            if cmd == 'debug':
                if len(args) == 1: switchdebug('Database', 'db')
                else: switchdebug('Database', 'db', args[1])


#                    if cfg.ui_debug_db: uiq.put(('Database debug output is off'))
#                    else: uiq.put(('Database debug output is on'))
#                elif args[1].lower() == 'on':
#                    cfg.ui_debug_db = True
#                    uiq.put(('Database debug output is on'))
#                elif args[1].lower() == 'off':
#                    cfg.ui_debug_db = False
#                    uiq.put(('Database debug output is off'))
#                else:
#                    uiq.put(('ERROR: Database debug setting must be \'on\' or \'off\'', 'ERR'))
#                    return
            else:
                uiq.put(('ERROR: Invalid db command', 'ERR'))
Beispiel #11
0
def pendulumDepart(g, L, t):
    '''pendulumDepart(gpio, level, tick) - pendulum has left IR sensor gate

    '''
    global prevArr, prevDep
    delta = t - prevDep
    if delta < 0: delta += 4294967295  # counter wrapped
    if prevArr == 0:
        return  # Ignore departure if the first arrival has not been seen
    hz = cfg.p_period / delta
    skew = delta - cfg.p_period
    message = "pendulum departure at " + str(t)
    prevDep = t
    if cfg.ui_showdepart: uiq.put((message, 'INFO'))
    # FIXME Storing 0 for clock error on departures for now, should probably do something else
    if cfg.db_engine: dbq.put((0, delta, hz, skew, 0))
    if cfg.mqtt_engine and cfg.mqtt_p_depart:
        mqq.put(('beatDepart', {
            'delta': delta,
            'Hz': hz,
            'skew': int(skew)
        }))  # publish beat to MQTT
Beispiel #12
0
def dbstorebeat(beattype, delta, hz, skew, err):
    '''database storage routine:
        beattype = 1/arrive, 0/depart
        delta = delta since last beat, in uS
        hz = calculated Hz
        skew = skew from desired beat frequency
    '''
    global dbx, temperature, humidity
    sql = "INSERT INTO beats(beattype, delta, hz, skew, temperature, humidity, error) VALUES ({}, {}, {}, {}, {}, {}, {})".format(
        beattype, delta, hz, skew, globs.temperature, globs.humidity, err)
    try:
        dbx
    except NameError:
        dbopen
    else:
        cur = dbx.cursor()
        try:
            debug('Executing SQL command {}'.format(sql), 3)
            #            if cfg.ui_debugmod['db']: uiq.put(('Executing SQL command {}'.format(sql), 'DEBUG', 3))
            cur.execute(sql)
            dbx.commit()
        except Error as dberr:
            uiq.put(('Database write error: {}'.format(dberr), 'ERR'))
Beispiel #13
0
 def do_debug(self, *args):
     '''set/display the debug output level\nUsage: debug [0|1|2|3]'''
     if len(args) == 0:
         uiq.put(('Debug level is {}'.format(cfg.ui_debug)))
     else:
         try:
             newdbg = int(args[0])
             if newdbg < 0 or newdbg > 3:
                 raise ValueError
         except ValueError:
             uiq.put(('ERROR: Debug level must be 0-3', 'ERR'))
             return
         cfg.ui_debug = newdbg
         uiq.put(('Debug level set to {}'.format(cfg.ui_debug)))
Beispiel #14
0
 def on_line_entered(self,line):
     if self._cmd:
         try:
             res = self._cmd(line)
         except Exception as e:
             uiq.put(('Error: %s'%e, 'ERR'))
             return
         if res==Commander.Exit:
             raise urwid.ExitMainLoop()
         elif res:
             uiq.put((str(res)))
     else:
         if line in ('q','quit','exit'):
             raise urwid.ExitMainLoop()
         else:
             uiq.put(('Unknown command \'{}\''.format(line),'NORMAL'))
Beispiel #15
0
    def do_mqtt(self, *args):
        '''set/display MQTT broker parameters
Usage: mqtt [cmd] [args]
where cmd/args are:
    (nothing)           display MQTT module status
    debug               display/set MQTT debug output status
        (none)          display MQTT debug output status
        [0-3]           set MQTT debug output level
    telemetry           display/set MQTT telemetry settings
        (none)          display MQTT telemetry settings
        [on|off]        turn MQTT telemetry on/off
        arr[ive]        display telemetry setting for pendulum arrival
            [on|off]    set pendulum arrival telemetry on/off
        dep[art]        display telemetry setting for pendulum departure
            [on|off]    set pendulum departure telemetry on/off
        int[erval]      display MQTT telemetry interval
            [n]         set MQTT telemetry interval
'''
        if len(args) == 0:
            if cfg.mqtt_engine:
                uiq.put(('MQTT engine is on'))
            else:
                uiq.pub(('MQTT engine is off'))
        else:
            cmd = args[0].lower()
            if cmd == 'debug':
                if len(args) == 1: switchdebug('MQTT', 'mqtt')
                else: switchdebug('MQTT', 'mqtt', args[1])
            elif cmd == 'telemetry':
                if len(args) == 1:
                    if cfg.mqtt_telemetry:
                        uiq.put((
                            'Telemetry is on: interval {}, arrivals {}, departures {}'
                            .format(cfg.mqtt_telemetry_interval, mqtt_p_arrive,
                                    mqtt_p_depart)))
                    else:
                        uiq.put(
                            ('Telemetry is off'
                             ))  # FIXME finish writing this stuff later  :)
Beispiel #16
0
 def do_resetdrift(self, *args):
     '''reset the drift statistics'''
     globs.driftavg = []
     uiq.put(('Drift statistics reset'))
Beispiel #17
0
    def do_pendulum(self, *args):
        '''set/display pendulum parameters
where cmd/args are:
    (nothing)       display pendulum sensor status
    arr[ive]        display pendulum arrival notification status
        [on|off]    set pendulum arrival notificions on or off
    dep[art]        display pendulum departure notification status
        [on|off]    set pendulum departure notifications on or off
    debug           display pendulum sensor debug output status
        [0-3]       set pendulum sensor debug output level
'''
        if len(args) == 0:
            pass  # CHANGE what's a good default output?
        else:
            cmd = args[0].lower()
            if cmd == 'debug':
                if len(args) == 1: switchdebug('Pendulum sensor', 'p')
                else: switchdebug('Pendulum sensor', 'p', args[1])
            elif cmd == 'arr' or cmd == 'arrive':
                if len(args) == 1:
                    if cfg.ui_showarrive:
                        uiq.put(('Pendulum arrival notifications on'))
                    else:
                        uiq.put(('Pendulum arrival notifications off'))
                    return
                elif str(args[1]).lower() == 'on':
                    cfg.ui_showarrive = True
                elif str(args[1]).lower() == 'off':
                    cfg.ui_showarrive = False
                else:
                    uiq.put((
                        'ERROR: Pendulum arrival notifications must be \'off\' or \'on\''
                    ), 'ERR')
                self.do_pendulum('arr')
            elif cmd == 'dep' or cmd == 'depart':
                if len(args) == 1:
                    if cfg.ui_showdepart:
                        uiq.put(('Pendulum departure notifications on'))
                    else:
                        uiq.put(('Pendulum departure notifications off'))
                    return
                elif str(args[1]).lower() == 'on':
                    cfg.ui_showdepart = True
                elif str(args[1]).lower() == 'off':
                    cfg.ui_showdepart = False
                else:
                    uiq.put((
                        'ERROR: Pendulum departure notifications must be \'off\' or \'on\''
                    ), 'ERR')
                self.do_pendulum('dep')
            else:
                uiq.put(('ERROR: Invalid pendulum command'))
Beispiel #18
0
def pendulumArrive(g, L, t):
    '''pendulumArrive(gpio, level, tick) - pendulum has arrived at IR sensor gate
    '''
    global prevArr, watchdog, timeoutrpt, clocktime
    globs.realtime = datetime.now()
    loglevel = 'INFO'  # Default to INFO, change to WARN or ERR if necessary
    timeoutrpt = 0
    message = "beat detect"
    if prevArr:
        delta = t - prevArr
        if delta < 0: delta += 4294967295  # counter wrapped
        delta *= (1e6 + globs.ntpdrift) / 1e6  # Adjust for oscillator drift
        skew = delta - cfg.p_period
        if abs(skew) > cfg.p_maxskew:  # Absurd arrival time, ignore
            uiq.put(
                ('Absurd arrival delta {} uS ignored.'.format(delta), 'DEBUG'))
            prevArr = t  # need this to compute next beat
            watchdog.reset()  # reset the watchdog timer
            return
        if abs(
                cfg.p_offset - skew
        ) > cfg.p_tolerance2:  # Pendulum period is outside "bad" tolerance
            loglevel = 'ERR'
        elif abs(
                cfg.p_offset - skew
        ) > cfg.p_tolerance1:  # Pendulum period is outside "warn" tolerance
            loglevel = 'WARN'
        hz = 1e6 / delta  # Compute pendulum frequency (Hz)
        drift = (-864e2 / cfg.p_period) * skew  # Compute drift (s/day)
        if isinstance(globs.newclocktime, datetime):  # clocktime was just run
            globs.clocktime += globs.realtime - globs.newclocktime  # add only a partial beat
            if cfg.ui_btcut > 0:
                uiq.put(('Clock time set to {}'.format(
                    globs.clocktime.strftime(cfg.ui_btfmt)[:-cfg.ui_btcut])))
            else:
                uiq.put(('Clock time set to {}'.format(
                    globs.clocktime.strftime(cfg.ui_btfmt))))
            globs.newclocktime = None
        else:
            globs.clocktime += timedelta(
                microseconds=cfg.p_period
            )  # add one pendlum period to the clock time
            # Build drift averages only when manual time *hasn't* been set (otherwise we get a ridiculous average)
            avgdrift(drift)
        beatstats = "{:+} uS / {:.6f} Hz / {:.1f} BPH / {:+.1f} s/day".format(
            int(skew), hz, 7200 * hz, drift)
        message += " ({})".format(beatstats)
        globs.beatbanner = "[ {} ]".format(beatstats)
        clockerr = (globs.clocktime - globs.realtime).total_seconds()
        if cfg.db_engine:
            dbq.put((1, delta, hz, skew, clockerr))  # store the database entry
        if cfg.mqtt_engine:
            if cfg.mqtt_p_arrive:
                mqq.put((
                    'beatArrive',
                    {  # Publish beat to MQTT
                        'delta': delta,
                        'Hz': hz,
                        'skew': int(skew),
                        'drift': drift
                    }))
            if cfg.mqtt_telemetry:
                globs.telemetry.append(skew)
            if cfg.ui_btcut > 0:
                mqrt = globs.realtime.strftime(cfg.ui_btfmt)[:-cfg.ui_btcut]
                mqct = globs.clocktime.strftime(cfg.ui_btfmt)[:-cfg.ui_btcut]
                mqdt = '{0:.6f}'.format(clockerr)[:-cfg.ui_btcut]
            else:
                mqrt = globs.realtime.strftime(cfg.ui_btfmt)
                mqct = globs.clocktime.strftime(cfg.ui_btfmt)
                mqdt = '{0:.6f}'.format(clockerr)
            mqq.put(('clocktime', {
                'realtime': mqrt,
                'clocktime': mqct,
                'delta': mqdt
            }))
    else:
        watchdog = Watchdog(cfg.p_timeout,
                            pendulumTimeout)  # Start the watchdog
        globs.beatbanner = "[ Waiting for second beat ]"
        globs.clocktime = datetime.now() + timedelta(seconds=globs.driftstate)
        globs.driftstate = 0  # To allow for pendulum restarts to be automatically "now"
    prevArr = t
    if cfg.ui_showarrive: uiq.put((message, loglevel))
    watchdog.reset()  # reset the watchdog timer
Beispiel #19
0
 def do_set(self, *args):
     '''set/reset/display a configuration variable\nUsage: set <variable> [arguments]'''
     if len(args) == 1:
         uiq.put(('{} is set to \'{}\''.format(args[0],
                                               cfg.__dict__[args[0]])))
         return
Beispiel #20
0
    def do_clocktime(self, *args):
        '''set/display the (physical) clock time
Usage: clocktime            Display the current physical clock time
            ([HH:]MM|now)   Set the clock time on the next beat
            (+|-)s          Set the clock time to the current time plus or minus s seconds
'''
        if len(args) == 0:
            if cfg.ui_btcut > 0:
                uiq.put(('Clock time is {}'.format(
                    globs.clocktime.strftime(cfg.ui_btfmt)[:-cfg.ui_btcut])))
            else:
                uiq.put(('Clock time is {}'.format(
                    globs.clocktime.strftime(cfg.ui_btfmt))))
            return
        elif args[0].lower() == 'now':
            globs.clocktime = datetime.now()
        elif args[0][0] == '+' or args[0][0] == '-':
            try:
                globs.clocktime = datetime.now() + timedelta(
                    seconds=float(args[0]))
            except ValueError:
                uiq.put(
                    ('ERROR: +/- time value must be some number of seconds',
                     'ERR'))
                return
        else:
            try:
                newclocktime = re.compile(r'((\d{1,2})?\D?(\d{2}))').search(
                    args[0]).groups()
            except AttributeError:
                uiq.put(('ERROR: Time must be in format [HH:]MM', 'ERR'))
                return
            if newclocktime[1] is None: newclockhour = globs.clocktime.hour
            else:
                try:
                    newclockhour = int(newclocktime[1])
                    if newclockhour < 0 or newclockhour > 23:
                        raise ValueError
                except ValueError:
                    uiq.put(('ERROR: Hour must be 0-23', 'ERR'))
                    return
            try:
                newclockmin = int(newclocktime[2])
                if newclockmin < 0 or newclockmin > 59:
                    raise ValueError
            except ValueError:
                uiq.put(('ERROR: Minute must be 0-59', 'ERR'))
                return
            globs.clocktime = globs.clocktime.replace(hour=newclockhour,
                                                      minute=newclockmin,
                                                      second=0,
                                                      microsecond=0)
        # Need to know when this ran, to compute a partial beat next time the pendulum arrives
        globs.newclocktime = datetime.now()
Beispiel #21
0
def error(msg):
    '''error message - display and halt'''
    uiq.put((msg, 'ERR'))