PhytO-ARM is robot operating system (ROS)-based toolkit for integration of Imaging FlowCytobot (IFCB) and other ocean sensors that are commonly deployed by the Brosnahan Lab. The supported ROS release is Noetic Ninjemys.
There are instructions for assembling a profiling configuration at our accompanying PhytO-ARM website. The profiling system consists of an IFCB, an AML ctd, and a winch built from a JVL servo motor and parts from Conedrive and Waterman Industries. Once assembled, code provided here enables a variety of continuous sampling behaviors including continuous CTD profiling and targeted collection of IFCB samples at chlorophyll maxima. Other scientific payloads are also supported, and PhytO-ARM can coordinate an arbitrary number of winches running concurrently.
PhytO-ARM observations of location, depth, water properties, etc. are written to IFCB metadata files at time of collection for distribution through an existing webservices system called [IFCB dashboard][ifcbdb]. Additional scripts are provided for export to CF-compliant NetCDF data formats and upload to repositories like the NOAA ERDDAP system.
During and after deployments, system operators can review system data using standard ROS tools like FoxGlove Studio.
Three types of ROS nodes are provided. Device nodes enable integration of IFCB, CTD, GPS, servo, and IP camera systems. Behavior nodes interpret device node data streams and direct changes in system behavior. System nodes log ROS traffic and make it available for real-time interactive display (Rosbridge).
The following are required for PhytO-ARM to run:
- CTD (AML and RBR currently supported)
- GPS (gpsd service must be present, but device itself can be absent)
The following devices are optional:
- IFCB with IFCBacquire running
- JVL motor (see
example.yamlfor how to enable/disable) - IP Camera
First ensure docker is installed:
docker -vIf this does not print a version number, install with:
sudo apt-get update && sudo apt-get install docker-ioRunning Docker will also require sudo unless you add your user to the docker group:
# Adds user 'ifcb' to 'docker' group
sudo usermod -aG docker ifcb && newgrp dockerFinally, pull the phyto-arm image. Container images are built for x86_64 and aarch64 and published automatically on Docker Hub by the continuous integration system.
docker pull whoi/phyto-arm:latestAlternatively, the container image can be built with
docker build --tag whoi/phyto-arm .
sudo ln -sf $(pwd)/phyto-arm.service /etc/systemd/system/phyto-arm.service
sudo systemctl daemon-reload
sudo systemctl enable phyto-arm
sudo systemctl start phyto-arm
ROS Launchpad is a simple web dashboard for managing ROS processes and config. The default Docker command will start it up, so simply starting the container is sufficient. ./scripts/docker_run.sh -d will also run the dashboard.
The phyto-arm tool starts all of the ROS nodes and loads the provided configuration file. This is typically run within the container.
phyto-arm start [-h] [--config_schema <schema file>] [--skip_validation] \
<launchfile name> <config file>
positional arguments:
<launchfile name>
Name of ROS launchfile to use in the phyto_arm package, excluding extension.
Currently one of 'main', 'arm_ifcb' or 'arm_chanos'.
<config file>
Path to YAML config file.
options:
-h, --help
Show help message.
--config_schema <schema file>
File to validate config against.
--skip_validation
Skip validation check altogether.
Below is an example Docker run script which includes both the phyto-arm command as well as the necesssary docker parameters to access devices, ports, and directories on the host. For your convenience, this command is also available as a script in scripts/docker_run.sh.
docker run --rm -it \
# Name of the container in docker
--name phyto-arm \
# Expose whatever port is being used by Foxglove
--publish 9090:9090/tcp \
# Expose whatever port is being used by web_node
--publish 8098:8098/tcp \
# If using an RBR CTD streaming over UDP, open the necessary port
--publish 12345:12345/udp \
# Bind config directory host:container. ':ro' = read-only
--volume "$(pwd)"/configs:/configs:ro \
# Bind IFCBAcquire routines directory host:container
--volume /home/ifcb/IFCBacquire/Host/Routines:/routines:ro \
# host:container bind for saving files to host, in this case
# for the log_dir and rosbag_prefix settings in example.yaml
--volume /mnt/data:/mnt/data \
# If using an AML CTD over serial, give access to device
--device /dev/ttyS3 \
# Name of Docker image:tag to use
whoi/phyto-arm:latest \
# Start command. Change to use desired config file, should be
# in the container config directory mapped above.
./phyto-arm start main /configs/config.yamlThe above script will start the main PhytO-ARM processes, but not launch any of the winch "arms" where science missions are defined. Currently there are two science payload implementations, for IFCB missions and Chanos sensor missions, called arm_ifcb and arm_chanos respectively.
Launching the arms independently means they can also be stopped, configured, and relaunched without interrupting other PhytO-ARM missions in progress.
After launching main with the script above, you can launch the arms with the following commands:
docker exec -it phyto-arm ./phyto-arm start arm_ifcb /configs/config.yamland
docker exec -it phyto-arm ./phyto-arm start arm_chanos /configs/config.yamlTo launch all three simultaneously as separate panes in a tmux session, run the scripts/tmux_run.sh script. Running this a second time will attach you to the existing session. To kill the session and all processes running within, use the scripts/tmux_kill.sh script.
Note that there are also launch files which substitute the IFCB, CTDs and motors for mock versions, suitable for local development and testing. These can be launched as:
docker exec -it phyto-arm start mock_arm_ifcb /configs/config.yamland
docker exec -it phyto-arm start mock_arm_chanos /configs/config.yaml
To run unit tests, execute
docker run -it --rm whoi/phyto-arm:latest /bin/bash -c "source devel/setup.bash && catkin test phyto_arm"Configuration files live in configs/ and use the YAML format. It is recommended that each deployment get its own configuration. An example config file is provided in example.yaml.
A strict YAML schema is not defined, instead another YAML file is used as a schema to compare against at runtime. configs/example.yaml is used by default, and validation is done by comparing both structure and data types.
You can skip validation by passing the --skip_validation argument to phyto-arm.
You can also provide your own schema by passing in the --config_schema <file> argument to phyto-arm. A few rules on how validation is completed:
- By default, all parameters in the schema file must also exist in the config file.
- Lines that contain
#optionalare not required. - The datatype for every parameter present in both config and schema must match.
- Parameters in config but not schema are ignored.
For more information see https://github.com/WHOIGit/example-yaml-validator
When running in a container, ensure the config YAML refers to the Docker host's IP address of 172.17.0.1 instead of using localhost when trying to access host services, such as in the config for /gps/host and /ifcb/address.
The entries in the config file are loaded into the ROS Parameter Server. Some parameters can be dynamically changed while the nodes are running:
$ source devel/setup.bash
$ rosparam get /conductor/schedule/every
60
$ rosparam set /profiler/data_topic /ctd/aml/port3/chloroblue
Be sure to make the corresponding edits to the config file to persist changes beyond the current session.
Use picocom to to talk to the appropriate serial device. Press Enter first to interrupt any current logging and get a prompt. Run the commands below in picocom to configure the AML output correctly.
$ picocom -b 115200 /dev/ttyS3
> set derive depth y
> set scan dep
> set derive salc y
> set scan sal
> set derive density y
> set scan den
> set scan raw
> mmonitor
...
Press Ctrl-A, Ctrl-X to exit picocom while the device is logging.
The time must be set to UTC. To sync the clock, you can use:
(echo; echo "set fulltime $(date -u "+%Y-%m-%d %H:%M:%S")") \
| picocom -b 115200 /dev/ttyS3
The current setup of the Chanos arm assumes the RBR CTD data is being transmitted to the host over UDP. In our setup, a proxy is running on a Raspberry Pi Zero which has a serial connection to the RBR, and forwards the CTD messages over UDP to the host.
The udp_to_ros.py node is used to read the proxied UDP packets and publish them on a ROS topic to be used by PhytO-ARM.
GPS tracking is provided via gpsd.
sudo apt install -y gpsd gpsd-clients
On Ubuntu, edit /etc/default/gpsd to configure the GPS device or network source. For example, to listen for UDP packets broadcast on the local network:
DEVICES="udp://192.168.13.255:22335"
Monitor that GPS updates are being received using gpsmon.
When running in a container, the gpsd service on the host needs to be modified to accept inbound connections from the container. Use systemctl edit gpsd.socket to create an override file:
# Allow clients to connect to gpsd from Docker.
# Based on https://stackoverflow.com/q/42240757
[Socket]
ListenStream=
ListenStream=/var/run/gpsd.sock
ListenStream=0.0.0.0:2947
Additional Ubuntu configuration that may be necessary to ensure GPS stays powered on and available.
Adding to the IP above, change /etc/default/gpsd to:
# Default settings for the gpsd init script and the hotplug wrapper.
# Start the gpsd daemon automatically at boot time
START_DAEMON="true"
# Use USB hotplugging to add new USB devices automatically to the daemon
USBAUTO="false"
# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
DEVICES="udp://192.168.13.255:22335"
# Other options you want to pass to gpsd
GPSD_OPTIONS=""Then use systemctl edit gpsd.service (may have to invoke sudo) and set the override file to:
# Always start GPSD, even if no one is connected to the socket
[Install]
WantedBy=multi-user.target- Config: YAML file created, compare against
example.yaml - CTD(s): hardware installed and powered on
- AML CTD: Used picocom to configure AML
- AML CTD: Confirmed broadcasting in
picocom - AML CTD: Device set correctly in config YAML
- AML CTD: Device mapped to container (if using Docker)
- RBR CTD: Raspberry Pi relay setup and powered on
- RBR CTD: Receiving UDP packets on host with
nc -lup 12345 - RBR CTD: Port mapped to container (if using Docker)
- GPS:
gpsdinstalled and running - GPS: If used, GPS device plugged in and IP configured
- IP camera: IP reserved and set correctly in config YAML
- Motor(s): If used, plugged in and outlets on
- Motor:
launch_args/ifcb_winchset to true if used, false otherwise - Motor:
launch_args/chanos_winchset to true if used, false otherwise - Motor(s): IPs reserved
- Motor(s): Ensure IP for each motor matches correct arm in YAML config
- Foxglove: If using container, Foxglove bridge port is exposed
- Data: Rosbag, data and logs directory exist as set in config YAML
- Data: If using container, data directory volumes are mounted
- IFCBacquire: Routines directory populated
- IFCBacquire: web_node server port exposed if using container
- IFCBacquire: Started and values set
- IFCB: Address, port, serial set in config YAML
- IFCB: Liquids loaded
- When ready, run PhytO-ARM for a cycle to confirm no errors
To develop in PhytO-ARM, it is recommended to use a development container with the provided development container configuration file. This can be done locally or on a remote machine (e.g. an IFCB).
- Install Visual Studio Code.
- After cloning the PhytO-ARM repository, open the directory in VS Code. If developing remotely, use Microsoft's Remote SSH extension to open the folder on the remote device.
- Open the
.devcontainer/devcontainer.jsonconfiguration file. You may need to comment out any lines which depend on hardware (e.g.,/dev/ttyS3) or folders (e.g./home/ifcb) not present on your development machine. - Install the Dev Containers extension published by Microsoft.
- The extension should automatically detect the existing
devcontainer.jsonconfiguration and prompt you to reopen the project as a development container. - Wait for the container to open. The first time may be slow as it builds the development image, expect to wait 10 minutes or longer depending on your hardware and Internet connection.
The development container allows you to develop and test from an environment identical to that used in production. If developing locally, launch arms with mocked hardware (see Running section above).
The .git repository folder is also mounted in the development container, making it possible to pull/commit/push as needed from within the container.
PhytO-ARM has evolved to handle multiple mission types and deployment configurations. Deployments so far include single-winch on a stationary platform, winchless on an autonomous surface vehicle, and multi-winch on a stationary platform.
PhytO-ARM has been modularized to support any configuration of winch and scientific payload. Scientific payloads are isolated into distinct "arms", each of which may have a winch providing movement for that arm. All arms should implement ArmBase, an interface class which handles winch movement logic and coordinates movement between arms by communicating with a central lock_manager node.
lock_manager is primarily to limit how many winches can move concurrently, as configured by the parameter /lock_manager/max_moving_winches. This is primarily to accommodate missions which may require inter-arm coordination, or to prevent overdraw of power on battery-powered setups.
PhytO-ARM comes with two arm implementations included, see arm_ifcb.py and arm_chanos.py in the phyto_arm package. Any number of arms is supported however, as illustrated below. Each box represents a set of nodes with an independent launch file.
graph TD
subgraph PhytO-ARM
subgraph Main
cameras(Cameras)
lm(Lock Manager)
gps(GPS)
end
subgraph Arm1
w1(Winch1)
subgraph Payload1
ifcb(IFCB)
c1(AML CTD)
end
end
subgraph Arm2
w2(Winch2)
subgraph Payload2
chanos(Chanos)
c2(RBR CTD)
end
end
subgraph ArmN
w3(WinchN)
subgraph PayloadN
unknown(?)
c4(? CTD)
end
end
end
lm <--> w1
lm <--> w2
lm <--> w3
Each arm implementing ArmBase must override get_next_task, which is the basis of an event loop where all decisions are made on which task to perform next. get_next_task is passed the previous task as context, and should return a new task containing movement instructions and a callback that will be executed when the movement completes.
graph
subgraph Arm Execution Loop
subgraph Main
lm(Lock Manager)
end
subgraph ArmBase
loop
winch(send_winch_goal)
snt(start_next_task)
subgraph Arm Implementation
gnt(get_next_task)
cb(Task callback)
end
end
end
loop -- Await clearance --> lm
lm -- Get task --> gnt
gnt -- Move to depth --> winch
gnt -. Skip if no winch .-> cb
winch -- Execute --> cb
cb --> snt
snt --> loop
The history of task states can be reconstructed via the task names published at /<arm name>/task.
In all deployments thus far, we have used the IFCB as the primary host to run PhytO-ARM and coordinate movements and sampling. The hardware architecture diagram below illustrates this.
graph
subgraph Platform
subgraph IFCB Arm
motor1(JVL motor)
aml(AML CTD)
subgraph IFCB
acquire{IFCB Acquire}
pa{PhytO-ARM}
end
end
subgraph Chanos Arm
pi(Rapsberry Pi)
motor2(JVL Motor)
chanos(Chanos)
rbr(RBR CTD)
end
end
subgraph Key
hw(Hardware)
sw{Software}
end
pa -- Drive (TCP) --> motor1
pa -- Drive (TCP) --> motor2
aml -- Serial --> pa
rbr -- Serial --> pi
pi -- UDP --> pa
chanos -- Timed sampling -->chanos
pa -- Trigger (TCP) --> acquire
Note that ROS path conventions are used, such that paths beginning with / are absolute, paths beginning with ~ start at the node, and paths without a prefix are relative to their namespace. Global namespace is used for main nodes, while arm_ifcb and arm_chanos namespaces are used for the IFCB and Chanos arms, respectively.
These nodes implement the core PhytO-ARM "algorithm" for sampling and are specific to the design of the platform.
-
lock_manager: Winch traffic control center, limits number of concurrent winch movements.- Publishes:
~acquirefor arms to request permission to move their winches~releasefor arms to release permission to move~checkfor arms to check whether they might have a dangling lock (e.g. after a crash or user stop)
- Publishes:
-
arm_base: Common task management capabilities for payloads, such as determining whether to move winch and enforcing geofence limits.- Publishes:
~task: Name of the current task being executed by the arm.
- Publishes:
-
arm_ifcb: Orchestrates IFCB payload behaviors- Subscribes:
ifcb_runner/sampleto run IFCB sampling actionsprofilerfor profile data, especially phy peakwinch/move_to_depth/resultto determine the success of a transitlock_manager/acquirefor getting clearance to move a winch. Some configurations may not permit simultaneous winch movementslock_manager/releaseto inform the semaphore that movement has completedlock_manager/checkcheck for dangling locks
- Subscribes:
-
ifcb_runner: Provides one high-level action for running IFCB samples- Subscribes:
/ifcb/infor IFCB status messages
- Publishes:
~statecurrent state for debugging purposes~sample/goalaction server for running IFCB sampling routines~sample/feedbacksampling action feedback~sample/resultsampling action result
- Subscribes:
-
mock_ifcb_runner: Mock version ofifcb_runner- Publishes:
~statecurrent state for debugging purposes~sample/goalaction server for running mock sampling routines~sample/feedbacksampling action feedback~sample/resultsampling action result
- Publishes:
-
arm_chanos: Orchestrates Chanos sensor payload behaviors- Subscribes:
winch/move_to_depth/resultto determine the success of a transitlock_manager/acquirefor getting clearance to move a winchlock_manager/releaseto inform the semaphore that movement has completedlock_manager/checkto check for dangling locks
- Subscribes:
-
profiler: Creates profiles of CTD data during a cast- Subscribes:
- user-chosen topic provided in config
- Publishes:
~with resampled profile data~downcastas above, but only downcasts~downcastas above, but only upcasts
- Subscribes:
-
web: Web API for attaching metadata to IFCB bins- Subscribes:
/gps/fixfor GPS fixes
- Subscribes:
-
winch: Controls depth using the motor- Subscribes:
ctd/depthfor monitoring depthmotor/motionfor monitoring motor statewinch/move_to_depth/goalfor setting the goal depthwinch/move_to_depth/cancelfor canceling the current goal
- Publishes:
winch/move_to_depth/feedbackwith progress updateswinch/move_to_depth/statuswith the status of the current goalwinch/move_to_depth/resultwith the result of the goal
- Subscribes:
-
mock_winch_node: Mock version ofwinch- Publishes:
winch/move_to_depth/feedbackwith progress updateswinch/move_to_depth/statuswith the status of the current goalwinch/move_to_depth/resultwith the result of the goal
- Publishes:
These nodes perform lower-level interactions with hardware components. These nodes are designed to be portable to future projects.
-
camera: Video stream- Publishes:
~image/compressedwith the compressed video feed
- Publishes:
-
ctd: Driver for the AML or RBR maestro3 CTD- Subscribes:
ctd_comms/infor receiving data from CTD.
- Publishes:
~ctdwith conductivity, temperature, and pressure data~depthwith depth and pressure data~aml/derive/xyzwith derived values (for AML only)~aml/port#/xyzwith measured values (for AML only)
- Subscribes:
-
ctd_comms: Bridge for CTD serial communications- Publishes:
ctd_comms/infor messages received from the CTD's serial portctd_comms/outfor messages sent to the CTD's serial port
- Publishes:
-
udp_to_ros: Translates UDP messages to ROS topics. Used for receiving proxied RBR data.- Publishes:
udp_streama continuous stream of all received UDP messages.
- Publishes:
-
gps: Provides GPS fixes to ROS fromgpsd- Publishes:
/gps/fixand/gps/extended_fix
- Publishes:
-
ifcb: Bridge for the IFCB websocket API- Publishes:
~infor messages received from the IFCB~outfor messages sent to the IFCB~imagefor full-frame images~roi/imagefor detected ROI images only~roi/image/rawrepublished ROI as raw (if classifier enabled)~roi/markerswith rectangular bounds of ROIs
- Services:
~commandto send a message to the IFCB~routineto send a routine to the IFCB
- Publishes:
-
motor: Driver for the JVL motor- Publishes:
~electricalwith electrical registers~errorwith error registers~motionwith motion registers
- Services:
~set_positionto set the motor's position~set_position_envelopeto set position limits~set_velocityto set the motor's velocity~stopto stop the motor
- Publishes:
-
mock_ctd: Mock version of a CTD publisher- Publishes:
~ctdwith conductivity, temperature, and pressure data~depthwith depth and pressure data
- Publishes:
These nodes provide functionality for recording data and connecting to other tools like Foxglove Studio.
-
rosbag_record: Records ROS traffic to ROS bag files- Publishes:
/begin_writewhen a new bag file is created
- Publishes:
-
rosbridge_websocket: Rosbridge server for Foxglove Studio -
rosout: Logging mechanism- Subscribes:
/rosoutfor log messages from each node
- Publishes:
/rosout_aggwith copies of all log messages
- Subscribes:
- Place a blank newline before comment lines to make them easier to spot, e.g.
# First comment
val1 = 'some value'
# Second comment
val2 = 'next value'
A style guide has not yet been rigorously defined, this section will expand as new policies are adopted.
Foxglove Studio is the recommended software for monitoring the PhytO-ARM system. To connect to a live system, use the "ROS Bridge WebSocket" connection type.
The ROS Bridge node uses TCP port 9090. Ensure this port is forwarded in your router settings for trusted clients only, or use SSH port forwarding:
$ ssh -NL 9090:localhost:9090 <PhytO-ARM host address>
In Studio, the connection address is ws://<PhytO-ARM host address>:9090 if connecting directly or ws://localhost:9090 if using the SSH tunnel.
Tip: For the most accurate time series plots, configure plot lines to use a message's header.stamp rather than its receive time, which is affected by network latency.
Data is logged to the ROS bag format using the rosbag record command. Bag files can also be viewed with Foxglove Studio.
Almost all internal ROS traffic is logged, with the exception of the camera feed (due to storage requirements) and ROS service calls (due to technical limtations).
The config file controls where the bag files are written:
launch_args:
rosbag_prefix: /mnt/data/rosbags/phyto-arm_hades
This results in files in the /mnt/data/rosbags/ directory with names like phyto-arm_hades_2022-04-01-14-47-11_0.bag. The name includes the timestamp of when PhytO-ARM was started, plus a numeric counter. A new bag is created, incrementing the counter, when a configured size limit is reached.
A bag file with the suffix .active is currently being written, or was left in an incomplete state by a previous session that was terminated abruptly. Such files can sometimes be repaired.