Skip to content

V1

PLCBlockManifest

Bases: WorkflowBlockManifest

Manifest for a PLC communication block using Ethernet/IP.

The block can be used in one of three modes: - 'read': Only reads specified tags. - 'write': Only writes specified tags. - 'read_and_write': Performs both reading and writing in one execution.

tags_to_read and tags_to_write are applicable depending on the mode chosen.

Source code in inference/enterprise/workflows/enterprise_blocks/sinks/PLCethernetIP/v1.py
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
class PLCBlockManifest(WorkflowBlockManifest):
    """Manifest for a PLC communication block using Ethernet/IP.

    The block can be used in one of three modes:
    - 'read': Only reads specified tags.
    - 'write': Only writes specified tags.
    - 'read_and_write': Performs both reading and writing in one execution.

    `tags_to_read` and `tags_to_write` are applicable depending on the mode chosen.
    """

    model_config = ConfigDict(
        json_schema_extra={
            "name": "PLC EthernetIP",
            "version": "v1",
            "short_description": "Generic PLC read/write block using pylogix over Ethernet/IP.",
            "long_description": LONG_DESCRIPTION,
            "license": "Roboflow Enterprise License",
            "block_type": "sinks",
        }
    )

    type: Literal["roboflow_core/sinks@v1"]

    plc_ip: Union[str, WorkflowParameterSelector(kind=[STRING_KIND])] = Field(
        description="IP address of the target PLC.", examples=["192.168.1.10"]
    )

    mode: Literal["read", "write", "read_and_write"] = Field(
        description="Mode of operation: 'read', 'write', or 'read_and_write'.",
        examples=["read", "write", "read_and_write"],
    )

    tags_to_read: Union[
        List[str], WorkflowParameterSelector(kind=[LIST_OF_VALUES_KIND])
    ] = Field(
        default=[],
        description="List of PLC tag names to read. Applicable if mode='read' or mode='read_and_write'.",
        examples=[["camera_msg", "sku_number"]],
    )

    tags_to_write: Union[
        Dict[str, Union[int, float, str]],
        WorkflowParameterSelector(kind=[LIST_OF_VALUES_KIND]),
    ] = Field(
        default={},
        description="Dictionary of tags and the values to write. Applicable if mode='write' or mode='read_and_write'.",
        examples=[{"camera_fault": True, "defect_count": 5}],
    )

    depends_on: Selector() = Field(
        description="Reference to the step output this block depends on.",
        examples=["$steps.some_previous_step"],
    )

    @classmethod
    def describe_outputs(cls) -> List[OutputDefinition]:
        return [
            OutputDefinition(
                name="plc_results",
                kind=[LIST_OF_VALUES_KIND],
            ),
        ]

    @classmethod
    def get_execution_engine_compatibility(cls) -> Optional[str]:
        return ">=1.0.0,<2.0.0"

PLCBlockV1

Bases: WorkflowBlock

A PLC communication workflow block using Ethernet/IP and pylogix.

Depending on the selected mode: - 'read': Reads specified tags. - 'write': Writes provided values to specified tags. - 'read_and_write': Reads and writes in one go.

In case of failures, errors are printed to terminal and the corresponding tag entry in the output is set to "ReadFailure" or "WriteFailure".

