Welcome to TwinPy’s documentation!

TwinPy is a package containing tools to easily create your own Python GUIs for TwinCAT based on Simulink models.

The GUIs are based on PyQt5 and the TwinCAT interface is done through pyads.

Installation

The modules are best installed using pip. This is explained in the ReadMe of the repository.

Using TwinPy with a remote target

You can also use TwinPy (and other pyads applications) remotely. In this scenario a client PC runs the GUI, interacting with a running TwinCAT instance on a target PC.

Using TwinPy remotely involves two steps:

  • Getting access to the compiled Simulink model’s XML file for the model structure

  • Making a remote ADS connection

Tip

Often in a target and client situation, the client is also the development PC (which compiles the TwinCAT solution for the target). When this is the case, giving access to the XML files for TwinPy is trivial since they already exist on the client computer.

Access to the XML file

When the client is not the computer that compiles the Simulink model, it won’t automatically have access to the most recent model XML. There are a few approaches:

  • Compile the model also on the client PC
    • This will require all the compile dependencies on the client PC, and each compilation has to be done twice.

  • Copy the XML from the compiled model to the client PC
    • No extra compilation is needed, but copying the XML each time can be time consuming.

  • Set up a file share to give direct access to the compiled XMLs
    • By sharing the XMLs directly a client has access to the model structure without extra steps

Sharing the XML files directly

On the development PC (could be the same as the target PC), create a network share for the TwinCAT modules directory. By default this is C:\TwinCAT\3.1\CustomConfig\Modules. Simply right-click on the Modules directory in Windows explorer and click ‘Give access to…’. Complete the wizard by adding the client PC. Read-only permissions should be all that’s needed.

On the client PC you should now be able to find the PC and view the modules. Note the absolute path, which might be something like \\<ip>\Modules.

Search for info on Windows file sharing in case you run into problems.

Remote ADS connection

See the pyads documentation on routing for more information: https://pyads.readthedocs.io/en/latest/documentation/routing.html

Adding routes on Windows

Use the TwinCAT UI and add the remote. Right-click on the TwinCAT icon in the taskbar and click ‘Router…’ > ‘Edit Routes’.

This gif exemplifies the procedure:

_images/add_route_to_target.gif

The route must not be set to ‘unidirectional’, which seems to be the default.

Note that you might need to add allow-rules in the firewall for both inbound and outbound traffic on TCP ports 48898 and 8016, and UPD port 48899.

Adding routes on Linux

The pyads.connection.Connection will create a route from the client to the target. You can then use pyads.ads.add_route_to_plc() to create a route back to the client. Or you can use the TwinCAT UI on the remote target to create the route back.

TwinPy Remote

In your application script, set the IP address, AMS net id and port correctly for the remote target when instantiating TwincatConnection.

In case no local XML file is available, specify an absolute path to the XML file when creating a SimulinkModel.

API

GUI

TwinCAT UI Elements

Here we define all the non-implemented TwinCAT stuff, without any platform.

The TcElement class should be extended by a class of another platform, ideally through multiple inheritance.

TcElement
class twinpy.element.TcElement(*args, **kwargs)[source]

Bases: ABC

Abstract class for TwinCAT element.

Must be inherited into a new class for something meaningful.

There are different event types, which determine how and when new remote values are retrieved:

  • EVENT_NOTIFICATION: An ADS notification is created, resulting in a callback on a remote value change. Suitable for rarely changing values or when a very quick response is needed. ADS notifications have some overhead. No more than 200 symbol notifications should exist at the same time.

  • EVENT_TIMER: New values are read at a fixed interval. Useful when remote values change often but no instant response is needed. This method has very little overhead.

  • EVENT_NONE: No attempts are made to update according to remote values.

You can override the defaults for your entire project by changing the TcWidget.DEFAULT_EVENT_TYPE and TcWidget.DEFAULT_UPDATE_FREQ class properties. Make sure to update these before any widgets are created and they will have new standard values.

Variables
  • DEFAULT_EVENT_TYPE – (default: EVENT_NOTIFICATION)

  • DEFAULT_UPDATE_FREQ – (default: 10.0 Hz)

Note: these methods do not affect when or how a value is _written_ to the ADS pool.

Parameters
  • args

  • kwargs – See list below - kwargs are passed along to connect_symbol too

