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

CIP EthernetIP protocol implementation #187

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

NithyaElango
Copy link

@NithyaElango NithyaElango commented Nov 16, 2017

CIP protocol support for Liota


Then, EtherNet/IP related parameters required in `send()` and `receive()` like tags, datatype etc., will be mentioned while starting the server. Please refer this [example](https://github.com/lucifercr07/liota/blob/can_bus/examples/canbus/simulated_canbus.py) which reads the data from the server and send it to DCC.

In SampleProp.conf, the EtherNetIP should be changed to the IP of the server.

Choose a reason for hiding this comment

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

CipEtherNetIp not EtherNetIP

Copy link
Author

Choose a reason for hiding this comment

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

ok


## Using EtherNetIP_DeviceComms

Initially run the writer program which keeps writing data to the server, check this [simulator]( ) for the code.

Choose a reason for hiding this comment

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

something missing in brackets?

## Using EtherNetIP_DeviceComms

Initially run the writer program which keeps writing data to the server, check this [simulator]( ) for the code.
Change the host to the IP of the machine where server is running in line number 6 in this [simulator]( ).

Choose a reason for hiding this comment

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

something missing in brackets?

from cpppo.server.enip.getattr import attribute_operations
import json

HOST = "host"

Choose a reason for hiding this comment

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

I guess this needs to be changed every time someone wants to run the code. Instead, assume all 3 components (simulator, liota and server) running on the same machine. Use 127.0.0.1 in sampleProp.conf and here. In Readme, provide the instruction of where to change it, if every component is running on different machine.

Copy link
Author

Choose a reason for hiding this comment

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

ok

def receive(self):
with self.conn:
try:
request_ = self.conn.read("Scada[0]")

Choose a reason for hiding this comment

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

I think "Scada" is the name/tag of the queue. Correct me if i am wrong. It seems to be hard-coded between liota and simulator. Make this also a configurable parameter in sampleProp.conf like IP address.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it is the tag. I will change it.

log = logging.getLogger(__name__)


class CIPEthernetIP:

Choose a reason for hiding this comment

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

CipEthernetIp instead of CIPEthernetIP.

Copy link
Author

Choose a reason for hiding this comment

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

ok

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

Choose a reason for hiding this comment

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

package name convention? cip_socket_graphite? I think even directory name needs to be changed. Check that.


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

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

Choose a reason for hiding this comment

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

I don't see a reason why we are declaring all these parameters. Just a simple call CipEtherNetIpDeviceComms(host=self.config['CipEtherNetIp']) should work. Isn't it?

Copy link
Author

Choose a reason for hiding this comment

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

Yes that would work, I will change it.

@@ -0,0 +1,22 @@
# Using CIP EtherNet Industrial Protocol as Transport in LIOTA

LIOTA offers CIP EtherNet Industrial protocol as transport at Device end via [EtherNetIP_DeviceComms](https://github.com/lucifercr07/liota/blob/can_bus/liota/device_comms/canbus_device_comms.py)

Choose a reason for hiding this comment

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

EtherNetIP_DeviceComms? Correct the name.

Copy link
Author

Choose a reason for hiding this comment

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

ok


## Starting the server

To get started with the CIP protocol, a server must be started in any linux machine. Install cpppo in that machine using

Choose a reason for hiding this comment

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

Should we provide a link to cpppo library?

#### [CIP EthernetIP Parameters] ####

CipEtherNetIp = "127.0.0.1"
Tag = "Scada[0]"

Choose a reason for hiding this comment

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

I think this should just be "Scada". The value from the tag is read at [0] position. That should be done by the program.

Copy link
Author

Choose a reason for hiding this comment

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

I changed this.

req = conn.write( tag, elements=elements, data=data,
tag_type=tagType)

except socket.error as exc:

Choose a reason for hiding this comment

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

Now what happens when other errors are received? Program stops? Should we have a catch all exceptions block and print the Exception?

Copy link
Author

Choose a reason for hiding this comment

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

I am not sure what has to be done here. Should I catch all exceptions?

Copy link
Author

Choose a reason for hiding this comment

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

I checked the client file in cpppo library, shutdown is not required, closing the connection is sufficient. so changed accordingly.

def clean_up(self):
for metric in self.metrics:
metric.stop_collecting()
self.ethernetIP_conn._disconnect()

Choose a reason for hiding this comment

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

Did you check the indent? Did you try unloading the package?

Copy link
Author

Choose a reason for hiding this comment

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

I did unload it, when I see the program, I don't find this indentation mistake.

## Using cip_ethernet_ip_device_comms

Initially run the writer program which keeps writing data to the server, check this [simulator](https://github.com/NithyaElango/liota/blob/CipEthernetIP/tests/cipethernetip/cip_ethernet_ip_simulator.py) for the code.
Modify the variable "host" in this [simulator](https://github.com/NithyaElango/liota/blob/CipEthernetIP/tests/cipethernetip/cip_ethernet_ip_simulator.py) if the server is running in a different machine. If not, the server,simulator and liota program are running in the same machine, then nothing needs to be changed.

Choose a reason for hiding this comment

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

The variable name you have used is HOST, not "host".

Copy link
Author

Choose a reason for hiding this comment

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

Changed this.

try:
self.conn.shutdown(socket.SHUT_WR)
except:
pass

Choose a reason for hiding this comment

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

Can you please log the exception?

Copy link
Author

Choose a reason for hiding this comment

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

ok

response = next(self.conn)
data = response['enip']['CIP']['send_data']['CPF']['item'][1]['unconnected_send']['request']['read_frag']['data'][0]
except AssertionError as error:
log.error("Failed to receive reply")

Choose a reason for hiding this comment

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

Change it to log.exception.

Copy link
Author

Choose a reason for hiding this comment

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

ok

except AssertionError as exc:
log.info("Response timed out!!")
except socket.error as exc:
log.error("Couldn't send command: %s" % (exc))

Choose a reason for hiding this comment

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

Change it to log.exception

Copy link
Author

Choose a reason for hiding this comment

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

ok

name=ethernet_device_metric_name,
unit=None,
interval=5,
sampling_function=lambda: read_value(self.ethernetIP_conn,self.tag)

Choose a reason for hiding this comment

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

space missing after comma? autopep8?

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 done.


def connect(self):
with client.connector(host=self.host) as self.conn:
if(self.conn):

Choose a reason for hiding this comment

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

Do we need the if statement here? If we are inside with, it will print the log.

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 if statement.

@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
Copy link
Contributor

Choose a reason for hiding this comment

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

rename cip_device_comms.py

Copy link
Author

Choose a reason for hiding this comment

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

CIP could be implemented over four different networks.
I have done it over ethernet, so It is known as EtherNet/IP, and thus me and Piyush named it as cip_ethernet_ip.
Is it necessary to rename?

log = logging.getLogger(__name__)


class CipEtherNetIpDeviceComms(DeviceComms):
Copy link
Contributor

Choose a reason for hiding this comment

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

Rename to CipDeviceComms

def receive(self, tag, index):
data = self.client.receive(tag, index)
return data

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove extra line

Copy link
Author

Choose a reason for hiding this comment

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

ok

self.broadcast = broadcast
self.source_address = source_address

def connect(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there no credentials support?

Copy link
Author

Choose a reason for hiding this comment

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

No, there is no credentials support

try:
req = self.conn.write(self.tag, elements=self.elements, data=self.data,
tag_type=self.tag_type)
except AssertionError as exc:
Copy link
Contributor

Choose a reason for hiding this comment

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

Reason for assertion error?

Copy link
Author

Choose a reason for hiding this comment

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

In cpppo library, which is being used here, the write() function calls another function "unconnected_send()" - where there is an assert statement https://github.com/pjkundert/cpppo/blob/master/server/enip/client.py#L768 and that is the reason for the assertion error.

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
Copy link
Contributor

Choose a reason for hiding this comment

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

There should be a unit test case as well.

Copy link
Author

Choose a reason for hiding this comment

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

I don't understand how this has to be done.

# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF #
# THE POSSIBILITY OF SUCH DAMAGE. #
# ----------------------------------------------------------------------------#

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add comments over here what are you trying to do in this particular test case?

Copy link
Author

Choose a reason for hiding this comment

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

ok

log.exception("Instance has no such attribute")
time.sleep(5)
i = i + 1

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove extra lines.

Copy link
Author

Choose a reason for hiding this comment

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

ok

from liota.device_comms.cip_ethernet_ip_device_comms import CipEtherNetIpDeviceComms
from liota.entities.devices.simulated_device import SimulatedDevice


Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add comments over here to explain the flow?

Copy link
Author

Choose a reason for hiding this comment

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

ok

return value


class PackageClass(LiotaPackage):
Copy link
Contributor

Choose a reason for hiding this comment

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

There should also be a normal Liota example with the package example on the usage of this protocol as we haven't moved away or neither decided to discard the current existing examples.

https://github.com/vmware/liota/tree/master/examples

Copy link
Author

Choose a reason for hiding this comment

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

You want me to write an liota example program other than a liota package for the usage of this protocol? And it should be under liota/examples right?

Copy link
Author

Choose a reason for hiding this comment

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

I wrote an example program for the same usage and placed it under liota/examples.

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