diff --git a/minicps/devices.py b/minicps/devices.py index 30d7b33..8b52952 100644 --- a/minicps/devices.py +++ b/minicps/devices.py @@ -254,13 +254,27 @@ def send(self, what, value, address, **kwargs): else: return self._protocol._send(what, value, address, **kwargs) + def send_multiple(self, what, value, address, **kwargs): + """Send (write) a list of values to another network host. + + ``kwargs`` dict is used to pass extra key-value pair according to the + used protocol. + + :param list what: fields identifiers + :param value: values to be setted + :param str address: ``ip[:port]`` + :returns: ``None`` or ``TypeError`` if ``what`` is not a ``tuple`` + """ + + return self._protocol._send_multiple(what, value, address, **kwargs) + def receive(self, what, address, **kwargs): """Receive (read) a value from another network host. ``kwargs`` dict is used to pass extra key-value pair according to the used protocol. - :param tuple what: field[s] identifier[s] + :param list what: field[s] identifier[s] :param str address: ``ip[:port]`` :returns: received value or ``TypeError`` if ``what`` is not a ``tuple`` @@ -271,6 +285,19 @@ def receive(self, what, address, **kwargs): else: return self._protocol._receive(what, address, **kwargs) + def receive_multiple(self, what, address, **kwargs): + """Receive (read) a value from another network host. + + ``kwargs`` dict is used to pass extra key-value pair according to the + used protocol. + + :param list what: field[s] identifier[s] + :param str address: ``ip[:port]`` + :returns: received value or ``TypeError`` if ``what`` is not a ``tuple`` + """ + + return self._protocol._receive_multiple(what, address, **kwargs) + # TODO: rename pre_loop and main_loop? class PLC(Device): diff --git a/minicps/protocols.py b/minicps/protocols.py index 9936a29..240ac2f 100644 --- a/minicps/protocols.py +++ b/minicps/protocols.py @@ -116,6 +116,16 @@ def _receive(self, what, address, **kwargs): print('_receive: please override me.') + + def _receive_multiple(self, what, address, **kwargs): + """Receive (read) a list of values from another host. + + :address: to receive from + :what: list to ask for + """ + + print('_receive_multiple: please override me.') + def _send_multiple(self, what, values, address): """Send (write) multiple values to another host. @@ -230,6 +240,27 @@ def __init__(self, protocol): # TODO: start UDP enip server + @classmethod + def _tuple_to_cpppo_tag_multiple(cls, what, values=None, serializer=':'): + """Returns a list of cpppo strings to read/write a server. + + Can be used both to generate cpppo scalar read query, like + SENSOR1:1, and scalar write query, like ACTUATOR1=1. + + Value correctness is up the client and it is blindly + converted to string and appended to the cpppo client query. + """ + tag_string = '' + + if values == None: + for i in range(len(what)): + tag_string += what[i][0] + EnipProtocol._SERIALIZER + str(what[i][1]) + " " + else: + for i in range(len(what)): + tag_string += what[i][0] + EnipProtocol._SERIALIZER + str(what[i][1]) + "=" + str(values[i]) + " " + + return tag_string + @classmethod def _tuple_to_cpppo_tag(cls, what, value=None, serializer=':'): """Returns a cpppo string to read/write a server. @@ -277,7 +308,7 @@ def _tuple_to_cpppo_tags(cls, tags, serializer=':'): tags_string += '=' tags_string += str(tag[-1]) tags_string += ' ' - print('DEBUG enip server tags_string: ', tags_string) + # print('DEBUG enip server tags_string: ', tags_string) return tags_string @@ -326,7 +357,7 @@ def _start_server_cmd(cls, address='localhost:44818', """ CMD = sys.executable + ' -m cpppo.server.enip ' - PRINT_STDOUT = '--print ' + PRINT_STDOUT = '--no-print ' HTTP = '--web %s:80 ' % address[0:address.find(':')] # print 'DEBUG: enip _start_server_cmd HTTP: ', HTTP ADDRESS = '--address ' + address + ' ' @@ -349,6 +380,32 @@ def _start_server_cmd(cls, address='localhost:44818', return cmd + def _send_multiple(self, what, values, address, **kwargs): + """Send (write) a list of values to another host. + It is a blocking operation the parent process will wait till the child + cpppo process returns. + :what: list of tuple addressing what + :values: sent + :address: ip[:port] + """ + + tag_string = '' + tag_string = EnipProtocol._tuple_to_cpppo_tag_multiple(what, values) + + cmd = shlex.split( + self._client_cmd + + '--log ' + self._client_log + + '--address ' + address + + ' ' + tag_string + ) + + try: + client = subprocess.Popen(cmd, shell=False) + client.wait() + + except Exception as error: + print('ERROR enip _send multiple: '), error + @classmethod def _stop_server(cls, server): """Stop an enip server. @@ -407,12 +464,12 @@ def _receive(self, what, address='localhost:44818', **kwargs): tag_string = '' tag_string = EnipProtocol._tuple_to_cpppo_tag(what) - print("DEBUG " + tag_string) + # print("DEBUG " + tag_string) cmd = shlex.split( self._client_cmd + '--log ' + self._client_log + - '--address ' + address + + ' --print --address ' + address + ' ' + tag_string ) # print 'DEBUG enip _receive cmd shlex list: ', cmd @@ -423,21 +480,61 @@ def _receive(self, what, address='localhost:44818', **kwargs): # client.communicate is blocking raw_out = client.communicate() - print('DEBUG1 ', raw_out) + # print('DEBUG1 ', raw_out) # value is stored as first tuple element # between a pair of square brackets - + raw_string = raw_out[0] - print("DEBUG2 " + str(raw_string)) + # print("DEBUG2 " + str(raw_string)) raw_string = str(raw_string) out = raw_string[(raw_string.find('[') + 1):raw_string.find(']')] - print("DEBUG4 " + out) + # print("DEBUG4 " + out) return out except Exception as error: print('ERROR enip _receive: ', error) + def _receive_multiple(self, what, address='localhost:44818', **kwargs): + """Receive (read) a value from another host. + It is a blocking operation the parent process will wait till the child + cpppo process returns. + :what: list to ask for + :address: to receive from + :returns: tag value as a `str` + """ + + tag_string = '' + tag_string = EnipProtocol._tuple_to_cpppo_tag_multiple(what) + + cmd = shlex.split( + self._client_cmd + + '--log ' + self._client_log + + ' --print --address ' + address + + ' ' + tag_string + ) + + try: + client = subprocess.Popen(cmd, shell=False, + stdout=subprocess.PIPE) + + # client.communicate is blocking + raw_out = client.communicate() + # print(f'DEBUG enip _receive_multiple {raw_out}: ', raw_out) + + # value is stored as first tuple element + # between a pair of square brackets + values =[] + raw_string = raw_out[0] + split_string = raw_string.split(b"\n") + for word in split_string: + values.append(word[(word.find(b'[') + 1):word.find(b']')]) + values.pop() + return values + + except Exception as error: + print('ERROR enip _receive_multiple: ', error) + # }}} @@ -821,5 +918,23 @@ def _receive(self, what, address='localhost:502', **kwargs): except Exception as error: print('ERROR modbus _receive: ', error) + + def _receive_multiple(self, what, address, **kwargs): + """Receive (read) a value from another host. + + :address: to receive from + :what: to ask for + """ + + raise NotImplementedError('Multiple receiving is not yet implemented for Modbus') + + def _send_multiple(self, what, values, address, **kwargs): + """Send (write) multiple values to another host. + + :address: to receive from + :what: to ask for + """ + + raise NotImplementedError('Multiple sending is not yet implemented for Modbus') # # }}} diff --git a/setup.py b/setup.py index c545e03..f4c53a7 100755 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ 'cryptography', 'pyasn1', 'pymodbus', - 'cpppo==4.3.4', + 'cpppo', 'pandas', ], # NOTE: specify files relative to the module path