Skip to content

Commit

Permalink
use PySide6 for UI given more permissive license; flake8 cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
David Conner committed Jun 4, 2024
1 parent b8f9e93 commit abc47dd
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 83 deletions.
31 changes: 31 additions & 0 deletions flexbe_input/flexbe_input/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python

# Copyright 2024 Philipp Schillinger, Team ViGIR, Christopher Newport University
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the Philipp Schillinger, Team ViGIR, Christopher Newport University nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""Initialize module for flexbe_input."""
92 changes: 57 additions & 35 deletions flexbe_input/flexbe_input/complex_action_server.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
#! /usr/bin/env python

# Copyright (c) 2009, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
Expand All @@ -30,18 +33,20 @@
# Author: Brian Wright.
# Based on C++ simple_action_server.h by Eitan Marder-Eppstein


import queue
import threading
import traceback

from flexbe_core import Logger

import rclpy
from rclpy.action import ActionServer
from rclpy.duration import Duration

import threading
import traceback
from flexbe_core import Logger


def nop_cb(goal_handle):
"""Execute no operation (pass through) callback."""
pass


Expand All @@ -51,16 +56,21 @@ def nop_cb(goal_handle):


class ComplexActionServer:
# @brief Constructor for a ComplexActionServer
# @param name A name for the action server
# @param execute_cb Optional callback that gets called in a separate thread whenever
# a new goal is received, allowing users to have blocking callbacks.
# Adding an execute callback also deactivates the goalCallback.
# @param auto_start A boolean value that tells the ActionServer wheteher or not
# to start publishing as soon as it comes up.
# THIS SHOULD ALWAYS BE SET TO FALSE TO AVOID RACE CONDITIONS and
# start() should be called after construction of the server.
"""
Constructor for a ComplexActionServer.
@param name A name for the action server
@param execute_cb Optional callback that gets called in a separate thread whenever
a new goal is received, allowing users to have blocking callbacks.
Adding an execute callback also deactivates the goalCallback.
@param auto_start A boolean value that tells the ActionServer wheteher or not
to start publishing as soon as it comes up.
THIS SHOULD ALWAYS BE SET TO FALSE TO AVOID RACE CONDITIONS and
start() should be called after construction of the server.
"""

def __init__(self, node, name, ActionSpec, execute_cb=None, auto_start=False):
"""Initialize instance of the ComplexActionServer."""
self.node = node
self.goals_received_ = 0
self.goal_queue_ = queue.Queue()
Expand Down Expand Up @@ -109,37 +119,41 @@ def __del__(self):
# @brief Accepts a new goal when one is available The status of this
# goal is set to active upon acceptance,
def accept_new_goal(self):
"""Accept a new goal."""
# with self.action_server.lock, self.lock:

Logger.logdebug("Accepting a new goal")
Logger.logdebug('Accepting a new goal')

self.goals_received_ -= 1

# get from queue
current_goal = self.goal_queue_.get()

# set the status of the current goal to be active
# current_goal.set_accepted("This goal has been accepted by the simple action server");
# current_goal.set_accepted('This goal has been accepted by the simple action server');
current_goal.succeed()

return current_goal

# @brief Allows polling implementations to query about the availability of a new goal
# @return True if a new goal is available, false otherwise
def is_new_goal_available(self):
"""Check if new goal is available."""
return self.goals_received_ > 0

# @brief Allows polling implementations to query about the status of the current goal
# @return True if a goal is active, false otherwise
def is_active(self):
"""Check if current goal is active."""
if self.current_goal and not self.current_goal.get_goal():
return False

return self.current_goal.is_active

# @brief Sets the status of the active goal to succeeded
# @param result An optional result to send back to any clients of the goal
def set_succeeded(self, result=None, text="", goal_handle=None):
def set_succeeded(self, result=None, text='', goal_handle=None):
"""Set status to success."""
goal_handle.succeed()
if not result:
result = self.get_default_result()
Expand All @@ -148,7 +162,8 @@ def set_succeeded(self, result=None, text="", goal_handle=None):

# @brief Sets the status of the active goal to aborted
# @param result An optional result to send back to any clients of the goal
def set_aborted(self, result=None, text="", goal_handle=None):
def set_aborted(self, result=None, text='', goal_handle=None):
"""Set goal aborted."""
goal_handle.abort()
if not result:
result = self.get_default_result()
Expand All @@ -158,26 +173,30 @@ def set_aborted(self, result=None, text="", goal_handle=None):
# @brief Publishes feedback for a given goal
# @param feedback Shared pointer to the feedback to publish
def publish_feedback(self, feedback):
"""Publish feedback."""
self.current_goal.publish_feedback(feedback)

def get_default_result(self):
"""Get result."""
return self.action_server.action_type

# @brief Allows users to register a callback to be invoked when a new goal is available
# @param cb The callback to be invoked
def register_goal_callback(self, cb):
"""Register callback."""
if self.execute_callback:
Logger.logwarn("Cannot call ComplexActionServer.register_goal_callback() "
"because an executeCallback exists. Not going to register it.")
Logger.logwarn('Cannot call ComplexActionServer.register_goal_callback() '
'because an executeCallback exists. Not going to register it.')
else:
self.goal_callback = cb

# @brief Callback for when the ActionServer receives a new goal and passes it on
def internal_goal_callback(self, goal):
"""Call when the ActionServer receives a new goal and passes it on."""
self.execute_condition.acquire()

try:
Logger.localinfo(f"A new goal {goal.goal_id} has been recieved by the single goal action server")
Logger.localinfo(f'A new goal {goal.goal_id} has been recieved by the single goal action server')

self.next_goal = goal
self.new_goal = True
Expand All @@ -191,19 +210,21 @@ def internal_goal_callback(self, goal):
self.execute_condition.release()

except Exception as e:
Logger.logerr("ComplexActionServer.internal_goal_callback - exception %s", str(e))
Logger.logerr('ComplexActionServer.internal_goal_callback - exception %s', str(e))
self.execute_condition.release()

# @brief Callback for when the ActionServer receives a new preempt and passes it on
def internal_preempt_callback(self, preempt):
"""Call when the ActionServer receives a new preempt and passes it on."""
return

# @brief Called from a separate thread to call blocking execute calls
def executeLoop(self):
"""Excute the server loop."""
loop_duration = Duration(seconds=0.1)

while (rclpy.ok()):
Logger.logdebug("ComplexActionServer: execute")
Logger.logdebug('ComplexActionServer: execute')

with self.terminate_mutex:
if (self.need_to_terminate):
Expand All @@ -212,22 +233,23 @@ def executeLoop(self):
if (self.is_new_goal_available()):
goal_handle = self.accept_new_goal()
if not self.execute_callback:
Logger.logerr("execute_callback_ must exist. This is a bug in ComplexActionServer")
Logger.logerr('execute_callback_ must exist. This is a bug in ComplexActionServer')
return

try:

print("run new executecb")
print('run new executecb')
thread = threading.Thread(target=self.run_goal, args=(goal_handle.get_goal(), goal_handle))
thread.start()

except Exception as ex:
Logger.logerr(f"Exception in your execute callback: {str(ex)}\n",
Logger.logerr(f'Exception in your execute callback: {str(ex)}\n',
f"{traceback.format_exc().replace('%', '%%')}")
self.set_aborted(None, f"Exception in execute callback: {str(ex)}")
self.set_aborted(None, f'Exception in execute callback: {str(ex)}')

with self.execute_condition:
self.execute_condition.wait(loop_duration.nanoseconds * 1.e-9)

def run_goal(self, goal, goal_handle):
"""Run goal callback."""
self.execute_callback(goal, goal_handle)
29 changes: 16 additions & 13 deletions flexbe_input/flexbe_input/flexbe_input.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python

# Copyright 2023 Philipp Schillinger, Team ViGIR, Christopher Newport University
# Copyright 2024 Philipp Schillinger, Team ViGIR, Christopher Newport University
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
Expand Down Expand Up @@ -37,10 +37,12 @@
@author: Philipp Schillinger, Brian Wright
"""

