diff --git a/dist/Soar-1.3.5.tar.gz b/dist/Soar-1.3.5.tar.gz new file mode 100644 index 0000000..d28b04e Binary files /dev/null and b/dist/Soar-1.3.5.tar.gz differ diff --git a/docs/source/conf.py b/docs/source/conf.py index 03f41fa..5123ac3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -67,9 +67,9 @@ def __getattr__(cls, name): # built documents. # # The short X.Y version. -version = '1.3.4' +version = '1.3.5' # The full version, including alpha/beta/rc tags. -release = '1.3.4' +release = '1.3.5' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/soar/__init__.py b/soar/__init__.py index a7379f2..096725a 100644 --- a/soar/__init__.py +++ b/soar/__init__.py @@ -15,13 +15,13 @@ # along with this program. If not, see . # # soar/__init__.py -""" Soar (Snakes on a Robot) v1.3.4 +""" Soar (Snakes on a Robot) v1.3.5 An extensible Python framework for both simulating and interacting with robots. Copyright (C) 2017 Andrew Antonitis. Licensed under the LGPLv3. """ -__version__ = '1.3.4' +__version__ = '1.3.5' blerb = 'Soar (Snakes on a Robot) v' + __version__ + ': A Python robotics framework.\n' \ 'https://github.com/arantonitis/soar\n\n' \ 'Copyright (C) 2017 Andrew Antonitis. Licensed under the LGPLv3.' diff --git a/soar/client.py b/soar/client.py index c976a38..080ebe0 100644 --- a/soar/client.py +++ b/soar/client.py @@ -93,6 +93,7 @@ def tk_wrap(*args, **kwargs): return_val = gui.synchronous_future(func, *args, after_idle=True, **kwargs) if isinstance(return_val, Exception): # If the function returned an exception, raise it raise return_val + return return_val return tk_wrap @@ -266,12 +267,15 @@ def __init__(self, title='Plotting Window', visible=True): plots.append(self) def __getattribute__(self, name): - attr = PlotWindow.__getattribute__(self, name) # Call base class method to avoid infinite recursion - # Wrap class methods so that they run in the Tk mainloop, if not already running in it - if callable(attr) and current_thread() != main_thread(): - return tkinter_execute(return_exceptions(attr)) - else: + if current_thread() != main_thread(): # If not on the main thread + # Force accessing the attribute to run on the main thread + attr = tkinter_execute(return_exceptions(PlotWindow.__getattribute__))(self, name) + # If the attribute is callable, wrap it so that it runs on the main thread + if callable(attr): + return tkinter_execute(return_exceptions(attr)) return attr + else: + return PlotWindow.__getattribute__(self, name) else: def __init__(self, title='Plotting Window', visible=True): # Ensure the window is not visible, and add the plot to the global list diff --git a/soar/gui/plot_window.py b/soar/gui/plot_window.py index 03c96c2..ce02150 100644 --- a/soar/gui/plot_window.py +++ b/soar/gui/plot_window.py @@ -6,8 +6,11 @@ Unlike use of the :func:`soar.hooks.tkinter_hook`, use of this module will not force brain methods to run on the main thread alongside Soar's GUI event loop. - `PlotWindow` is wrapped by the client if it is imported by a Soar brain. This wrapper ensures that the proper mode - (GUI or headless) is enforced, despite what the brain might pass to the constructor. + However, methods of `PlotWindow` *will* run on the main thread, regardless of what thread the other brain methods + run in. + + `PlotWindow` is wrapped by the client to ensure that the proper mode (GUI or headless) is enforced regardless + of the arguments passed to the constructor by the brain methods. The client will also ensure that, if logging is occurring, any `PlotWindow` objects will have their image data included in the log whenever the controller is shut down. diff --git a/soar/robot/arcos.py b/soar/robot/arcos.py index 876716d..2025ee1 100644 --- a/soar/robot/arcos.py +++ b/soar/robot/arcos.py @@ -39,9 +39,10 @@ DHEAD = 13 #: Turn at `SETRV` speed relative to current heading; (+) counterclockwise or (-) clockwise degrees. SAY = 15 """ Play up to 20 duration, tone sound pairs through User Control panel piezo speaker. -The argument is a string consisting of duration, tone pair bytes. Duration is in 20 millisecond increments. -A value of 0 means silence. The values 1-127 are the corresponding MIDI notes. The remaining values are frequencies -computed as `tone - 127*32` equivalent frequencies from 1-4096, in 32Hz increments. +The argument is a string whose first byte must be the number of (duration, tone) pairs to play, followed by each (duration, tone) pair. +Duration is in 20 millisecond increments. +A value of 0 means silence. The values 1-127 are the corresponding MIDI notes, with 60 being middle C. The remaining values are frequencies +computed as `(tone - 127)*32`, ranging from 1-4096 in 32Hz increments. """ CONFIG = 18 #: Request a configuration SIP. ENCODER = 19 #: Request a single (1), a continuous stream (>1), or stop (0) encoder SIPS. @@ -332,6 +333,7 @@ def send_command(self, code, data=None): Args: code: The command code. Must be in :data:`soar.robot.arcos.command_types`. data (optional): The associated command argument, assumed to be of the correct type. + For commands that take a string argument, a `bytes` object may also be used. Raises: `Timeout`: If the write timeout of the port was exceeded. @@ -347,7 +349,7 @@ def send_command(self, code, data=None): b = [data & 0xff, (data >> 8) & 0xff] self.send_packet(code, arg_type, *b) else: # command_types[code] == str - b = [ord(c) for c in data] # Convert the string to bytes and add a null terminator + b = list(bytes(data)) b.append(0) arg_type = 0x2b self.send_packet(code, arg_type, *b)