Giter VIP home page Giter VIP logo

gologix's Introduction

gologix

gologix is a communication driver written in native go that lets you easily read/write values from tags in Rockwell Automation ControlLogix, and CompactLogix PLC's over Ethernet I/P using GO. PLCs that use CIP over Ethernet/IP are supported (controllogix, compactlogix, micro820). Models like PLC5, SLC, and MicroLogix that use PCCC instead of CIP are not supported.

It is modeled after pylogix with changes to make it usable in go.

Your First Client Program:

There are a few examples in the examples folder, here is an abriged version of the SimpleRead example. See the actual example for a more thorough description of what is going on.

package main

import (
	"fmt"
	"gologix"
)

func main() {
	client := gologix.NewClient("192.168.2.241")
	err := client.Connect()
	if err != nil {
		log.Printf("Error opening client. %v", err)
		return
	}
	defer client.Disconnect()

	var tag1 int16
	err = client.Read("testint", &tag1)
	if err != nil {
		log.Printf("error reading testint. %v", err)
        return
	}
	log.Printf("tag1 has value %d", tag1)
}

Your First Server Program:

There are a few examples in the examples folder, here is an abriged version of the Server_Class3 example. See the actual example(s) for a more thorough description of what is going on. Basically it listens to incoming MSG instructions doing CIP Data Table Writes and CIP Data Table Reads and maps the data to/from an internal golang map. You can then access the data through that map as long as you get the lock on it.

package main

import (
	"fmt"
	"gologix"
	"os"

)

func main() {
	r := gologix.PathRouter{}

	p1 := gologix.MapTagProvider{}
	path1, err := gologix.ParsePath("1,0")
	if err != nil {
		log.Printf("problem parsing path. %v", err)
		os.Exit(1)
	}
	r.AddHandler(path1.Bytes(), &p1)


	s := gologix.NewServer(&r)
	go s.Serve()

	t := time.NewTicker(time.Second * 5)
	for {
		<-t.C
		p1.Mutex.Lock()
		log.Printf("Data 1: %v", p1.Data)
		p1.Mutex.Unlock()


	}
}

Other Features

Can behave as a class 1 or class 3 server allowing push messages from a PLC (class 3 via MSG instruction) or implicit messaging (class 1). See the *server examples.

You can read/write multiple tags at once by defining a struct with each field tagged with gologix:"tagname". see MultiRead in the examples directory.

To read multiple items from an array, pass a slice to the Read method.

To read more than one arbitrary tags at once, use the ReadList method - the first parameter is a slice of tag names and the second parameter is a slice of each tags type.

You can read UDTs in if you define an equivalent struct to blit the data into. Arrays of UDTs also works. (see limitation below about UDTs with packed bools)

There is also a Server type that lets you recive msg instructions from the controller. See "Server" in the examples folder. It currently handles reads and writes of atomic data types (SINT, INT, DINT, REAL). You could use this to create a "push" mechanism instead of having ot poll the controller for data changes.

Limitations

You cannot write multiple items from an array at once yet, but you can do them piecewise if needed.

You can write to BOOL tags but NOT to bits of integers yet (ex: "MyBool" is OK, but "MyDint.3" is NOT). You can read from either just fine. I think there is a "write with mask" that I'll need to implement to do this.

If the UDT you're reading has bools packed in it, you'll need to use the ReadPacked() function instead of client.Read(). The plan is to eventually migrate this functionality to client.Read automatically.

No UDTs or arrays in the server yet. This will eventually be implemented and that will greatly improve functionality.

License

This project is licensed under the MIT license.

Acknowledgements

  • pylogix
  • go-ethernet-ip

gologix's People

Contributors

danomagnum avatar jacobogle avatar

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

Watchers

 avatar  avatar

gologix's Issues

Issue on Header when using ReadMulti()

HI,
I'm new to the Ethernet/IP protocol and thanks to this library I'm trying to develop a driver that reads from a PLC. I don't have the PLC yet, so I used the Class 3 Server example and set up some very simple tags of type Int32 and Int16.

