Ejemplo n.º 1
0
def runtest(cfg: Config, args: argparse.Namespace,
            df: Optional[Datafile]) -> bool:
    logger = logging.getLogger()
    # Extract parameters from configuration files
    try:
        adc = ADS1115(address=cfg.get_int('Adc', 'Addr'),
                      busnum=cfg.get_int('Adc', 'Bus'))
        sens = AnalogFlowMeter(adc, cfg.get_int('AnalogFlowSensor', 'Chan'),
                               cfg.get_expr('AnalogFlowSensor', 'Gain'),
                               cfg.get_array('AnalogFlowSensor', 'Coeff'))
    except BadEntry:
        logger.exception("Configuration error")
        return False

    interval = 1. / args.rate
    print("Enter ctrl-c to exit ...", file=sys.stderr)
    sens.reset()
    try:
        for tick in ticker(interval):
            vol, secs = sens.amount()
            rec = OrderedDict(elapsed=round(secs, 3), vol=round(vol, 3))
            writerec(rec)
            if df:
                df.add_record("flow", rec, ts=tick)
    except KeyboardInterrupt:
        print("done", file=sys.stderr)

    return True
Ejemplo n.º 2
0
def quick_validate(cfg: Config):
    """
    Verify that all required configuration entries are present, raises
    a config.BadEntry exception if any are missing.
    """
    for k, vals in required.items():
        for val in vals:
            cfg.get_string(k, val)
Ejemplo n.º 3
0
def validate(cfg: Config) -> List[str]:
    """
    Verify that all required configuration entries are present, returns
    a list of all missing entries.
    """
    missing = []
    for k, vals in required.items():
        for val in vals:
            try:
                cfg.get_string(k, val)
            except BadEntry:
                missing.append("/".join([k, val]))
    return missing
Ejemplo n.º 4
0
class ValidationTest(unittest.TestCase):
    def setUp(self):
        path = os.path.join(os.path.dirname(__file__), "example.cfg")
        self.cfg = Config(path)
        self.badcfg = Config(StringIO(INPUT))

    def test_validate(self):
        missing = self.cfg.validate()
        self.assertEqual(len(missing), 0)

    def test_validate_fail(self):
        missing = self.badcfg.validate()
        self.assertGreater(len(missing), 0)
Ejemplo n.º 5
0
def main() -> int:
    args = parse_cmdline()

    try:
        cfg = Config(args.syscfg)
    except Exception as e:
        print("Error loading system config file; " + str(e), file=sys.stderr)
        return 1

    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
                        level=logging.INFO,
                        datefmt='%Y-%m-%d %H:%M:%S',
                        stream=sys.stderr)
    # eDNA uses the Broadcom SOC pin numbering scheme
    GPIO.setmode(GPIO.BCM)
    # If we don't suppress warnings, as message will be printed to stderr
    # everytime GPIO.setup is called on a pin that isn't in the default
    # state (input).
    GPIO.setwarnings(False)

    status = False
    try:
        if args.out:
            status = runtest(cfg, args, Datafile(args.out))
        else:
            status = runtest(cfg, args, None)
    except Exception as e:
        logging.exception("Error running the pump test")
    finally:
        if args.clean:
            GPIO.cleanup()
    return 0 if status else 1
Ejemplo n.º 6
0
def main():
    parser = argparse.ArgumentParser(
        description="Validate an eDNA deployment configuration")
    parser.add_argument("cfg",
                        metavar="FILE",
                        nargs="?",
                        default="",
                        help="deployment configuration file")
    parser.add_argument(
        "--sys",
        metavar="FILE",
        default=os.path.expanduser("~/.config/edna/system.cfg"),
        help="location of the system confguration file")
    args = parser.parse_args()

    try:
        cfg = Config(args.sys)
    except Exception as e:
        print("Error loading system config file; " + str(e), file=sys.stderr)
        return 1

    if args.cfg:
        try:
            cfg.load(args.cfg)
        except Exception as e:
            print("Error loading deployment config file; " + str(e),
                  file=sys.stderr)
            return 1

    try:
        missing = validate(cfg)
        if missing:
            print("Missing entries: {}".format(";".join(missing)))
            return 1
    except Exception as e:
        print("Validation failed: {}".format(str(e)), file=sys.stderr)
        return 1
    return 0
Ejemplo n.º 7
0
def main() -> int:
    args = parse_cmdline()

    try:
        cfg = Config(args.syscfg)
    except Exception as e:
        print("Error loading system config file; " + str(e), file=sys.stderr)
        return 1

    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
                        level=logging.INFO,
                        datefmt='%Y-%m-%d %H:%M:%S',
                        stream=sys.stderr)
    status = False
    try:
        if args.out:
            status = runtest(cfg, args, Datafile(args.out))
        else:
            status = runtest(cfg, args, None)
    except Exception:
        logging.exception("Error running the sensor test")

    return 0 if status else 1
Ejemplo n.º 8
0
def main() -> int:
    args = parse_cmdline()

    try:
        cfg = Config(args.syscfg)
    except Exception as e:
        print("Error loading system config file; " + str(e), file=sys.stderr)
        return 1

    try:
        cfg.load(args.cfg)
    except Exception as e:
        print("Error loading deployment config file; " + str(e), file=sys.stderr)
        return 1

    # Validate configuration files
    missing = cfg.validate()
    if missing:
        print("Missing configuration entries: {}".format(";".join(missing)))
        return 1

    # Generate deployment ID and directory name
    id = datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y%m%dT%H%M%S")
    dir = os.path.join(args.datadir, "edna_" + id)
    deployment = Deployment(id=id, dir=dir)

    # Create deployment directory
    os.makedirs(deployment.dir, exist_ok=True)
    # Initialize logging
    init_logging(deployment.dir, deployment.id, debug=args.debug)
    # Save configuration to deployment directory
    with open(os.path.join(deployment.dir, "deploy.cfg"), "w") as fp:
        cfg.write(fp)

    # Save deployment ID if specified
    if args.id != "":
        with open(os.path.join(deployment.dir, "id"), "w") as fp:
            print(args.id, file=fp)

    signal.signal(signal.SIGINT, abort)
    signal.signal(signal.SIGTERM, abort)

    logger = logging.getLogger()
    # eDNA uses the Broadcom SOC pin numbering scheme
    GPIO.setmode(GPIO.BCM)
    # If we don't suppress warnings, a message will be printed to stderr
    # everytime GPIO.setup is called on a pin that isn't in the default
    # state (input).
    GPIO.setwarnings(False)

    prfilt: Callable[[float], float]
    if args.alpha > 0:
        prfilt = EMA(args.alpha)
    else:
        prfilt = lambda x: x

    status = False
    try:
        name = "edna_" + deployment.id + ".ndjson"
        with open(os.path.join(deployment.dir, name), "w") as fp:
            status = runedna(cfg, deployment, Datafile(fp), prfilt)
    except Exception:
        logger.exception("Deployment aborted with an exception")

    os.makedirs(args.outbox, exist_ok=True)
    # Archive the deployment directory to the OUTBOX
    arpath = os.path.join(args.outbox, "edna_" + deployment.id + ".tar.gz")
    logger.info("Archiving deployment directory to %s", arpath)
    with tarfile.open(arpath, "w:gz") as tar:
        head, tail = os.path.split(deployment.dir)
        os.chdir(head)
        tar.add(tail)

    if args.clean:
        GPIO.cleanup()

    return 0 if status else 1
Ejemplo n.º 9
0
def runedna(cfg: Config,
            deployment: Deployment,
            df: Datafile,
            prfilt: Callable[[float], float]) -> bool:
    logger = logging.getLogger()
    logger.info("Starting deployment: %s", deployment.id)

    if cfg.has_section("Metadata"):
        meta = []
        for key in cfg.options("Metadata"):
            meta.append(key)
            meta.append(cfg.get_string("Metadata", key))
        df.add_record("metadata", meta)

    # Extract parameters from configuration files
    try:
        pr = dict()
        adc = ADS1115(address=cfg.get_int('Adc', 'Addr'),
                      busnum=cfg.get_int('Adc', 'Bus'))
        pr["Filter"] = PrSensor(adc,
                                cfg.get_int('Pressure.Filter', 'Chan'),
                                cfg.get_expr('Pressure.Filter', 'Gain'),
                                coeff=cfg.get_array('Pressure.Filter', 'Coeff'))
        pr["Env"] = PrSensor(adc,
                             cfg.get_int('Pressure.Env', 'Chan'),
                             cfg.get_expr('Pressure.Env', 'Gain'),
                             coeff=cfg.get_array('Pressure.Env', 'Coeff'))
        # Discard the first sample
        psi_to_dbar(pr["Env"].read())

        prmax = cfg.get_float('Pressure.Filter', 'Max')

        def checkpr() -> Tuple[float, bool]:
            psi = pr["Filter"].read()
            return psi, psi < prmax

        def checkdepth(limits: Tuple[float, float]) -> Tuple[float, bool]:
            dbar = psi_to_dbar(pr["Env"].read())
            return prfilt(dbar), limits[0] <= dbar <= limits[1]

        pumps = dict()
        for key in ("Sample", "Ethanol"):
            pumps[key] = Pump(cfg.get_int('Motor.'+key, 'Enable'))

        valves = dict()
        for key in ("1", "2", "3", "Ethanol"):
            vkey = "Valve." + key
            valves[key] = Valve(cfg.get_int(vkey, 'Enable'),
                                cfg.get_int(vkey, 'IN1'),
                                cfg.get_int(vkey, 'IN2'),
                                lopen=cfg.get_string(vkey, 'open'),
                                lclose=cfg.get_string(vkey, 'close'))

        fm = AnalogFlowMeter(adc,
                             cfg.get_int('AnalogFlowSensor', 'Chan'),
                             cfg.get_expr('AnalogFlowSensor', 'Gain'),
                             cfg.get_array('AnalogFlowSensor', 'Coeff'))
        sample_rate = cfg.get_float('FlowSensor', 'Rate')
        ledctl = LedCtl(obj=LED(cfg.get_int("LED", "GPIO")),
                        fast=cfg.get_float("LED", "fast"),
                        slow=cfg.get_float("LED", "slow"),
                        fade=cfg.get_float("LED", "fade"))

        limits = dict()
        for key in ("Sample", "Ethanol"):
            limits[key] = FlowLimits(amount=cfg.get_float('Collect.'+key, 'Amount'),
                                     time=cfg.get_float('Collect.'+key, 'Time'))

        deployment.seek_err = cfg.get_float('Deployment', 'SeekErr')
        deployment.depth_err = cfg.get_float('Deployment', 'DepthErr')
        deployment.pr_rate = cfg.get_float('Deployment', 'PrRate')
        deployment.seek_time = cfg.get_int('Deployment', 'SeekTime')
        deployment.downcast = cfg.get_bool('Deployment', 'Downcast')

        # Each entry in depths is a tuple containing the depth and
        # the sample index.
        depths = []
        for i, key in enumerate(['Sample.1', 'Sample.2', 'Sample.3']):
            depths.append((cfg.get_float(key, 'Depth'), i+1))
    except BadEntry:
        logger.exception("Configuration error")
        return False

    try:
        batteries = [Battery(SMBus(0)), Battery(SMBus(1))]
    except Exception:
        logger.exception("Battery monitoring disabled")
        batteries = []

    # Samples are collected in depth order, not index order.
    depths.sort(key=lambda e: e[0], reverse=not deployment.downcast)
    for target, index in depths:
        logger.info("Seeking depth for sample %d; %.2f +/- %.2f",
                    index, target, deployment.seek_err)
        drange = (target-deployment.seek_err, target+deployment.seek_err)
        with blinker(ledctl.obj, ledctl.slow):
            depth, status = seekdepth(df,
                                      partial(checkdepth, drange),
                                      deployment.pr_rate,
                                      deployment.seek_time)
        if not status:
            logger.critical("Depth seek time limit expired. Aborting.")
            return False

        logger.info("Collecting sample %d", index)
        drange = (depth-deployment.depth_err, depth+deployment.depth_err)
        status = collect(df, index,
                         (pumps["Sample"], pumps["Ethanol"]),
                         valves,
                         fm, sample_rate,
                         (limits["Sample"], limits["Ethanol"]),
                         checkpr,
                         partial(checkdepth, drange), batteries)

    return True
Ejemplo n.º 10
0
 def setUp(self):
     path = os.path.join(os.path.dirname(__file__), "example.cfg")
     self.cfg = Config(path)
     self.badcfg = Config(StringIO(INPUT))
Ejemplo n.º 11
0
 def setUp(self):
     self.cfg = Config(StringIO(INPUT))
     self.cfg.load(StringIO(OVERRIDE))
Ejemplo n.º 12
0
class ConfigTestCase(unittest.TestCase):
    def setUp(self):
        self.cfg = Config(StringIO(INPUT))
        self.cfg.load(StringIO(OVERRIDE))

    def test_get_int(self):
        x = self.cfg.get_int('Valve.1', 'Enable')
        self.assertEqual(x, 27)

    def test_get_int_hex(self):
        x = self.cfg.get_int('Adc', 'Addr')
        self.assertEqual(x, 0x48)

    def test_get_str(self):
        s = self.cfg.get_string('System', 'LogDir')
        self.assertEqual(s, "/home/pi/eDNA/logs")

    def test_get_float(self):
        x = self.cfg.get_float('Foo', 'Bar')
        self.assertEqual(x, 2.5)

    def test_get_array(self):
        x = self.cfg.get_array('Foo', 'Array')
        self.assertEqual(x, [5., 6., 7., 8.])

    def test_override(self):
        x = self.cfg.get_int('Foo', 'Baz')
        self.assertEqual(x, 43)

    def test_expr(self):
        x = self.cfg.get_expr('Adc', 'Gain')
        self.assertEqual(x, 2 / 3)

    def test_bad_key(self):
        self.assertRaises(BadEntry, self.cfg.get_int, 'Valve.5', 'Enable')

    def test_get_bool(self):
        x = self.cfg.get_bool('Deployment', 'Downcast')
        self.assertEqual(x, True)

    def test_bool_default(self):
        x = self.cfg.get_bool('Foo', 'NotFound')
        self.assertEqual(x, False)
