scy-phy / minicps

MiniCPS: a framework for Cyber-Physical Systems real-time simulation, built on top of mininet
MIT License
165 stars 70 forks source link

plc receive function always return empty byte #35

Open abuyusif01 opened 2 years ago

abuyusif01 commented 2 years ago

As the title said, am trying to use the swat example given in examples/swat-s1, but when i try receiving something in plc1 from plc2 it always return an empty byte. I tried sending the tag with self.send(TAG, VALUE, PLC1_ADDR) and self.send(TAG, VALUE, PLC2_ADDR) neither works. I did thesame with receive, self.recieve(TAG, PLC1_ADDR) and self.recieve(TAG, PLC2_ADDR) but no luck.

From what i understand minicps use cpppo to send/receive stuff. Since i received an empty bytes i cant call float on it. It resulted to a str cant be converted to float (which make sense)

I tested it on ubuntu and endeavoros(arch based) but all result in thesame.

Sample output from plc1 terminal

DEBUG SENSO2-FL:2
DEBUG1 (b'', None)
DEBUG2 b''
DEBUG4 b'

Am really lost

RedClouud commented 1 year ago

I had the same issue. It is likely due to the fact that MiniCPS doesn't work with the newest version of cpppo. Although, there has been a commit in the past month which seems to have fixed it but I haven't tested it.

To fix the issue, I had to use cpppo's functions directly rather than using MiniCPS's higher level functions.

Here is an example to host a value on PLC2 called FIT201 and access it from PLC1:

  1. Go into the utils.py folder and change PLC2_PROTOCOL mode to 0 (this disables the creation of a server as we will be doing this manually)
    PLC2_PROTOCOL = {
    'name': 'enip',
    'mode': 0,
    'server': PLC2_SERVER
    }
  2. In PLC2, import the required libraries
    import shlex
    import subprocess
    from cpppo.server.enip.get_attribute
    import proxy_simple
  3. In PLC2, under the def main_loop(self): method:
    # Start enip server and to host a REAL value, available @22/1/1
    tag_string = 'FIT201:2@22/1/1=REAL'
        cmd = shlex.split(
            'enip_server --print' +
            ' ' + tag_string
        )
    client = subprocess.Popen(cmd, shell=False)
  4. In PLC1, import required libraries:
    from cpppo.server.enip.get_attribute import proxy_simple
    from cpppo import logging
  5. In PLC1, define a request for FIT201 from PLC2:
    
    # Request value of FIT201 from PLC2
    class PLC2Parameters(proxy_simple):
    PARAMETERS = dict(proxy_simple.PARAMETERS,
                      fit201_2 = proxy_simple.parameter('@22/1/1', 'REAL', 'm^3/h'),
    )

PLC2_COMMS = PLC2Parameters(host=PLC2_ADDR)

6. In the `def main_loop(self):` of PLC1, request FIT201 from PLC2:
```python
# Get from PLC2
            try:
                params = PLC2_COMMS.parameter_substitution("fit201_2")
                value, = PLC2_COMMS.read(params)
            except Exception as exc:
                logging.warning("Access to fit201_2 at PLC2 failed: %s", exc)
                PLC2_COMMS.close_gateway(exc)
                raise

            fit201 = float(value[0])

It's janky but it works.


Updating hosted values in PLC2's server

And if you want to update the values in PLC2 then you can use the following code: In the def main_loop(self): loop but outside the while(count <= PLC_SAMPLES): loop

via = proxy_simple('192.168.1.20')
fit201_tag = '@22/1/1'

Inside the while(count <= PLC_SAMPLES): loop:

with via: result, = via.read([(fit201_tag + '=(REAL)' + str(fit201), fit201_tag)])
print 'this is the return value: %s' % result

If you do this, you might receive an error with plc2.py. If you do, just run the script again, adding an "&" at the end: python plc2.py & Then do it for a second time, and it should function as expected: python plc2.py

I know that this answer is long and not perfect so if you need any clarification then do ask!

hugo223325 commented 6 months ago

this confuses me for a long time