Giter Club home page Giter Club logo

libplctag's Introduction

libplctag - a C library for PLC communication

OS Architecture/CPU Version 64-bit 32-bit
Ubuntu x86, Arm 18.04 Supported Supported
Windows x86, Arm 10 (Server 19) Supported Supported
macOS x86, Arm 11 Supported Not Supported
Latest Release Version Latest CI Status
Latest Release Release branch CI status

libplctag is an open source C library for Linux, Windows and macOS using EtherNet/IP or Modbus TCP to read and write tags in PLCs. The library has been in production since early 2012 and is used by multiple organizations for many tasks including controlling radio telescopes, large and precision manufacturing, controlling fitness equipment, food handling and many, many more.

Current Stable Version: 2.6

Old Stable Version: 2.5

WARNING - DISCLAIMER

Note: PLCs control many kinds of equipment and loss of property, production or even life can happen if mistakes in programming or access are made. Always use caution when accessing or programming PLCs!

We make no claims or warrants about the suitability of this code for any purpose.

Be careful!

Get It

Do you know what you want already? Download it from the releases page!

Features

High Level Features

  • EtherNet/IP and Modbus TCP support.
  • Open source licensing under the MPL 2.0 or LGPL 2+.
  • Pure C library for portability across Linux, Windows and macOS as well as 32-bit and 64-bit.
  • Support for x86, ARM and MIPS, and probably others.
  • Very stable API with almost no changes other than feature additions since 2012.
  • Low memory use and very high performance and capacity. Uses protocol-specific features to increase performance.
  • Simple API with minimal use of language-specific data to enable easy wrapping in other languages.
  • Extensive example programs showing use of all library features.
  • Wrappers for higher level languages like C#/.Net, Julia etc.
  • Free!

Detailed Features

PLC Support

  • support for Rockwell/Allen-Bradley ControlLogix(tm) PLCs via CIP-EtherNet/IP (CIP/EIP or EIP).
    • read/write 8, 16, 32, and 64-bit signed and unsigned integers.
    • read/write single bits/booleans.
    • read/write 32-bit and 64-bit IEEE format (little endian) floating point.
    • raw support for user-defined structures (you need to pull out the data piece by piece)
    • read/write arrays of the above.
    • multiple-request support per packet.
    • packet size negotiation with newer firmware (version 20+) and hardware.
    • tag listing, both controller and program tags.
  • support for Rockwell/Allen-Bradley Micro 850 PLCs.
    • Support as for ControlLogix where possible.
  • support for older Rockwell/Allen-Bradley such as PLC-5 PLCs (Ethernet upgraded to support Ethernet/IP), SLC 500 and MicroLogix with Ethernet via CIP.
    • read/write of 16-bit INT.
    • read/write of 32-bit floating point.
    • read/write of arrays of the above (arrays not tested on SLC 500).
  • support for older Rockwell/Allen-Bradley PLCs accessed over a DH+ bridge (i.e. a LGX chassis with a DHRIO module) such as PLC/5, SLC 500 and MicroLogix.
    • read/write of 16-bit INT.
    • read/write of 32-bit floating point.
    • read/write of arrays of the above.
  • extensive example code. Including
    • tag listing.
    • setting up and handling callbacks.
    • logging data from multiple tags.
    • reading and writing tags from the command line.
    • getting and setting individual bits as tags.
  • Support for Omron NX/NJ series PLCs as for Allen-Bradley Micro800.
  • Support for Modbus TCP.

Platform Support

  • CMake build system for better cross-platform support on Windows, Linux and macOS.
    • Native CMake support is present in recent versions of Microsoft Visual Studio.
  • Semantic versioning used and supported with specific library APIs for compatibility and feature checking.
  • C library has no dependencies apart from libc (and pthreads on some platforms).
  • Binary releases built for Ubuntu 18.04, macOS 10.15 and Windows 10. All 64-bit, with 32-bit binary releases for Windows and Ubuntu.
  • RaspberryPi supported. Both Linux and Windows IoT-based (some effort required to configure Visual Studio to build).

Alternate Programming Languages

The C library is designed for easy wrapping. Wrappers for many other languages include the following:

Code

How to Get The Code

The code for the core library is at libplctag. Stable code is on the default release branch. If you check out code from GitHub, it will default to the release branch.

If you want pre-built binaries, we have them available on the releases page. Just pick the one you want and download the ZIP file for your system. We have 32 and 64-bit builds for x86 Linux and Windows and 64-bit builds for x86-64 macOS.

Go to the main project at the libplctag organization to see the other wrappers. We are in a state of transition right now as we move more alternate language wrappers into the GitHub organization.

Example Code

Oh, wait, you want code! There are many examples in the examples directory.

A good place to start is simple.c.

This code reads several 32-bit signed integers (DINT), updates them, then writes them back out and rereads them from a tag named TestBigArray in a Logix-class Allen-Bradley PLC.

The README file in the examples directory describes some of the more interesting ones.

API

Most of the functions in the API are for data access. Direct support for single bits, 8-bit, 16-bit, 32-bit and 64-bit words (integer and floating point) are provided by the library.

See the API for more information.

Help Wanted

We need and welcome help with the following:

  • bug reports! We may not have your hardware so your bugs can help us make sure the library works in cases we cannot find!
  • bug fixes.
  • other protocols like Modbus, SBus etc.
  • other platforms like Android, iOS etc.
  • changes and additions for other PLCs.
  • additional compilers.
  • more language wrappers!
  • patches and updates for existing language wrappers!
  • Testing and more testing!

How to Contribute

We love contributions! Many users have contributed wrappers, extra functionality and bug fixes over the years. The library is much better for all the help that users have provided. We ask that your code contributions to the core library are under the same dual MPL/LGPL license.

Testing is difficult for us as we do not have access to all the different hardware out there. If you can, a great way to contribute is to test prereleases. These are on the prerelease branch! We appreciate all the help we get from our users this way.

The easiest way to contribute to the core library is to raise a PR on GitHub.

Wrappers in other languages are generally split off into separate projects. Those may have different licenses and contribution processes. Please look at the documentation for the wrapper in question.

History

See the wiki history page for more details on how libplctag was created and why we built it.

Contact and Support

There are two ways to ask for help or contact us.

libplctag Forum

If you have general questions or comments about the library, its use, or about one of the wrapper libraries, please join the Google group libplctag!

The forum is open to all, but is by request only to keep the spammers down. The traffic is fairly light with usually a small number of emails per month. It is our primary means for users to ask questions and for discussions to happen. Announcements about releases happen on the forum.

GitHub

If you find bugs or need specific features, please file them on GitHub's issue tracker for the main C library project. Each individual wrapper project has its own issue tracker.

If needed, we will initiate private communication from there.

License

See the license files (LICENSE.MPL or LICENSE.LGPL) for our legal disclaimers of responsibility, fitness or merchantability of this library as well as your rights with regards to use of this library. This code is dual licensed under the Mozilla Public License 2.0 (MPL 2.0) or the GNU Lesser/Library General Public License 2 or later (LGPL 2+).

This dual license applies to the core C library. Additional wrappers for other languages may be under different licenses. Please see those projects for more information.

Attributions and Trademarks

PLC5, SLC 500, MicroLogix, Micro8X0, CompactLogix and ControlLogix are trademarks of Rockwell/Allen Bradley. Windows and Visual Studio are trademarks of Microsoft. Apple owns the trademark on macOS.

Please let us know if we missed some so that we can get all the attributions correct!

End Note

Have fun and let us know if this library is useful to you. Please send test cases if you run into bugs. As PLC hardware is fairly expensive, we may not be able to test out your test scenarios. If possible, please send patches. We do not ask that you transfer copyright over to us, but we do ask that you make any submitted patches under the same licenses we use. We will not take any patches under the GPL license or licenses that are incompatible with the MPL 2.0 license.

We hope you find this library as useful as we do!

  • the libplctag team

libplctag's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libplctag's Issues

extra param/size issue with Python wrapper on Windows/RPi

Hi everyone, Im getting a plc tag size error.
I am expecting to read from tag name "scada" with 100 integer values.
I am currently testing the python module.

This is the command i passed:
python tag_rw.py -t sint32 -p "protocol=ab_eip&gateway=192.168.1.23&path=1,0&cpu=LGX&elem_size=4&elem_count=100&debug=1&name=scada"

I have using -t uint32 , -t uint8, -t sint32 with element size of 4, but the library always responds with the below :

Traceback (most recent call last):
File "tag_rw.py", line 162, in
main()
File "tag_rw.py", line 95, in main
tag_size = libplctag.plc_tag_get_size(tag)
File "/home/xeon/libplctag/contrib/Python/plctag/libplctag.py", line 266, in plc_tag_get_size
return plcTagGetSize(tag)
TypeError: this function takes at least 2 arguments (1 given)

Reading error from PLC LOGIX 5573

Hi Kyle
I am using Logix 5573, ip address: 192.168.1.207. I created different tags in a very simple program, one tag for each data type, and tried to read Real, it is version of simple.c. I am able to ping PLC. I setup the connection string as shown below and tried to read a tag named 'Real' as float value but I am getting an error message "ERROR: Unable to read the data! Got error: " -5 (PLC_TAG_ERR_BAD_PARAM). Please can you help me to identify what I am doing wrong. Quickly question for Logix 5573 the cpu name I have to use is LGX, correct? Thanks

Please see below the configuration:
test_rockwell

Please see code below:

extern "C" {

include "libplctag.h"

}

include <unistd.h>

define TAG_PATH "protocol=ab_eip&gateway=192.168.1.207&path=1,0&cpu=LGX&elem_size=4&elem_count=1&name=Real"

define ELEM_COUNT 10

define ELEM_SIZE 4

define DATA_TIMEOUT 5000

int testingAB_CIP() // Testing Allen Bradley PLC
{
plc_tag tag = PLC_TAG_NULL;
int rc;
int i;

// create the tag
tag = plc_tag_create(TAG_PATH);

// everything OK?
if(!tag) {
    fprintf(stderr,"ERROR: Could not create tag!\n");

    return 0;
}

// let the connect succeed we hope
while(plc_tag_status(tag) == PLCTAG_STATUS_PENDING) {
    sleep(1);
}

if(plc_tag_status(tag) != PLCTAG_STATUS_OK) {
    fprintf(stderr,"Error setting up tag internal state.\n");
    return 0;
}

// get the data
rc = plc_tag_read(tag, DATA_TIMEOUT);

if(rc != PLCTAG_STATUS_OK) {
    fprintf(stderr,"ERROR: Unable to read the data! Got error code %d\n",rc);

    return 0;
}
cout << plc_tag_get_float32(tag,0);

// we are done
plc_tag_destroy(tag);

};

int main(int argc, char *argv[])
{
testingAB_CPI()
return 0;
}

PLCTAG_ERR_TIMEOUT

Hello. I am seeing an issue where I occasionally get the error status of "PLCTAG_ERR_TIMEOUT" when writing a single value to a PLC. I reduced the frequency I write to the PLC (every 5 sec instead of 3 sec) and increased the timeout to 10,000 ms instead of 5,000 ms, but I still get this occasional error. I'm worried this might happen in the middle of a customer transaction and end it early. I see the error after hours of successful writes (this is a PLC heartbeat so it knows it's connected to the PC) and reads of system_ready from the PLC. Should I increase the timeout? I am attaching some log files for your reference (search for text "[error]").
Logs.zip