Kwargs
  • symbol: Symbol to link to

    (i.e. to read from and/or write to)

  • format: Formatter symbol, e.g. ‘%.1f’ or ‘%d’ or callable

    (‘%.3f’ by default, ignored when not relevant) Callable must have a single argument

  • event_type: Possible values are EVENT_* constants

    (default: DEFAULT_EVENT_TYPE)

  • update_freq: Frequency (Hz) for timed update

    (for EVENT_TIMER only, default: DEFAULT_UPDATE_FREQ)

  • greyed: When true, the widget is visibly disabled

    When false, the widget is shown normally even when disconnected (default: true)

DEFAULT_EVENT_TYPE = 'notification'
DEFAULT_UPDATE_FREQ = 10.0
EVENT_NONE = 'none'
EVENT_NOTIFICATION = 'notification'
EVENT_TIMER = 'timer'
connect_symbol(new_symbol: Optional[Symbol] = None, **kwargs) bool[source]

Connect a symbol (copy of symbol is left as property).

By default a device callback is created with an on-change event from TwinCAT. Old callbacks are deleted first. Pass None to only clear callbacks. The notification handles are stored locally. Extend (= override but call the parent first) this method to configure more of the widget, useful if e.g. widget callbacks depend on the symbol.

Parameters
  • new_symbol – Symbol to link to (set None to only clear the previous)

  • kwargs – See list below - Keyword arguments are passed along as device notification settings too

Returns

True if new symbol was connected

Kwargs
format(value: Any) str[source]

“Use the stored formatting to created a formatted text.

In case the format specifier is a string and the new value is a list, element-wise string formatting will be concatenated automatically.

static make_timer(plc, interval)[source]

Create timer instance - to be extended by a different implementation

on_mass_timeout()[source]

Callback for the event timer.

This assumes the remote read was already performed!

abstract twincat_receive(value)[source]

Callback attached to the TwinCAT symbol.

Note: changing a state of a widget (e.g. checkbox being checked through setChecked(True)) will typically fire the on-change events again. So be careful to prevent an event loop when updating a widget based on a remote change: a change could result in a state change, which could result in a remote change, etc.

Parameters

value – New remote value

twincat_receive_wrapper(value)[source]

Intermediate twincat_receive callback to prevent event loops.

twincat_send(value: Any)[source]

Set value in symbol (and send to TwinCAT).

Method is safe: if symbol is not connected, nothing will happen.

Qt Widgets

TwinCAT widgets are Qt elements that are easily linked to an ADS symbol.

E.g. a label that shows an output value or an input box which changes a parameter.

The @pyqtSlot() is Qt decorator. In many cases it is not essential, but it’s good practice to add it anyway.

TcWidget
class twinpy.ui.TcWidget(*args, **kwargs)[source]

Bases: TcElement

Abstract class, to be multi-inherited together with a Qt item.

It is important to call this init() as late as possible from a subclass! The order should be:

  1. Subclass specific stuff (e.g. number formatting)

  2. Call to super().__init__(…) - Now the QWidget stuff has been made ready

  3. QWidget related stuff

Parameters
  • args

  • kwargs – See TcElement.__init__()

static close_with_error(err: Exception)[source]

Show an error popup and close the active application.

This uses QApplication.instance() to find the current application and won’t work perfectly.

connect_symbol(new_symbol: Optional[Symbol] = None, **kwargs) bool[source]

Connect a symbol (copy is left as property).

Parameters
  • new_symbol – Symbol to link to (set None to only clear the previous)

  • kwargs – See TcElement.connect_symbol()

static make_timer(plc, interval) TcTimer[source]

Use QTimer based timer instead.

twincat_send(value: Any)[source]

Set value in symbol (and send to TwinCAT).

Method is safe: if symbol is not connected, nothing will happen.

value_format: Union[str, Callable[[Any], str]]
TcLabel
class twinpy.ui.TcLabel(*args, **kwargs)[source]

Bases: QLabel, TcWidget

Label that shows a value.

Parameters
twincat_receive(value)[source]

Callback attached to the TwinCAT symbol.

Note: changing a state of a widget (e.g. checkbox being checked through setChecked(True)) will typically fire the on-change events again. So be careful to prevent an event loop when updating a widget based on a remote change: a change could result in a state change, which could result in a remote change, etc.

