Skip to content
This repository has been archived by the owner on Mar 15, 2021. It is now read-only.

EtherNetIP protocol support #182

Closed
wants to merge 31 commits into from
Closed

EtherNetIP protocol support #182

wants to merge 31 commits into from

Conversation

NithyaElango
Copy link

Added EtherNetIP protocol support for liota, which uses cpppo library internally.

Copy link

@pmasrani pmasrani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -*-

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be CipEtherentDeviceComms. Ethernet is a generic protocol.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://en.wikipedia.org/wiki/EtherNet/IP is a specific implementation of CIP. I think we should call this: CipEtherNetIpDeviceComms.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay

import logging

from liota.device_comms.device_comms import DeviceComms
from liota.lib.transports.ENIP import EtherNetIP

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename ENIP

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed as EtherNetIP



if host is None:
log.error("Host can't be none")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove log statement. Once you raise an exception, it will automatically get logged.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the log statement.

data = self.client.read()
return data

def send(self, message):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove print, log if necessary

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these magic strings coming from? Is it always the same? What if one of them is None?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it is not UDP?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the default value of host None, if we are immediately checking for it to be not none?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when port is none? where does it connect?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@pmasrani pmasrani requested a review from KohliDev November 14, 2017 05:02
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a README file with instructions how to use this protocol.

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay




def clean_up(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disconnect the DeviceComms as well after stopping the metric collection.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay

self.config = read_user_config(config_path + '/sampleProp.conf')


ethernetIP_conn = CipEtherNetIpDeviceComms(host=self.config['EtherNetIP'],port=None,timeout=None, dialect=None,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None values need not be specified here. So remove them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, will 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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Run autopep8 to correct the formatting.

@@ -0,0 +1,28 @@
import time

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright missing.

tag_type=tag_type)

except AssertionError:
print "Response timed out!! Tearing Connection and Reconnecting!!!!!"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting? use autopep8

import logging

from liota.device_comms.device_comms import DeviceComms
from liota.lib.transports.EtherNetIP import EtherNetIP

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename EtherNetIP to CipEtherNetIp

if data is None:
raise TypeError("Data can't be none")
else:
self.client.send(tag,elements,data,tagType)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting.

@@ -49,3 +49,6 @@ LivingRoomTemperatureTopic = "home/living-room/temperature"
LivingRoomHumidityTopic = "home/living-room/humidity"
LivingRoomLightTopic = "home/living-room/light"

#### [EtherNet/IP] ####
EtherNetIP = "EtherNet/IP IP"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to CipEtherNetIp

@@ -49,3 +49,6 @@ LivingRoomTemperatureTopic = "home/living-room/temperature"
LivingRoomHumidityTopic = "home/living-room/humidity"
LivingRoomLightTopic = "home/living-room/light"

#### [EtherNet/IP] ####

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide better comment like CIP Etherent/IP Device Comms parameters

@pmasrani
Copy link

Closing this pull request, as this was incorrectly created and a new one (#187) is raised.

@pmasrani pmasrani closed this Nov 16, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants