stlehmann / pyads

Python wrapper for TwinCAT ADS
MIT License
252 stars 93 forks source link

Device Notification Decorator inside Class #344

Closed gbcarlos closed 1 year ago

gbcarlos commented 1 year ago

Hi,

This might be more a Python related question than to Pyads but maybe there is a workaround. I'm trying to use the device notification decorator inside a class, self, however, is not known when using it in the decorator. Did someone try out to use the decorator inside a class?

This is my code:


class plc_as:

    def __init__(self):

        # Communication constants
        self.TARGET_IP = "192.168.0.789" 
        self.TARGET_USERNAME = "Administrator"
        self.TARGET_PASSWORD = "1"
        self.ROUTE_NAME = "TEST"

        ## PLC Setup ##
        # Add route to PLC
        port = pyads.open_port()
        if port:
            pyads.set_local_address(self.CLIENT_NETID)
            pyads.add_route_to_plc(self.CLIENT_NETID, self.CLIENT_IP, self.TARGET_IP, self.TARGET_USERNAME, self.TARGET_PASSWORD, route_name=self.ROUTE_NAME)
            pyads.close_port()

        # Open connection to PLC
        self.plc = pyads.Connection('192.168.0.789.1.1', pyads.PORT_TC3PLC1)
        self.plc.open()

        # Add Callback when value changes
        error_variables = [
            'MAIN.fbMc_PowerKippmotor.Error',
            'MAIN.fbMc_PowerKippmotor.ErrorID'
        ]

        self.err_handle = self.plc.add_device_notification('MAIN.fbMc_PowerKippmotor.Error', pyads.NotificationAttrib(sizeof(pyads.PLCTYPE_BOOL)), self.callback_err)
        self.errID_handle = self.plc.add_device_notification('MAIN.fbMc_PowerKippmotor.ErrorID', pyads.NotificationAttrib(sizeof(pyads.PLCTYPE_UDINT)), self.callback_errid)

    #---- PLC Device Notification Callbacks ----#

    # Error
    def callback_err(self, notification, data):
        handle, timestamp, self.plc_err_state = self.plc.parse_notification(notification, pyads.PLCTYPE_BOOL)

    # ErrorID
    def callback_errid(self, notification, data):
        if self.plc_err_state:
            handle, timestamp, value = self.plc.parse_notification(notification, pyads.PLCTYPE_UDINT)

if __name__ == "__main__":
    plc_seq = plc_as()
chrisbeardy commented 1 year ago

I have an implementation of this, let me dig it out for you. Give me a few days as quite busy and can't copy it in verbatim as its work related

chrisbeardy commented 1 year ago

So...you can't use the decorator inside a class unfortunately, you have to set up the device notifications manually.

e.g.

class Example:
    def __init__:
        plc = pyads.Conection(...)
        heartbeat = 0
        set_up_notifications()

    def set_up_notifications(self):
        self.plc.add_device_notification(
            VAR_NAME,
            pyads.NotificationAttrib(ctypes.sizeof(pyads.PLCTYPE_BOOL)),
            self.on_plc_heartbeat,
        )

    def on_plc_heartbeat(self, *_):
        self.heartbeat += 1

An example for detcting the plc status change:

            self._plc.add_device_notification(
                (int("0xF100", 16), int("0x0000", 16)),  # ADSIGRP_DEVICE_DATA, ADSIOFFS_DEVDATA_ADSSTATE
                pyads.NotificationAttrib(ctypes.sizeof(pyads.PLCTYPE_INT)),
                self._on_plc_status_change,

    def _on_plc_status_change(self, notification, _):
        """Notification callback for a change in PLC state (run to config etc)."""
        *_, value = self._plc.parse_notification(notification, pyads.PLCTYPE_INT)  # type: ignore
        if value != pyads.ADSSTATE_RUN:
            self._plc_fatal_com_error_message = "PLC exited run mode"

Please can yu close this issue if that helps. Thanks.

gbcarlos commented 1 year ago

Thanks a lot @chrisbeardy! That helps