-
Notifications
You must be signed in to change notification settings - Fork 120
CIP EthernetIP protocol implementation #187
base: master
Are you sure you want to change the base?
Conversation
|
||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CipEtherNetIp not EtherNetIP
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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]( ). |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]") |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CipEthernetIp instead of CIPEthernetIP.
There was a problem hiding this comment.
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 -*- |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
packages/sampleProp.conf
Outdated
#### [CIP EthernetIP Parameters] #### | ||
|
||
CipEtherNetIp = "127.0.0.1" | ||
Tag = "Scada[0]" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 -*- |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove extra line
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reason for assertion error?
There was a problem hiding this comment.
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 -*- |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. # | ||
# ----------------------------------------------------------------------------# | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove extra lines.
There was a problem hiding this comment.
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 | ||
|
||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
CIP protocol support for Liota