Resource manager and driver to plc AB SLC-504

I’m trying to develop a resource manager but I don’t have idea how to develop this for plc AB SLC 504 or any. This forum will try to know “How”.

Steps:
1.- Resource Manager (attach the device name and device register access, etc)
2.- Driver

Documentation:
qnx.com/developers/docs/6.3. … esmgr.html

Any Idea, suggestion or anything?.. sorry my english.

Thanks

This is consulting work, there are LOTS of thing to discuss and IMHO goes beyond the scope of this forum.

A guide, extra documentation… anything?.. Consulting work? … it’s only for fun and knowledge. The PLC was a gift jeje :smiley:. Any knowledge about that I’ll write it here.

It is not necessarily related a RM with a PLC driver. You can just write a proccess (not a RM) which implements the TCP protocol to communicate with that PLC.

So, the first thing to know is which protocol the PLC speak, and get information about it. It is also possible that the PLC needs an extra communicaton module (maybe it’s integrated).

Generally speaking you can communicate with a PLC opening a socket (if we are talking about a TCP communication) and sending and receving messages, to and from it, in a specific way. If the PLC is AB, maybe you have to implement something like ControlNet (it’s open but not free, as I remember, you have to check the license agreement), but I really don’t know wich available interfaces (or modules) the PLC have.

Check the PLC documentation.

Good luck!
JM

The protocol is complex, check on the web for cip and csp, complete spec are VERY hard to find. The reason I say consulting is because this is not the typical “fun type” of project. You will face many hurdle and most of PLC related stuff you will learn will not apply to anything else, depending on the model of SLC 504 and module that came with it, the protocol in use can be from the dinosaur era (exagerting just a bit), DataHighway.

A solid driver for an experience programmer would take many weeks 3-4. That doesn’t sound to me like a fun project to work on evening and weekend

As for the QNX side there are so many ways to go about it… And it’s been cover many time on openqnx. But as JM said, start simple, just a program that can upon pressing some keys read and write some IO to the PLC.

Mario, the time is not a problem, I have a lot. :slight_smile:… fun != solid development. Thanks for you reply.

juanplacco thanks for the information. My idea was a POSIX development, thats is the reason that I want develop the RM. The communication would be via tcp/ip (serial in a future). Of course that I was thinking in a just a process but the POSIX is rounding at my mind jaja.

the first question about the RM woulb be…

How do I do to attach the device name?.. well the doumentation have that with examples. But…

How do I do to attach a sector device name?.. for example if I want to read a address N40:1/10 (a bit) in /dev/myplc/N40/1/10 (with a cat command). am I crazy? Is this possible?.. The documentation have something called OCB but it’s not clear to me…yet :slight_smile:.

Thanks all, and good luck.

If you take over /dev/myplc what ever is underneath you can do what you please with it. I mean you can interpret N40/1/10 as you’d like. For every open there is an OCB created for you. You need to change the default ocb allocator for your own. Store the info in that extended OCB, which will get pass to every fonctor ( read/write). This is well documented.

For a real application you need something else then this type of interface which is very practical for debugging purposes ( access via shell ) but VERY inefficient for ready something like a bit :wink: Imagine a program using 200 bits, that would consume 200 file descripts on the client side, create 200 OCB on the server.

It would probably be more pratical to support something like N40:1-N40-20
to get 20 bytes at time.

Yeha that’s a good idea Mario.

I registered my device names /dev/plc and /dev/plc/0-20. Now I’m using the OCB…

well… Where is the data struct who give me the name for the device (in a string or an unique id) that call the io_write o io_read function?.

I saw some structures and I didn’t see any member as a (char *) with the name as I wrote in the resmgr_attach() function.

The io_read/io_write function parameters are a IOFUNC_OCB_T pointer, a resmgr_context_t pointer and io_write_t or io_read_t pointer.

Is that information in a special structure?

The string is passed to the open callback. When you create the extended OCB it’s there that you need to store some sort of information that you extracted from the path and can later use from within the read/write/etc callback. I would store binary information so the other callback don’t have to parse the path every time.

It’s also in the open that you can validate the path and check for proper format. If there is an problem with the path, just return an error. That way you don’t have to go over the validation all over again for every read/write/etc request.

Also while you are at it, you might want to support io_notify. That way an application can request to be notify when an some input data changes.

You could also create an asynchronous design. A thread in the resource manager would perform reads, and writes at a constant rate. I like constant rate, makes everything more deterministic, keep the network at a constant usage rate etc. When a client makes a request the operation is perform on the mirror image kept in memory. That means the operations are real fast. Draw back is when an application does a write it may take some time for the data to get to the PLC and the application can’t really tell when it made it and the read return data that may have not been updated. It’s possible to write some sort of notification for when data is written, but in most case i’ve seen that’s no really needed. Nothing prevents you from implementing both a synchronous and asynchronous method. The user could select the method to use with the NON_BLOCKING flag in open().

I like the asynchronous method because it has one nice extra feature, it easy to replace the thread that talk to the PLC with some sort of simulation thread that could for example read some file and simulate some input sequences. Nice for creating some repeatable test cases.

It would also mean you can very cleanly replace the thread that support PLC500 with some another thread that supports another protocol. That bring the issue of making the type of PLC or the protocol being use transparent to the client application. But that is probably an issue that goes beyond what you are trying to achieve.

I tried to put data in extended ocb structure, and I don’t know where the data is set.

struct ocb{
iofunc_ocb_t
struct ocb *next;
struct ocb **prev;
char dev_name[256];
}

I tried to put data in extended mount structure and the data doesn’t exist in the io_* functions, I acess to de data with the ocb pointer (ocb->hdr.attr->attr.dev_name), the extended information exist but the data that I set in main function doesn’t exist. If I acess to “dev_mount”, I get the diferent data, for example, the /dev/plc1 is 20 bytes ((ocb->hdr.attr->attr.mount->dev_mount.blocksize) and /dev/plc2 is 0 bytes… So, if I called dev_mount.blocksize (and /dev/plc1 called io_read) the result is 20 and diferent (with 0) when the callback is from /dev/plc2.
If I called dev_name in my io_* function, no data in.

struct ex_mount;

#define IOFUNC_MOUNT_T ex_mount;

struct ex_mount{
iofunc_mount_t dev_mount;
char dev_name[256];
}

I dislike write a iofunc.h and put dev_name directly. Any help??. Was wrong… am I blind? jeje. Thanks for the reply.

The ocb data in your extend ocb is set by the frame work. the extra data it`s up to you to set the extra data. You can set it in the ocb allocator that you supplied, or in the open.

Would have to see your code to find out what is wrong.

well this is the code with a extended mount structure

//Resource Manager
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>

/*

  • define THREAD_POOL_PARAM_T such that we can avoid a compiler
  • warning when we use the dispatch_*() functions below
    */
    #define THREAD_POOL_PARAM_T dispatch_context_t

/* Define our overrides before including <sys/iofunc.h> */
struct device;
#define IOFUNC_ATTR_T struct device
struct ocb;
#define IOFUNC_OCB_T struct ocb
struct ex_mount;
#define IOFUNC_MOUNT_T struct ex_mount

#include <sys/iofunc.h>
#include <sys/dispatch.h>

struct ocb {
iofunc_ocb_t hdr;
struct ocb *next;
struct ocb **prev;
};

struct device {
iofunc_attr_t attr;
struct ocb *list;
};

struct ex_mount {
iofunc_mount_t dev_mount;
char dev_name[256];
};

struct ocb *ocb_calloc (resmgr_context_t *ctp, struct device *device);
void ocb_free (struct ocb *ocb);

iofunc_funcs_t ocb_funcs = { /* our ocb allocating & freeing functions */
_IOFUNC_NFUNCS,
ocb_calloc,
ocb_free
};

/* The mount structures.*/
iofunc_mount_t mountpoint1 = { 0, 0, 0, 0, &ocb_funcs };
iofunc_mount_t mountpoint2 = { 0, 0, 0, 20,&ocb_funcs};

/* Two structures device per attached name */
struct device deviceattr1, deviceattr2 ;
struct ex_mount mount1, mount2 ;

int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb);
int io_write (resmgr_context_t *ctp, io_write_t *msg,RESMGR_OCB_T *ocb);

static resmgr_connect_funcs_t connect_funcs;
static resmgr_io_funcs_t io_funcs;

int main(int argc, char *argv)
{
/
declare variables we’ll be using */
thread_pool_attr_t pool_attr;
resmgr_attr_t resmgr_attr;
dispatch_t *dpp;
thread_pool_t *tpp;
dispatch_context_t *ctp;
int id;

/* initialize dispatch interface */
if((dpp = dispatch_create()) == NULL) {
    fprintf(stderr, "%s: Unable to allocate dispatch handle.\n",
            argv[0]);
    return EXIT_FAILURE;
}

/* initialize resource manager attributes */
memset(&resmgr_attr, 0, sizeof resmgr_attr);
resmgr_attr.nparts_max = 1;
resmgr_attr.msg_max_size = 2048;

/* initialize functions for handling messages */
iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs, 
                 _RESMGR_IO_NFUNCS, &io_funcs);
                 
io_funcs.read = io_read;
io_funcs.write = io_write;

/* initialize attribute structure used by the device */
iofunc_attr_init(&deviceattr1.attr, S_IFNAM | 0666, 0, 0);
iofunc_attr_init(&deviceattr2.attr, S_IFNAM | 0666, 0, 0);

deviceattr1.attr.mount = &mountpoint1;
deviceattr2.attr.mount = &mountpoint2;

/*Set structures*/
mount1.dev_mount = mountpoint1;
mount2.dev_mount = mountpoint2;

/*Here the extra data is set*/

sprintf(mount1.dev_name,"%s","/dev/plc1");
sprintf(mount2.dev_name,"%s","/dev/plc2");

printf("%s y %s Registered \n",mount1.dev_name, mount2.dev_name);

/* attach our device name */
id = resmgr_attach(dpp,            /* dispatch handle        */
                   &resmgr_attr,   /* resource manager attrs */
                   mount1.dev_name,  /* device name            */
                   _FTYPE_ANY,     /* open type              */
                   0,              /* flags                  */
                   &connect_funcs, /* connect routines       */
                   &io_funcs,      /* I/O routines           */
                   &deviceattr1);         /* handle                 */
resmgr_attach(dpp,            /* dispatch handle        */
                   &resmgr_attr,   /* resource manager attrs */
                   mount2.dev_name,  /* device name            */
                   _FTYPE_ANY,     /* open type              */
                   0,              /* flags                  */
                   &connect_funcs, /* connect routines       */
                   &io_funcs,      /* I/O routines           */
                   &deviceattr2);         /* handle                 */
if(id == -1) {
    fprintf(stderr, "%s: Unable to attach name.\n", argv[0]);
    return EXIT_FAILURE;
}

/* initialize thread pool attributes */
memset(&pool_attr, 0, sizeof pool_attr);
pool_attr.handle = dpp;
pool_attr.context_alloc = dispatch_context_alloc;
pool_attr.block_func = dispatch_block; 
pool_attr.unblock_func = dispatch_unblock;
pool_attr.handler_func = dispatch_handler;
pool_attr.context_free = dispatch_context_free;
pool_attr.lo_water = 2;
pool_attr.hi_water = 4;
pool_attr.increment = 1;
pool_attr.maximum = 50;

/* allocate a thread pool handle */
if((tpp = thread_pool_create(&pool_attr, 
                             POOL_FLAG_EXIT_SELF)) == NULL) {
    fprintf(stderr, "%s: Unable to initialize thread pool.\n",
            argv[0]);
    return EXIT_FAILURE;
}

/* start the threads, will not return */
thread_pool_start(tpp);

}

int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb)
{
printf(“READ -------------- \n”);
printf (“Received %d bytes, Blocksize is %d in %s Device\n”, msg → i.nbytes, ocb->hdr.attr->attr.mount->dev_mount.blocksize, ocb->hdr.attr->attr.mount->dev_name);
/Driver Function, this function go to the interface “abslc.h” who call libabslc.so.1 (driver)/
//ReadPLC(&buffer, &address);
return (_RESMGR_NPARTS (1));
}

int io_write (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb)
{
int status;
char *buf;

if ((status = iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK)
    return (status);

if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE)
    return(ENOSYS);

/* set up the number of bytes (returned by client's write()) */

_IO_SET_WRITE_NBYTES (ctp, msg->i.nbytes);

buf = (char *) malloc(msg->i.nbytes + 1);
if (buf == NULL)
    return(ENOMEM);

/* Reread the data from the sender's message buffer. */
resmgr_msgread(ctp, buf, msg->i.nbytes, sizeof(msg->i));
buf [msg->i.nbytes] = '\0'; /* just in case the text is not NULL terminated */
printf ("Received %d bytes = %s, Blocksize is %d in %s Device\n", msg -> i.nbytes, buf, ocb->hdr.attr->attr.mount->dev_mount.blocksize, ocb->hdr.attr->attr.mount->dev_name);

/*Driver Function, this function go to the interface "abslc.h" who call libabslc.so.1 (driver)*/
//WritePLC(&buffer, &address);

free(buf);
if (msg->i.nbytes > 0)
    ocb->hdr.attr->attr.flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_CTIME;
printf("WRITE -------------- \n");
return (_RESMGR_NPARTS (0));

}

/*

  • ocb_calloc

  • The purpose of this is to give us a place to allocate our own OCB.

  • It is called as a result of the open being done

  • (e.g. iofunc_open_default causes it to be called). We

  • registered it through the mount structure.
    */
    struct ocb * ocb_calloc (resmgr_context_t *ctp, IOFUNC_ATTR_T *device)
    {
    struct ocb *ocb;

    if (!(ocb = calloc (1, sizeof (*ocb)))) {
    return 0;
    }

    /* see note 3 */
    ocb → prev = &device → list;
    if (ocb → next = device → list) {
    device → list → prev = &ocb → next;
    }
    device → list = ocb;

    return (ocb);
    }

/*

  • ocb_free
  • The purpose of this is to give us a place to free our OCB.
  • It is called as a result of the close being done
  • (e.g. iofunc_close_ocb_default causes it to be called). We
  • registered it through the mount structure.
    */
    void ocb_free (IOFUNC_OCB_T ocb)
    {
    /
    see note 3 */
    if (*ocb → prev = ocb → next) {
    ocb → next → prev = ocb → prev;
    }
    free (ocb);
    }

with OCB extended is the same but the diference that i don[t know where data is set.

You are using 2 mount structures. You put the device name in mount*.dev_name but the resmgr framework isn’t told to use this mount structure, because it is being passed mountpoint*.

Get rid of mount*, change
iofunc_mount_t mountpoint1 = { 0, 0, 0, 0, &ocb_funcs };
to
IOFUNC_MOUNT_T mountpoint1 = { 0, 0, 0, 0, &ocb_funcs };

Then you should be good to go.

Ok i did this:

I get rid how you told me before…
IOFUNC_MOUNT_T mountpoint1 = { 0, 0, 0, 0, &ocb_funcs };
IOFUNC_MOUNT_T mountpoint2 = { 0, 0, 0, 0, &ocb_funcs };

and I changed the mount structure reference…

deviceattr1.attr.mount = &mountpoint1;
deviceattr2.attr.mount = &mountpoint2;

to
deviceattr1.attr.mount = &mount1;
deviceattr2.attr.mount = &mount2;

and all work fine!! :slight_smile:… thanks alot Mario.

Heu? You need to use mountpointX not mountX, because otherwise you aren’t overloading the ocb_uncs.

if I put

deviceattr1.attr.mount = &mountpoint1;
deviceattr2.attr.mount = &mountpoint2;

this doesn’t work. I don’t know why. No data happens.

Did you set the mountpoint1 to be of the IO_… Is it in mountpoint1 that you see the devname ?

Yep, but the extended structures work like a magic arm, and my ocb functions work as told you before.

deviceattr1.attr.mount = &mountpoint1;
deviceattr2.attr.mount = &mountpoint2;

/Set structures/
mount1.dev_mount = mountpoint1;
mount2.dev_mount = mountpoint2;

and the attached handler is referenced to mount1 or 2. And my ocb_calloc and free functions work and now my dev_name has a " /dev/plc* " with de same reference using mountpoint1 or 2

ocb->hdr.attr->attr.mount->dev_name

Now…

to acess a specified register in the read action, Is readblock() function the best form? or exist other fucntions that I should use to pass data or block information to read.

With the write is no problem. but with read?

I’m not sure what your question is. If it works for write what is the problem with read. You really on the path to specify what to read, and the data is specify in the write operation. The same principle should apply for read?

You just need to use the offset and handle read operation that are smaller then the amount of data that should be read.