Parameters

value – New remote value

value_format: Union[str, Callable[[Any], str]]
TcLineEdit
class twinpy.ui.TcLineEdit(*args, **kwargs)[source]

Bases: QLineEdit, TcWidget

Readable and writable input box.

Parameters
on_editing_finished()[source]

Called when [Enter] is pressed or box loses focus.

on_text_edited(*_value)[source]

Callback when text was modified (i.e. on key press).

twincat_receive(value) Any[source]

Callback attached to the TwinCAT symbol.

Note: changing a state of a widget (e.g. checkbox being checked through setChecked(True)) will typically fire the on-change events again. So be careful to prevent an event loop when updating a widget based on a remote change: a change could result in a state change, which could result in a remote change, etc.

Parameters

value – New remote value

value_format: Union[str, Callable[[Any], str]]
TcPushButton
class twinpy.ui.TcPushButton(*args, **kwargs)[source]

Bases: QPushButton, TcWidget

Button that sends value when button is held pressed.

Parameters
  • args

  • kwargs

Kwargs
  • value_pressed: Value on press (default: 1), None for no action

  • value_released: Value on release (default: 0), None for no action

  • See TcWidget

on_pressed()[source]

Callback on pressing button.

on_released()[source]

Callback on releasing button.

twincat_receive(value)[source]

Do nothing, method requires definition anyway.

value_format: Union[str, Callable[[Any], str]]
TcRadioButton
class twinpy.ui.TcRadioButton(*args, **kwargs)[source]

Bases: QRadioButton, TcWidget

Radiobutton that updates the symbol when it is selected.

The radiobutton will _not_ update the symbol when another selection is made. Instead a write could be performed if that other radio is also a TcWidget.

Use TcRadioButtonGroupBox instead to create a set of radio buttons together that all update the same ADS symbol.

When connecting to a boolean symbol, use 0 and 1 as values for the best result instead of True and False.

Radios need to be in a QButtonGroup together to link together.

Parameters
  • label (str) – Label of this radio button

  • args

  • kwargs

Kwargs
  • value_checked: Value when radio becomes checked (default: 1)

  • See TcWidget

on_toggled()[source]

Callback when radio state is togged (either checked or unchecked).

twincat_receive(value)[source]

Set checked state if the new value is equal to the is-checked value.

value_format: Union[str, Callable[[Any], str]]
TcRadioButtonGroupBox
class twinpy.ui.TcRadioButtonGroupBox(*args, **kwargs)[source]

Bases: QGroupBox, TcWidget

An instance on this class forms a group of radio buttons.

The group of radio buttons together control a single ADS variable.

Instances of QRadioButton will be automatically created through this class. Using literal instances of TcRadioButton is not efficient because of duplicate callbacks.

When the remote value changes to a value that is not listed as an option in the radio_toggle, the displayed value simply won’t change at all.

The options argument is required.

Parameters
  • title (str) – Title of this QGroupBox

  • args

  • kwargs

Kwargs
  • options: List of tuples that form the label-value pairs of the radio,

    e.g. [(‘Low Velocity’, 0.5), (‘High Velocity’, 3.0)]

  • layout_class: Class of the layout used inside the QGroupBox (default:

    QVBoxLayout)

  • See TcWidget

on_click(button: QAbstractButton)[source]

Callback when a button of the group was pressed.

twincat_receive(value)[source]

Callback for a remote value change.

value_format: Union[str, Callable[[Any], str]]
TcCheckBox
class twinpy.ui.TcCheckBox(*args, **kwargs)[source]

Bases: QCheckBox, TcWidget

Checkbox to control a symbol.

Set either value to None to send nothing on that state. For the best results, use 1 and 0 for a boolean variable instead of True and False.

Parameters
  • label (str) – Label of this radio button

  • args

  • kwargs

Kwargs
  • value_checked: Value when checkbox becomes checked (default: 1)

  • value_unchecked: Value when checkbox becomes unchecked (default: 0)

  • See TcWidget

on_toggled()[source]

Callback when box state is togged (either checked or unchecked).

twincat_receive(value)[source]

Set checked state if the new value is equal to the is-checked value.

value_format: Union[str, Callable[[Any], str]]
TcSlider
class twinpy.ui.TcSlider(*args, **kwargs)[source]

Bases: QWidget, TcWidget