Source code in inference/enterprise/workflows/enterprise_blocks/sinks/PLCethernetIP/v1.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
class PLCBlockV1(WorkflowBlock):
    """A PLC communication workflow block using Ethernet/IP and pylogix.

    Depending on the selected mode:
    - 'read': Reads specified tags.
    - 'write': Writes provided values to specified tags.
    - 'read_and_write': Reads and writes in one go.

    In case of failures, errors are printed to terminal and the corresponding tag entry in the output is set to "ReadFailure" or "WriteFailure".
    """

    @classmethod
    def get_manifest(cls) -> Type[WorkflowBlockManifest]:
        return PLCBlockManifest

    def _read_single_tag(self, comm, tag):
        try:
            response = comm.Read(tag)
            if response.Status == "Success":
                return response.Value
            logger.error(f"Error reading tag '%s': %s", tag, response.Status)
            return "ReadFailure"
        except Exception as e:
            logger.error(f"Unhandled error reading tag '%s': %s", tag, e)
            return "ReadFailure"

    def _write_single_tag(self, comm, tag, value):
        try:
            response = comm.Write(tag, value)
            if response.Status == "Success":
                return "WriteSuccess"
            logger.error(
                "Error writing tag '%s' with value '%s': %s",
                tag,
                value,
                response.Status,
            )
            return "WriteFailure"
        except Exception as e:
            logger.error(f"Unhandled error writing tag '%s': %s", tag, e)
            return "WriteFailure"

    def run(
        self,
        plc_ip: str,
        mode: str,
        tags_to_read: List[str],
        tags_to_write: Dict[str, Union[int, float, str]],
        depends_on: any,
        image: Optional[WorkflowImageData] = None,
        metadata: Optional[VideoMetadata] = None,
    ) -> dict:
        """Run PLC read/write operations using pylogix over Ethernet/IP.

        Args:
            plc_ip (str): PLC IP address.
            mode (str): 'read', 'write', or 'read_and_write'.
            tags_to_read (List[str]): Tags to read if applicable.
            tags_to_write (Dict[str, Union[int, float, str]]): Tags to write if applicable.
            depends_on (any): The step output this block depends on.
            image (Optional[WorkflowImageData]): Not required for this block.
            metadata (Optional[VideoMetadata]): Not required for this block.

        Returns:
            dict: A dictionary with `plc_results` as a list containing one dictionary. That dictionary has 'read' and/or 'write' keys.
        """
        read_results = {}
        write_results = {}

        with pylogix.PLC() as comm:
            comm.IPAddress = plc_ip

            if mode in ["read", "read_and_write"]:
                read_results = {
                    tag: self._read_single_tag(comm, tag) for tag in tags_to_read
                }

            if mode in ["write", "read_and_write"]:
                write_results = {
                    tag: self._write_single_tag(comm, tag, value)
                    for tag, value in tags_to_write.items()
                }

        plc_output = {}
        if read_results:
            plc_output["read"] = read_results
        if write_results:
            plc_output["write"] = write_results

        return {"plc_results": [plc_output]}

run(plc_ip, mode, tags_to_read, tags_to_write, depends_on, image=None, metadata=None)

Run PLC read/write operations using pylogix over Ethernet/IP.

Parameters:

Name Type Description Default
plc_ip str

PLC IP address.

required
mode str

'read', 'write', or 'read_and_write'.

required
tags_to_read List[str]

Tags to read if applicable.

required
tags_to_write Dict[str, Union[int, float, str]]

Tags to write if applicable.

required
depends_on any

The step output this block depends on.

required
image Optional[WorkflowImageData]

Not required for this block.

None
metadata Optional[VideoMetadata]

Not required for this block.

None

Returns:

Name Type Description
dict dict

A dictionary with plc_results as a list containing one dictionary. That dictionary has 'read' and/or 'write' keys.

Source code in inference/enterprise/workflows/enterprise_blocks/sinks/PLCethernetIP/v1.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def run(
    self,
    plc_ip: str,
    mode: str,
    tags_to_read: List[str],
    tags_to_write: Dict[str, Union[int, float, str]],
    depends_on: any,
    image: Optional[WorkflowImageData] = None,
    metadata: Optional[VideoMetadata] = None,
) -> dict:
    """Run PLC read/write operations using pylogix over Ethernet/IP.

    Args:
        plc_ip (str): PLC IP address.
        mode (str): 'read', 'write', or 'read_and_write'.
        tags_to_read (List[str]): Tags to read if applicable.
        tags_to_write (Dict[str, Union[int, float, str]]): Tags to write if applicable.
        depends_on (any): The step output this block depends on.
        image (Optional[WorkflowImageData]): Not required for this block.
        metadata (Optional[VideoMetadata]): Not required for this block.

    Returns:
        dict: A dictionary with `plc_results` as a list containing one dictionary. That dictionary has 'read' and/or 'write' keys.
    """
    read_results = {}
    write_results = {}

    with pylogix.PLC() as comm:
        comm.IPAddress = plc_ip

        if mode in ["read", "read_and_write"]:
            read_results = {
                tag: self._read_single_tag(comm, tag) for tag in tags_to_read
            }

        if mode in ["write", "read_and_write"]:
            write_results = {
                tag: self._write_single_tag(comm, tag, value)
                for tag, value in tags_to_write.items()
            }

    plc_output = {}
    if read_results:
        plc_output["read"] = read_results
    if write_results:
        plc_output["write"] = write_results

    return {"plc_results": [plc_output]}