EtherNetIP protocol support #182
Conversation
pmasrani
left a comment
There was a problem hiding this comment.
Add the package which you have written as well. Without the package, nobody can try out this code.
Also, cpppo library needs to be added to requirements.txt
| @@ -0,0 +1,95 @@ | |||
| # -*- coding: utf-8 -*- | |||
There was a problem hiding this comment.
Use Pep 8 convention for naming the module. https://www.python.org/dev/peps/pep-0008/#package-and-module-names
Not only for this module and class, but for all of your code.
| log = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class EtherNetDeviceComms(DeviceComms): |
There was a problem hiding this comment.
I think this should be CipEtherentDeviceComms. Ethernet is a generic protocol.
There was a problem hiding this comment.
EtherNet/IP (EtherNet Industrial Protocol) means CIP implemented over ethernet.
So I changed the class name to EtherNetIPDeviceComms which I thought would be more suitable.
Or should I make it CipEtherentDeviceComms?
There was a problem hiding this comment.
https://en.wikipedia.org/wiki/EtherNet/IP is a specific implementation of CIP. I think we should call this: CipEtherNetIpDeviceComms.
| import logging | ||
|
|
||
| from liota.device_comms.device_comms import DeviceComms | ||
| from liota.lib.transports.ENIP import EtherNetIP |
|
|
||
|
|
||
| if host is None: | ||
| log.error("Host can't be none") |
There was a problem hiding this comment.
Remove log statement. Once you raise an exception, it will automatically get logged.
There was a problem hiding this comment.
Removed the log statement.
| data = self.client.read() | ||
| return data | ||
|
|
||
| def send(self, message): |
There was a problem hiding this comment.
Why can't we implement send() and receive()? Whatever functionality you have in write() has to be part of send() and read() has to be part of receive()
There was a problem hiding this comment.
Made these changes. Now, Send() contains write() and receive () contains read().
| assert self.conn.readable( timeout=1.0 ), "Failed to receive reply" | ||
| rpy = next( self.conn ) | ||
| data = rpy['enip']['CIP']['send_data']['CPF']['item'][1]['unconnected_send']['request']['read_frag']['data'][0] | ||
| print(data) |
There was a problem hiding this comment.
Removed all print statements
| req = self.conn.read("Scada[0]") | ||
| assert self.conn.readable( timeout=1.0 ), "Failed to receive reply" | ||
| rpy = next( self.conn ) | ||
| data = rpy['enip']['CIP']['send_data']['CPF']['item'][1]['unconnected_send']['request']['read_frag']['data'][0] |
There was a problem hiding this comment.
Where are these magic strings coming from? Is it always the same? What if one of them is None?
There was a problem hiding this comment.
This is to fetch the data from the response got from the server, which is in json format.
It is the same always.
|
|
||
|
|
||
| def shutdown( self ): | ||
| if not self.udp: |
There was a problem hiding this comment.
I'm not able to figure this out, could you please take a look at this - https://github.com/pjkundert/cpppo/blob/master/server/enip/client.py#L401
| DeviceComms for EtherNetIP protocol | ||
| """ | ||
|
|
||
| def __init__(self, host=None, port=None, timeout=None, dialect=None, profiler=None, udp=False, broadcast=False, source_address=None): |
There was a problem hiding this comment.
Why is the default value of host None, if we are immediately checking for it to be not none?
There was a problem hiding this comment.
I made the localhost as default, if host is empty, as mentioned in https://github.com/pjkundert/cpppo/blob/master/server/enip/client.py#L300
| def __init__(self, host=None, port=None, timeout=None, dialect=None, profiler=None, udp=False, broadcast=False, source_address=None): | ||
|
|
||
| self.host = host | ||
| self.port = port |
There was a problem hiding this comment.
What happens when port is none? where does it connect?
There was a problem hiding this comment.
When port is None, I think the default port will be used. I want you to check this once - https://github.com/pjkundert/cpppo/blob/master/server/enip/client.py#L326
Also, it is mentioned if default port is not used, use 44818 - https://github.com/pjkundert/cpppo/blob/master/server/enip/client.py#L302
As of now I have changed the port as 44818 in the code.
There was a problem hiding this comment.
ok. Thanks for checking it out. If the underlying library accepts None, then yes we should accept None. Let us not hard-code it to 44818, as it is a non-standard port, and if library implementation changes the port, we will get an error.
| @@ -0,0 +1,95 @@ | |||
| # -*- coding: utf-8 -*- | |||
There was a problem hiding this comment.
Please add a README file with instructions how to use this protocol.
| @@ -0,0 +1,91 @@ | |||
| # -*- coding: utf-8 -*- | |||
There was a problem hiding this comment.
This is not a simulation. Let us name it as CipSocketGraphite.py to indicate it is have Cip as DeviceComms, Socket as DCCComms and Graphite as DCC.
Also add the writer code as "simulator", so that someone can test this package. In Readme, also add the complete steps of how to run the simulator and load this package.
|
|
||
|
|
||
|
|
||
| def clean_up(self): |
There was a problem hiding this comment.
disconnect the DeviceComms as well after stopping the metric collection.
| self.config = read_user_config(config_path + '/sampleProp.conf') | ||
|
|
||
|
|
||
| ethernetIP_conn = CipEtherNetIpDeviceComms(host=self.config['EtherNetIP'],port=None,timeout=None, dialect=None, |
There was a problem hiding this comment.
None values need not be specified here. So remove them.
|
|
||
|
|
||
| def _connect(self): | ||
| self.client = EtherNetIP(self.host,self.port,self.timeout,self.dialect,self.profiler,self.udp,self.broadcast,self.source_address) |
There was a problem hiding this comment.
Run autopep8 to correct the formatting.
| @@ -0,0 +1,28 @@ | |||
| import time | |||
| tag_type=tag_type) | ||
|
|
||
| except AssertionError: | ||
| print "Response timed out!! Tearing Connection and Reconnecting!!!!!" |
There was a problem hiding this comment.
Are you actually reconnecting anywhere??
| elements = len (data) | ||
| tag_type = client.enip.DINT.tag_type | ||
| tag = "Scada" | ||
| req = conn.write( tag, elements=elements, data=data, |
| import logging | ||
|
|
||
| from liota.device_comms.device_comms import DeviceComms | ||
| from liota.lib.transports.EtherNetIP import EtherNetIP |
| if data is None: | ||
| raise TypeError("Data can't be none") | ||
| else: | ||
| self.client.send(tag,elements,data,tagType) |
| LivingRoomLightTopic = "home/living-room/light" | ||
|
|
||
| #### [EtherNet/IP] #### | ||
| EtherNetIP = "EtherNet/IP IP" |
| LivingRoomHumidityTopic = "home/living-room/humidity" | ||
| LivingRoomLightTopic = "home/living-room/light" | ||
|
|
||
| #### [EtherNet/IP] #### |
There was a problem hiding this comment.
Provide better comment like CIP Etherent/IP Device Comms parameters
|
Closing this pull request, as this was incorrectly created and a new one (#187) is raised. |
Added EtherNetIP protocol support for liota, which uses cpppo library internally.