Ejemplo n.º 13
0
def main():
    parser = argparse.ArgumentParser(description="Prime an eDNA flow path")
    parser.add_argument("cfg",
                        metavar="FILE",
                        help="system configuration file")
    parser.add_argument("valve",
                        metavar="N",
                        type=int,
                        help="valve# to prime, 1-3")
    parser.add_argument("--time",
                        metavar="SECS",
                        type=float,
                        default=30,
                        help="runtime in seconds")
    parser.add_argument("--prmax",
                        metavar="PSI",
                        type=float,
                        default=12,
                        help="maximum filter pressure in psi")
    args = parser.parse_args()

    try:
        cfg = Config(args.cfg)
    except Exception as e:
        print("Error loading config file; " + str(e), file=sys.stderr)
        sys.exit(1)

    if args.valve < 1 or args.valve > 3:
        print("Valve number must be between 1 and 3", file=sys.stderr)
        sys.exit(1)

    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
                        level=logging.INFO,
                        datefmt='%Y-%m-%d %H:%M:%S',
                        stream=sys.stderr)

    # eDNA uses the Broadcom SOC pin numbering scheme
    GPIO.setmode(GPIO.BCM)
    try:
        adc = ADS1115(address=0x48, busnum=cfg.get_int('Adc', 'Bus'))
        pr_chan = cfg.get_int('Pressure.Filter', 'Chan')
        pr_gain = cfg.get_float('Pressure.Filter', 'Gain')

        motor = cfg.get_int('Motor.Sample', 'Enable')
        GPIO.setup(motor, GPIO.OUT)

        # Config file key for valve
        vkey = "Valve." + str(args.valve)
        sample_valve = Valve(cfg.get_int(vkey, 'Enable'),
                             cfg.get_int(vkey, 'Power'),
                             cfg.get_int(vkey, 'Gnd'))
        eth_valve = Valve(cfg.get_int('Valve.Ethanol', 'Enable'),
                          cfg.get_int('Valve.Ethanol', 'Power'),
                          cfg.get_int('Valve.Ethanol', 'Gnd'))
    except BadEntry as e:
        print(str(e), file=sys.stderr)
        GPIO.cleanup()
        sys.exit(2)

    try:
        prime_cycle(sample_valve, eth_valve, motor,
                    partial(checkpr, adc, pr_chan, pr_gain, args.prmax),
                    args.time)
    finally:
        GPIO.cleanup()
Ejemplo n.º 14
0
def runtest(cfg: Config, args: argparse.Namespace,
            df: Optional[Datafile]) -> bool:
    logger = logging.getLogger()
    # Extract parameters from configuration files
    try:
        sens = dict()
        adc = ADS1115(address=cfg.get_int('Adc', 'Addr'),
                      busnum=cfg.get_int('Adc', 'Bus'))
        sens["Filter"] = PrSensor(adc, cfg.get_int('Pressure.Filter', 'Chan'),
                                  cfg.get_expr('Pressure.Filter', 'Gain'))

        prmax = cfg.get_float('Pressure.Filter', 'Max')

        def checkpr() -> Tuple[float, bool]:
            psi = sens["Filter"].read()
            return psi, psi < prmax

        pumps = dict()
        for key in ("Sample", "Ethanol"):
            pumps[key.lower()] = Pump(cfg.get_int('Motor.' + key, 'Enable'))

        valves = dict()
        for key in ("1", "2", "3", "Ethanol"):
            vkey = "Valve." + key
            valves[key.lower()] = Valve(cfg.get_int(vkey, 'Enable'),
                                        cfg.get_int(vkey, 'IN1'),
                                        cfg.get_int(vkey, 'IN2'),
                                        lopen=cfg.get_string(vkey, 'open'),
                                        lclose=cfg.get_string(vkey, 'close'))

        fm = AnalogFlowMeter(adc, cfg.get_int('AnalogFlowSensor', 'Chan'),
                             cfg.get_expr('AnalogFlowSensor', 'Gain'),
                             cfg.get_array('AnalogFlowSensor', 'Coeff'))
    except BadEntry:
        logger.exception("Configuration error")
        return False

    if args.pump not in pumps:
        logger.critical("Invalid pump name: '%s'", args.pump)
        return False

    if args.valve not in valves:
        logger.critical("Invalid valve: '%s'", args.valve)
        return False

    interval = 1. / args.rate
    print("Enter ctrl-c to exit ...", file=sys.stderr)
    try:
        fm.reset()
        with valves[args.valve]:
            with pumps[args.pump]:
                for tick in ticker(interval):
                    amount, secs = fm.amount()
                    pr, pr_ok = checkpr()
                    rec = OrderedDict(elapsed=round(secs, 3),
                                      vol=round(amount, 3),
                                      pr=round(pr, 3))
                    writerec(rec)
                    if df:
                        df.add_record("flow", rec, ts=tick)
    except KeyboardInterrupt:
        print("done", file=sys.stderr)

    return True