Interactive slider.

Also has built-in slider numbers (unlike the basic QSlider). This class extends a plain widget so a layout can be added for any labels.

The basic QSlider only supports integer values. To support floating point numbers too, the slider values are multiplied by a scale (e.g. 100) when writing, and divided again when reading from the slider. Use this with the float and float_scale options. This is done automatically if interval is not an integer.

Variables

slider – QSlider instance

Parameters
  • orientation – Either QtCore.Qt.Horizontal (default) or Vertical

  • args

  • kwargs

Kwargs
  • min: Slider minimum value (default: 0)

  • max: Slider maximum value (default: 100)

  • interval: Slider interval step size (default: 1)

  • show_labels: When true (default), show the min and max values with

    labels

  • show_value: When true (default), show the current slider value with a

    label

  • float: When true, QSlider values are scaled to suit floats (default:

    False)

  • float_scale: Factor between QSlider values and real values (default:
  • See TcWidget

on_value_changed(new_value)[source]

Callback when the slider was changed by the user.

slider_to_value(value: int) Union[float, int][source]
twincat_receive(value) Any[source]

On remote value change.

This will be triggered by on_value_changed too. A small timeout is added to prevent a loop between the two callbacks, received changes right after a user change are ignored.

value_format: Union[str, Callable[[Any], str]]
value_to_slider(value: float) Union[int, float][source]
TcGraph
class twinpy.ui.TcGraph(*args, **kwargs)[source]

Bases: GraphWidget, TcWidget

Draw rolling graph of symbol values.

TcGraph works only well with EVENT_TIMER!

The graph refresh rate is limited to self.FPS, while data is being requested at update_freq. For research measurements, use a log file or a TwinCAT measurement project instead. Even with a high update_freq there is no guarantee all data is captured!

If no symbol for the x-axis is selected, the local time will be used instead. Note that due to how PyQt events are handled, the local time can be slightly warped with respect the ADS symbol values.

See GraphWidget for more options.

Parameters
  • args

  • kwargs

Kwargs
  • symbols: List of symbols to plot (for the y-axis)

  • symbol_x: Symbol to use on the x-axis (optional)

connect_symbol(new_symbol: Optional[Union[Symbol, List[Symbol]]] = None, **kwargs)[source]

Connect to list of symbols (override).

on_mass_timeout()[source]

Callback for the event timer (override).

This assumes the remote read was already performed!

twincat_receive(value)[source]

Abstract implementation.

All useful code is in on_mass_timeout() instead.

value_format: Union[str, Callable[[Any], str]]
class twinpy.ui.GraphWidget(labels: List[str], units: Optional[List[str]] = None, buffer_size: int = 100, values_in_legend: bool = True, *args, **kwargs)[source]

Bases: QWidget

Class to make an rolling plot of some data.

When data comes in faster than FS, the plot won’t be refreshed. The data will still be stored and show up when it’s time.

Variables

FPS – Maximum refresh rate (Hz), faster samples are buffered but not yet plotted.

Parameters
  • labels – List of strings corresponding to plotted variable names

  • units – List of display units for each variable (default: None)

  • buffer_size – Number of points to keep in graph

  • values_in_legend – When True (default), put the last values in the legend

FPS = 30.0
add_data(y_list: List[float], x: Optional[float] = None)[source]

Add new datapoint to the rolling graph.

Parameters
  • y_list – List of new y-values

  • x – New x-value (default: use system time instead)

update_plot()[source]

Refresh the plot based on self.data.

Base GUI

Module containing the Base GUI class.

This class should be extended to make your own GUI.

class twinpy.ui.base_gui.BaseGUI(actuator: Optional[SimulinkModel] = None, controller: Optional[SimulinkModel] = None, **kwargs)[source]

Bases: TcMainWindow

Base TwinCAT GUI, specific for the WE2 actuators and controller model.

Extends this class if you want to use those models. For other models, using twinpy.ui.TcMainWindow might be more appropriate.

The left side of the main window consists of the basic control elements. The right contains a tabs widget, to which you can add your own custom tabs.

An example of an extended GUI could be:

class CustomTab(QWidget):
    # Widget containing a set of TcWidgets

class MyGUI(BaseGUI):

    def __init__():
        super().__init__()

        self.custom_tab = CustomTab()
        # Add new tab to the existing list:
        self.tabs.addTab(custom_tab, "Custom Tab")

        # Make the custom tab the default:
        self.tabs.setCurrentWidget(self.custom_tab)

Note: if the pyqtconsole package is not found, the console won’t be created and the tabs list could be empty.

Parameters
  • actuator – The WE2_actuators model (or a derivative)

  • controller – The WE2_controller model (or a derivative)

  • kwargs

closeEvent(event)[source]

Callback when window is closed.

Base Widgets

These widgets are specific implementations of TcWidgets.

They are not intended to be overridden again. They are separate classes mostly because their specific logic became significant.

TcErrorsLabel
class twinpy.ui.TcErrorsLabel(*args, **kwargs)[source]

Bases: TcLabel

Extension of TcLabel for a list of joint errors.

This is separate class because the amount of logic got a little bit much.

When clicking on the widget, a new window pops up showing the decoded errors.

Parameters
  • args

  • kwargs

Kwargs
  • format: Callback to format errors

    (default: show hexadecimal representation of error values)

  • popup: Whether or not to enable a detailed popup window

    (default: True)

  • play_sound: When True, play a beep sound on a new error

    (default: False)

  • See TcLabel

static format_errors_list(error_list: List[int]) str[source]

Set text for errors label.

mousePressEvent(event: QMouseEvent) None[source]

On clicking on the label.

QLabel does not have an on-click signal already.

static to_hex(value: int) str[source]

Create human-readable hex from integer.

twincat_receive(value)[source]

Callback on remote value change.

value_format: Union[str, Callable[[Any], str]]
DrivesWidget
class twinpy.ui.DrivesWidget(actuator: Optional[SimulinkModel] = None)[source]

Bases: QGroupBox

Group with buttons for the drives.

on_drives_enabled_change(enabled_list: List[float])[source]

An additional callback for the drive state change.

Manual callback instead of TcWidget symbol connection so we can also change button state and label color.

SystemBackpackWidget
class twinpy.ui.SystemBackpackWidget(actuator: Optional[SimulinkModel] = None)[source]

Bases: QGroupBox

Widget containing labels for the temperature and voltages.

This widget is for the old backpack system.

SystemWRBSWidget
class twinpy.ui.SystemWRBSWidget(actuator: Optional[SimulinkModel] = None)[source]

Bases: QGroupBox

Widget containing labels for the temperature and voltages.

This widget is for the new wearable robotics base station.

ErrorsWidget
class twinpy.ui.ErrorsWidget(actuator: Optional[SimulinkModel] = None)[source]

Bases: QWidget

Widget for the current and last errors.

Layout contains two groupboxes, and each can be clicked for a popup with more info.

You might want to call the close_windows() method from inside the closeEvent() function from the main window, to close the popups when closing the GUI.

close_windows()[source]

Close the popup windows in case they were opened.

Tabs

Module containing complete UI tabs.

ConsoleTab
class twinpy.ui.ConsoleTab(actuator: Optional[SimulinkModel] = None, controller: Optional[SimulinkModel] = None)[source]

Bases: PythonConsole

Tab with the Python console.

The clear() command is available in the console.

FirmwareTab
class twinpy.ui.FirmwareTab(actuator: Optional[SimulinkModel] = None)[source]

Bases: QWidget

Widget for the firmware CtrlWord options (Base Station only).

Main - Example

Example of a script using a GUI.

You can run the BaseGUI itself (linked to the WE actuator model) by running

$ python -m twinpy

(I.e. by executing the module.) This provides a quick demo of how such a GUI can work, and a quick check to see everything is working on your system.

This script should not be used in your program! Instead, your application should have it’s own main script and create a GUI instance from there.

class twinpy.__main__.GUI(actuator: Optional[SimulinkModel] = None, controller: Optional[SimulinkModel] = None, **kwargs)[source]

Bases: BaseGUI

Some extension of the BaseGUI.

Custom GUIs should extend the base class.

Parameters
  • actuator – The WE2_actuators model (or a derivative)

  • controller – The WE2_controller model (or a derivative)

  • kwargs

Module Info

TwinPy package.

author

Robert Roos <robert.soor@gmail.com>

license

MIT, see license file or https://opensource.org/licenses/MIT

created on

2021-01-08 16:13:00

Indices and tables