ottowayi / pycomm3

A Python Ethernet/IP library for communicating with Allen-Bradley PLCs.
MIT License
398 stars 88 forks source link

Micrologix Status #51

Closed AndrewStuhr closed 3 years ago

AndrewStuhr commented 4 years ago

Hello

I asked this on issue #15, but thought I'd ask in it's own issue:

What's the status of MicroLogix support? If it's being developed, do you have any thoughts on when it will be ready?

Thanks!

ottowayi commented 4 years ago

Currently I'm not working on SLC/MicroLogix support and I don't really have any long-term plans to at the moment. I've left the module in there in case someone would like to try fixing it. Sorry to disappoint, but this library is a side project and used in my other applications. Those apps will never work with SLC/MicroLogix so I just don't have the spare cycles to work on something I won't be using. If someone does want to fix/finish the slc.py module, I would definitely accept a pull request.

AndrewStuhr commented 4 years ago

Gotcha. It's too bad, but I'm very happy you uploaded PyComm3, so thanks again!

Maybe I'll trying working on the slc.py module at some point.

ottowayi commented 4 years ago

see the latest release (0.10.0) for a working (hopefully) SLCDriver. I did some initial testing with a SLC, but I don't have any MicroLogix hardware to test with. Feel free to try it out and let me know if there's issues with it.

AndrewStuhr commented 4 years ago

I've been using the SLC driver a bit and it's nice. Very glad you uploaded it!

Do you know if it's possible to pull back a list of register names from a SLC or MicroLogix? For example a list with I1, N7, etc. I mean do you know if it's even possible with the protocol these devices use?

ottowayi commented 4 years ago

I was just thinking about that attempting that. It doesn't look like the original pycomm had it and I haven't really added any features to the SLCDriver yet. I thought I could use wireshark and see how RSLinx does it when using the data monitor. I didn't see anything in the docs on how to do it, so I might have to reverse engineer it. I'll see if I can get some time to try it out, but I can't give any sort of timetable for it.

AndrewStuhr commented 4 years ago

You're an animal!!!

ottowayi commented 4 years ago

So it's looking promising. So far I've figured out how to read the SYS 0 file that and able to get the number of data files and logic files. I still need to parse it to get the file types and sizes. I found a DF1 VB app that I'm trying to use as a guide and it seems to be going well. I don't have any MicroLogix to test against, but once I have it working I'll let you know. I'll need someone to run a couple tests against different MicroLogixes to get the processor type values, I only have the SLC ones at the moment.

AndrewStuhr commented 4 years ago

We've got a Micrologix 1000, 1100, 1400, and 1500 where I work. Let me know how I can help!

ottowayi commented 4 years ago

oh awesome. I made a branch called slc_list_files where I'm putting these changes. If you'd be able to pull from that and run the get_processor_type method on those different models it would help a lot. For the SLC 5/05 we have it returns '5/05', but I have no idea what the MicroLogixes return and I can't find anything online what they would be. I'd think it'd be the catalog numbers, but have no way of knowing for sure without running trying it. The different models have slightly different parameters for reading those system files, so I'd like to make sure it'll work before merging into master.

AndrewStuhr commented 4 years ago

Sounds good! I'll work on this around 10am pacific time tomorrow and let you know what's returned.

AndrewStuhr commented 4 years ago

Here's the code I'm using:

with SLCDriver('192.168.100.204/0') as plc: print(plc.get_processor_type())

And here are the responses for 2 PLC types: MicroLogix1100: "1763-LEC" MicroLogix1400: "1766-LEC"

The 1000 and 1500 have some ethernet issues so I didn't test those. Hopefully this helps!

ottowayi commented 4 years ago

Awesome thanks, I'll just assume the others will be also be the part number. I made some progress on identifying the files, but not all the way there yet. I found that if there is a gap in the numbers I can't seem to get any files after the missing one. My SLC goes N22, A23, N25, N30, N31, N40, N41 and I can't seem to get anything after A23. The data is in there because I compared it to the packet capture from RSLinx and it matches exactly, just something weird happens to the file format when skipping a number. So it might take me longer than expected to get it figured out.

ottowayi commented 4 years ago

I think I got it. The issue I was having in the previous comment was a dumb typo/commented out line of code that I didn't notice until I took a day off and came back to it lol. This method and this method are the ones that have get the specific values for the different SLC/MLX models. I filled them in based on what I found in that other application, but without actually testing it I don't know if they're correct. Everything is still in that slc_list_files fork, I haven't merged anything yet because there is some cleanup and stuff I want to do, as well as testing on the MLXs.