Library Version: 1.5.3
OS: Ubuntu 16.04 LTS, 64-bit
PLC: MircroLogix 1400

Thank you.

Change debug after tag creation.

Not a issue or rather important.

At the moment the only way to change the debug open is to recreate the tag. It would be nice if there was a method plc_tag_set_debug within the API. It gives the ability to turn it on/off while the application using the API is running.

Thanks again!

Memory Leak on Failed connection.

It seems I have hit a memory leak. However you may already know about the issue as theres a comment above the destroy function:

  • FIXME - this leaves a dangling pointer. Should we take the address
  • of the tag pointer as an arg and zero out the pointer? That may not be
  • as portable.

It looks like the plc_tag variable isn't destroyed because tag->mut is NULL meaning tag->vtable->destroy(tag); function will not get called.

The tag->mut is NULL because it doesn't get past if(tag && tag->status == PLCTAG_STATUS_OK) within plc_tag_create because the connection is failing. The IP address I have passed is not valid so the API is giving a PLCTAG_ERR_BAD_GATEWAY.

I was doing some stress tests and it doesn't take long for the PC to grind to a halt if you are looping around creating, connecting and attempting to destroy the tag because it failed.

I am using a controllogix as the test PLC but I don't think it makes much difference.

Thanks again!

MainPogram tags logix5000

Hi there, recently I have tested the liblpctag with CompactLogix L35E e L35RE and this works petty well.
But by some reason I only can read/write tags created in the Controller Tags and I can't read the MainProgram tags (In the controller organizer).

I find the issue "Addressing tags with : in the tag name" where the problem with ":" was resolved to read tags in others scopes, but I try to read the MainProgramTags (MainPogram:PIC_7112.KP_SUP) and the lib always return the -5 error.

Someone had a similar error when trying access tags in the MainProgram scope on Logix5000?

logix5000-tags

When I put &debug=1 the log is:

debug-tagaccess

The plc_tag_status() return OK, the problems appear when I try read the tag.

Thanks in advance!

Segfault from destroying tags

Hello. I was able to capture a crash from closing tags with &debug=3 on each tag. The log file is long. The connection recovered successfully the first time, but segmentation faulted the second time. I disconnect the Ethernet cable from the PLC and try to write to it afterwards, then reconnect it to recover from the fault. The code crashes after I reconnect the PLC the second time. I know this is an extreme case, but I want to make you aware of the issue.
seg_fault_log.txt

Library Version: 1.5.3
OS: Debian 8, 32-bit
PLC: MicroLogix 1400 (quantity 3)

Thank you.

Implicit messages

Kyle
It would be very helpful in some applications to allow work with implicit messages in the library, specially if need to collect the tags value from group at exact time when the trigger was made. Also maybe will be useful to provide functions for mapping or getting values from package received based on offset inside the buffer.

Thank you

Reading array of strings in Micrologix returns only first string

