Exemple #1
0
    def set_time_filter(self, value):
        """
        Interval in seconds before a new position should be sent to server
        """
        from cp_lib.parse_duration import TimeDuration

        # handle the "null" or 0 times, as disable
        value = self._test_value_as_none(value)

        if value is None:
            # then disable the distance filter
            if self.time_filter is not None:
                self.logger.debug("Disable time_filter")
            self.time_filter = None
        else:
            try:
                duration = TimeDuration(value)
            except AttributeError:
                raise ValueError("Bad Time Filter Value:{}".format(value))

            value = duration.get_seconds()

            # test Min/Max, clamp or throw ValueError
            value = self._test_value_limits(value, self.TIME_FILTER_LIMITS)
            if value != self.time_filter:
                self.time_filter = value
                self.logger.debug("Set time_filter = {} sec".format(value))
        return
Exemple #2
0
    value = get_item(tree, path, throw_exception)
    if value is None:
        # assuming throw_exception=False, we'll return the None
        return None

    try:
        value = parse_integer_or_float(value)

    except (ValueError, TypeError):
        raise DataTreeItemBadValue("Item[{}] is not float type".format(value))

    # since may be int, force to float
    return float(value)


_time_duration = TimeDuration(0)


def get_item_time_duration_to_seconds(tree, path, throw_exception=False):
    """
    Get the item value, running through TimeDuration() to convert things
    like 300, "300 sec", and "5 min" to seconds

    If value can't be forced well to int, throw exception

    :param dict tree:
    :param str path:
    :param bool throw_exception:
    :return:
    """
    value = get_item(tree, path, throw_exception)
Exemple #3
0
    def main(self, app_main):
        """
        :param str app_main: the file name, such as "network.tcp_echo"
        :return int: code for sys.exit()
        """

        # in windows, safer to use time.clock(), but is useless in Linux
        start_time = time.time()

        logging.info("Router APP starting: {}".format(app_main))

        # follow SDK design to load settings, should be in root of tar.gzip
        self.settings = load_settings_json()

        # delay until all desired conditions are met
        self.start_delay()

        app_mod = importlib.import_module(app_main)
        app_base = app_mod.RouterApp(app_main)

        if True:
            # app_mod = importlib.import_module(app_main, "RouterApp")
            # app_mod = importlib.import_module("network.tcp_echo.tcp_echo")
            # print(app_mod)
            result = app_base.run()
        else:
            result = 0

        # see if we should delay before existing
        exit_delay = self.DEFAULT_EXIT_DELAY
        if self.SECTION_STARTUP in self.settings:
            # then we do have a [startup] section in settings.json

            if self.SETS_EXIT_DELAY in self.settings[self.SECTION_STARTUP]:
                # how many seconds to wait for boot conditions to be satisfied
                # here is string, we don't care what yet
                exit_delay = self.settings[self.SECTION_STARTUP][
                    self.SETS_EXIT_DELAY].lower()

        if exit_delay in ('forever', 'loop', 'true'):
            # we loop forever - use 'uninstall' action to stop
            while True:
                app_base.logger.debug("App Finished - Looping forever")
                time.sleep(self.EXIT_DELAY_PERIOD)

        elif exit_delay in (None, 'none', 'null', 'false'):
            # default - no delay at all
            app_base.logger.info("App exited - MAIN Exiting without delay")

        else:
            # if this throws exception, well we exit anyway
            # use time_duration to handle "300" or "5 min"
            time_duration = TimeDuration(exit_delay)
            exit_delay = time_duration.get_seconds()

            time_diff = time.time() - start_time
            if time_diff >= exit_delay:
                # app took longer than 'min delay', so just exist
                app_base.logger.info("No need for exit delay")
            else:
                # else, app was short/fast, delay to reduce restart thrashing
                app_base.logger.info(
                    "Exit Delay for at least {} seconds".format(exit_delay))
                while time_diff < exit_delay:
                    app_base.logger.debug(
                        "Exit Delay, wait at least %d seconds more" %
                        int(exit_delay - time_diff))
                    time.sleep(self.EXIT_DELAY_PERIOD)
                    time_diff = time.time() - start_time
                app_base.logger.info("Exit Delay finished")

        return result
