Skip to content

Commit

Permalink
Add power command (#169)
Browse files Browse the repository at this point in the history
* Always output status of IEB in status command

* Move the ieb power and ieb switch command to power

* Update changelog

* Do not send command in status if no positioners

* Fix outputing category
  • Loading branch information
albireox committed Dec 5, 2021
1 parent 2c73856 commit 0454c30
Show file tree
Hide file tree
Showing 8 changed files with 440 additions and 382 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

* [#163](https://github.com/sdss/jaeger/issues/163) The to and from destination trajectories are saved when `BaseConfiguration.decollide_and_get_paths()` is called. The reverse path can be sent from the actor using `configuration reverse`. The paths can be generated in advance when loading the design. An ``--epoch-delay`` parameter can be passed when loading the design to create a configuration for some time in the future.
* [#167](https://github.com/sdss/jaeger/issues/167) Add the ability of loading a configuration from the current positions of the robots.
* [#169](https://github.com/sdss/jaeger/issues/169) Move `ieb power` and `ieb switch` to simply `power`.

### ✨ Improved

Expand Down
1 change: 1 addition & 0 deletions python/jaeger/actor/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .ieb import *
from .pollers import *
from .positioner import *
from .power import *
from .snapshot import *
from .talk import *
from .unwind import *
327 changes: 7 additions & 320 deletions python/jaeger/actor/commands/ieb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,27 @@
# @Filename: ieb.py
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause)

import asyncio
import math
from __future__ import annotations

from typing import Tuple
from typing import TYPE_CHECKING, Tuple

import click

from clu.command import Command
from clu.parsers.click import pass_args

from jaeger import config
from jaeger.fps import FPS
from jaeger.ieb import IEB
from jaeger.testing import VirtualFPS
from jaeger.ieb import IEB, _get_category_data

from .. import JaegerActor
from . import jaeger_parser


__all__ = ["ieb"]


async def _get_category_data(command, category) -> list:

ieb = command.actor.fps.ieb
schema = command.actor.model.schema
if TYPE_CHECKING:
from clu.command import Command

items = schema["properties"][category]["items"]
measured = []
from .. import JaegerActor

async with ieb:
for item in items:
name = item["title"]
type_ = item["type"]
device = ieb.get_device(name)
value = (await device.read(connect=False))[0]
if type_ == "boolean" and device.__type__ == "relay":
value = True if value == "closed" else False
elif type_ == "integer":
value = int(value)
elif type_ == "number":
if "multipleOf" in item:
precision = int(-math.log10(item["multipleOf"]))
else:
precision = 3
value = round(value, precision)
measured.append(value)

return measured
__all__ = ["ieb"]


@jaeger_parser.group()
Expand Down Expand Up @@ -98,291 +70,6 @@ async def status(command, fps):
return command.finish(message=status_data, concatenate=True)


@ieb.command()
@click.argument("DEVICES", type=str, nargs=-1)
@click.option(
"--on/--off",
default=None,
help="the desired state of the device. If not passed, switches the current state.",
)
@click.option(
"--cycle",
is_flag=True,
help="power cycles a relay. The final status is on.",
)
@click.option(
"--delay",
type=float,
default=1,
help="When powering multiple devices, the delay to wait between them.",
)
@click.option(
"--force",
is_flag=True,
help="Forces a device to turn on/off even if disabled..",
)
async def switch(command, fps, devices=(), on=None, cycle=False, delay=1, force=False):
"""Switches the status of an on/off device."""

ieb = fps.ieb

if len(devices) == 0:
return command.fail(error="No devices specified.")

for idev, device in enumerate(devices):

if device.upper() in config["ieb"]["disabled_devices"]:
if force is False:
command.warning(text=f"{device} is disabled. Skipping.")
continue
else:
command.warning(text=f"{device} is disabled but overriding.")

if len(devices) > 1 and idev != 0:
await asyncio.sleep(delay)

if cycle:
on = False

try:
device_obj = ieb.get_device(device)
dev_name = device_obj.name
category = device_obj.category.lower()
except ValueError:
return command.fail(error=f"cannot find device {device!r}.")

if device_obj.module.mode not in ["holding_register", "coil"]:
return command.fail(error=f"{dev_name!r} is not an output.")

if on is None: # The --on/--off was not passed
current_status = (await device_obj.read())[0]
if current_status == "closed":
on = False
elif current_status == "open":
on = True
else:
return command.fail(
error=f"invalid status for device {dev_name!r}: {current_status!r}."
)

try:
if on is True:
await device_obj.close()
elif on is False:
await device_obj.open()
except Exception:
return command.fail(error=f"failed to set status of device {dev_name!r}.")

if cycle:
command.write("d", text="waiting 1 second before powering up.")
await asyncio.sleep(1)
try:
await device_obj.close()
except Exception:
return command.fail(
error=f"failed to power device {dev_name!r} back on."
)

# If we read the status immediately sometimes we still get the old one.
# Sleep a bit to avoid that.
await asyncio.sleep(0.2)

status = "on" if (await device_obj.read())[0] == "closed" else "off"

command.info(
message={
"text": f"device {dev_name!r} is now {status!r}.",
category: await _get_category_data(command, category),
}
)

return command.finish()


async def _power_sequence(command, ieb, seq, mode="on", delay=3) -> bool:
"""Applies the power on/off sequence."""

# To speed up tests
if isinstance(command.actor.fps, VirtualFPS):
delay = 0.01

relay_result = "closed" if mode == "on" else "open"

command.info(text=f"Running power {mode} sequence")

# First check that the SYNC line is open.
sync = ieb.get_device("SYNC")
if (await sync.read())[0] == "closed":
command.debug(text="SYNC line is high. Opening it.")
await sync.open()

if (await sync.read())[0] != "open":
command.fail(error="Failed opening SYNC line.")
return False

command.debug(power_sync=[False])

disabled = config["ieb"]["disabled_devices"]

for devname in seq:

do_delay = False
if isinstance(devname, str):
if devname.upper() in config["ieb"]["disabled_devices"]:
command.warning(text=f"{devname} is disabled. Skipping.")
continue

dev = ieb.get_device(devname)
category = dev.category.lower()

if (await dev.read())[0] == relay_result:
command.debug(text=f"{devname} already powered {mode}.")
else:
command.debug(text=f"Powering {mode} {devname}.")
await dev.close() if mode == "on" else await dev.open()
await asyncio.sleep(0.2)
if (await dev.read())[0] != relay_result:
command.fail(error=f"Failed powering {mode} {devname}.")
return False

do_delay = True

command.debug({category: await _get_category_data(command, category)})

elif isinstance(devname, (tuple, list)):
devname = list([dn for dn in devname if dn not in disabled])
devs = [ieb.get_device(dn) for dn in devname]
category = devs[0].category.lower()

status = list(await asyncio.gather(*[dev.read() for dev in devs]))

keep = []
for ii, res in enumerate(status):
if res[0] == relay_result:
command.debug({"text": f"{devname[ii]} already powered {mode}."})
continue
keep.append(ii)

devs = [devs[ii] for ii in keep]
devname = [devname[ii] for ii in keep]

if len(devs) > 0:
command.debug(text=f"Powering {mode} {', '.join(devname)}")
await asyncio.gather(
*[dev.close() if mode == "on" else dev.open() for dev in devs]
)
do_delay = True

status = list(await asyncio.gather(*[dev.read() for dev in devs]))
for ii, res in enumerate(status):
if res[0] != relay_result:
command.fail(error=f"Failed powering {mode} {devname[ii]}.")
return False

command.debug({category: await _get_category_data(command, category)})

else:
command.fail(error=f"Invalid relay {devname!r}.")
return False

if do_delay:
await asyncio.sleep(delay)

return True


@ieb.group()
@pass_args()
def power(command, fps):
"""Runs the power on/off sequences."""

pass


@power.command()
@click.option("--no-gfas", is_flag=True, help="Do not power the GFAs.")
async def on(command, fps: FPS, no_gfas: bool = False):
"""Powers on all the FPS IEB components."""

command.info(text="Turning pollers off.")
await fps.pollers.stop()

ieb = fps.ieb
if not isinstance(ieb, IEB) or ieb.disabled:
return command.fail(error="IEB is not conencted or is disabled.")

# Sequence of relays to power on. Tuples indicate relays that can be powered
# on concurrently.
on_seq = [
("CM1", "CM2", "CM3", "CM4", "CM5", "CM6"),
"PS1",
"PS2",
"PS3",
"PS4",
"PS5",
"PS6",
"NUC1",
"NUC2",
"NUC3",
"NUC4",
"NUC5",
"NUC6",
]

if no_gfas is False:
on_seq += [
"GFA1",
"GFA2",
"GFA3",
"GFA4",
"GFA5",
"GFA6",
]

if not (await _power_sequence(command, ieb, on_seq, mode="on")):
return

return command.finish(text="Power on sequence complete.")


@power.command()
@click.option("--nucs", is_flag=True, help="Also power down NUCs")
async def off(command, fps, nucs):
"""Powers off all the FPS IEB components."""

command.info(text="Turning pollers off.")
await fps.pollers.stop()

ieb = fps.ieb
if not isinstance(ieb, IEB) or ieb.disabled:
return command.fail(error="IEB is not conencted or is disabled.")

# Sequence of relays to power off. Tuples indicate relays that can be powered
# off concurrently.
off_seq = [
"PS1",
"PS2",
"PS3",
"PS4",
"PS5",
"PS6",
"GFA1",
"GFA2",
"GFA3",
"GFA4",
"GFA5",
"GFA6",
]

if nucs:
off_seq += ["NUC1", "NUC2", "NUC3", "NUC4", "NUC5", "NUC6"]

if not (await _power_sequence(command, ieb, off_seq, mode="off")):
return

return command.finish(text="Power off sequence complete.")


@ieb.command()
@click.argument("device_names", metavar="DEVICES", type=str, nargs=-1)
@click.argument("VALUE", type=click.FloatRange(0, 100))
Expand Down

0 comments on commit 0454c30

Please sign in to comment.