thread(0001) 2386-04-30 23:21:00.732 INFO ab_tag_create:171 Starting.
thread(0001) 2386-04-30 23:21:00.732 INFO session_create_unsafe:524 Starting
thread(0001) 2386-04-30 23:21:00.733 INFO session_create_unsafe:578 Done
thread(0001) 2386-04-30 23:21:00.733 INFO session_init:594 Starting.
thread(0001) 2386-04-30 23:21:00.733 INFO session_connect:624 Starting.
thread(0001) 2386-04-30 23:21:00.737 INFO session_connect:644 Done.
thread(0001) 2386-04-30 23:21:00.737 INFO session_register:750 Starting.
thread(0001) 2386-04-30 23:21:00.737 INFO session_register:785 sending data:
thread(0001) 2386-04-30 23:21:00.737 INFO session_register:786 00000 65 00 04 00 00 00 00 00 00 00
thread(0001) 2386-04-30 23:21:00.737 INFO session_register:786 00010 00 00 00 00 00 00 00 00 00 00
thread(0001) 2386-04-30 23:21:00.737 INFO session_register:786 00020 00 00 00 00 01 00 00 00 00
thread(0001) 2386-04-30 23:21:00.749 INFO session_register:861 received response:
thread(0001) 2386-04-30 23:21:00.749 INFO session_register:862 00000 65 00 04 00 26 b4 85 db 00 00
thread(0001) 2386-04-30 23:21:00.749 INFO session_register:862 00010 00 00 00 00 00 00 00 00 00 00
thread(0001) 2386-04-30 23:21:00.749 INFO session_register:862 00020 00 00 00 00 01 00 00 00 00
thread(0001) 2386-04-30 23:21:00.749 INFO session_register:885 Done.
thread(0001) 2386-04-30 23:21:00.749 INFO session_init:609 Done.
thread(0001) 2386-04-30 23:21:00.749 INFO ab_tag_create:354 Done.
thread(0001) 2386-04-30 23:21:00.749 INFO plc_tag_create:266 Returning mapped tag 00004001
thread(0001) 2386-04-30 23:21:00.750 INFO plc_tag_read:517 Starting.
thread(0001) 2386-04-30 23:21:00.750 INFO eip_pccc_tag_read_start:122 Starting
thread(0001) 2386-04-30 23:21:00.750 INFO session_add_request_unsafe:968 Starting.
thread(0001) 2386-04-30 23:21:00.750 INFO request_acquire:71 Acquiring request.
thread(0001) 2386-04-30 23:21:00.750 INFO refcount_acquire:61 Ref count is now 2
thread(0001) 2386-04-30 23:21:00.750 INFO session_add_request_unsafe:997 Total requests in the queue: 0
thread(0001) 2386-04-30 23:21:00.750 INFO session_add_request_unsafe:999 Done.
thread(0001) 2386-04-30 23:21:00.750 INFO eip_pccc_tag_read_start:236 Done.
thread(0002) 2386-04-30 23:21:00.751 INFO session_check_outgoing_data_unsafe:1078 Readying unconnected packet to send.
thread(0002) 2386-04-30 23:21:00.751 INFO request_acquire:71 Acquiring request.
thread(0002) 2386-04-30 23:21:00.751 INFO refcount_acquire:61 Ref count is now 3
thread(0002) 2386-04-30 23:21:00.751 INFO session_check_outgoing_data_unsafe:1086 sending packet, so 1 unconnected requests in flight.
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:65 Sending unconnected packet with session sequence ID 6eac
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:93 Sending packet of size 67
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:94 00000 6f 00 2b 00 26 b4 85 db 00 00
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:94 00010 00 00 ac 6e 00 00 00 00 00 00
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:94 00020 00 00 00 00 00 00 00 00 01 00
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:94 00030 02 00 00 00 00 00 b2 00 1b 00
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:94 00040 4b 02 20 67 24 01 07 3d f3 45
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:94 00050 43 50 21 0f 00 aa 6e 68 00 00
thread(0002) 2386-04-30 23:21:00.752 INFO send_eip_request_unsafe:94 00060 02 00 06 30 00 02 00 00
thread(0002) 2386-04-30 23:21:00.755 INFO request_release:83 Releasing request.
thread(0002) 2386-04-30 23:21:00.755 INFO refcount_release:91 Refcount is now 2
thread(0002) 2386-04-30 23:21:00.757 INFO session_check_outgoing_data_unsafe:1057 1 unconnected requests in flight.
thread(0002) 2386-04-30 23:21:00.759 INFO session_check_outgoing_data_unsafe:1057 1 unconnected requests in flight.
thread(0002) 2386-04-30 23:21:00.760 INFO session_check_outgoing_data_unsafe:1057 1 unconnected requests in flight.
thread(0002) 2386-04-30 23:21:00.763 INFO session_check_outgoing_data_unsafe:1057 1 unconnected requests in flight.
thread(0002) 2386-04-30 23:21:00.764 INFO session_check_outgoing_data_unsafe:1057 1 unconnected requests in flight.
thread(0002) 2386-04-30 23:21:00.766 INFO session_check_outgoing_data_unsafe:1057 1 unconnected requests in flight.
thread(0002) 2386-04-30 23:21:00.767 INFO recv_eip_response_unsafe:202 Received unconnected packet with session sequence ID 6eac
thread(0002) 2386-04-30 23:21:00.767 INFO receive_response:710 Packet sent initially 12ms ago and was sent 1 times
thread(0002) 2386-04-30 23:21:00.768 INFO update_resend_samples:690 Packet round trip time 13, running average round trip time is 129
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:715 got full packet of size 65
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:716 00000 6f 00 29 00 26 b4 85 db 00 00
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:716 00010 00 00 ac 6e 00 00 00 00 00 00
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:716 00020 00 00 00 00 00 00 00 00 00 04
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:716 00030 02 00 00 00 00 00 b2 00 19 00
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:716 00040 cb 00 00 00 07 3d f3 45 43 50
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:716 00050 21 4f 00 aa 6e 39 08 53 74 72
thread(0002) 2386-04-30 23:21:00.768 INFO receive_response:716 00060 69 6e 67 20 31 00
thread(0001) 2386-04-30 23:21:00.777 INFO request_release:83 Releasing request.
thread(0001) 2386-04-30 23:21:00.777 INFO refcount_release:91 Refcount is now 1
thread(0001) 2386-04-30 23:21:00.777 INFO request_release:83 Releasing request.
thread(0001) 2386-04-30 23:21:00.778 INFO refcount_release:91 Refcount is now 0
thread(0001) 2386-04-30 23:21:00.778 INFO refcount_release:94 Calling destructor function.
thread(0001) 2386-04-30 23:21:00.778 INFO check_read_status:350 Done.
thread(0001) 2386-04-30 23:21:00.779 INFO plc_tag_read:588 elapsed time 29ms
thread(0001) 2386-04-30 23:21:00.779 INFO plc_tag_read:591 Done
string 0 (82 chars) = 'String 1'
string 1 (82 chars) = ''

There should be two strings. Only one is returned in the response packet. It looks like the wrong read command might be issued.

Help request with DH+ path

Hi Kyle,

We are using the library, and it works well for AB PLC direct connection, with standard path of '1,0'