Exemple #4
0
    def start_delay(self):
        """
        Delay up to config seconds, waiting for boot conditions to be true

        :return: None
        """
        # we only delay if at least ONE condition is still not satisfied
        wait_conditions = 0

        time_duration = TimeDuration()

        # start with the defaults
        # use time_duration to handle "300" or "5 min"
        time_duration.parse_time_duration_to_seconds(self.DEF_BOOT_DELAY_SEC)
        delay_seconds = time_duration.get_seconds()
        delay_for_valid_time = self.DEF_DELAY_FOR_TIME
        delay_for_uplink = self.DEF_DELAY_FOR_WAN

        if self.SECTION_STARTUP in self.settings:
            # then we do have a [startup] section in settings.json

            if self.SET_BOOT_DELAY_SEC in self.settings[self.SECTION_STARTUP]:
                # how many seconds to wait for boot conditions to be satisfied
                time_duration.parse_time_duration_to_seconds(self.settings[
                    self.SECTION_STARTUP][self.SET_BOOT_DELAY_SEC])
                delay_seconds = time_duration.get_seconds()

            if self.SET_DELAY_FOR_TIME in self.settings[self.SECTION_STARTUP]:
                # see if we delay until time.time() is returning valid info,
                # which prevents initial time-series data from being
                # generated with bogus 1-1-1970 time-stamps
                delay_for_valid_time = \
                    self.settings[self.SECTION_STARTUP][self.SET_DELAY_FOR_TIME]

            if self.SET_DELAY_FOR_WAN in self.settings[self.SECTION_STARTUP]:
                # see if we delay until router has a valid WAN uplink, which
                # prevents cloud clients from mistakenly flipping into
                # FAULT/RECOVERY modes because they tried to connect to fast
                delay_for_uplink = self.settings[self.SECTION_STARTUP][
                    self.SET_DELAY_FOR_WAN]

        if delay_for_valid_time:
            wait_conditions += 1

        if delay_for_uplink:
            wait_conditions += 1

        # we cannot use clock() because under Linux it means nothing
        # TODO - what happens when time() jumps?
        start_time = time.time()
        while (wait_conditions > 0) and \
                (time.time() - start_time) < delay_seconds:
            # loop until end of time period, or all conditions are okay

            if delay_for_valid_time and hw_status.router_time_is_valid():
                # then check on time, if okay neutralize our conditions
                delay_for_valid_time = False
                wait_conditions -= 1
            else:
                logging.debug("Delay - waiting for valid time")

            if delay_for_uplink and hw_status.router_wan_online():
                # then check on wan-uplink, is okay neutralize our conditions
                delay_for_uplink = False
                wait_conditions -= 1
            else:
                logging.debug("Delay - waiting for WAN uplink")

            if wait_conditions > 0:
                time.sleep(self.BOOT_DELAY_SLEEP)
            # else we'll break / leave the WHILE loop

        return
    def test_tag_decode(self):

        obj = TimeDuration()

        self.assertEqual(obj.decode_time_tag('ms'), obj.DURATION_MSEC)
        self.assertEqual(obj.decode_time_tag('msec'), obj.DURATION_MSEC)
        self.assertEqual(obj.decode_time_tag('millisecond'), obj.DURATION_MSEC)
        self.assertEqual(obj.decode_time_tag('milliseconds'),
                         obj.DURATION_MSEC)
        self.assertEqual(obj.get_tag_as_string(obj.DURATION_MSEC), 'ms')

        self.assertEqual(obj.decode_time_tag('sec'), obj.DURATION_SECOND)
        self.assertEqual(obj.decode_time_tag('second'), obj.DURATION_SECOND)
        self.assertEqual(obj.decode_time_tag('seconds'), obj.DURATION_SECOND)
        self.assertEqual(obj.get_tag_as_string(obj.DURATION_SECOND), 'sec')
        self.assertEqual(obj.get_tag_as_string(0), 'sec')

        self.assertEqual(obj.decode_time_tag('min'), obj.DURATION_MINUTE)
        self.assertEqual(obj.decode_time_tag('minute'), obj.DURATION_MINUTE)
        self.assertEqual(obj.decode_time_tag('minutes'), obj.DURATION_MINUTE)
        self.assertEqual(obj.get_tag_as_string(obj.DURATION_MINUTE), 'min')

        self.assertEqual(obj.decode_time_tag('hr'), obj.DURATION_HOUR)
        self.assertEqual(obj.decode_time_tag('hour'), obj.DURATION_HOUR)
        self.assertEqual(obj.decode_time_tag('hours'), obj.DURATION_HOUR)
        self.assertEqual(obj.get_tag_as_string(obj.DURATION_HOUR), 'hr')

        self.assertEqual(obj.decode_time_tag('dy'), obj.DURATION_DAY)
        self.assertEqual(obj.decode_time_tag('day'), obj.DURATION_DAY)
        self.assertEqual(obj.decode_time_tag('days'), obj.DURATION_DAY)
        self.assertEqual(obj.get_tag_as_string(obj.DURATION_DAY), 'day')

        self.assertEqual(obj.decode_time_tag('mon'), obj.DURATION_MONTH)
        self.assertEqual(obj.decode_time_tag('month'), obj.DURATION_MONTH)
        self.assertEqual(obj.decode_time_tag('months'), obj.DURATION_MONTH)
        self.assertEqual(obj.get_tag_as_string(obj.DURATION_MONTH), 'mon')

        self.assertEqual(obj.decode_time_tag('yr'), obj.DURATION_YEAR)
        self.assertEqual(obj.decode_time_tag('year'), obj.DURATION_YEAR)
        self.assertEqual(obj.decode_time_tag('years'), obj.DURATION_YEAR)
        self.assertEqual(obj.get_tag_as_string(obj.DURATION_YEAR), 'yr')

        obj.reset()
        with self.assertRaises(ValueError):
            obj.get_tag_as_string(-1)
            obj.get_tag_as_string(7)
            obj.decode_time_tag('homey')

        with self.assertRaises(TypeError):
            obj.get_tag_as_string('hello')
            obj.decode_time_tag(None)
            obj.decode_time_tag(3)

        return
    def test_parse_time_duration(self):

        obj = TimeDuration()

        self.assertEqual(obj.parse_time_duration_to_seconds(1), 1.0)
        self.assertEqual(obj.get_period_as_string(), "1 sec")

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1'), 1.0)
        self.assertEqual(obj.get_period_as_string(), "1 sec")

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('01'), 1.0)
        self.assertEqual(obj.get_period_as_string(), "1 sec")

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('0x01'), 1.0)
        self.assertEqual(obj.get_period_as_string(), "1 sec")

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 ms'), 0.001)
        self.assertEqual(obj.get_period_as_string(), '1 ms')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 msec'), 0.001)
        self.assertEqual(obj.get_period_as_string(), '1 ms')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 sec'), 1.0)
        self.assertEqual(obj.get_period_as_string(), '1 sec')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(b'1 sec'), 1.0)
        self.assertEqual(obj.get_period_as_string(), '1 sec')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 second'), 1.0)
        self.assertEqual(obj.get_period_as_string(), '1 sec')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 min'), 60.0)
        self.assertEqual(obj.get_period_as_string(), '1 min')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 minute'), 60.0)
        self.assertEqual(obj.get_period_as_string(), '1 min')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 minutes'), 60.0)
        self.assertEqual(obj.get_period_as_string(), '1 min')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 hr'), 3600.0)
        self.assertEqual(obj.get_period_as_string(), '1 hr')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 HR'), 3600.0)
        self.assertEqual(obj.get_period_as_string(), '1 hr')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 Hours'), 3600.0)
        self.assertEqual(obj.get_period_as_string(), '1 hr')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 day'), 86400.0)
        self.assertEqual(obj.get_period_as_string(), '1 day')

        # note: these handled, but have NO 'seconds' result
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 month'), None)
        self.assertEqual(obj.get_period_as_string(), '1 mon')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('1 year'), None)
        self.assertEqual(obj.get_period_as_string(), '1 yr')

        # repeat with more than 1 - a random value
        seed = random.randint(101, 999)
        source = "{0} ms".format(seed)
        expect_sec = seed * 0.001
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(source),
                         expect_sec)
        self.assertEqual(obj.get_period_as_string(), source)

        seed = random.randint(2, 59)
        source = "{0} sec".format(seed)
        expect_sec = seed * 1.0
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(source),
                         expect_sec)
        self.assertEqual(obj.get_period_as_string(), source)

        seed = random.randint(2, 59)
        source = "{0} min".format(seed)
        expect_sec = seed * 60.0
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(source),
                         expect_sec)
        self.assertEqual(obj.get_period_as_string(), source)

        seed = random.randint(2, 23)
        source = "{0} hr".format(seed)
        expect_sec = seed * 3600.0
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(source),
                         expect_sec)
        self.assertEqual(obj.get_period_as_string(), source)

        seed = random.randint(2, 9)
        source = "{0} day".format(seed)
        expect_sec = seed * 86400.0
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(source),
                         expect_sec)
        self.assertEqual(obj.get_period_as_string(), source)

        # note: these handled, but have NO 'seconds' result
        seed = random.randint(2, 9)
        source = "{0} mon".format(seed)
        expect_sec = None
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(source),
                         expect_sec)
        self.assertEqual(obj.get_period_as_string(), source)

        seed = random.randint(2, 9)
        source = "{0} yr".format(seed)
        expect_sec = None
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(source),
                         expect_sec)
        self.assertEqual(obj.get_period_as_string(), source)

        return
    def test_utc_decoration(self):

        obj = TimeDuration()

        self.assertFalse(obj._decode_utc_element('1 ms'))
        self.assertTrue(obj._decode_utc_element('1 ms utc'))

        self.assertFalse(obj._decode_utc_element('1 sec'))
        self.assertTrue(obj._decode_utc_element('1 sec ZulU'))

        self.assertFalse(obj._decode_utc_element('1 min'))
        self.assertTrue(obj._decode_utc_element('1 min GM'))

        self.assertFalse(obj._decode_utc_element('1 hr'))
        self.assertTrue(obj._decode_utc_element('1 hr Z'))

        self.assertFalse(obj._decode_utc_element('1 day'))
        self.assertTrue(obj._decode_utc_element('1 day UCT'))

        self.assertFalse(obj._decode_utc_element('1 mon'))
        self.assertTrue(obj._decode_utc_element('1 mon UTC'))

        return
    def test_parse_time_duration_plus_minus(self):

        obj = TimeDuration()
        # check the signs - +/- to allow things like "do 5 minutes before X"

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+1 ms'), 0.001)
        self.assertEqual(obj.get_period_as_string(), '1 ms')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('-1 ms'), -0.001)
        self.assertEqual(obj.get_period_as_string(), '-1 ms')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+1 sec'), 1.0)
        self.assertEqual(obj.get_period_as_string(), '1 sec')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(b'-1 sec'), -1.0)
        self.assertEqual(obj.get_period_as_string(), '-1 sec')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+1 min'), 60.0)
        self.assertEqual(obj.get_period_as_string(), '1 min')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('-5 min'), -300.0)
        self.assertEqual(obj.get_period_as_string(), '-5 min')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+2 hr'), 7200.0)
        self.assertEqual(obj.get_period_as_string(), '2 hr')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('-3 hr'), -10800.0)
        self.assertEqual(obj.get_period_as_string(), '-3 hr')

        # confirm the UTC 'decoration' is ignored,
        # including ('z', 'zulu', 'gm', 'utc', 'uct')
        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+1 ms utc'),
                         0.001)
        self.assertEqual(obj.get_period_as_string(), '1 ms')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('-1 ms UTC'),
                         -0.001)
        self.assertEqual(obj.get_period_as_string(), '-1 ms')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+1 sec z'), 1.0)
        self.assertEqual(obj.get_period_as_string(), '1 sec')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds(b'-1 sec Z'), -1.0)
        self.assertEqual(obj.get_period_as_string(), '-1 sec')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+1 min gm'), 60.0)
        self.assertEqual(obj.get_period_as_string(), '1 min')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('-5 min GM'),
                         -300.0)
        self.assertEqual(obj.get_period_as_string(), '-5 min')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('+2 hr uct'),
                         7200.0)
        self.assertEqual(obj.get_period_as_string(), '2 hr')

        obj.reset()
        self.assertEqual(obj.parse_time_duration_to_seconds('-3 hr zulu'),
                         -10800.0)
        self.assertEqual(obj.get_period_as_string(), '-3 hr')

        return
