First library, its tested and working
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
#include "atom_bridge.h"
|
||||
#include "atom_mb.h"
|
||||
#include "atom_rtu.h"
|
||||
#include "atom_server.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
typedef struct { am_client c; ar_transport rtu; int serial; uint32_t generation; int used, retired; } slot;
|
||||
static slot slots[AM_POOL_SIZE];
|
||||
static uint32_t token(unsigned i) { return (slots[i].generation<<8)|(i+1u); }
|
||||
static slot *lookup(uint32_t h) {
|
||||
unsigned n=h&255u;
|
||||
if(!n || n>AM_POOL_SIZE) return NULL;
|
||||
slot *s=&slots[n-1];
|
||||
return s->used && s->generation==(h>>8)?s:NULL;
|
||||
}
|
||||
static void snapshot(const am_client *c,uint32_t h,uint32_t *s) {
|
||||
s[0]=h; s[1]=(uint32_t)c->state; s[2]=(uint32_t)c->busy;
|
||||
s[3]=(uint32_t)c->done; s[4]=(uint32_t)c->error; s[5]=(uint32_t)c->result;
|
||||
s[6]=(uint32_t)c->last_error; s[7]=(uint32_t)c->socket_error;
|
||||
s[8]=c->exception; s[9]=(uint32_t)c->write_uncertain;
|
||||
s[10]=c->connect_attempts; s[11]=c->reconnects; s[12]=c->completed; s[13]=c->failed;
|
||||
s[14]=(uint32_t)c->has_success; s[15]=(uint32_t)c->last_success_ms;
|
||||
s[16]=(uint32_t)(c->last_success_ms>>32); s[17]=c->quantity;
|
||||
s[18]=c->function; s[19]=c->unit; s[20]=(uint32_t)c->enabled;
|
||||
s[21]=c->connect_timeout_ms; s[22]=c->response_timeout_ms; s[23]=c->retry_delay_ms;
|
||||
}
|
||||
uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
uint8_t unit,uint8_t f,uint16_t addr,uint16_t q,uint16_t *data,uint32_t cap,
|
||||
uint32_t *status,uint32_t sc,uint64_t now,uint32_t ct,uint32_t rt,uint32_t retry) {
|
||||
slot *s; unsigned i; uint32_t result=0;
|
||||
if(!status || sc<AM_STATUS_WORDS) return AB_BAD_CAPACITY;
|
||||
if(cmd>=AS_OPEN && cmd<=AS_PULL)return as_call(cmd,h,ip,port,f,addr,q,data,cap,status,now,retry);
|
||||
/* Caller must provide distinct data/status buffers with declared capacities. */
|
||||
if(cmd==AB_OPEN || cmd==AB_OPEN_RTU) {
|
||||
char text[128];
|
||||
memset(status,0,AM_STATUS_WORDS*sizeof(*status));
|
||||
if(cmd==AB_OPEN && !port) return AM_ARGUMENT;
|
||||
for(i=0;i<AM_POOL_SIZE;i++) if(!slots[i].used && !slots[i].retired) break;
|
||||
if(i==AM_POOL_SIZE) return AB_POOL_FULL;
|
||||
s=&slots[i];
|
||||
snprintf(text,sizeof(text),"%u.%u.%u.%u",(unsigned)(ip>>24),(unsigned)((ip>>16)&255u),
|
||||
(unsigned)((ip>>8)&255u),(unsigned)(ip&255u));
|
||||
s->serial=(cmd==AB_OPEN_RTU);
|
||||
if(s->serial) {
|
||||
if(!data || !q || q>=sizeof(text) || cap<q)return AM_ARGUMENT;
|
||||
for(unsigned j=0;j<q;j++) { if(data[j]==0 || data[j]>127)return AM_ARGUMENT;text[j]=(char)data[j]; }
|
||||
text[q]=0;
|
||||
if(!ar_init(&s->c,&s->rtu,text,ip,unit,f,(uint8_t)addr,ct))return AM_ARGUMENT;
|
||||
} else if(!am_init(&s->c,text,port)) return AM_ARGUMENT;
|
||||
s->generation++; s->used=1; h=token(i);
|
||||
snapshot(&s->c,h,status); return 0;
|
||||
}
|
||||
s=lookup(h);
|
||||
if(!s) { memset(status,0,AM_STATUS_WORDS*sizeof(*status)); return AB_BAD_HANDLE; }
|
||||
switch(cmd) {
|
||||
case AB_CLOSE:
|
||||
if(s->serial)ar_close(&s->c);else am_close(&s->c); s->used=0;
|
||||
/* Never recycle a generation after wrap: stale handles stay invalid. */
|
||||
if(s->generation==0xffffffu) s->retired=1;
|
||||
snapshot(&s->c,0,status); return 0;
|
||||
case AB_ENABLE: am_enable(&s->c,1,now); break;
|
||||
case AB_DISABLE: if(s->serial)ar_close(&s->c);else am_enable(&s->c,0,now); break;
|
||||
case AB_TICK: if(s->serial)ar_tick(&s->c,&s->rtu,now);else am_tick(&s->c,now); break;
|
||||
case AB_RECONNECT: if(s->serial)ar_reconnect(&s->c,&s->rtu,now);else am_reconnect(&s->c,now); break;
|
||||
case AB_CONFIGURE:
|
||||
if(s->c.enabled) result=AM_BUSY;
|
||||
else if(!ct || !rt || !retry) result=AM_ARGUMENT;
|
||||
else { s->c.connect_timeout_ms=ct; s->c.response_timeout_ms=rt; s->c.retry_delay_ms=retry; }
|
||||
break;
|
||||
case AB_SUBMIT:
|
||||
if((f==5 || f==6 || f==15 || f==16) && (!data || cap<q)) result=AB_BAD_CAPACITY;
|
||||
else result=(uint32_t)(s->serial?ar_request(&s->c,&s->rtu,unit,f,addr,q,data,now):am_request(&s->c,unit,f,addr,q,data,now));
|
||||
break;
|
||||
case AB_COPY_READ:
|
||||
if(!s->c.done || s->c.function>4 || s->c.function<1) result=AM_ARGUMENT;
|
||||
else if(!data || cap<s->c.quantity) result=AB_BAD_CAPACITY;
|
||||
else memcpy(data,s->c.data,s->c.quantity*sizeof(*data));
|
||||
break;
|
||||
case AB_ACK: s->c.done=0; s->c.error=0; break;
|
||||
case AB_REQUEST_TIMEOUT:
|
||||
if(s->c.busy) result=AM_BUSY;
|
||||
else if(!rt) result=AM_ARGUMENT;
|
||||
else s->c.response_timeout_ms=rt;
|
||||
break;
|
||||
default: result=AB_BAD_COMMAND; break;
|
||||
}
|
||||
snapshot(&s->c,h,status); return result;
|
||||
}
|
||||
void ab_shutdown(void) {
|
||||
as_shutdown();
|
||||
unsigned i;
|
||||
for(i=0;i<AM_POOL_SIZE;i++) if(slots[i].used) {
|
||||
if(slots[i].serial)ar_close(&slots[i].c);else am_close(&slots[i].c); slots[i].used=0;
|
||||
if(slots[i].generation==0xffffffu) slots[i].retired=1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef ATOM_BRIDGE_H
|
||||
#define ATOM_BRIDGE_H
|
||||
#include <stdint.h>
|
||||
/* Fixed pool, one IEC task owns ALL calls. Do not share across tasks.
|
||||
* Only synchronous pointer access; generation-checked handles, no heap. */
|
||||
#define AM_STATUS_WORDS 24
|
||||
#define AM_POOL_SIZE 16
|
||||
enum { AB_OPEN=1, AB_CLOSE, AB_ENABLE, AB_DISABLE, AB_TICK, AB_SUBMIT,
|
||||
AB_COPY_READ, AB_RECONNECT, AB_CONFIGURE, AB_ACK, AB_REQUEST_TIMEOUT };
|
||||
enum { AB_OPEN_RTU=20 };
|
||||
enum { AB_BAD_HANDLE=256, AB_POOL_FULL, AB_BAD_CAPACITY, AB_BAD_COMMAND };
|
||||
uint32_t ab_call(uint32_t command,uint32_t handle,uint32_t ip,uint16_t port,
|
||||
uint8_t unit,uint8_t function,uint16_t address,uint16_t quantity,
|
||||
uint16_t *data,uint32_t capacity,uint32_t *status,uint32_t status_capacity,
|
||||
uint64_t now,uint32_t connect_ms,uint32_t response_ms,uint32_t retry_ms);
|
||||
/* Call on component unload; invalidate handles and close all sockets. */
|
||||
void ab_shutdown(void);
|
||||
#endif
|
||||
@@ -0,0 +1,147 @@
|
||||
#include "atom_mb.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
static uint16_t get16(const uint8_t *p) { return (uint16_t)((p[0]<<8)|p[1]); }
|
||||
static void put16(uint8_t *p,uint16_t v) { p[0]=(uint8_t)(v>>8); p[1]=(uint8_t)v; }
|
||||
static int writing(uint8_t f) { return f==5 || f==6 || f==15 || f==16; }
|
||||
static void drop(am_client *c) { if(c->fd>=0) { if(c->serial_port)ioctl(c->fd,TIOCNXCL);close(c->fd); } c->fd=-1; }
|
||||
static void finish(am_client *c,am_error e) {
|
||||
c->busy=0; c->done=e==AM_OK; c->error=e!=AM_OK; c->result=e;
|
||||
if(e==AM_OK) c->completed++; else { c->failed++; c->last_error=e; }
|
||||
}
|
||||
static void fault(am_client *c,am_error e,uint64_t now) {
|
||||
if(c->busy) { c->write_uncertain=writing(c->function) && c->tx_pos>0; finish(c,e); }
|
||||
c->last_error=e; drop(c); c->state=c->enabled?AM_RETRY:AM_DISABLED;
|
||||
c->deadline_ms=now+c->retry_delay_ms;
|
||||
}
|
||||
int am_init(am_client *c,const char *ip,uint16_t port) {
|
||||
struct in_addr a;
|
||||
if(!c || !ip || strlen(ip)>=16 || !port || inet_pton(AF_INET,ip,&a)!=1) return 0;
|
||||
memset(c,0,sizeof(*c)); c->fd=-1; strcpy(c->ip,ip); c->port=port;
|
||||
c->connect_timeout_ms=2000; c->response_timeout_ms=1000; c->retry_delay_ms=1000;
|
||||
return 1;
|
||||
}
|
||||
void am_close(am_client *c) { c->enabled=0; fault(c,AM_CANCELLED,0); }
|
||||
void am_enable(am_client *c,int enabled,uint64_t now) {
|
||||
if(!enabled) { if(c->enabled) am_close(c); return; }
|
||||
if(!c->enabled) { c->enabled=1; c->state=AM_RETRY; c->deadline_ms=now; }
|
||||
}
|
||||
void am_reconnect(am_client *c,uint64_t now) { fault(c,AM_CANCELLED,now); c->deadline_ms=now; }
|
||||
am_error am_request(am_client *c,uint8_t unit,uint8_t f,uint16_t addr,uint16_t q,
|
||||
const uint16_t *v,uint64_t now) {
|
||||
size_t n=12, bytes=0; unsigned i;
|
||||
if(c->busy) return AM_BUSY;
|
||||
if(c->state!=AM_READY) return AM_OFFLINE;
|
||||
if(!q || (uint32_t)addr+q>65536u) return AM_ARGUMENT;
|
||||
switch(f) {
|
||||
case 1: case 2: if(q>2000) return AM_ARGUMENT; break;
|
||||
case 3: case 4: if(q>125) return AM_ARGUMENT; break;
|
||||
case 5: case 6: if(q!=1 || !v || (f==5 && v[0]>1)) return AM_ARGUMENT; break;
|
||||
case 15: if(q>1968 || !v) return AM_ARGUMENT;
|
||||
for(i=0;i<q;i++) if(v[i]>1) return AM_ARGUMENT;
|
||||
bytes=(q+7u)/8u; n=13+bytes; break;
|
||||
case 16: if(q>123 || !v) return AM_ARGUMENT; bytes=2u*q; n=13+bytes; break;
|
||||
default: return AM_ARGUMENT;
|
||||
}
|
||||
memset(c->tx,0,sizeof(c->tx)); c->transaction++;
|
||||
put16(c->tx,c->transaction); put16(c->tx+4,(uint16_t)(n-6));
|
||||
c->tx[6]=unit; c->tx[7]=f; put16(c->tx+8,addr); put16(c->tx+10,q);
|
||||
if(f==5 || f==6) put16(c->tx+10,f==5?(v[0]?0xff00:0):v[0]);
|
||||
if(f==15 || f==16) {
|
||||
c->tx[12]=(uint8_t)bytes;
|
||||
for(i=0;i<q;i++) {
|
||||
if(f==16) put16(c->tx+13+2*i,v[i]);
|
||||
else if(v[i]) c->tx[13+i/8]|=(uint8_t)(1u<<(i%8));
|
||||
}
|
||||
}
|
||||
c->unit=unit; c->function=f; c->address=addr; c->quantity=q;
|
||||
c->tx_len=n; c->tx_pos=0; c->rx_len=0; c->rx_need=7;
|
||||
c->busy=1; c->done=0; c->error=0; c->exception=0; c->write_uncertain=0;
|
||||
c->result=AM_OK; c->state=AM_SENDING; c->deadline_ms=now+c->response_timeout_ms;
|
||||
return AM_OK;
|
||||
}
|
||||
void am_accept_response(am_client *c,uint64_t now) {
|
||||
unsigned i; size_t bytes;
|
||||
if(c->rx[7]==(uint8_t)(c->function|0x80)) {
|
||||
if(c->rx_need!=9 || !c->rx[8]) { fault(c,AM_PROTOCOL,now); return; }
|
||||
c->exception=c->rx[8]; finish(c,AM_EXCEPTION); c->state=AM_READY; return;
|
||||
}
|
||||
if(c->rx[7]!=c->function) { fault(c,AM_PROTOCOL,now); return; }
|
||||
if(writing(c->function)) {
|
||||
if(c->rx_need!=12 || memcmp(c->rx+8,c->tx+8,4)) { fault(c,AM_PROTOCOL,now); return; }
|
||||
} else {
|
||||
bytes=c->function<=2?(c->quantity+7u)/8u:2u*c->quantity;
|
||||
if(c->rx_need!=9+bytes || c->rx[8]!=bytes) { fault(c,AM_PROTOCOL,now); return; }
|
||||
for(i=0;i<c->quantity;i++) c->data[i]=c->function<=2?
|
||||
(uint16_t)((c->rx[9+i/8]>>(i%8))&1):get16(c->rx+9+2*i);
|
||||
}
|
||||
c->last_success_ms=now; c->has_success=1; finish(c,AM_OK); c->state=AM_READY;
|
||||
}
|
||||
void am_tick(am_client *c,uint64_t now) {
|
||||
struct sockaddr_in a; struct pollfd p; int e,r; socklen_t len; ssize_t n;
|
||||
if(!c->enabled) return;
|
||||
if(c->state==AM_RETRY) {
|
||||
if(now<c->deadline_ms) return;
|
||||
c->connect_attempts++; if(c->connect_attempts>1) c->reconnects++;
|
||||
c->socket_error=0; c->fd=socket(AF_INET,SOCK_STREAM,0);
|
||||
if(c->fd<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; }
|
||||
r=fcntl(c->fd,F_GETFL,0);
|
||||
if(r<0 || fcntl(c->fd,F_SETFL,r|O_NONBLOCK)<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; }
|
||||
memset(&a,0,sizeof(a)); a.sin_family=AF_INET; a.sin_port=htons(c->port);
|
||||
if(inet_pton(AF_INET,c->ip,&a.sin_addr)!=1) { fault(c,AM_ARGUMENT,now); return; }
|
||||
r=connect(c->fd,(struct sockaddr*)&a,sizeof(a));
|
||||
if(r==0) c->state=AM_READY;
|
||||
else if(errno==EINPROGRESS) { c->state=AM_CONNECTING; c->deadline_ms=now+c->connect_timeout_ms; }
|
||||
else { c->socket_error=errno; fault(c,AM_SOCKET,now); }
|
||||
return;
|
||||
}
|
||||
if(c->state==AM_CONNECTING) {
|
||||
if(now>=c->deadline_ms) { fault(c,AM_CONNECT_TIMEOUT,now); return; }
|
||||
p.fd=c->fd; p.events=POLLOUT; p.revents=0; r=poll(&p,1,0);
|
||||
if(r<0) { if(errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } return; }
|
||||
if(!r) return;
|
||||
len=sizeof(e); e=0;
|
||||
if(getsockopt(c->fd,SOL_SOCKET,SO_ERROR,&e,&len)<0) e=errno;
|
||||
if(e) { c->socket_error=e; fault(c,AM_SOCKET,now); } else c->state=AM_READY;
|
||||
return;
|
||||
}
|
||||
if(c->state==AM_READY) {
|
||||
/* Detect idle peer closure; unsolicited bytes cannot belong to a request. */
|
||||
uint8_t b; n=recv(c->fd,&b,1,MSG_PEEK);
|
||||
if(n==0) fault(c,AM_PEER_CLOSED,now);
|
||||
else if(n>0) fault(c,AM_PROTOCOL,now);
|
||||
else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); }
|
||||
return;
|
||||
}
|
||||
if(now>=c->deadline_ms) { fault(c,AM_RESPONSE_TIMEOUT,now); return; }
|
||||
if(c->state==AM_SENDING) {
|
||||
n=send(c->fd,c->tx+c->tx_pos,c->tx_len-c->tx_pos,MSG_NOSIGNAL);
|
||||
if(n>0) { c->tx_pos+=(size_t)n; if(c->tx_pos==c->tx_len) c->state=AM_RECEIVING; }
|
||||
else if(n==0) fault(c,AM_PEER_CLOSED,now);
|
||||
else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); }
|
||||
return;
|
||||
}
|
||||
if(c->state!=AM_RECEIVING) return;
|
||||
n=recv(c->fd,c->rx+c->rx_len,c->rx_need-c->rx_len,0);
|
||||
if(n==0) { fault(c,AM_PEER_CLOSED,now); return; }
|
||||
if(n<0) { if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } return; }
|
||||
c->rx_len+=(size_t)n;
|
||||
if(c->rx_len!=c->rx_need) return;
|
||||
if(c->rx_need==7) {
|
||||
uint16_t l=get16(c->rx+4);
|
||||
if(get16(c->rx)!=c->transaction || get16(c->rx+2)!=0 || c->rx[6]!=c->unit || l<3 || l>254) {
|
||||
fault(c,AM_PROTOCOL,now); return;
|
||||
}
|
||||
c->rx_need=6u+l; return;
|
||||
}
|
||||
am_accept_response(c,now);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef ATOM_MB_H
|
||||
#define ATOM_MB_H
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
/* Single owner/task per context. Init once, close before disposal. No heap allocation.
|
||||
* tick() time must be monotonic milliseconds. POSIX IPv4 transport, numeric IP only. */
|
||||
typedef enum { AM_DISABLED, AM_RETRY, AM_CONNECTING, AM_READY, AM_SENDING, AM_RECEIVING } am_state;
|
||||
typedef enum { AM_OK, AM_ARGUMENT, AM_BUSY, AM_OFFLINE, AM_SOCKET, AM_CONNECT_TIMEOUT,
|
||||
AM_RESPONSE_TIMEOUT, AM_PEER_CLOSED, AM_PROTOCOL, AM_EXCEPTION, AM_CANCELLED } am_error;
|
||||
typedef struct {
|
||||
int fd, enabled, serial_port;
|
||||
am_state state;
|
||||
am_error last_error, result;
|
||||
int socket_error;
|
||||
uint8_t exception, unit, function;
|
||||
uint16_t transaction, address, quantity;
|
||||
uint32_t connect_timeout_ms, response_timeout_ms, retry_delay_ms;
|
||||
uint64_t deadline_ms, last_success_ms;
|
||||
uint32_t connect_attempts, reconnects, completed, failed;
|
||||
int busy, done, error, write_uncertain, has_success;
|
||||
char ip[16]; uint16_t port;
|
||||
uint8_t tx[260], rx[260];
|
||||
size_t tx_len, tx_pos, rx_len, rx_need;
|
||||
/* Reads: coils/discretes unpacked as 0/1; registers as host-order uint16_t.
|
||||
* Result is copied only after the entire response validates. */
|
||||
uint16_t data[2000];
|
||||
} am_client;
|
||||
int am_init(am_client *c, const char *ip, uint16_t port);
|
||||
void am_enable(am_client *c, int enabled, uint64_t now_ms);
|
||||
void am_reconnect(am_client *c, uint64_t now_ms);
|
||||
void am_close(am_client *c);
|
||||
void am_tick(am_client *c, uint64_t now_ms);
|
||||
/* Supported functions: 1,2,3,4,5,6,15,16. Zero-based protocol addresses.
|
||||
* Writes copy input immediately. No automatic replay of a failed request.
|
||||
* Completion flags persist until next accepted request; result is per-request. */
|
||||
am_error am_request(am_client *c, uint8_t unit, uint8_t function,
|
||||
uint16_t address, uint16_t quantity, const uint16_t *values, uint64_t now_ms);
|
||||
/* Shared validated transport response parser. */
|
||||
void am_accept_response(am_client *, uint64_t);
|
||||
#endif
|
||||
@@ -0,0 +1,101 @@
|
||||
#ifndef _DEFAULT_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
#include "atom_rtu.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#ifdef __linux__
|
||||
#include <linux/serial.h>
|
||||
#endif
|
||||
uint16_t ar_crc(const uint8_t *p,size_t n) {
|
||||
uint16_t crc=0xffff;
|
||||
while(n--) { crc^=*p++; for(unsigned i=0;i<8;i++) crc=(uint16_t)((crc>>1)^((crc&1)?0xa001:0)); }
|
||||
return crc;
|
||||
}
|
||||
static speed_t ar_speed(uint32_t baud) {
|
||||
switch(baud) { case 1200:return B1200;case 2400:return B2400;case 4800:return B4800;
|
||||
case 9600:return B9600;case 19200:return B19200;case 38400:return B38400;
|
||||
case 57600:return B57600;case 115200:return B115200;default:return 0; }
|
||||
}
|
||||
int ar_init(am_client *c,ar_transport *r,const char *path,uint32_t baud,uint8_t parity,uint8_t bits,uint8_t stops,uint32_t rs485) {
|
||||
if(!path || !*path || strlen(path)>=sizeof(r->device) || !ar_speed(baud) || parity>2 || bits!=8 || (stops!=1 && stops!=2) || rs485>1) return 0;
|
||||
am_init(c,"127.0.0.1",1);c->serial_port=1;memset(r,0,sizeof(*r));strcpy(r->device,path);
|
||||
r->baud=baud;r->parity=parity;r->bits=bits;r->stops=stops;r->rs485=rs485;
|
||||
r->gap_ms=baud>19200?2:(uint32_t)((3500u*(1u+bits+(parity!=0)+stops)+baud-1u)/baud);
|
||||
return 1;
|
||||
}
|
||||
static void ar_fault(am_client *c,ar_transport *r,am_error error,uint64_t now) {
|
||||
if(c->busy) { c->write_uncertain=(c->function>=5 && r->tx_pos>0);c->busy=0;c->done=0;c->error=1;c->result=error;c->failed++; }
|
||||
c->last_error=error;if(c->fd>=0){ioctl(c->fd,TIOCNXCL);close(c->fd);}c->fd=-1;c->state=c->enabled?AM_RETRY:AM_DISABLED;
|
||||
c->deadline_ms=now+c->retry_delay_ms;r->quiet_until=c->deadline_ms;r->rx_len=0;
|
||||
}
|
||||
am_error ar_request(am_client *c,ar_transport *r,uint8_t unit,uint8_t f,uint16_t addr,uint16_t q,const uint16_t *v,uint64_t now) {
|
||||
/* Broadcast writes have no response; reject explicitly rather than fabricate success. */
|
||||
if(unit<1 || unit>247)return AM_ARGUMENT;
|
||||
am_error error=am_request(c,unit,f,addr,q,v,now);if(error!=AM_OK)return error;
|
||||
r->tx_len=c->tx_len-6;memcpy(r->tx,c->tx+6,r->tx_len);
|
||||
uint16_t crc=ar_crc(r->tx,r->tx_len);r->tx[r->tx_len++]=(uint8_t)crc;r->tx[r->tx_len++]=(uint8_t)(crc>>8);
|
||||
r->tx_pos=0;r->rx_len=0;r->rx_need=0;return AM_OK;
|
||||
}
|
||||
void ar_tick(am_client *c,ar_transport *r,uint64_t now) {
|
||||
struct termios options;ssize_t n;
|
||||
if(!c->enabled)return;
|
||||
if(c->state==AM_RETRY) {
|
||||
if(now<c->deadline_ms)return;
|
||||
c->connect_attempts++;if(c->connect_attempts>1)c->reconnects++;
|
||||
c->socket_error=0;c->fd=open(r->device,O_RDWR|O_NOCTTY|O_NONBLOCK|O_CLOEXEC);
|
||||
if(c->fd<0) { c->socket_error=errno;ar_fault(c,r,AM_SOCKET,now);return; }
|
||||
if(ioctl(c->fd,TIOCEXCL)<0 || tcgetattr(c->fd,&options)<0) goto serial_error;
|
||||
cfmakeraw(&options);options.c_cflag&=~(CSIZE|PARENB|PARODD|CSTOPB|CRTSCTS);
|
||||
options.c_cflag|=CLOCAL|CREAD|(r->bits==7?CS7:CS8);
|
||||
if(r->parity)options.c_cflag|=PARENB|(r->parity==1?PARODD:0);
|
||||
if(r->stops==2)options.c_cflag|=CSTOPB;
|
||||
options.c_cc[VMIN]=0;options.c_cc[VTIME]=0;
|
||||
if(cfsetispeed(&options,ar_speed(r->baud))<0 || cfsetospeed(&options,ar_speed(r->baud))<0 || tcsetattr(c->fd,TCSANOW,&options)<0 || tcflush(c->fd,TCIOFLUSH)<0) goto serial_error;
|
||||
#ifdef __linux__
|
||||
if(r->rs485) { struct serial_rs485 mode;memset(&mode,0,sizeof(mode));mode.flags=SER_RS485_ENABLED|SER_RS485_RTS_ON_SEND;
|
||||
if(ioctl(c->fd,TIOCSRS485,&mode)<0)goto serial_error; }
|
||||
#endif
|
||||
c->state=AM_READY;r->quiet_until=now+r->gap_ms;return;
|
||||
}
|
||||
if(c->state==AM_READY)return;
|
||||
if(now>=c->deadline_ms) { ar_fault(c,r,AM_RESPONSE_TIMEOUT,now);return; }
|
||||
if(c->state==AM_SENDING) {
|
||||
if(now<r->quiet_until)return;
|
||||
if(r->tx_pos==0 && tcflush(c->fd,TCIFLUSH)<0)goto serial_error;
|
||||
n=write(c->fd,r->tx+r->tx_pos,r->tx_len-r->tx_pos);
|
||||
if(n>0) { r->tx_pos+=(size_t)n;c->tx_pos=r->tx_pos;if(r->tx_pos==r->tx_len)c->state=AM_RECEIVING; }
|
||||
else if(n<0 && errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR)goto serial_error;
|
||||
return;
|
||||
}
|
||||
if(c->state==AM_RECEIVING) {
|
||||
n=read(c->fd,r->rx+r->rx_len,sizeof(r->rx)-r->rx_len);
|
||||
if(n<0) { if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR)goto serial_error;return; }
|
||||
if(n==0)return;
|
||||
r->rx_len+=(size_t)n;r->quiet_until=now+r->gap_ms;
|
||||
if(r->rx_len>=2 && (r->rx[0]!=c->unit || (r->rx[1]!=c->function && r->rx[1]!=(uint8_t)(c->function|0x80)))) { ar_fault(c,r,AM_PROTOCOL,now);return; }
|
||||
if(r->rx_len>=2) {
|
||||
if(r->rx[1]&0x80)r->rx_need=5;
|
||||
else if(c->function>=5)r->rx_need=8;
|
||||
else if(r->rx_len>=3)r->rx_need=5u+r->rx[2];
|
||||
}
|
||||
if(r->rx_need>sizeof(r->rx) || (r->rx_need && r->rx_len>r->rx_need)) { ar_fault(c,r,AM_PROTOCOL,now);return; }
|
||||
if(!r->rx_need || r->rx_len<r->rx_need)return;
|
||||
if(ar_crc(r->rx,r->rx_need)!=0) { ar_fault(c,r,AM_PROTOCOL,now);return; }
|
||||
/* Convert validated RTU ADU to the shared response validator's MBAP representation. */
|
||||
size_t payload=r->rx_need-2;memcpy(c->rx+6,r->rx,payload);c->rx_need=6+payload;
|
||||
am_accept_response(c,now);
|
||||
if(c->state==AM_RETRY)r->quiet_until=c->deadline_ms;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
serial_error:
|
||||
c->socket_error=errno;ar_fault(c,r,AM_SOCKET,now);
|
||||
}
|
||||
|
||||
void ar_close(am_client *c){if(c->fd>=0)ioctl(c->fd,TIOCNXCL);am_close(c);}
|
||||
void ar_reconnect(am_client *c,ar_transport *r,uint64_t now){ar_fault(c,r,AM_CANCELLED,now);c->deadline_ms=now;}
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef ATOM_RTU_H
|
||||
#define ATOM_RTU_H
|
||||
#include "atom_mb.h"
|
||||
typedef struct {
|
||||
char device[128]; uint32_t baud, rs485;
|
||||
uint8_t parity, bits, stops;
|
||||
uint8_t tx[256], rx[256]; size_t tx_len, tx_pos, rx_len, rx_need;
|
||||
uint64_t quiet_until; uint32_t gap_ms;
|
||||
} ar_transport;
|
||||
int ar_init(am_client *, ar_transport *,const char *,uint32_t,uint8_t,uint8_t,uint8_t,uint32_t);
|
||||
am_error ar_request(am_client *,ar_transport *,uint8_t,uint8_t,uint16_t,uint16_t,const uint16_t *,uint64_t);
|
||||
void ar_tick(am_client *,ar_transport *,uint64_t);
|
||||
uint16_t ar_crc(const uint8_t *,size_t);
|
||||
void ar_close(am_client *);
|
||||
void ar_reconnect(am_client *, ar_transport *, uint64_t);
|
||||
#endif
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "atom_server.h"
|
||||
#include "atom_mb.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#define AS_POOL 4
|
||||
#define AS_PEERS 8
|
||||
#define AS_ITEMS 2000
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
typedef struct { int fd;uint8_t rx[260],tx[260];size_t received,needed,sent,length;uint64_t deadline; } as_peer;
|
||||
typedef struct {
|
||||
int used,enabled,listener,running,error,os_error;uint32_t generation,received,replied,exceptions,retry;
|
||||
uint32_t ip;uint16_t port,base[4],count[4],values[4][AS_ITEMS];uint64_t retry_at;
|
||||
as_peer peers[AS_PEERS];
|
||||
} as_server;
|
||||
static as_server as_slots[AS_POOL];
|
||||
static uint16_t as_get16(const uint8_t *p){return (uint16_t)((p[0]<<8)|p[1]);}
|
||||
static void as_put16(uint8_t *p,uint16_t v){p[0]=(uint8_t)(v>>8);p[1]=(uint8_t)v;}
|
||||
static void as_peer_close(as_peer *p){if(p->fd>=0)close(p->fd);p->fd=-1;p->received=0;p->needed=7;p->sent=0;p->length=0;}
|
||||
static void as_stop(as_server *s){if(s->listener>=0)close(s->listener);s->listener=-1;s->running=0;for(unsigned i=0;i<AS_PEERS;i++)as_peer_close(&s->peers[i]);}
|
||||
static void as_snapshot(as_server *s,uint32_t h,uint32_t *o){
|
||||
memset(o,0,24*sizeof(*o));o[0]=h;o[1]=(uint32_t)s->running;o[2]=(uint32_t)s->error;o[3]=s->error?AM_SOCKET:AM_OK;o[4]=(uint32_t)s->os_error;
|
||||
for(unsigned i=0;i<AS_PEERS;i++)if(s->peers[i].fd>=0)o[5]++;
|
||||
o[6]=s->received;o[7]=s->replied;o[8]=s->exceptions;
|
||||
}
|
||||
static int as_nonblock(int fd){int flags=fcntl(fd,F_GETFL,0);return flags>=0 && fcntl(fd,F_SETFL,flags|O_NONBLOCK)==0;}
|
||||
static int as_range(as_server *s,unsigned table,uint16_t addr,uint16_t count){return count && addr>=s->base[table] && (uint32_t)addr+count<=(uint32_t)s->base[table]+s->count[table];}
|
||||
static void as_reply(as_server *s,as_peer *p){
|
||||
uint8_t f=p->rx[7],exception=0;uint16_t address=0,q=0;unsigned table=0,index=0;size_t n=9;
|
||||
if(p->needed<12){exception=(f==1 || f==2 || f==3 || f==4 || f==5 || f==6 || f==15 || f==16)?3:1;goto finish;}
|
||||
address=as_get16(p->rx+8);q=as_get16(p->rx+10);
|
||||
switch(f){
|
||||
case 1:case 2:case 3:case 4:
|
||||
table=f==1?0:f==2?1:f==3?2:3;
|
||||
if(p->needed!=12 || !q || q>(f<=2?2000:125)){exception=3;break;}
|
||||
if(!as_range(s,table,address,q)){exception=2;break;}
|
||||
index=address-s->base[table];p->tx[8]=(uint8_t)(f<=2?(q+7u)/8u:2u*q);n=9+p->tx[8];memset(p->tx+9,0,n-9);
|
||||
for(unsigned i=0;i<q;i++){if(f<=2){if(s->values[table][index+i])p->tx[9+i/8]|=(uint8_t)(1u<<(i%8));}else as_put16(p->tx+9+2*i,s->values[table][index+i]);}
|
||||
break;
|
||||
case 5:case 6:
|
||||
table=f==5?0:2;
|
||||
if(p->needed!=12 || (f==5 && q!=0 && q!=0xff00)){exception=3;break;}
|
||||
if(!as_range(s,table,address,1)){exception=2;break;}
|
||||
s->values[table][address-s->base[table]]=f==5?(q!=0):q;memcpy(p->tx+8,p->rx+8,4);n=12;break;
|
||||
case 15:case 16:
|
||||
table=f==15?0:2;
|
||||
if(!q || q>(f==15?1968:123) || p->needed<13 || p->rx[12]!=(f==15?(q+7u)/8u:2u*q) || p->needed!=(size_t)(13+p->rx[12])){exception=3;break;}
|
||||
if(!as_range(s,table,address,q)){exception=2;break;}
|
||||
index=address-s->base[table];
|
||||
for(unsigned i=0;i<q;i++)s->values[table][index+i]=f==15?(uint16_t)((p->rx[13+i/8]>>(i%8))&1):as_get16(p->rx+13+2*i);
|
||||
memcpy(p->tx+8,p->rx+8,4);n=12;break;
|
||||
default:exception=1;break;
|
||||
}
|
||||
finish:
|
||||
s->received++;memcpy(p->tx,p->rx,4);p->tx[6]=p->rx[6];p->tx[7]=f;
|
||||
if(exception){p->tx[7]=(uint8_t)(f|0x80);p->tx[8]=exception;n=9;s->exceptions++;}
|
||||
as_put16(p->tx+4,(uint16_t)(n-6));p->length=n;p->sent=0;
|
||||
}
|
||||
static void as_tick(as_server *s,uint64_t now){
|
||||
if(!s->enabled)return;
|
||||
if(s->listener<0){
|
||||
if(now<s->retry_at)return;
|
||||
struct sockaddr_in a;int reuse=1;s->listener=socket(AF_INET,SOCK_STREAM,0);
|
||||
if(s->listener<0)goto listener_error;
|
||||
setsockopt(s->listener,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
|
||||
if(!as_nonblock(s->listener))goto listener_error;
|
||||
memset(&a,0,sizeof(a));a.sin_family=AF_INET;a.sin_port=htons(s->port);a.sin_addr.s_addr=htonl(s->ip);
|
||||
if(bind(s->listener,(struct sockaddr *)&a,sizeof(a))<0 || listen(s->listener,AS_PEERS)<0)goto listener_error;
|
||||
s->running=1;s->error=0;s->os_error=0;
|
||||
}
|
||||
/* One accept and one I/O action per peer per scan; fixed work/storage bounds. */
|
||||
int fd=accept(s->listener,NULL,NULL);
|
||||
if(fd>=0){unsigned i;for(i=0;i<AS_PEERS;i++)if(s->peers[i].fd<0)break;
|
||||
if(i==AS_PEERS || !as_nonblock(fd))close(fd);else{s->peers[i].fd=fd;s->peers[i].deadline=now+60000;}}
|
||||
else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR)goto listener_error;
|
||||
for(unsigned i=0;i<AS_PEERS;i++){
|
||||
as_peer *p=&s->peers[i];if(p->fd<0)continue;
|
||||
if(now>=p->deadline){as_peer_close(p);continue;}
|
||||
if(p->length){
|
||||
ssize_t n=send(p->fd,p->tx+p->sent,p->length-p->sent,MSG_NOSIGNAL);
|
||||
if(n>0){p->sent+=(size_t)n;if(p->sent==p->length){s->replied++;p->length=0;p->sent=0;p->received=0;p->needed=7;p->deadline=now+60000;}}
|
||||
else if(n==0 || (errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR))as_peer_close(p);
|
||||
}else{
|
||||
ssize_t n=recv(p->fd,p->rx+p->received,p->needed-p->received,0);
|
||||
if(n>0){if(p->received==0)p->deadline=now+5000;p->received+=(size_t)n;
|
||||
if(p->received==7){uint16_t length=as_get16(p->rx+4);if(as_get16(p->rx+2)!=0 || length<2 || length>254){as_peer_close(p);continue;}p->needed=6u+length;}
|
||||
if(p->received==p->needed){as_reply(s,p);p->deadline=now+5000;}
|
||||
}else if(n==0 || (errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR))as_peer_close(p);
|
||||
}
|
||||
}
|
||||
return;
|
||||
listener_error:
|
||||
s->os_error=errno;s->error=1;as_stop(s);s->retry_at=now+s->retry;
|
||||
}
|
||||
uint32_t as_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t table,uint16_t addr,uint16_t q,uint16_t *data,uint32_t cap,uint32_t *out,uint64_t now,uint32_t retry){
|
||||
as_server *s;unsigned i;uint32_t result=0;
|
||||
if(cmd==AS_OPEN){
|
||||
memset(out,0,24*sizeof(*out));if(!port || !retry)return AM_ARGUMENT;
|
||||
for(i=0;i<AS_POOL;i++)if(!as_slots[i].used && as_slots[i].generation<0xffffffu)break;
|
||||
if(i==AS_POOL)return 257;
|
||||
s=&as_slots[i];uint32_t generation=s->generation+1;memset(s,0,sizeof(*s));s->generation=generation;s->used=1;s->listener=-1;s->ip=ip;s->port=port;s->retry=retry;
|
||||
for(unsigned j=0;j<AS_PEERS;j++){s->peers[j].fd=-1;as_peer_close(&s->peers[j]);}
|
||||
h=(generation<<8)|(i+65u);as_snapshot(s,h,out);return 0;
|
||||
}
|
||||
i=(h&255u);if(i<65 || i>=65+AS_POOL){memset(out,0,24*sizeof(*out));return 256;}i-=64;
|
||||
if( !as_slots[i-1].used || as_slots[i-1].generation!=(h>>8)){memset(out,0,24*sizeof(*out));return 256;}
|
||||
s=&as_slots[i-1];
|
||||
switch(cmd){
|
||||
case AS_CLOSE:as_stop(s);s->used=0;as_snapshot(s,0,out);return 0;
|
||||
case AS_ENABLE:s->enabled=1;break;
|
||||
case AS_DISABLE:s->enabled=0;as_stop(s);break;
|
||||
case AS_TICK:as_tick(s,now);break;
|
||||
case AS_PUSH:case AS_PULL:
|
||||
if(table>3 || !q || q>AS_ITEMS || (uint32_t)addr+q>65536u)result=AM_ARGUMENT;
|
||||
else if(!data || cap<q)result=258;
|
||||
else if(cmd==AS_PUSH){
|
||||
if(table<2){for(unsigned j=0;j<q;j++)if(data[j]>1){result=AM_ARGUMENT;break;}}
|
||||
if(!result){s->base[table]=addr;s->count[table]=q;memcpy(s->values[table],data,q*sizeof(*data));}
|
||||
}else if(s->base[table]!=addr || s->count[table]!=q)result=AM_ARGUMENT;
|
||||
else memcpy(data,s->values[table],q*sizeof(*data));
|
||||
break;
|
||||
default:result=259;break;
|
||||
}
|
||||
as_snapshot(s,h,out);return result;
|
||||
}
|
||||
void as_shutdown(void){for(unsigned i=0;i<AS_POOL;i++)if(as_slots[i].used){as_stop(&as_slots[i]);as_slots[i].used=0;}}
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef ATOM_SERVER_H
|
||||
#define ATOM_SERVER_H
|
||||
#include <stdint.h>
|
||||
enum { AS_OPEN=30,AS_CLOSE,AS_ENABLE,AS_DISABLE,AS_TICK,AS_PUSH,AS_PULL };
|
||||
uint32_t as_call(uint32_t,uint32_t,uint32_t,uint16_t,uint8_t,uint16_t,uint16_t,uint16_t *,uint32_t,uint32_t *,uint64_t,uint32_t);
|
||||
void as_shutdown(void);
|
||||
#endif
|
||||
Reference in New Issue
Block a user