Skip to content

Commit 94145ce

Browse files
authored
Merge pull request #36 from afmurillo/master
Support for multitag requests on ENIP
2 parents 12e8558 + 49ab74d commit 94145ce

File tree

3 files changed

+152
-10
lines changed

3 files changed

+152
-10
lines changed

minicps/devices.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,27 @@ def send(self, what, value, address, **kwargs):
254254
else:
255255
return self._protocol._send(what, value, address, **kwargs)
256256

257+
def send_multiple(self, what, value, address, **kwargs):
258+
"""Send (write) a list of values to another network host.
259+
260+
``kwargs`` dict is used to pass extra key-value pair according to the
261+
used protocol.
262+
263+
:param list what: fields identifiers
264+
:param value: values to be setted
265+
:param str address: ``ip[:port]``
266+
:returns: ``None`` or ``TypeError`` if ``what`` is not a ``tuple``
267+
"""
268+
269+
return self._protocol._send_multiple(what, value, address, **kwargs)
270+
257271
def receive(self, what, address, **kwargs):
258272
"""Receive (read) a value from another network host.
259273
260274
``kwargs`` dict is used to pass extra key-value pair according to the
261275
used protocol.
262276
263-
:param tuple what: field[s] identifier[s]
277+
:param list what: field[s] identifier[s]
264278
:param str address: ``ip[:port]``
265279
266280
:returns: received value or ``TypeError`` if ``what`` is not a ``tuple``
@@ -271,6 +285,19 @@ def receive(self, what, address, **kwargs):
271285
else:
272286
return self._protocol._receive(what, address, **kwargs)
273287

288+
def receive_multiple(self, what, address, **kwargs):
289+
"""Receive (read) a value from another network host.
290+
291+
``kwargs`` dict is used to pass extra key-value pair according to the
292+
used protocol.
293+
294+
:param list what: field[s] identifier[s]
295+
:param str address: ``ip[:port]``
296+
:returns: received value or ``TypeError`` if ``what`` is not a ``tuple``
297+
"""
298+
299+
return self._protocol._receive_multiple(what, address, **kwargs)
300+
274301

275302
# TODO: rename pre_loop and main_loop?
276303
class PLC(Device):

minicps/protocols.py

Lines changed: 123 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ def _receive(self, what, address, **kwargs):
116116

117117
print('_receive: please override me.')
118118

119+
120+
def _receive_multiple(self, what, address, **kwargs):
121+
"""Receive (read) a list of values from another host.
122+
123+
:address: to receive from
124+
:what: list to ask for
125+
"""
126+
127+
print('_receive_multiple: please override me.')
128+
119129
def _send_multiple(self, what, values, address):
120130
"""Send (write) multiple values to another host.
121131
@@ -230,6 +240,27 @@ def __init__(self, protocol):
230240

231241
# TODO: start UDP enip server
232242

243+
@classmethod
244+
def _tuple_to_cpppo_tag_multiple(cls, what, values=None, serializer=':'):
245+
"""Returns a list of cpppo strings to read/write a server.
246+
247+
Can be used both to generate cpppo scalar read query, like
248+
SENSOR1:1, and scalar write query, like ACTUATOR1=1.
249+
250+
Value correctness is up the client and it is blindly
251+
converted to string and appended to the cpppo client query.
252+
"""
253+
tag_string = ''
254+
255+
if values == None:
256+
for i in range(len(what)):
257+
tag_string += what[i][0] + EnipProtocol._SERIALIZER + str(what[i][1]) + " "
258+
else:
259+
for i in range(len(what)):
260+
tag_string += what[i][0] + EnipProtocol._SERIALIZER + str(what[i][1]) + "=" + str(values[i]) + " "
261+
262+
return tag_string
263+
233264
@classmethod
234265
def _tuple_to_cpppo_tag(cls, what, value=None, serializer=':'):
235266
"""Returns a cpppo string to read/write a server.
@@ -277,7 +308,7 @@ def _tuple_to_cpppo_tags(cls, tags, serializer=':'):
277308
tags_string += '='
278309
tags_string += str(tag[-1])
279310
tags_string += ' '
280-
print('DEBUG enip server tags_string: ', tags_string)
311+
# print('DEBUG enip server tags_string: ', tags_string)
281312

282313
return tags_string
283314

@@ -326,7 +357,7 @@ def _start_server_cmd(cls, address='localhost:44818',
326357
"""
327358