func main() {

	r := gologix.PathRouter{}

	p1 := gologix.MapTagProvider{}
	path1, err := gologix.ParsePath("1,0")
	if err != nil {
		log.Printf("problem parsing path. %v", err)
		os.Exit(1)
	}
	r.Handle(path1.Bytes(), &p1)

	p1.TagWrite("TestDint", int32(12))
	p1.TagWrite("TestInt", int16(3))

	s := gologix.NewServer(&r)

	go s.Serve()

	t := time.NewTicker(time.Second * 5)
	for {
		<-t.C
		p1.Mutex.Lock()
		log.Printf("Data 1: %v", p1.Data)
		p1.Mutex.Unlock()

	}

}

With another script (Client) I tried to read/write a single tag successfully following the SimpleRead and Write in the examples folder:
(I omit the client creation, connection and disconnection part)

var tag1 int32
err = client.Read("TestDint", &tag1)
	if err != nil {
		log.Printf("error reading TestDint. %v", err)
	}
	// do whatever you want with the value
	log.Printf("tag1 has value %d", tag1)

so I can read well. If I want to write, I can do it successfully too because I can see its change on the server side:

var tag1 int32
err = client.Write("TestDint", tag1)
	if err != nil {
		log.Printf("Error writing TestDint. Error: %v", err)
				
	}

However, if I want to use the ReadMulti() function (following the MultiRead in the examples folder) I get an error both on the client and server side:

var mr multiread

err = client.ReadMulti(&mr)
	if err != nil {
		log.Printf"error reading multiread. %v", err)
			} else {
				
				log.Printf"multiread struct has values %+v", mr)
			}
       }

Error on the Client side:

problem in read list: problem reading header from socket: EOF: < nil >

Looking in the code, I saw that this is generated by the recv_data() function in the sendreceive.go file.

On the Server side, however, I read two LOGs:

  • Trying to read from MapTagProvider
  • Error on connection 192.168.1.153:44188. problem with sendunitdata problem handling multi service. problem getting data from provider. tag not in map.

I understood from the LOGs that in fact the name of the tag I want to read is empty and there is a blank space.
Do you have any idea why this happens?

Thanks for your great work.

Modification Request?

So not exactly an issue, but I since there isn't a Discussions area I figured this would be the next best option.

I was working on building a similar project, but then I ran across yours and it is much further developed than mine. I first started taking some inspiration from your project but then realized that I was narrowing in on similar methods for solving the problems as what you had taken so I figured I and hopefully others would be better served from just helping this project out.
Would you be interested/open to me issuing some fairly heavy updates to your code that don't change a huge amount but do make a few things more idiomatic to go and knock out some of the TODOs that were listed throughout the code?

If not, that is cool. I just didn't want to fork and issue a bunch of PRs that you are not interested in.

[Bug] Cannot read string using ReadList() / ReadMulti()

Hi,

I am trying to use your driver which is working well, and thank you for your work.

But, I am facing an issue I do not really understand. I cannot read a string using the ReadList() method, or the ReadMulti(). I always get a an error: problem reading tag {TAGNAME}: Struct!.

And if I try to read this same tag using the simple Read() method, it works out-of-the-box.

I tried with CIPTypeString and CIPTypeStruct and get the same error message.

Do you have any idea?

Thanks,

Charly

[Question] Timeout

How can I set a connect or read timeout?
Because for now, it can stop after 1 minute or more.

I tried to set SocketTimeout to 1 second, but it panics immediately:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x80 pc=0x74d7b2]

goroutine 56 [running]:
github.com/danomagnum/gologix.(*Client).Connect(0xc0002e7b90?)
        /home/charly/go/pkg/mod/github.com/danomagnum/[email protected]!beta/connect.go:14 +0x12

I am probably not doing the right thing...

Connection reset by peer, using ETHERNET-MODULE for Class 1 Ethernet/IP connection