from rclpy.action import ActionClient
from flexbe_core import Logger

from flexbe_msgs.action import BehaviorInput
from flexbe_core import Logger

from rclpy.action import ActionClient

from .complex_action_server import ComplexActionServer


Expand All @@ -57,39 +59,40 @@ def __init__(self, node):
execute_cb=self.execute_cb,
auto_start=False)

Logger.loginfo("Ready for data requests...")
Logger.loginfo('Ready for data requests...')

def execute_cb(self, goal, goal_handle):
Logger.loginfo("--> Got a request!")
"""Execute callback."""
Logger.loginfo('--> Got a request!')
Logger.loginfo(f"'{goal.msg}'")

relay_ocs_client_ = ActionClient(self._node,
BehaviorInput,
'flexbe/operator_input')

# wait for data msg
Logger.localinfo("FlexBEInput waiting to relay from OCS ...")
Logger.localinfo('FlexBEInput waiting to relay from OCS ...')
relay_ocs_client_.wait_for_server()
Logger.localinfo("FlexBEInput is ready!")
Logger.localinfo('FlexBEInput is ready!')

# Fill in the goal here
result = relay_ocs_client_.send_goal(goal) # This is a blocking call!

# result.data now serialized
data_str = result.data
Logger.localinfo(f"FlexBEInput result={data_str}")
Logger.localinfo(f'FlexBEInput result={data_str}')

if result.result_code == BehaviorInput.Result.RESULT_OK:
self._as.set_succeeded(BehaviorInput.Result(result_code=BehaviorInput.Result.RESULT_OK,
data=data_str), "ok", goal_handle)
data=data_str), 'ok', goal_handle)

elif result.result_code == BehaviorInput.Result.RESULT_FAILED:
# remove
self._as.set_succeeded(BehaviorInput.Result(result_code=BehaviorInput.Result.RESULT_FAILED,
data=data_str), "failed", goal_handle)
Logger.loginfo("<-- Replied with FAILED")
data=data_str), 'failed', goal_handle)
Logger.loginfo('<-- Replied with FAILED')

elif result.result_code == BehaviorInput.Result.RESULT_ABORTED:
self._as.set_succeeded(BehaviorInput.Result(result_code=BehaviorInput.Result.RESULT_ABORTED,
data=data_str), "Aborted", goal_handle)
Logger.loginfo("<-- Replied with ABORT")
data=data_str), 'Aborted', goal_handle)
Logger.loginfo('<-- Replied with ABORT')
Loading

0 comments on commit abc47dd

Please sign in to comment.