328359
CMD = sys.executable + ' -m cpppo.server.enip '
329-
PRINT_STDOUT = '--print '
360+
PRINT_STDOUT = '--no-print '
330361
HTTP = '--web %s:80 ' % address[0:address.find(':')]
331362
# print 'DEBUG: enip _start_server_cmd HTTP: ', HTTP
332363
ADDRESS = '--address ' + address + ' '
@@ -349,6 +380,32 @@ def _start_server_cmd(cls, address='localhost:44818',
349380

350381
return cmd
351382

383+
def _send_multiple(self, what, values, address, **kwargs):
384+
"""Send (write) a list of values to another host.
385+
It is a blocking operation the parent process will wait till the child
386+
cpppo process returns.
387+
:what: list of tuple addressing what
388+
:values: sent
389+
:address: ip[:port]
390+
"""
391+
392+
tag_string = ''
393+
tag_string = EnipProtocol._tuple_to_cpppo_tag_multiple(what, values)
394+
395+
cmd = shlex.split(
396+
self._client_cmd +
397+
'--log ' + self._client_log +
398+
'--address ' + address +
399+
' ' + tag_string
400+
)
401+
402+
try:
403+
client = subprocess.Popen(cmd, shell=False)
404+
client.wait()
405+
406+
except Exception as error:
407+
print('ERROR enip _send multiple: '), error
408+
352409
@classmethod
353410
def _stop_server(cls, server):
354411
"""Stop an enip server.
@@ -407,12 +464,12 @@ def _receive(self, what, address='localhost:44818', **kwargs):
407464
tag_string = ''
408465
tag_string = EnipProtocol._tuple_to_cpppo_tag(what)
409466

410-
print("DEBUG " + tag_string)
467+
# print("DEBUG " + tag_string)
411468

412469
cmd = shlex.split(
413470
self._client_cmd +
414471
'--log ' + self._client_log +
415-
'--address ' + address +
472+
' --print --address ' + address +
416473
' ' + tag_string
417474
)
418475
# print 'DEBUG enip _receive cmd shlex list: ', cmd
@@ -423,21 +480,61 @@ def _receive(self, what, address='localhost:44818', **kwargs):
423480

424481
# client.communicate is blocking
425482
raw_out = client.communicate()
426-
print('DEBUG1 ', raw_out)
483+
# print('DEBUG1 ', raw_out)
427484

428485
# value is stored as first tuple element
429486
# between a pair of square brackets
430-
487+
431488
raw_string = raw_out[0]
432-
print("DEBUG2 " + str(raw_string))
489+
# print("DEBUG2 " + str(raw_string))
433490
raw_string = str(raw_string)
434491
out = raw_string[(raw_string.find('[') + 1):raw_string.find(']')]
435-
print("DEBUG4 " + out)
492+
# print("DEBUG4 " + out)
436493
return out
437494

438495
except Exception as error:
439496
print('ERROR enip _receive: ', error)
440497

498+
def _receive_multiple(self, what, address='localhost:44818', **kwargs):
499+
"""Receive (read) a value from another host.
500+
It is a blocking operation the parent process will wait till the child
501+
cpppo process returns.
502+
:what: list to ask for
503+
:address: to receive from
504+
:returns: tag value as a `str`
505+
"""
506+
507+
tag_string = ''
508+
tag_string = EnipProtocol._tuple_to_cpppo_tag_multiple(what)
509+
510+
cmd = shlex.split(
511+
self._client_cmd +
512+
'--log ' + self._client_log +
513+
' --print --address ' + address +
514+
' ' + tag_string
515+
)
516+
517+
try:
518+
client = subprocess.Popen(cmd, shell=False,
519+
stdout=subprocess.PIPE)
520+
521+
# client.communicate is blocking
522+
raw_out = client.communicate()
523+
# print(f'DEBUG enip _receive_multiple {raw_out}: ', raw_out)
524+
525+
# value is stored as first tuple element
526+
# between a pair of square brackets
527+
values =[]
528+
raw_string = raw_out[0]
529+
split_string = raw_string.split(b"\n")
530+
for word in split_string:
531+
values.append(word[(word.find(b'[') + 1):word.find(b']')])
532+
values.pop()
533+
return values
534+
535+
except Exception as error:
536+
print('ERROR enip _receive_multiple: ', error)
537+
441538
# }}}
442539

443540

@@ -821,5 +918,23 @@ def _receive(self, what, address='localhost:502', **kwargs):
821918

822919
except Exception as error:
823920
print('ERROR modbus _receive: ', error)
921+
922+
def _receive_multiple(self, what, address, **kwargs):
923+
"""Receive (read) a value from another host.
924+
925+
:address: to receive from
926+
:what: to ask for
927+
"""
928+
929+
raise NotImplementedError('Multiple receiving is not yet implemented for Modbus')
930+
931+
def _send_multiple(self, what, values, address, **kwargs):
932+
"""Send (write) multiple values to another host.
933+
934+
:address: to receive from
935+
:what: to ask for
936+
"""
937+
938+
raise NotImplementedError('Multiple sending is not yet implemented for Modbus')
824939
#
825940
# }}}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
'cryptography',
4444
'pyasn1',
4545
'pymodbus',
46-
'cpppo==4.3.4',
46+
'cpppo',
4747
'pandas',
4848
],
4949
# NOTE: specify files relative to the module path

0 commit comments

Comments
 (0)