Could you provide some guidance on what path should be if we are now trying to interface with a AB
PLC5 through a DH+ gateway? We know the IP address of the gateway (I'm just not posting all of it).

We know the PLC is supposed to be on channel A, node 27, through the DH+. Not sure what
the source address should be. In fact, we aren't even sure if we need the colons instead of
commas, and something like '1,,1.A.27' which we tried first.

So recap, have tried:

protocol=ab_eip&gateway=xxx.yy.23.39&path=A:1:27&cpu=LGX&elem_size=2&elem_count=1&name=N15:40

protocol=ab_eip&gateway=xxx.yy.23.39&path=1,,1.A.27&cpu=LGX&elem_size=2&elem_count=1&name=N15:40

We get good status when creating the session, but bad status when attempting the first ever plc_tag_read call.

Thanks for any help you can provide!
-Matt

reading time in CompactLogix L32E

I recently noticed that to read a single tag takes around 11 ms in CompactLogix L32E. Is there any way we can speed up this time in the reading process? The challenge is for example if need to read 200 tags, that could takes around 2 200 ms as total scan cycle. Please do you have any recommendation. Thanks

Building the Library

Is there a guide perhaps on getting this set up and compiled? Like, Building a simple user program?

#include #include //#include <../lib/libplctag.h> #include "libplctag.h" //#include "utils.h" ... cc -o test test.c ... cc -o readTag readTag.c /tmp/ccKd8UdD.o: In function `main': readTag.c:(.text+0x1d): undefined reference to `plc_tag_create' readTag.c:(.text+0x32): undefined reference to `plc_tag_read' readTag.c:(.text+0x7f): undefined reference to `plc_tag_get_int32' readTag.c:(.text+0xb3): undefined reference to `plc_tag_destroy' collect2: error: ld returned 1 exit status

libplctag on Windows

Hello,
Does the current version of libplctag work on Windows ? I tried to compile it but VS didn't find the correct references to some .h files.

I'm not very familiar wit Linux so if the library is compiled on Windows with VS , after that can I use the compiled dlls on windows without any additional Linux specific frameworks etc ?

Thanks

AB PLCs drop packets on flooded connection-based sessions

When using connected messages, the ControlLogix I have will drop packets when high rates of packets are seen. The stress test program can overrun the PLC even with two threads when using connected messaging.

When using unconnected messaging, no packet loss is seen.

Theory: perhaps there is some sort of limited internal buffer for each connection? The buffer is not large if that is the case.

Experiments have shown:

  1. Packets can be lost even with just two threads. However, no packets are lost with just one (which ends up being effectively synchronous.
  2. Packet loss seems to occur at a RPI of 3-4ms. Higher than that will sometimes cause packet loss, but much more rarely.
  3. Some packet order inversion was seen, but it seems to be very rare.
  4. Simple back off on the whole session works, but impacts unconnected messages.

plc_tag_destroy never returns

Hello. My application uses multi-threading with C++ as the wrapper in Linux. Occasionally, not all of the time, when a read / write to a tag fails (because I unplugged the Ethernet cable) the destroy function never returns. I go into error recovery mode after losing connection. When I can ping the PLC again, I unlock then destroy each tag before recreating them. I can unlock the tag fine, but destroying it sometimes never returns.

plc_tag_unlock(tag);
int status = plc_tag_destroy(tag);

Is it correct to say that you should destroy all tags from a PLC if you lose connection with it, regain it, and want to recreate your tags?

Destroying the tag inside plc_tag_destroy(plc_tag tag)

Above the plc_tag_destroy function (in the FIXME comment) there is a mention that this function leaves a dangling pointer. As a suggestion, could this below be used to destroy the tag object and avoid this particular problem?

I am having issues with instabilities when using libplctag via the Python wrapper. The issues encountered are Python thread(s) becoming unresponsive and various kernel related problems as shown below.

Jun 13 04:48:05 SERVERNAME kernel:  [  233.667827] traps: python3[1579] general protection ip:7fb8c84f7542 sp:7fb8c84e4b50 error:0 in libplctag.so[7fb8c84e7000+15000]
Jun 15 15:39:44 SERVERNAME kernel:  [  794.702409] traps: python3[1668] general protection ip:7f8b9d25c227 sp:7f8b99b757c0 error:0 in libc-2.19.so[7f8b9d222000+1bb000]
Jun 17 15:53:24 SERVERNAME kernel:  [ 5442.060720] traps: python3[1635] general protection ip:7f8973b39c25 sp:7f8973b32c20 error:0 in libplctag.so[7f8973b35000+15000]
Jun 17 16:27:47 SERVERNAME kernel:  [ 1830.463503] traps: python3[1643] general protection ip:7ff0f7e93227 sp:7ff0f2f68cc0 error:0 in libc-2.19.so[7ff0f7e59000+1bb000]
Jun 17 16:40:21 SERVERNAME kernel:  [  238.774811] traps: python3[1641] general protection ip:7f947537cd6e sp:7f9471c109d0 error:0 in libc-2.19.so[7f94752fd000+1bb000]
Jun 17 20:51:12 SERVERNAME kernel:  [  456.961823] traps: python3[1685] general protection ip:7fab0910a0b7 sp:7fab090ffb90 error:0 in libplctag.so[7fab09102000+15000]
Jun 17 20:55:15 SERVERNAME kernel:  [   32.542667] traps: python3[1639] general protection ip:7f2c06b3d227 sp:7f2c03416540 error:0 in libc-2.19.so[7f2c06b03000+1bb000]
Jun 19 11:05:48 SERVERNAME kernel:  [ 2470.992905] traps: python3[1731] general protection ip:51858f sp:7f50094e80a0 error:0 in python3.4[400000+2fe000]
Jun 19 11:15:08 SERVERNAME kernel:  [  319.783533] traps: python3[1637] general protection ip:4d0a2b sp:7fe828e65040 error:0 in python3.4[400000+2fe000]

The frequency of instability issues increase when I read tags that have bigger sizes (50% of my tags are arrays, with size around 200-300 UINT16 elements, the rest are single UINT16 or UINT32 tags).

Around the 13th June I was testing with reading unique tags (ex: N317[12], N317[2], ...). Later I added reading the entire arrays at once, then using libplctag.plc_tag_get_uint16(tag, index_offset) to extract the actual values and the frequency of kernel issues increased.

Here the updated code section, which may (?) fix the issue. My knowledge of C is rather limited and I'd welcome any feedback on whether this may help or even solve the dangling pointer issue referred to in the FIXME.

LIB_EXPORT int plc_tag_destroy(plc_tag tag)
{
        /* NEW : function to destroy the tag struct */
        void destroy_tag_struct(struct plc_tag_t **param)
        {
        *param = NULL;
        }

        if(!tag)
                return PLCTAG_STATUS_OK;

        int debug = tag->debug;
        mutex_p temp_mut;
        int rc = PLCTAG_STATUS_OK;

        pdebug(debug, "Starting.");

        /* clear the mutex */
        if(tag->mut) {
                temp_mut = tag->mut;
                tag->mut = NULL;

                critical_block(temp_mut) {
                        if(!tag->vtable || !tag->vtable->destroy) {
                                pdebug(debug, "tag destructor not defined!");
                                tag->status = PLCTAG_ERR_NOT_IMPLEMENTED;
                                return PLCTAG_ERR_NOT_IMPLEMENTED;
                        }

                        /*
                         * It is the responsibility of the destroy
                         * function to free all memory associated with
                         * the tag.
                         */
                        rc = tag->vtable->destroy(tag);
                }

                mutex_destroy(&temp_mut);

                /* NEW finally destroy the tag struct */
                destroy_tag_struct(&tag);

        }

        return rc;
}

Thank you.

Add accessory/helper function to get tag list from Logix PLCs

Kyle, been checking out pylogix lately (https://github.com/dmroeder/pylogix), a small Python library for dealing with Logix PLCs. That library includes a function aptly named GetTagList(). In future this would be very helpful to add to the library, I think. When I have some more free time I might be able to port the Python code from that project to C and submit a PR. It's Apache licensed so we would just give attribution and link or something.

Tag Path structure for different PLCs

Kyle
Please can you help me to understand what is correct tag path structure to use in the library based on cpu, I am facing some little challenges with it in the wrapper because my unknown about it, and unfortunately don't have experience with different types of AB PLC.

So far we have following CPUs supported by the library:

1-PLC5
2-SLC
3-Micrologix
4-Compactlogix
5-Controllogix
6-Flexlogix

Also I was thinking it could be useful, as suggestion, to have two functions like GetCPUPath that receive as input parameters (protocol, ip, port type, slot, cpu) and returns the part of the tag path related with the cpu, and GetTagPath that receive (protocol, ip, port type, slot, cpu, tagName, elemCount, elemSize) returns the completed tagPath.

Thank you

Compilation Error

Hello Guys! Thanks for this great library.
I have downloaded it but when I try to compile the simple.c program, it tells me that there are undefined references to some tags:
I have already checked the routes to the #include <libplctag.h> and <utils.h>, and i placed them in the /usr/include folder but I can't find the cause of the errors, I have placed them on different folder using "" in case of a different folder or < > in case that i place them in /usr/include.
This is the message the compiler sends:

It's in spanish:
simplep.c:(.text+0x16): referencia a plc_tag_create' sin definir simplep.c:(.text+0x53): referencia a sleep_ms' sin definir
simplep.c:(.text+0x5f): referencia a plc_tag_status' sin definir simplep.c:(.text+0x70): referencia a plc_tag_status' sin definir
simplep.c:(.text+0x80): referencia a plc_tag_status' sin definir simplep.c:(.text+0x87): referencia a plc_tag_decode_error' sin definir
simplep.c:(.text+0xbe): referencia a plc_tag_read' sin definir simplep.c:(.text+0xd1): referencia a plc_tag_decode_error' sin definir
simplep.c:(.text+0x11b): referencia a plc_tag_get_int32' sin definir simplep.c:(.text+0x164): referencia a plc_tag_get_int32' sin definir
simplep.c:(.text+0x1a5): referencia a plc_tag_set_int32' sin definir simplep.c:(.text+0x1c0): referencia a plc_tag_write' sin definir
simplep.c:(.text+0x1d3): referencia a plc_tag_decode_error' sin definir simplep.c:(.text+0x20d): referencia a plc_tag_read' sin definir
simplep.c:(.text+0x220): referencia a plc_tag_decode_error' sin definir simplep.c:(.text+0x267): referencia a plc_tag_get_int32' sin definir
simplep.c:(.text+0x29b): referencia a `plc_tag_destroy' sin definir

Can you please help me to find and correct the problem?
Thanks in advance!

apparent packet corruption on very long names

When using a name like abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz.abcdefghijklmnopqrstuvwxyz the resulting read packet is malformed:

thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00000 6f 00 7a 00 01 00 d9 00 00 00
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00010 00 00 78 3d 00 00 00 00 00 00
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00020 00 00 00 00 00 00 00 00 01 00
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00030 02 00 00 00 00 00 b2 00 6a 00
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00040 52 02 20 06 24 01 0a 05 5c 00
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00050 52 2a 91 1a 61 62 63 64 65 66
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00060 67 68 69 6a 6b 6c 6d 6e 6f 70
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00070 71 72 73 74 75 76 77 78 79 7a
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00080 91 1a 61 62 63 64 65 66 67 68
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00090 69 6a 6b 6c 6d 6e 6f 70 71 72
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00100 73 74 75 76 77 78 79 7a 91 1a
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00110 61 62 63 64 65 55 00 00 00 6a
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00120 6b 6c 6d 6e 6f 70 71 72 73 74
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00130 75 76 77 78 79 7a 01 00 00 00
thread(0002) 2386-10-29 12:26:13.469 INFO send_eip_request_unsafe:94 00140 00 00 01 00 6a 6b 00

There are two problems here. The first is that the tag name appears to be encoded incorrectly. The second is that the path should be 1,0 but appears to be 6a,6b.

tag_rw error connecting to SoftLogix

// sorry, but I can find any docs on what the tag read error means. this is trying to read a single
// float from Softlogix controller in slot 2.
//
tag_rw -t real32 -p 'protocol=ab_eip&gateway=192.168.0.111&path=1,2&cpu=LGX&elem_size=4&elem_count=1&name=AWARE_OPTDEMAND'
Arg[0]=./build/tag_rw
Arg[1]=-t
Arg[2]=real32
Arg[3]=-p
Arg[4]=protocol=ab_eip&gateway=192.168.0.111&path=1,2&cpu=LGX&elem_size=4&elem_count=1&name=AWARE_OPTDEMAND
INFO: Got tag 0x4001
INFO: tag status 0
INFO: reading tag 0x4001
ERROR: tag read error, tag status: PLCTAG_ERR_REMOTE_ERR

unable to pull the data after half an hour continuous read

Kyle,

I have a list of 10 BOOL tags namely tag1, tag2...tag10[controller tags] in controllogix . I 'm continuously pulling the state of these tags and logging in to Database. However, after about around half an hour, i fail to read the state of these tags; the respective tag values become null.
Give me your insight in to the issue.

Best
Aishwarya

Multi-threaded Capabilities

Thanks in advance for taking the time to read this.

I have multiple AB PLCs that I create, connect, read & write on different threads so that they run in parallel. However when a PLC is offline I try to reconnect/create the tags again until it succeeds. My question is, should this affect the other PLCs that are connected and are reading/writing?

At the moment I am having issues that it seems to be blocking the other PLC threads.

Thanks again.

Android Support

We badly need android support for this library. It can revolutionize the industry!

SCL5/04 DH+ over serial

Is it possible to read DH+ over serial?

The examples seem to use a gateway are there other options to do this natively?

undefined reference to 'plc-tags_create' ... in Code::Blocks (C++)

Please it is a very nice work. I love it. I only has one challenge. I have tried to compile and link with Code::Blocks using C++, where I have all classes of our software created, but I got an error undefined reference to 'plc-tags_create'. I am pointing to the correct library after make install. I have tried unsuccessfully many things.

Please can you help me,

Thanks

Windows build comments

First, thanks for your work on this library!

I have a few answers to questions posed in your BUILD.md instructions.

  • It seems all versions of Visual Studio 2015 and newer don't include C++ compilers unless you choose the option, it's not only an Express edition or download limitation. I had to modify the installation, since I originally missed that.
  • cmake-gui definitely works and I prefer it to the command line on Windows as well :) You only need to do the following after installing CMake:
    • Open cmake-gui
    • For "Where is the source code" choose the project root directory, i.e. where CMakeLists.txt is located
    • For "Where to build the binaries" choose a build folder inside of that directory, or anywhere else
    • Press Configure
    • Choose your desired version of Visual Studio from the list, and press Finish
    • You can set CMAKE_INSTALL_PREFIX to a different folder, recommended because the default isn't writable (only needed if you build the Install project in VS, which is nice for organization)
    • Press Generate
    • There is now an Open Project button, so you can open the project automatically in the correct version of Visual Studio
  • It was not necessary to run the developer command prompt, in my testing. This is with a default installation of the latest CMake, and VS 2015 Community Edition on Windows 10. I think this simplifies the instructions a bit.

plc_tag_get_uint16 big endian

I could be wrong but it looks like the code in plc_tag_get_uint16 for big endian is wrong. Currently its:
res = ((uint16_t)(t->data[offset+2]) << 8) + ((uint16_t)(t->data[offset+3]));

I believe it should be this:
res = (int16_t)(((uint16_t)(data[offset]) << 8) + ((uint16_t)(data[offset + 1])));

String support

Kyle,
It would be great to have capability of reading/writing string tags in the library to be more completed. In some applications the strings manners and it is so important as numeric data. Maybe when Get String we can have as parameters: offset, and max. length, and type of string (null terminating, leading length), to allow read/write string at least for most recently AB PLCs.

Thank you a lot for very nice work and sharing with community

Many aborting request debug messages

I'm constantly reading a few specific tags about every 50ms. My console gets bloated with messages like below.

request_handler_func:723 aborting request 000000000262CBB0

Am I not setting this up correctly? These debug messages appear to have something to do with multiple sessions running that aren't being handled.

Once I call plc_tag_create I begin reading plc_tag_read that tag and I never destroy it until the program is finished running. From the documentation I would think this would be okay.

API is not threadsafe

The current API is no threadsafe, by design. However, this is a problem when the user does not control the concurrency of the base application framework.

Internally, the library should provide for threadsafe calling of the public API.

Reading multiple tags

It is possible to request several tags in one request through eip (although the size of a single request is limited to ~500 bytes, at least in in AllenBradley PLCs). When polling many tags this is a big performance win.

This involves figuring out how to split a given list of tags into several requests of limited size.

Adding plctag.dll into c# projects

Hi There,

I have follow all the steps and created the plctag.dll and it is working as well when calling using provided exe programs like tag_rw.exe etc.

I wanted to include functionality in my c# project and make direct calls to plctag.dll instead of using provided exe programs.

Is there a way, I can add this in my c# programs and call it from there.

Please advise.

Rishi

ER: make PLC selection easier

It is currently confusing for users to have to enter the protocol and CPU type.

There seem to be a few options:

  1. simplify the PLC selection to one value and create a look up table on the Wiki.
  2. use make/family/model in the tag string. This requires an internal lookup table in the library code though and search functionality.
  3. use the existing protocol/cpu, but put a better look up table on the Wiki.
  4. use the manufacturer part numbers directly. This could be done either in the library or on the Wiki.
  5. other?

I am leaning toward the first one.

Reading Strings - PLCTAG_ERR_NO_DATA

I'm trying to read string data at the moment and following along to the example you provide here.

When calling plc_tag_read(tag, 5000) I am continually getting the -23 error (PLCTAG_ERR_NO_DATA).

Are you able to shed some light on what conditions might cause this response? No data doesn't make much sense to me considering that the tag path I'm using exists and there should be a value.

Thanks!

MicroLogix/SLC500 support?

Hi, this is a question rather than an issue, but does this library support SLC500 PLCs like the MicroLogix series? I actually just got done forking and fixing TuxEIP for our own purposes and then found your library which might work better for us. Thanks.

P.S. I might be able to test on a CompactLogix (as well as the MicroLogix), if you are still needing testing. I might be able to help out with coding too.

C++ Wrapper discussion

This issue is being opened to gather ideas on a C++ wrapper. Feel free to add to this!

Q: Proper Sequence of Destroying a Tag

Hello Kyle,

What is the proper order to destroy a tag in a multithreaded environment? I just noticed your abort tag function as well.

  1. plc_tag_unlock
  2. plc_tag_abort
  3. plc_tag_destroy
  • If another thread is using a tag that I am trying to destroy, assuming that it's locked, can/should I abort it before unlocking it?
  • Should I use my own scoped mutex to make sure all threads are not running before destroying all tags?
  • Do I need to lock and unlock tags if I use my own mutex?
  • Will a tag ever get stuck and never return? (would aborting it fix this issue?)

Library Version: 1.5.7
OS: Debian 8, 32-bit

Thank you.

Segmentation fault from unlocking / destroying tags from a thread

Hello. I am using the latest release (1.5.2) and am experiencing a segmentation fault from unlocking and destroying tags. I compiled with debugging (-g) and ran it with gdb and got the following back trace output:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xa7368b40 (LWP 6194)]
0xa784b49c in send_eip_request_unsafe () from /usr/local/lib/libplctag.so
(gdb) bt
#0  0xa784b49c in send_eip_request_unsafe () from /usr/local/lib/libplctag.so
#1  0xa7849113 in session_send_current_request () from /usr/local/lib/libplctag.so
#2  0xa78493bf in session_check_outgoing_data_unsafe () from /usr/local/lib/libplctag.so
#3  0xa784945f in process_session_tasks_unsafe () from /usr/local/lib/libplctag.so
#4  0xa784952a in request_handler_func () from /usr/local/lib/libplctag.so
#5  0xa7d06d97 in start_thread (arg=0xa7368b40) at pthread_create.c:309
#6  0xa764f64e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:129

I will attach the relevant files in the zip file. The destroy function seg faults instead of blocking at least! That is an improvement in my opinion.
libplctag seg fault on close.zip

Rewrite core to use tasklet idea

Rewrite the core of the software to use something like Java's Runnable/tasklets. Pull most of the work over into the back-end rather than the current 50/50 split between the front end and the back.

The front end would have a single, simpler, plctag struct that would contain:

  • a reference to the attribs (currently discarded after tag creation).
  • a reference to the location of the data array.
  • a set of flags for read requests, write requests, abort requests and delete requests. Do these need to be single ints (or whatever size atomic wants) or should they be protected by an atomic flag and be bit flags. Leaning toward single ints as those could be handled with a CAS.
  • size information about the data array (do we care about element size or just total bytes?).
  • status information (one int as now or a set of flags?).

When a client called the library API functions on a tag, they will set one of the flags to ask the back-end to perform the requested operation. This will be the only interaction between the front and back end.

Status will be read only on the front end. The back end will only set it.

Delete requests will cause the back-end to asynchronously remove the resources used by the tag. This includes the memory the for the public side of the tag as well.

The back end will have one entry point called from the front end: tag creation. The sequence of events will look something like this:

  • client code calls plc_tag_create.
  • plc_tag_create allocates memory for the tag and sets up what it can.
  • plc_tag_create determines which back-end protocol creation function to call and calls it with the completed tag.
  • xyz_tag_create creates any auxiliary structures needed and a tasklet for the tag. The tasklet will reference (or will be) the aux data and reference the plc_tag struct. This will require mutex or spin-lock protection. Mutexes are cheap on Linux but expensive on most other platforms. Maybe use spin-locks as fine-grained protection.
  • the tasklet will go through its initial state machine to determine whether there is an existing socket it can reuse and any other intermediate objects it needs to create.
  • each socket will have a tasklet as well. This will need to be protocol specific.
  • intermediate tasklets (such as for connected AB CIP tags) may exist as well.

Next feature discussion

For the next changes to the library there are several features I am considering.

  • Modbus TCP - just read/write registers for now. But, it may be simple to add the other things.
  • read-only produced tags. This is a big bandwidth saver if all you are doing is reading some data over and over from the PLC. The trick here is that there does not seem to be any complete public documentation about how these work.
  • multi-tags. AB's protocol has the ability to read more than one tag at the same time per request. This would lower the number of network back-and-forth trips a lot.

There are a few other things that I would like to fix too:

  • the initial TCP connect() can delay several seconds and block everything in some circumstances (right after the Ethernet cable is pulled on the PLC). That needs to be changed to be the same as the timeout.
  • simplify some of the internals of the library. It would be much nicer to have one thread per tag, but I think the resource overhead would be far too big. I can go half-way on that and keep one thread or perhaps one thread per TCP channel.

Please comment.

Addressing tags with : in the tag name

Hi, I'm trying to read some tags in a Control Logix PLC, which are not global in scope. I was previously able to read them using TuxEIP, addressing the tags with names such as:

Program:Supervisor.pidLocal_PV
Program:Zone1.flow_CV
etc.

How can I address these tags with libplctag? When I try to read them, I get error -33 returned. Reading other controller-wide tags works fine.

Thanks!

python throw error cannot find plctag module

I have download the git and go to directory and use make command.
The moment I want to run, it throw the error cannot find the module for plctag
Can I know the proper way to install the module?

Thanks

any issue with compiling for x64 in visual studio?

If I wanted to use this API as part of a C++ program compiled as a 64-bit program (and running on windows 7 pro x64) will we encounter any functionality issues due to compiling and running as a x64 executable?

Will we have issues with memory alignment or any hard-coded bit-fiddling within this library? Thanks for any clarifications you can give.

We are stuck with having to compile for 64 bit target due to another 3rd party library.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.