with SLCDriver('10.10.20.11') as slc:
    pprint.pprint(slc.get_file_directory(), sort_dicts=False)

image

AndrewStuhr commented 4 years ago

Nice!

I tried running the new code for a MicroLogix 1400 & 1100, and I got the following errors:

1400 error1

1100 error2

Any ideas? How can I help?!?!?

ottowayi commented 4 years ago

Would you be able to do a Wireshark capture? What I've been doing is turning off all my drivers, navigating to the PLC in the tree, starting the capture, opening the data monitor, then stopping the capture as soon as it loads all the data files. With that, I can see what it's requesting. The identifiers for the SYS 0 file is different between the models as well as the position of the data files inside it. If I can see what linx is doing, I can probably update my code to match those.

AndrewStuhr commented 4 years ago

Hey man I'll try to get to this today

ottowayi commented 4 years ago

Awesome thanks, I'm on the road for the rest of the week so I might not have much time to look at it until next week

ottowayi commented 4 years ago

Hey don't worry about running Wireshark. I'm at a plant doing a network audit and they have an 1100 and a 1400, so I was able to grab some packet captures that I'll analyze when I get back.

AndrewStuhr commented 4 years ago

Sweet!

ottowayi commented 4 years ago

I think I got it working, I just pushed a few changes to that separate branch. I didn't test it on the hardware, but simulated it with the packet captures I grabbed.

AndrewStuhr commented 4 years ago

Here's the code I'm using and the error being returned while trying to read a microLogix1100:

image

image

Let me know if there's something I can do / print to help.

ottowayi commented 4 years ago

dammit. I noticed a small difference when I try to read the size of file I was requesting 4 bytes, but linx was doing 8 for MLX and 4 for SLC. I'm not sure if that makes a difference, I only have a couple hours before I leave this morning, but I will try it again and see what I can get working. What's really weird is for the MLXs, the 'size' returned is way larger. Like I added up all the data linx got it was 492, but the size returned in the request was 20460. Which is 19968 larger.. and the other MLX was the same difference. I have no idea what that means, so I just subtract that from the size and assumed it was some weird constant Rockwell adds to it. I was hoping it was not a coincidence that both this 1100 and 1400 were both off by that amount.

ottowayi commented 4 years ago

I ended up finding a MLX1100 to test against and got it working for both it and the SLC we have. It was only an minor change to the function code. I noticed RSLinx was using 0xA1 for the MLX but 0xA2 for the SLC (which I was using for both), but if I used 0xA1 for both it seemed to work.

AndrewStuhr commented 4 years ago

Cool! I tested it out, and got interesting results. It works perfect for our MLX1100. However, I get the following error for the 1400:

image

Let me know if there's some other numbers I should try, and where I'd make the change.

ottowayi commented 4 years ago

Okay so we're making progress lol. It looks like I got my wireshark captures confused when looking at the differences between the 1100 and 1400, the parameters are different. In slc.py, on line 326 is the _get_sys0_info method. Try creating a new branch for the 1400 like this: image

We might still have to break out the 1200 and 1500, but without the hardware I have no way of knowing what the parameters are. If each model ends up being different, I'll change the if x in set to just a dict lookup or something. For now, that method just returns the parameters.

AndrewStuhr commented 4 years ago

Here's what I got after making this change:

image

Thoughts?

ottowayi commented 4 years ago

Can you write the data to a file and attach it? Right at the beginning of _parse_file0 on line 297 would be a good spot like: image

I'm betting our start position is off. Can you also send me a sceenshot of the data monitor from RSLinx? And how many logic files are in it? I'm betting those positions are different as well.

AndrewStuhr commented 4 years ago

I can get to this eventually, I'm just swamped right now with work haha

ottowayi commented 4 years ago

I understand, no rush. I just re-opened this issue so I don't have to search for it in the closed issues lol

jeffdavidcrockett commented 3 years ago

Is there any chance that we’ll be able to read strings from micrologix PLC’s ever? Right now, for barcodes, I’m having to string extract each individual character. Then using string to integer, then pulling that value from the plc with python.

ottowayi commented 3 years ago

I don't have a MicroLogix, but I did test reading from ST files in a SLC. So it should work for a MLX then too, but if it doesn't, open an issue and I can look into it.

ottowayi commented 3 years ago

@jeffdavidcrockett the ST and A file support was just added in version 0.11.0

zN3utr4l commented 3 years ago

I really appreciate what you did, a complete job, I really hope you can work hard to take it to extreme levels. I was reading this issues so I tried to run too: " " And my output is: " image "

My PLC panel is this: 'vendor': 'Rockwell Automation / Allen-Bradley', 'product_type': 'Programmable Logic Controller', 'device_type ':' 1768-L43 / B LOGIX5343 '

It's a bit old but I only use it as a tester and I wanted to help you improve pycomm3, can I help you?

I state that, however, I can access the PLC variables via the EtherNet / IP connection.

I await your reply :)

ottowayi commented 3 years ago

Yeah, I'm always willing to accept help, thank you for offering. I believe that PLC is a CompactLogix though, so I think you'd want to use the LogixDriver for it. The SLCDriver is for PLCs where you use RSLogix 500 to program them (SLCs and MicroLogix) and the LogixDriver is for ControlLogix, CompactLogix, Micro800.

zN3utr4l commented 3 years ago

Ah ok, sorry for the stupid question I have been taking this project since yesterday and wanted to help. I ask you another question, can I perform the connection with a PLC located on the network like the ones I find on Shodan? I have seen that many have port 44818 open. Another thing, what would be the substantial difference between libplctag and pycomm3?

ottowayi commented 3 years ago

No worries, not a stupid question at all. As for will it work for internet devices listed on Shodan? Technically, yes. Is it legal? Most likely not and I wouldn't risk it.

As for the libplctag differences, essentially you can do the same thing with both. But, libplctag is a much lower-level library written in C and can be used for embedded devices. pycomm3 tries to have as simple of a API as possible and abstract much of the EIP/CIP specifics away from the user. For example, to read a tag in pycomm3 you simply call the read('A_Tag'), in libplctag you need to know the data type of the tag and use the correct method to read it. pycomm3 will automatically upload all the tags in the PLC, including the structure (AOIs, UDTs, etc). This allows you to give just the name and it will do the rest. This includes reading a structure tag and getting all attributes and their values in a single call, as far as I'm aware no other EIP library does that. It also supports writing full structures as well. This is especially useful for STRING tags, since they are structs of LEN: DINT, DATA[]: SINT. In other libraries it'd take multiple steps to read a string: first read the LEN, then read LEN-elements from the DATA array, then convert the DATA to a string. In pycomm3, you just give the name of the string tag and it returns a string, same with writing, you just pass in a string for the value and it handles everything else. There is a link in the README to the ReadTheDocs site if you want to check out more of the features. The docs are still a work in progress, but should give you a pretty good idea what you can do with it.

zN3utr4l commented 3 years ago

Thanks for the tips and above all for the tips on Shodan hahaahahha, in the coming days I will continue to use pycomm3 and for ideas or bugs I will not hesitate to write to you, but the write method makes an UPDATE of a variable that already exists in the PLC or goes to create a new one, this part is not clear to me. Thanks again

ottowayi commented 3 years ago

read/write work on the values of tags in the PLC, this library can't create or delete tags. Write changes the current value, but that doesn't mean code in the PLC won't change it either. I typically use this library for configuring setpoints, values that are used by the logic in the PLC but aren't written to in the PLC code.

Example:

with LogixDriver('192.168.1.100') as plc:
    print(plc.read('a_tag'))
    plc.write(('a_tag', 200))
    print(plc.read('a_tag'))
# output
Tag(tag='a_tag', value=100, type='DINT', error=None)
Tag(tag='a_tag', value=200, type='DINT', error=None)
zN3utr4l commented 3 years ago

ok of course, the use I should make of it is to monitor the values ​​of variables of a motor or alarms, very often DINT, DINT [], BIT OR BOOL, remotely, with the PLC placed in the network domain, which what i understand pycomm3 performs to the maximum. Now I'm on the phone and I don't have the Docs in front of me, the read method returns what kind of object, that is, I saw that it is an object made up of several attributes, is there a sub-function to call for example only some of these attributes? Or do I necessarily have to save them as a string and use split () or truncate () where I need it?

ottowayi commented 3 years ago

can we continue this thru email instead? You can get mine on my github profile, I'll gladly answer any questions and offer advice as I can. I think this conversation would be better one-to-one, since this issue is not really related to what we've been talking about.

zN3utr4l commented 3 years ago

I wrote to you at the email: ian@ottoway.dev

AndrewStuhr commented 3 years ago

Is the tag list scanning working for MicroLogix? I never got back to testing...

ottowayi commented 3 years ago

The file directory listing? Maybe.. it's there for a couple models, but each one is different. I got it working for I think 1100 and 1400, but I don't remember for sure. If it doesn't work for a model, I'll need a wireshark capture of RSLinx opening the data monitor to fix it.