def run_router_app(app_base):
    """

    :param CradlepointAppBase app_base: prepared resources: logger, cs_client
    :return:
    """
    # logger.debug("Settings({})".format(sets))

    # first, confirm no other function using serial
    value = SerialRedirectorConfig(app_base)
    value.refresh()
    if value.enabled():
        app_base.logger.error("Serial Redirector Function is Active!")
        app_base.logger.error("Aborting SDK application")
        return -3
    app_base.logger.debug("Good: Serial Redirector Function is Disabled.")

    value = SerialGpsConfig(app_base)
    value.refresh()
    if value.enabled():
        app_base.logger.error("Serial GPS Echo Function is Active!")
        app_base.logger.error("Aborting SDK application")
        return -4
    app_base.logger.debug("Good: Serial GPS Function is Disabled.")

    value = SerialGPIOConfig(app_base)
    value.refresh()
    if value.enabled():
        app_base.logger.error("Serial GPIO Function is Active!")
        app_base.logger.error("Aborting SDK application")
        return -5
    app_base.logger.debug("Good: Serial GPIO Function is Disabled.")

    server_loop = ModbusBridge()
    server_loop.logger = app_base.logger

    if "modbus_ip" in app_base.settings:
        temp = app_base.settings["modbus_ip"]
        if "host_ip" in temp:
            # assume is string of correct format
            server_loop.host_ip = clean_string(temp["host_ip"])

        if "host_port" in temp:
            # force to integer
            server_loop.host_port = parse_integer(temp["host_port"])

        if "idle_timeout" in temp:
            # support seconds, or like '5 min'
            duration = TimeDuration(clean_string(temp["idle_timeout"]))
            server_loop.idle_timeout = duration.get_seconds()

        if "protocol" in temp:
            value = validate_ia_protocol(clean_string(temp["protocol"]))
            if value not in (IA_PROTOCOL_MBTCP, IA_PROTOCOL_MBRTU,
                             IA_PROTOCOL_MBASC):
                raise ValueError("Invalid IP-packed Modbus Protocol {}".format(
                    type(value)))
            server_loop.host_protocol = value

    if "modbus_serial" in app_base.settings:
        temp = app_base.settings["modbus_serial"]
        if "port_name" in temp:
            server_loop.serial_name = clean_string(temp["port_name"])

        if "baud_rate" in temp:
            server_loop.serial_baud = parse_integer(temp["baud_rate"])

        if "parity" in temp:
            server_loop.serial_baud = parse_integer(temp["baud_rate"])

        if "protocol" in temp:
            value = validate_ia_protocol(clean_string(temp["protocol"]))
            # confirm is serial, so RTU or ASCII
            if value not in (IA_PROTOCOL_MBRTU, IA_PROTOCOL_MBASC):
                raise ValueError("Invalid Serial Modbus Protocol {}".format(
                    type(value)))
            server_loop.serial_protocol = value

    # this should run forever
    try:
        result = server_loop.run_loop()

    except KeyboardInterrupt:
        result = 0

    return result
Exemple #10
0
    from cp_lib.load_settings_ini import copy_config_ini_to_json, \
        load_sdk_ini_as_dict
    from cp_lib.parse_duration import TimeDuration

    copy_config_ini_to_json()

    app_path = "demo/gps_replay"
    my_app = CradlepointAppBase(app_path, call_router=False)
    # force a heavy reload of INI (app base normally only finds JSON)
    my_app.settings = load_sdk_ini_as_dict(app_path)

    if len(sys.argv) == 2:
        # assume is numeric seconds
        shifter = parse_float(sys.argv[1])

    elif len(sys.argv) >= 3:
        # assume is tagged time, like "15 min"
        period = TimeDuration(sys.argv[1] + ' ' + sys.argv[2])
        shifter = parse_float(period.get_seconds())

    else:
        my_app.logger.warning("You need to append the time in seconds")
        sys.exit(-1)

    my_app.logger.info("Time shifter = {} seconds".format(shifter))

    _result = run_router_app(my_app, shifter)

    my_app.logger.info("Exiting, status code is {}".format(_result))
    sys.exit(_result)
Exemple #11
0
    def __init__(self, name, app_base):
        """
        prep our thread, but do not start yet

        :param str name: name for the thread
        :param CradlepointAppBase app_base: prepared resources: logger, etc
        """
        threading.Thread.__init__(self, name=name)

        self.app_base = app_base
        self.app_base.logger.info("started INIT")

        # how long to delay between checking the GPIO
        self.loop_delay = self.app_base.settings["power_loss"].get(
            "check_input_delay", 15)
        # support things like '1 min' or 15
        duration = TimeDuration(self.loop_delay)
        self.loop_delay = float(duration.get_seconds())

        # how long to wait, to double-check LOSS
        self.loss_delay = self.app_base.settings["power_loss"].get(
            "loss_delay", 1)
        self.loss_delay = duration.parse_time_duration_to_seconds(
            self.loss_delay)

        # how long to wait, to double-check RESTORE
        self.restore_delay = self.app_base.settings["power_loss"].get(
            "restore_delay", 1)
        self.restore_delay = duration.parse_time_duration_to_seconds(
            self.restore_delay)

        # when GPIO matches this state, then power is lost
        self.state_in_alarm = self.app_base.settings["power_loss"].get(
            "match_on_power_loss", False)
        # support 'true', '1' etc - but finally is True/False
        self.state_in_alarm = parse_boolean(self.state_in_alarm)

        # when 'power is lost', send to LED
        self.led_in_alarm = self.app_base.settings["power_loss"].get(
            "led_on_power_loss", None)
        try:
            # see if the setting is None, to disable
            self.led_in_alarm = parse_none(self.led_in_alarm)

        except ValueError:
            # support 'true', '1' etc - but finally is True/False
            self.led_in_alarm = parse_boolean(self.led_in_alarm)

        # when GPIO matches this state, then power is lost
        self.site_name = self.app_base.settings["power_loss"].get(
            "site_name", "My Site")

        # create an event to manage our stopping
        # (Note: on CP router, this isn't strictly true, as when the parent is
        # stopped/halted, the child dies as well. However, you may want
        # your sub task to clean up before it exists
        self.keep_running = threading.Event()
        self.keep_running.set()

        # hold the .get_power_loss_status()
        self.last_state = None

        # special tweak to announce 'first poll' more smartly
        self.starting_up = True

        self.email_settings = dict()
        self.prep_email_settings()

        return