Skip to content

Commit

Permalink
feat: add digital logger switch node
Browse files Browse the repository at this point in the history
  • Loading branch information
johnwaalsh authored and rgov committed Jan 23, 2025
1 parent 06dd4ff commit 944af64
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 0 deletions.
28 changes: 28 additions & 0 deletions configs/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ launch_args:
classifier: false
ifcb_winch: false
chanos_winch: false
digital_logger: true


alerts:
Expand Down Expand Up @@ -253,3 +254,30 @@ web:
default: -999.999 # Replace with desired default long
defaultOnlyExample: #optional
default: 'static_value' # No topic, making this default value permanent

# Configure digital logger control
# Status topic: /digital_logger/outlets/{outlet num}/status
# D/L message: {"name": "camera", "status": "on"}
# Control service: /digital_logger/control
# C/L message: {"name": "camera", "status": "off"}
digital_logger: #optional
username: "admin"
password: "1234"
address: "192.168.1.2"
outlets:
- name: "ifcb"
outlet: 0
- name: "arm_ifcb"
outlet: 1
- name: "arm_chanos"
outlet: 2
- name: "camera"
outlet: 3
- name: "gps"
outlet: 4
- name: "ctd"
outlet: 5
- name: "winch"
outlet: 6
- name: "starlink"
outlet: 7
1 change: 1 addition & 0 deletions src/phyto_arm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_message_files(
ConductorState.msg
ConductorStates.msg
DepthProfile.msg
OutletStatus.msg
)

add_service_files(
Expand Down
2 changes: 2 additions & 0 deletions src/phyto_arm/launch/main.launch
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
</node>
</group>

<node if="$(arg digital_logger)" name="digital_logger" pkg="phyto_arm" type="digital_logger_node.py"/>

<!--
disabled
<node name="instant_replay" pkg="phyto_arm" type="instant_replay_node.py"
Expand Down
2 changes: 2 additions & 0 deletions src/phyto_arm/msg/OutletStatus.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
string name
bool is_active
109 changes: 109 additions & 0 deletions src/phyto_arm/src/digital_logger_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Functionality for monitoring and controlling a Digital Loggers Web Power Switch Pro model.
"""
import base64
import urllib.request

import rospy

from phyto_arm.msg import OutletStatus

# Global variables relevant to digital logger control. These variables are defined once the digital
# logger node is initialized.
AUTH = ''
ADDRESS = ''
OUTLETS = ''
OUTLET_NAMES = ''


def control_outlet(msg):
"""
Send the given msg to the digital logger as an HTTP request.
"""

outlet_num = OUTLET_NAMES.get(msg.name)

data = f'value={str(msg.is_active).lower()}'
url = f'http://{ADDRESS}/restapi/relay/outlets/{outlet_num}/state/'

# Create a PUT request
req = urllib.request.Request(url, data=data.encode("utf-8"), method="PUT")
req.add_header("Authorization", AUTH)
req.add_header("X-CSRF", 'x')

try:
# Send the PUT request
response = urllib.request.urlopen(req)
except urllib.error.HTTPError as http_err:
raise ValueError(f"HTTP Error: {http_err.code} : {http_err.reason}") from http_err
except urllib.error.URLError as url_err:
raise ValueError(f"URL Error: {url_err.reason}") from url_err

result = response.read().decode('utf-8')

rospy.loginfo(f'sent status={str(msg.is_active).lower()} to {ADDRESS}:{url}, received: code {response.status} : {result}')


def run_digital_logger_node():
"""
Run the digital logger node. Publishes outlet statuses at
/digital_logger/OUTLETS/{outlet num}/status. The OUTLETS can be controlled by publishing a
OutletStatus message to /digital_logger/control.
"""
global AUTH, ADDRESS, OUTLETS, OUTLET_NAMES

rospy.init_node('digital_logger')

rospy.init_node('digital_logger')
username = rospy.get_param('~username')
password = rospy.get_param('~password')
AUTH = f"Basic {base64.b64encode(f'{username}:{password}'.encode()).decode()}"
ADDRESS = rospy.get_param('~address')
OUTLETS = rospy.get_param('~outlets')
OUTLET_NAMES = {outlet['name']: int(outlet['outlet']) for outlet in OUTLETS}

# subscribe to the digital logger control topic
rospy.Subscriber('/digital_logger/control', OutletStatus, control_outlet)

outlet_publishers = []
for outlet_num, _ in enumerate(OUTLETS):
outlet_publishers.append(rospy.Publisher(f'/digital_logger/outlet/{outlet_num}/status/', OutletStatus, queue_size=10))

# Monitor outlets at 1Hz
rate = rospy.Rate(1)

while not rospy.is_shutdown():
# send a status request for each available outlet
for outlet_index, _ in enumerate(OUTLETS):
# Construct request
url = f'http://{ADDRESS}/restapi/relay/outlets/{outlet_index}/state/'
request = urllib.request.Request(url)
request.add_header("Authorization", AUTH)

try:
# Send the GET request
with urllib.request.urlopen(request) as response:
# Read and decode the response
response_data = response.read().decode('utf-8')
except urllib.error.HTTPError as http_err:
raise ValueError(f"HTTP Error: {http_err.code} : {http_err.reason}") from http_err
except urllib.error.URLError as url_err:
raise ValueError(f"URL Error: {url_err.reason}") from url_err

# publish the status of each outlet to its specific topic
outlet_status = OutletStatus()
outlet_status.name = OUTLETS[outlet_index]['name']
# DL API uses 'true' and 'false' to denote outlet status, which need to be converted to Python booleans
outlet_status.is_active = response_data == 'true'

outlet_publishers[outlet_index].publish(outlet_status)

rate.sleep()


if __name__ == '__main__':
try:
run_digital_logger_node()
except rospy.ROSInterruptException:
pass

0 comments on commit 944af64

Please sign in to comment.