I'm trying to set up communication between a PLC and a linux server on the same subnet. I'm using the example examples/Server_Class1_V2. I edited the argument to ParsePath, but I honestly am not quite sure what these parameters mean. The PLC is in slot 0 and the ethernet module is in slot 1 (I believe, I'm not too familiar with PLC terminology). The other thing that's different from the example is that on the PLC I'm unable to create ETHERNET-BRIDGE modules, they're grayed out when I try to create a new module, so I'm attempting to use ETHERNET-MODULE. When I run the server, I am met with a connection reset by peer error message. Am I doing something wrong here? Any help would be appreciated!

server.go:

package main

import (
	"log"
	"os"
	"time"

	"github.com/danomagnum/gologix"
)

// these types will be the input and output data section for the io connection.
// the input/output nomenclature is from the PLC's point of view - Input goes to the PLC and output
// comes to us.
//
// the size (in bytes) of these structures has to match the size you set up in the IO tree for the IO connection.
// presumably you can also use other formats than bytes for the data type, but the sizes still have to match.
type InStr struct {
	Data  [9]byte
	Count byte
}
type OutStr struct {
	Data [10]byte
}

func main() {

	////////////////////////////////////////////////////
	// First we set up the tag providers.
	//
	// Each one will have a path and an object that fulfills the gologix.TagProvider interface
	// We set those up and then pass them to the Router object.
	// here we're using the build in io tag provider which just has 10 bytes of inputs and 10 bytes of outputs
	//
	////////////////////////////////////////////////////

	r := gologix.PathRouter{}

	// define the Input and Output instances.  (Input and output here is from the plc's perspective)
	inInstance := InStr{}
	outInstance := OutStr{}

	// an IO handler in slot 2
	//p3 := gologix.IOProvider[InStr, OutStr]{}
	p3 := gologix.IOProvider[InStr, OutStr]{
		In:  &inInstance,
		Out: &outInstance,
	}
	path3, err := gologix.ParsePath("0,1")
	if err != nil {
		log.Printf("problem parsing path. %v", err)
		os.Exit(1)
	}
	r.Handle(path3.Bytes(), &p3)

	s := gologix.NewServer(&r)
	go s.Serve()

	t := time.NewTicker(time.Second)

	for {
		<-t.C
		inInstance.Count++
		p3.InMutex.Lock()
		log.Printf("PLC Input: %v", inInstance)
		p3.InMutex.Unlock()
		p3.OutMutex.Lock()
		log.Printf("PLC Output: %v", outInstance)
		p3.OutMutex.Unlock()
	}

}

PLC I/O Configuration and properties of the ETHERNET-MODULE:
image

Server output:

$ go run server.go
2024/05/30 18:37:36 Listening on TCP port 44818
2024/05/30 18:37:36 Listening on UDP port 2222
2024/05/30 18:37:37 PLC Input: {[0 0 0 0 0 0 0 0 0] 1}
2024/05/30 18:37:37 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:38 PLC Input: {[0 0 0 0 0 0 0 0 0] 2}
2024/05/30 18:37:38 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:39 PLC Input: {[0 0 0 0 0 0 0 0 0] 3}
2024/05/30 18:37:39 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:40 PLC Input: {[0 0 0 0 0 0 0 0 0] 4}
2024/05/30 18:37:40 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:40 new connection from 192.168.1.11:2112
2024/05/30 18:37:40 context: 0
2024/05/30 18:37:41 PLC Input: {[0 0 0 0 0 0 0 0 0] 5}
2024/05/30 18:37:41 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:42 PLC Input: {[0 0 0 0 0 0 0 0 0] 6}
2024/05/30 18:37:42 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:43 PLC Input: {[0 0 0 0 0 0 0 0 0] 7}
2024/05/30 18:37:43 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:44 PLC Input: {[0 0 0 0 0 0 0 0 0] 8}
2024/05/30 18:37:44 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:45 PLC Input: {[0 0 0 0 0 0 0 0 0] 9}
2024/05/30 18:37:45 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:46 PLC Input: {[0 0 0 0 0 0 0 0 0] 10}
2024/05/30 18:37:46 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:47 PLC Input: {[0 0 0 0 0 0 0 0 0] 11}
2024/05/30 18:37:47 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:48 PLC Input: {[0 0 0 0 0 0 0 0 0] 12}
2024/05/30 18:37:48 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:49 PLC Input: {[0 0 0 0 0 0 0 0 0] 13}
2024/05/30 18:37:49 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:50 PLC Input: {[0 0 0 0 0 0 0 0 0] 14}
2024/05/30 18:37:50 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:51 PLC Input: {[0 0 0 0 0 0 0 0 0] 15}
2024/05/30 18:37:51 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:52 PLC Input: {[0 0 0 0 0 0 0 0 0] 16}
2024/05/30 18:37:52 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:53 PLC Input: {[0 0 0 0 0 0 0 0 0] 17}
2024/05/30 18:37:53 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:54 PLC Input: {[0 0 0 0 0 0 0 0 0] 18}
2024/05/30 18:37:54 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:55 PLC Input: {[0 0 0 0 0 0 0 0 0] 19}
2024/05/30 18:37:55 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:56 PLC Input: {[0 0 0 0 0 0 0 0 0] 20}
2024/05/30 18:37:56 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:57 Error on connnection 192.168.1.11:2112. problem reading eip header. read tcp 192.168.1.205:44818->192.168.1.11:2112: read: connection reset by peer
2024/05/30 18:37:57 PLC Input: {[0 0 0 0 0 0 0 0 0] 21}
2024/05/30 18:37:57 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:58 PLC Input: {[0 0 0 0 0 0 0 0 0] 22}
2024/05/30 18:37:58 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:37:59 PLC Input: {[0 0 0 0 0 0 0 0 0] 23}
2024/05/30 18:37:59 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:38:00 PLC Input: {[0 0 0 0 0 0 0 0 0] 24}
2024/05/30 18:38:00 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:38:01 PLC Input: {[0 0 0 0 0 0 0 0 0] 25}
2024/05/30 18:38:01 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:38:02 PLC Input: {[0 0 0 0 0 0 0 0 0] 26}
2024/05/30 18:38:02 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:38:03 PLC Input: {[0 0 0 0 0 0 0 0 0] 27}
2024/05/30 18:38:03 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:38:04 PLC Input: {[0 0 0 0 0 0 0 0 0] 28}
2024/05/30 18:38:04 PLC Output: {[0 0 0 0 0 0 0 0 0 0]}
2024/05/30 18:38:05 new connection from 192.168.1.11:2113
2024/05/30 18:38:05 context: 0
^Csignal: interrupt

client.GetAttrList for Identity Object returns expected results only for L7x controllers

client.GetAttrList for Identity Object in my testing returns expected results only for L7x controllers.

For L8z, EN2T, other in-chassis modules and PowerFlex drives I am only getting 6 bytes back
In getattr.go add a print statement and it is 0 for everything other than LOGIX5575
count, err := items[1].Int16() // Count
fmt.Println(count) // prints 0

Getting single attributes works as expected.
Is this a known issue?

Concurrency issue

Hi,

We am using this project in our company's project. When creating multiple clients by this library to read ethernet ip devices, the ioi_cache is possible to be accessed in the same time which causes the program is terminated. Is it possible to make it as a member of client structure to avoid concurrency issue?

// ioi.go
var ioi_cache map[string]*tagIOI
fatal error: concurrent map read and map write

goroutine 58 [running]:
github.com/danomagnum/gologix.(*Client).NewIOI(0xc000202140, {0xc000451080, 0x1b}, 0xca)
        /home/goProjects/pkg/mod/github.com/danomagnum/[email protected]!beta/ioi.go:132 +0x2d5
github.com/danomagnum/gologix.(*Client).ReadList(0xc000202140, {0xc000236000, 0x50, 0x0?}, {0xc000548410, 0x50, 0xf?})
        /home/goProjects/pkg/mod/github.com/danomagnum/[email protected]!beta/read.go:530 +0x1005

goroutine 8 [runnable]:
github.com/danomagnum/gologix.marshalIOIPart({0xc000451334?, 0x7?})
        /home/goProjects/pkg/mod/github.com/danomagnum/[email protected]!beta/ioi.go:212 +0x22b
github.com/danomagnum/gologix.(*Client).NewIOI(0xc000212820, {0xc000741160, 0x1b}, 0xca)
        /home/goProjects/pkg/mod/github.com/danomagnum/[email protected]!beta/ioi.go:198 +0x777
github.com/danomagnum/gologix.(*Client).ReadList(0xc000212820, {0xc000530a00, 0x50, 0x0?}, {0xc000436140, 0x50, 0xe?})
        /home/goProjects/pkg/mod/github.com/danomagnum/[email protected]!beta/read.go:530 +0x1005

Thank you

[QUESTION] How I can get the Tag variable type from the PLC?

Hello, our system design currently lacks a mechanism to identify variable/tag types. As a result, I am unable to execute the Read(tag, &v) function with v set to the correct type before initiating communication with the Rockwell PLC.

I would like to verify if there is any existing method or recommended practice that addresses the identification of tag/variable types. Today I'm trying each type by the "most used" type we have at the PLCs and caching it to be reused.

ReadMulti() potential problem due to struct having some non-gologix tags

In ReadMulti(), the range that builds the lists for the ReadList() call, I believe it isn't quite right.

I believe this code

    for i := range vf {
        field := vf[i]                                                                                                                  
        tagpath, ok := field.Tag.Lookup("gologix")                                                                                      
        v := val.Field(i).Interface()                                                                                                   
        ct, elem := GoVarToCIPType(v)                                                                                                   
        types = append(types, ct)                                                                                                       
        elements = append(elements, elem)
        if !ok {
            continue
        }
        tags = append(tags, tagpath)                                                                                                    
        tag_map[tagpath] = i
    }                                                                                                                                   

should be changed to perform the !ok check before appending to types and elements. Ie, this:

    for i := range vf {
        field := vf[i]                                                                                                                  
        tagpath, ok := field.Tag.Lookup("gologix")                                                                                      
        if !ok {
            continue
        }
        v := val.Field(i).Interface()                                                                                                   
        ct, elem := GoVarToCIPType(v)                                                                                                   
        types = append(types, ct)                                                                                                       
        elements = append(elements, elem)
        tags = append(tags, tagpath)                                                                                                    
        tag_map[tagpath] = i
    }                                                                                                                                   

By doing this, ReadList() now has correct arguments wherein the indexes correctly correspond to all "gologix" tagged fields in the structure. By not doing this, ReadList() would associate a tag with an incorrect type and element.

Consider the following struct...

type pollDataMix struct {
    TruthOne   bool `gologix:"BOOL_ONE"`
    TruthTwo   bool `gologix:"BOOL_TWO"`
    LocalFlag1 uint64
    InspectTO  int16 `gologix:"INSPECT_TIMEOUT"`
    LocalFlag2 uint64
    LocalFlag3 uint64
}       

ReadList() would see a len(tags)=3, len(types)=6, len(elements)=6. And field InspectTO (index 2) would be assigned an incorrect type (LWORD instead of an INT). I can't see an issue with changing the code. Both of the affected lists (types, elements) are not used outside of being passed into ReadList() where they should reflect the tags list.

how to add field in struct by ReadMulti function?

Hello, when using the readmulti method, you need to pass a struct, which is identified as a tag by the fields in the struct. If I want to add 5000 fields to this struct in a loop to represent 5000 tags, what is the method?

	type name struct {
		TAG1 float32 `gologix:"TAG1"`
		TAG2 float32 `gologix:"TAG2"`
		TAG3 float32 `gologix:"TAG3"`
                .....
                // Dynamically add 5000 fields
                TAG5000 float32 `gologix:"TAG5000"`
	}
        var n name
        client.ReadMulti(&n)

thanks

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.