Initial checkin of "pab014share"

This *was not written by me*. I'm just checking it in to preserve it.
I emailed the original creator - michael.russe@cox.net - and gotten no
response. Until I hear otherwise, I'm going to assume a sufficiently
permissive license to allow me to modify this program for my uses.
This commit is contained in:
Eli Ribble 2023-05-23 16:57:22 -07:00
parent 0f048d61dd
commit 606770f64e
11 changed files with 2857 additions and 0 deletions

42
Makefile Normal file
View File

@ -0,0 +1,42 @@
PRGS = padec iFlow iPump iComII iPmon
IPRG = aprs485
HLOG = aprs485
%: %.c aprs485.h; gcc -O -Wall -I. -o $* $*.c -lm
all: $(PRGS) $(IPRG)
clean:; @l=`echo $(PRGS) $(IPRG)` ; for f in $$l ; do if [ -f $$f ] ; then rm -f $$f; fi ; done
# this is what I use....
INSDIR = /usr/local/bin
VLOGDIR = /var/log
HLOGDIR = /home/log
UOWN = root
UGRP = bin
IFLAGS = -o $(UOWN) -g $(UGRP) --backup=numbered
TGT := $(shell basename `pwd`)
PNM := $(shell echo $(TGT) | sed "s=[0-9]*==g")
BUPRT = ~/BUP/$(PNM)
install: $(IPRG)
@if [ `whoami` != "root" ] ; then echo YOU NEED TO BE root TO UPDATE INSTALLATION ; \
else \
for l in $(VLOG); do \
dd=`echo $(VLOGDIR)/$$l`; \
if [ -d $$dd ]; then echo "- ok -" $$dd; else \
echo CREATE $$dd; install -o $(UOWN) -g $(UGRP) -m 0777 -d $$dd ; fi ; \
done; \
for l in $(HLOG); do \
dd=`echo $(HLOGDIR)/$$l`; \
if [ -d $$dd ]; then echo "- ok -" $$dd; else \
echo CREATE $$dd; install -o $(UOWN) -g $(UGRP) -m 0777 -d $$dd ; fi ; \
done; \
dd=`echo $(INSDIR)` ; for f in $(IPRG) ; do \
if cmp -s $$f $$dd/$$f; then echo "- ok -" $$dd/$$f; else \
echo UPDATE $$dd/$$f; install $(IFLAGS) $$f $$dd ; fi ; \
done ; \
fi

331
README.txt Normal file
View File

@ -0,0 +1,331 @@
WHEN: September 2009
WHO: michael.russe@cox.net
WHAT: Controlling an IntelliFlow VS pump sans Pentair controller.
WHY: I bought an IntelliFlow VS pump for my pool in 2007 and was surprised
to learn that I had to get a Pentair Controller to use most of its
advanced features. Granted, you can run it like a 'regular pump' by
switching power ON/OFF and get the benefit of a variable speed pump that
can regulate its flow, but you are stuck with a manual setting of the
flow rate on the pump.
Talking to Pentair, I was told out that the protocol on the RS485 bus
is proprietary and therefore there is no documentation available to
anybody.
The most cost effective Pentair solution to this problem is the Pentair
IntelliCom* controller, that will 'talk' to the pump in response to
activation of one of 4 inputs if the pump is in the 'right' mode. It is
still a $385 list solution with the only benefit being able to run
4 different 'Ext.Ctrl' programs on a VS or to select one of the 4 speeds
of a 4x160. This is, at least in my book, not good enough to justify the
investment, so I put this project on the shelf until I had some more
time to deal with this shortcoming.
Early 2009 research on the Internet surfaced that the hardware protocol
is 9600N81 and that this bus is quite active once a Pentair controller
is on it, which explained why one does not see any activity if one
looks for data in a system with just 'the pump'. It turns out the pump
is quite communicative once you know how to ask it for information. In
addition to that it will take instructions on what to do!
Called Pentair again, they had not changed their position on sharing
the protocol, this time around I was told some mambo about safety ...
so I decided to figure out what is so special about it, that they will
not share this information.
HOW: I wrote a collection of small programs on a Linux system that put me
into the position to monitor and log bus traffic on a life Pentair bus
with a controller that is running the show. All one needs is a cheap
RS485 adapter hooked up to a serial port of a computer. Friends chimed
in and sent me lots of raw data from their systems for analysis.
HOWTO: This collection comes with a Makefile and all the sources. A 'make'
does compile all programs in the current directory on a Linux 2.6.21.5
with make 3.81 and gcc 3.4.1. There are NO errors or warnings!
(FYI: on my system 'vi' is setup with 'ts=8 sw=8')
It helps to be fluent in 'C' and Linux to understand and modify the
programs. Due to portability and demonstration purposes all programs
are single threaded and use the 'select' system call to do multi point
I/O and timeouts.
An understanding of sockets helps, but is not really necessary. The
important thing is that a SOCK_DGRAM socket in Linux never returns
an incomplete packet on a read() call as long as the packets are small.
This is not true for the <tty> interface, since it is byte oriented
and does not do packages without a defined end of package indicator,
which does not exist in this application.
APRS485 access point to RS485 network
=====================================
NAME aprs485 - RS485 access point
USAGE aprs485 [-<options>] <bus>
INFO All programs in this collection use the servives of this access point
server to communicate with the RS485 half-duplex bus. The access point
uses datagrams for communication. It provides the ability for multiple
programs to share the physical RS485 bus. Programs connect to 'tabs'
in the access point server. All 'tabs' receive data occurring on the
bus and may send data to the bus.
<bus> can be either a <tty> like '/dev/ttyS4' with a RS485 adapter or
an IP address of a tunnel device port. A tunnel device could be
the TCP port of a wireless communication endpoint.
options:
-d Debug mode, aprs485 will not go into the background and print
all activity of the access point with a timestamp.
Hit <Escape> to terminate the program.
-p <#> Use alternate portnumber <#> as server port (default is 10485).
This comes in handy if you need to run two or more instances of
'aprs485' on the same computer.
-l <dir> Creates log files in directory <dir>. The filenames used are
constructed from the current date on the machine: 'YYMMDD.log'.
Files are appended to and created if they don't exist. The log
file records are ASCII, 1 record per line. They start with the
date followed by a timestamp, a single character record type,
a size field and the message. Most messages are a hexadecimal
representation of data on the bus.
API 'aprs485.h' contains code to support 'easy' attachment to a tab.
#define APRS485_API 1
#include "aprs485.h"
int main(int ac, char **av)
{
int bd, n;
char msg[128];
if ((bd = hub_at(0,0)) < 0) return -1;
n = read(bd,msg,sizeof(msg)); /* would be a receive */
if (0) write(bd,"hello",5); /* would be a send */
hub_dt(bd);
return 0;
}
The protocol
============
The protocol uses short binary packages for information exchange. The
packages have variable size. The minimum length is 8 bytes and the
theoretical maximum is 6+256+2=264 bytes. The largest I have seen on
a live Pentair bus is 37 bytes.
The format is:
<ldb> <sub> <dst> <src> <cfi> <len> [<data>] <ckh> <ckl>
<lpb> - leading packet byte, 0xa5
<sub> - ?
<dst> - destination address
<src> - source address
<cfi> - command/function/instruction
<len> - size of data field (may be 0!)
<data ...>
<ckh> - most significant byte of checksum
<ckl> - least significant byte of checksum
The checksum is a 16 bit unsigned sum of all bytes in the message up to
the checksum field.
Most packages are preceded by a preamble of 1 or more 0xff bytes and a
0x00 0xff sequence.
Every device on the bus has an address:
0x0f - is the broadcast address, it is used by the more sophisticated
controllers as <dst> in their system status broadcasts most
likely to keep queries for system status low.
0x1x - main controllers (IntelliComII, IntelliTouch, EasyTouch ...)
0x2x - remote controllers
0x6x - pumps, 0x60 is pump 1
Apart from unsolicited broadcasts, information exchange is done by
a device A sending a message to device B and device B sending an answer
to device A. Messages for simple exchanges only differ in the fact
that <dst> and <src> are swapped.
For example:
C: A5 00 60 10 04 01 ff 02 19
P: A5 00 10 60 04 01 ff 02 19
is a request from the controller to the pump to turn panel control off,
which enables it to send other commands to the pump. The pump confirms
the action in the answer.
The following sequence will turn panel control back on:
C: A5 00 60 10 04 01 00 01 1A
P: A5 00 10 60 04 01 00 01 1A
The interpretation of a <cfi> depends on the destination of a message,
meaning a <cfi> for one device might mean something else for another.
And there are exceptions to this protocol. The IntelliChlor C40 puts
messages on the bus that start with a 0x10 0x02 sequence, a data field,
a single byte checksum and a 0x10 0x03 trailer sequence... The checksum
is the unsigned sum over the data field + 18.
There are many <cfi>s I have seen in data dumps, the interpretation of
the datafield is somewhat cumbersome without knowing the system, so my
focus is more on messages to and from a pump.
However, here are some basics I found:
A <cfi> to a controller seems to work like this:
bits <76543210>
00xxxxxx - ?
01xxxxxx - ?
10xxxxxx - transfer(write) <cfi>&0x3f to controller
controller acknowledges the write with <0x01><0x01><cfi>
11xxxxxx - request <cfi>&0x3f from controller
the controller broadcasts it in response
My pump is an IntelliFlow VS, it does follow instructions from an
IntelliComII controller, provided the external programs are setup and
enabled. It has to be in FILTER mode AND Started to make it go.
Unlike other controllers, which take over full control of the pump, the
IntelliComII grabs control only for a short interval of time, every 30
seconds, to communicate what external program to run. If all inputs are
off it does not even do that after it has had a respone from the pump.
The sequence for input 1 active is:
C: A500 d=60 s=10 c=04 l=01 FF <0219> SETCTRL remote
P: A500 d=10 s=60 c=04 l=01 FF <0219> CTRL is remote
* C: A500 d=60 s=10 c=01 l=04 03210008 <0146> WRITE (0x0008) to 0x0321
P: A500 d=10 s=60 c=01 l=02 0008 <0120> VALIS (0x0008)
C: A500 d=60 s=10 c=04 l=01 00 <011A> SETCTRL local
P: A500 d=10 s=60 c=04 l=01 00 <011A> CTRL is local
* C: A500 d=60 s=10 c=01 l=04 03210000 <013E> is a stop
* C: A500 d=60 s=10 c=01 l=04 03210010 <014E> is program 2
* C: A500 d=60 s=10 c=01 l=04 03210018 <0156> is program 3
* C: A500 d=60 s=10 c=01 l=04 03210020 <015E> is program 4
If one quits repeating the sequence for about a minute the pump stops.
The IntelliComII is not aware of the status of the pump and will keep
repeating the sequence as long as an input is active. You can stop and
start the pump with the START/STOP button on the control panel anytime,
unless, of course, you hit the period when it is in remote control.
More decoding of binary data from an IntelliTouch controlled system
with a VS pump surfaced that there is a status report from the pump.
It is only obtainable when the pump is in remote control.
C: A500 d=60 s=10 c=07 l=00 <011C> SEND status
P: A500 d=10 s=60 c=07 l=0f 0A0602024A08AC120000000A000F22 <028A>
RUN 0a Started
MOD 06 Feature 1
PMP 02 ? drive state
PWR 024a 586 WATT
RPM 08ac 2220 RPM
GPM 12 18 GPM
PPC 00 0 %
b09 00 ?
ERR 00 ok
b11 0a ?
TMR 00 0 MIN
CLK 0f22 15:34
The above sequence is embedded within the cyclic exchange of data
between the controller and the pump. The full cyclic sequence is:
C: A500 d=60 s=10 c=04 l=01 FF <0219> SETCTRL remote
P: A500 d=10 s=60 c=04 l=01 FF <0219> CTRL is remote
C: A500 d=60 s=10 c=01 l=04 02E40012 <0212> WRITE (18) to 0x02e4
P: A500 d=10 s=60 c=01 l=02 0012 <012A> VALIS (18)
C: A500 d=60 s=10 c=05 l=01 06 <0121> SETMOD 06 (Feature 1)
P: A500 d=10 s=60 c=05 l=01 06 <0121> MOD is 06
C: A500 d=60 s=10 c=06 l=01 0A <0126> SETRUN 0a Started
P: A500 d=10 s=60 c=06 l=01 0A <0126> RUN is 0a Started
C: A500 d=60 s=10 c=07 l=00 <011C> SEND status
P: A500 d=10 s=60 c=07 l=0f 0A0602024908B1120000000A000F22 <028E>
The controller never releases the pump as long as it is in AUTO mode.
The display on the pump shows "Display not active..." and the LEDs
above FEATURE 1 and START/STOP are on. Experiments with my pump showed
that one can change the GPM setpoint 0x02e4 on the fly, it follows it!
If the controller releases the pump the cyclic sequence changes to:
C: A500 d=60 s=10 c=04 l=01 00 <011A> SETCTRL local
P: A500 d=10 s=60 c=04 l=01 00 <011A> CTRL is local
It is important for any serious controller implementation to know when a
pump runs into trouble and the Pentair IntelliFlow VS is fully capable
of doing that!
Data decoder
============
NAME padec - pabus data decoder
USAGE padec [-<options>] <datafile>
INFO Offline binary <datafile> decoder. Prints to standard output.
The <datafile> may be a raw dump of traffic from a Pentair
RS485 bus or the <logfile> of 'palog'.
Options (default is to 'skip it' if not set):
-d decode messages
-s print preamble bytes (if you really care)
-a print record positions in <datafile>
-f <#> print only messages from/to address <#>
-r print full decode of repeated messages
-h print 'palog' records
-t print timestamps, only useful if data is from 'palog'
Should you decide to experiment with the programs below ...
!BE WARE YOU ARE ON YOUR OWN!
!! THE FOLLOWING PROGRAMS PUT DATA ON THE BUS !!
! DO NOT USE THIS IF YOU ALREADY HAVE A CONTROLLER IN YOUR SYSTEM !
! MAKE SURE YOU CAN KILL POWER TO THE PUMP BY A HW SWITCH ANYTIME !
You will need the HW switch in case things get out of control!
Especially when you start messing with the code and work on a live system.
Simulator Program
=================
NAME iFlow - IntelliFlow simulator on pabus
USAGE iFlow [<ap>]
INFO Attaches to an 'aprs485' tab and emulates bus behavior of an IntelliFlow
pump. This program can be used to test controller software before you
let it go for the real thing. It is not a full implementation, but has
enough to get the basic bugs out of controller programs, and then, one
can always add to it...
Program exits if you type 'q' followed by a <Return> or it receives an
EOT from the hub.
Simple Controller programs
==========================
The programs in this section will exit if they detect traffic on the bus.
NAME iComII - IntelliComII emulator on pabus
USAGE iComII [<ap>]
INFO Interactive program, attaches to access point tab and emulates behavior
of an IntelliComII controller. It has an 'extra' feature which gives
you, the user, the ability to pull status from the pump. The pump will
will follow your commands, if the external programs are setup AND
it is in FILTER mode AND the START/STOP light is on.
The commands are:
q - quit
s - pump status
0 - all inputs off
1 - input 1 active
2 - input 2 active
3 - input 3 active
4 - input 4 active
The prompt displays the version number of the program, the count down
timer for the next cyclic transmission and the currently active program
sent to the pump.
Program exits if you enter the 'quit' command or it receives an EOT
from the hub.
NAME iPump - IntelliFlow controller
USAGE iPump [<ap>]
INFO Experimental interactive controller program.
Program exits if you enter the 'quit' command or it receives an EOT
from the hub.
NAME iPmon - IntelliFlow pump status monitor
USAGE iPmon [<ap>]
INFO Attaches to access point tab and polls pump status every 15 seconds.
Program exits if you enter the 'quit' command or it receives an EOT
from the hub.

698
aprs485.c Normal file
View File

@ -0,0 +1,698 @@
/*
* aprs485 - RS485 access point/bridge/hub
*/
#include "aprs485.h"
#include <termios.h>
char version[] = "@(#) aprs485 0.09 20100514";
typedef struct { /* log entry */
tmv_t tim;
char buf[256-8];
} sle_t;
void slog(char *fmt, ...);
void slog_dump();
typedef struct {
tmv_t tim; /* pwr on time */
int pwr;
soa_t adr;
int pks;
u08_t pkb[UDPSIZ];
} tab_t;
struct {
int exit;
tmv_t tboot;
char bus[128]; /* bus adapter (tty or TCP socket) */
int esel; /* consecutive error count select() */
int esin; /* consecutive error count soc_in() */
int dbug;
char *ldir; /* log directory */
int lprd; /* log partial reads on bus */
int nrcv; /* bus receive buffer */
u08_t brcv[UDPSIZ];
tab_t tabs[8]; /* tab clients */
tab_t *tsnd; /* tab waiting to send */
int nsle; /* log mechanism */
sle_t sles[16];
} gl;
/*
* log mechanism
*/
static int tm2yymmdd(struct tm *tm)
{
return (tm->tm_year%100)*10000+(tm->tm_mon+1)*100+tm->tm_mday;
}
void slog(char *fmt, ...)
{
sle_t *se;
int *a, i;
se = &gl.sles[gl.nsle++];
if (gl.nsle >= NEL(gl.sles)) return;
gettimeofday(&se->tim,0);
a = (int *)&fmt; a++;
i = snprintf(se->buf,NEL(se->buf)-1,fmt,a[0],a[1],a[2],a[3],a[4],a[5]);
se->buf[i] = 0;
}
void slog_dump()
{
sle_t *se;
int ymd, lf, fd, k;
char *p, buf[BUFSIZ], lfn[BUFSIZ];
struct tm tm;
fd = lf = -1;
for (se = gl.sles, k = MIN(NEL(gl.sles),gl.nsle); --k >= 0; se++) {
p = buf;
localtime_r(&se->tim.tv_sec,&tm);
ymd = tm2yymmdd(&tm);
p += sprintf(p,"%06d",ymd);
p += sprintf(p," %02d:%02d:%02d.%03lu",tm.tm_hour,tm.tm_min,tm.tm_sec,se->tim.tv_usec/1000);
p += sprintf(p," %.*s",NEL(se->buf),se->buf);
if (p[-1] != '\n' && p[-1] != '\r') *p++ = '\n';
if (gl.ldir) {
if (fd < 0 || lf != ymd) {
if (fd >= 0) close(fd);
lf = ymd;
sprintf(lfn,"%s/%06d.log",gl.ldir,lf);
fd = open(lfn,O_WRONLY|O_CREAT|O_APPEND,0644);
}
if (fd >= 0) write(fd,buf,p-buf);
}
if (gl.dbug) *p++ = '\r', write(1,buf,p-buf);
}
if (fd >= 0) close(fd);
gl.nsle = 0;
}
/*
* tab management
*/
tab_t *soa2tab(soa_t *sa)
{
tab_t *t;
int n;
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++)
if (t->pwr != 1) continue;
else if (t->adr.sin_addr == sa->sin_addr &&
t->adr.sin_port == sa->sin_port) return t;
return 0;
}
/*
* tab[] client/server communication
* messages are 2 bytes:
*
* client server
* BEL <x> ACK <tab#> client request to attach to hub tab
* NAK <#tabs> error, no tab available
*
* DEL <x> ACK <x> client detach request
* NAK <x> error, not on a tab
*
* EOT 0 server exit
*/
int tab_man(soa_t *sa, u08_t *pkb, int pks)
{
tab_t *t;
int n;
if (pks != 2) return 0;
switch (pkb[0]) {
case BEL: /* request tab */
if ((t = soa2tab(sa)) == 0) {
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++)
if (t->pwr == 0) {
t->pwr = 1;
t->adr = *sa;
t->pks = 0;
gettimeofday(&t->tim,0);
break;
}
if (n < 0) t = 0;
else slog("T __ add [%d]",t-gl.tabs);
}
pkb[0] = t ? ACK : NAK;
pkb[1] = t ? (t-gl.tabs) : NEL(gl.tabs);
return 2;
break;
case DEL: /* drop tab */
if ((t = soa2tab(sa))) {
if (t == gl.tsnd) gl.tsnd = 0;
t->pwr = 0;
pkb[0] = ACK;
slog("T __ del [%d]",t-gl.tabs);
}
else pkb[0] = NAK;
return 2;
break;
}
return 0;
}
void snd2tabs(int sd, tab_t *ts, u08_t *pkb, int pks)
{
tab_t *t;
int n, k;
if (pks <= 0) return;
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++)
if (t->pwr == 1 && t != ts) {
k = sendto(sd,pkb,pks,0,&t->adr.sa,sizeof(t->adr.sa));
if (k != pks) {
t->pwr = 0;
slog("T __ drop [%d] %s",t-gl.tabs,k<0?strerror(errno):"");
}
}
}
char *hex(u08_t *b, int nb, char *hb, int nh)
{
char *h = hb;
int i;
for (i = MIN((nh/2-1),nb); --i >= 0; b++) {
*h++ = "0123456789abcdef"[(*b>>4)&0x0f];
*h++ = "0123456789abcdef"[(*b>>0)&0x0f];
}
*h = 0;
return hb;
}
/*
* debug I/O
*/
void usr_in(fd)
{
int k;
char buf[32];
if ((k = read(fd,buf,sizeof(buf))) != 1) {
slog("E __ term read()=%d %s",k,k<0?strerror(errno):"");
gl.exit = 1;
return;
}
switch (buf[0]) {
default: slog("D __ hit <Escape> to exit"); break;
case ESC: gl.exit = 1; break;
case 'D': gl.lprd ^= 1; slog("D __ log partial read %d now",gl.lprd); break;
}
}
/*
* bus I/O
*/
void bus_in(int bd)
{
int k;
char hb[128];
if ((k = NEL(gl.brcv) - gl.nrcv) <= 0) {
slog("E __ bus buffer overrun drop %d bytes",gl.nrcv);
gl.nrcv = 0; /* drop everything */
k = NEL(gl.brcv);
}
if ((k = read(bd,&gl.brcv[gl.nrcv],k)) <= 0) {
slog("E __ bus read()=%d %s",k,k<0?strerror(errno):"disconnect!");
gl.exit = 1;
return;
}
if (gl.lprd) slog("r %2d %s",k,hex(&gl.brcv[gl.nrcv],k,hb,sizeof(hb)));
gl.nrcv += k;
}
void bus_out(int sd, int bd)
{
tab_t *t;
int k;
char hb[UDPSIZ*2+4];
if ((t = gl.tsnd) == 0) return;
slog("S %2d %s",t->pks,hex(t->pkb,t->pks,hb,sizeof(hb)));
if ((k = write(bd,t->pkb,t->pks)) != t->pks) {
slog("E __ bus write(%d)=%d %s",t->pks,k,k<0?strerror(errno):"");
if (k < 0) gl.exit = 1;
}
else snd2tabs(sd,t,t->pkb,t->pks);
t->pks = 0;
gl.tsnd = 0;
}
/*
* client interface
*/
int str2av(char *line, char **av, int nav)
{
char *b, *s, *d, del;
int ac;
for (b = line; *b && *b != '\n' && *b != '\r'; b++);
*b = 0;
for (b = line, ac = 0; ac < nav; ) {
while (*b == ' ' || *b == '\t') b++;
if (*b == 0 || *b == '#') break;
if (*b == '\"') {
del = *b++;
av[ac] = b;
while (*b != 0 && (*b != del || b[-1] == '\\')) b++;
if (*b == del) *b++ = 0; else av[ac] -= 1;
for (s = d = av[ac]; *s && s < b; s++)
if (s > av[ac] && *s == del && d[-1] == '\\') d[-1] = del;
else if (s == d) d++;
else *d++ = *s;
*d = 0;
ac++;
}
else {
av[ac++] = b;
while (*b != 0 && *b != ' ' && *b != '\t') b++;
}
if (*b == 0) break;
*b++ = 0;
}
return ac;
}
void srv_client(int sd, soa_t *fa, int na, char **av)
{
tab_t *t;
int n;
u08_t *u;
char *r, *a, ans[UDPSIZ];
struct tm tm;
a = ans;
if (na <= 0 || !strcmp(av[0],"stat")) {
a += sprintf(a,"%s bus:%s",version+5,gl.bus);
localtime_r(&gl.tboot.tv_sec,&tm);
a += sprintf(a,"\nboot: %04d/%02d/%02d %02d:%02d:%02d",
tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
for (t = gl.tabs, n = NEL(gl.tabs); --n >= 0; t++) {
if (t->pwr == 0) continue;
a += sprintf(a,"\ntab[%d]",t-gl.tabs);
localtime_r(&t->tim.tv_sec,&tm);
a += sprintf(a," %04d/%02d/%02d %02d:%02d:%02d",
tm.tm_year+1900,tm.tm_mon+1,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
u = (u08_t *)&t->adr.sin_addr;
a += sprintf(a," %d.%d.%d.%d:%d",u[0],u[1],u[2],u[3],ntohs(t->adr.sin_port));
}
}
else if (!strcmp(av[0],"kill")) {
if (na != 2 || (n = strtol(av[1],&r,0)) < 0 || n >= NEL(gl.tabs) || r == av[1] || *r)
a += sprintf(a,"which one?");
else {
t = &gl.tabs[n];
if (t->pwr == 0) a += sprintf(a,"tab[%d] is not active",n);
else {
sendto(sd,EOT_STR,2,0,&t->adr.sa,sizeof(t->adr.sa));
t->pwr = 0;
slog("T __ kill [%d]",t-gl.tabs);
a += sprintf(a,"done");
}
}
}
else if (!strcmp(av[0],"exit")) {
a += sprintf(a,"bye!");
gl.exit = 1;
}
else if (!strcmp(av[0],"l") || !strcmp(av[0],"logdir")) {
a += sprintf(a,"%s",gl.ldir?gl.ldir:"none");
}
else a += sprintf(a,"what?");
if (a > ans) sendto(sd,ans,a-ans,0,&fa->sa,sizeof(fa->sa));
}
/*
* socket I/O
*/
void soc_in(int sd)
{
tab_t *t;
soa_t fa;
int pks, k;
u08_t pkb[UDPSIZ+4];
char *av[32];
socklen_t len;
len = sizeof(fa.sa);
pks = recvfrom(sd,pkb,sizeof(pkb)-4,0,&fa.sa,&len);
if (pks <= 0) {
if (++gl.esin >= 3) gl.exit = 1;
slog("E __ socket read()=%d %s",pks,pks<0?strerror(errno):"");
return;
}
gl.esin = 0;
if (pks <= 2 && (k = tab_man(&fa,pkb,pks)) > 0) {
sendto(sd,pkb,k,0,&fa.sa,len);
return;
}
if (pkb[0] == 'c' && pks >= 6 && !strncmp((char *)pkb,"client",6)) {
pkb[pks] = 0;
if ((k = str2av((char *)pkb+6,av,NEL(av))) > 0)
srv_client(sd,&fa,k,av);
return;
}
if (gl.tsnd) return; /* busy, drop it */
if ((t = soa2tab(&fa)) == 0) return; /* not registered */
bcopy(pkb,t->pkb,t->pks=pks);
gl.tsnd = t;
}
int a5end(u08_t *pk, int np)
{
pa5_t *p;
u08_t *b, *e;
for (e = (b = pk) + np; b < e && *b != 0xa5; b++);
if (b >= e) return np;
p = (pa5_t *)b;
b = &p->dat[p->len+2];
if (b >= e || *b != 0xff) return np;
return b - pk;
}
void soc_out(int sd)
{
int n, i, k;
char hb[UDPSIZ*2+4];
if ((n = gl.nrcv) > 2) {
for (i = 0; i < n; i += k) {
k = a5end(gl.brcv+i,n-i);
slog("R %2d %s",k,hex(gl.brcv+i,k,hb,sizeof(hb)));
snd2tabs(sd,0,gl.brcv+i,k);
}
}
else slog("E %2d %s noise?",n,hex(gl.brcv,n,hb,sizeof(hb)));
gl.nrcv = 0;
}
/*
* bridge server
*/
#define MTICK (15*60)
#define TBRTO 20000 /* bus read timeout (end of package) [microseconds] */
int bridge(int bd, int sd)
{
fd_set pfds, rfds;
tmv_t tc, tv;
int md, ct, lt, n;
FD_ZERO(&pfds);
if (gl.dbug) FD_SET(0,&pfds);
FD_SET(bd,&pfds);
FD_SET(sd,&pfds);
md = MAX(bd,sd) + 1;
ct = lt = -1;
slog("B __ %s bus:%s",version+5,gl.bus);
while (!gl.exit) {
gettimeofday(&tc,0);
if ((ct = (tc.tv_sec%(60*60))/MTICK) != lt) { /* mark tick */
if (lt >= 0) slog("M __ %s bus:%s",version+5,gl.bus);
lt = ct;
}
slog_dump();
tv.tv_sec = tv.tv_usec = 0;
if (gl.nrcv <= 0 && gl.tsnd == 0) {
tv.tv_sec = ((tc.tv_sec/MTICK)+1)*MTICK;
timersub(&tv,&tc,&tv);
}
if (tv.tv_sec == 0 && tv.tv_usec < TBRTO)
tv.tv_usec = TBRTO;
rfds = pfds;
if ((n = select(md,&rfds,0,0,&tv)) < 0) {
if (++gl.esel >= 3) gl.exit = 1;
slog("E __ select() %s",strerror(errno));
continue;
}
gl.esel = 0;
if (n == 0) {
if (gl.nrcv > 0) soc_out(sd);
else if (gl.tsnd) bus_out(sd,bd);
}
else {
if (gl.dbug && FD_ISSET(0,&rfds)) usr_in(0);
if (FD_ISSET(bd,&rfds)) bus_in(bd);
if (FD_ISSET(sd,&rfds)) soc_in(sd);
}
}
snd2tabs(sd,0,(u08_t *)EOT_STR,2);
slog("X __ %s bus:%s",version+5,gl.bus);
slog_dump();
return 0;
}
int open_aptty(char *dev)
{
int bd, eno;
struct termios tio;
if ((bd = open(dev,O_RDWR|O_NOCTTY|O_NDELAY)) < 0)
return -1;
while (1) {
tcflush(bd,TCIOFLUSH);
if (tcgetattr(bd,&tio) < 0) break;
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = CREAD|CLOCAL|B9600|CS8;
tio.c_lflag = 0;
tio.c_line = 0;
tio.c_cc[VMIN] = 0;
tio.c_cc[VTIME] = 0;
if (tcsetattr(bd,TCSANOW,&tio) < 0) break;
return bd;
}
eno = errno;
close(bd);
errno = eno;
return -1;
}
int open_apsoc(char *ip, char *err)
{
soa_t sa;
int bd, i;
char *p, *r, buf[BUFSIZ];
struct hostent *h;
if (ip == 0 || strlen(ip) >= NEL(buf)) {
if (err) sprintf(err,"name too long");
return -1;
}
strcpy(buf,ip);
bzero(&sa,sizeof(sa));
sa.sin_fmly = AF_INET;
if ((p = strrchr(buf,':')) == 0) {
if (err) sprintf(err,"port not specified");
return -1;
}
*p++ = 0;
i = strtol(p,&r,10);
if (r <= p || *r || i <= 0 || i >= 0xffff) {
if (err) sprintf(err,"bad port '%s'",p);
return -1;
}
sa.sin_port = htons(i);
if (*(p = buf) == 0) p = "localhost";
if ((h = gethostbyname(p)) == 0) {
if (err) sprintf(err,"%s",hstrerror(h_errno));
return -1;
}
bcopy(h->h_addr_list[0],&sa.sin_addr,h->h_length);
if ((bd = socket(sa.sin_fmly,SOCK_STREAM,0)) < 0) {
if (err) sprintf(err,"%s",strerror(errno));
return -1;
}
if (connect(bd,&sa.sa,sizeof(sa.sa))) {
if (err) sprintf(err,"%s",strerror(errno));
close(bd);
return -1;
}
fcntl(bd,F_SETFL,O_NONBLOCK);
return bd;
}
#include <signal.h>
void sighandler(int sig)
{
switch (sig) {
case SIGHUP:
case SIGTERM: gl.exit++; break;
}
}
void sigsetup()
{
struct sigaction action;
sigaction(SIGHUP,NULL,&action);
action.sa_handler = sighandler;
action.sa_flags = 0;
sigaction(SIGHUP, &action,0);
sigaction(SIGTERM,&action,0);
sigaction(SIGALRM,&action,0);
}
int av2str(int na, char **av, char *str, int sob)
{
char *s, *d, *e;
int i;
if ((d = str) && sob > 0) {
for (e = d + sob - 1, i = 0; i < na && d < e; i++) {
if (i) *d++ = ' ';
for (s = av[i]; d < e && (*d = *s++); d++);
}
*d = 0;
}
return d - str;
}
int client(int sd, soa_t *sa, int ac, char **av)
{
fd_set rds;
tmv_t tv;
int n;
char buf[UDPSIZ];
n = sprintf(buf,"client ");
if (ac <= 0) n += sprintf(buf+n,"stat");
else n += av2str(ac,av,buf+n,sizeof(buf)-1-n);
if (sendto(sd,buf,n,0,&sa->sa,sizeof(sa->sa)) != n) return errno;
FD_ZERO(&rds);
FD_SET(sd,&rds);
tv.tv_sec = 1; tv.tv_usec = 0;
if ((n = select(sd+1,&rds,0,0,&tv)) < 0) return errno;
if (n == 0) return ETIME;
if ((n = read(sd,buf,sizeof(buf))) < 0) return errno;
printf("%.*s\n",n,buf);
return 0;
}
int main(int ac, char **av)
{
char *p;
soa_t sa;
int bd, sd, k, eno;
struct termios tio[2];
bzero(&gl,sizeof(gl));
gettimeofday(&gl.tboot,0);
bzero(&sa,sizeof(sa));
sa.sin_fmly = AF_INET;
sa.sin_port = htons(APRS485_PORT);
for (k = 0; ++k < ac; )
if (*(p = av[k]) == '-')
switch (*(++p)) {
default:
USAGE: printf("usage: [-<options>] <bus>\n");
printf("-p <port> - server port (default: %d)\n",APRS485_PORT);
printf("-l <logdir> - log directory\n");
printf("-d - debug, don't fork\n");
printf("-D - log partial reads from <bus>\n");
printf("<bus> - <ttydev> or <ipaddr:port>\n");
printf("-c <cmd ..> - send <cmd> to running server\n");
return EINVAL;
break;
case 'd': gl.dbug++; break;
case 'D': gl.lprd++; break;
case 'c':
if ((sd = socket(sa.sin_fmly,SOCK_DGRAM,0)) < 0) {
printf("socket: %s\n",strerror(eno=errno));
return eno;
}
k++;
eno = client(sd,&sa,ac-k,av+k);
close(sd);
if (eno) printf("client: %s\n",strerror(eno));
return 0;
break;
case 'p':
if (++k >= ac) goto USAGE;
eno = strtol(av[k],0,0);
if (eno <= 1024 || eno > 0xffff) goto USAGE;
sa.sin_port = htons(eno);
break;
case 'l':
if (++k >= ac) goto USAGE;
gl.ldir = av[k];
break;
}
else if (gl.bus[0]) goto USAGE;
else if (*p == 'S' || (*p >= '0' && *p <= '9'))
sprintf(gl.bus,"/dev/tty%s",p);
else sprintf(gl.bus,"%.*s",sizeof(gl.bus)-1,p);
if (gl.bus[0] == 0) goto USAGE;
if (tcgetattr(0,&tio[0]) < 0) {
printf("tcgetattr: %s\n",strerror(eno=errno));
return eno;
}
if ((sd = socket(sa.sin_fmly,SOCK_DGRAM,0)) <= 0) {
printf("socket: %s\n",strerror(eno=errno));
return eno;
}
if (fcntl(sd,F_SETFL,O_NONBLOCK)) {
printf("fcntl(O_NONBLOCK): %s\n",strerror(eno=errno));
return eno;
}
if (bind(sd,&sa.sa,sizeof(sa.sa))) {
printf("bind to port %d: %s\n",ntohs(sa.sin_port),strerror(eno=errno));
return eno;
}
k = 1; setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&k,sizeof(k));
if (strchr(gl.bus,':')) {
char msg[128];
if ((bd = open_apsoc(gl.bus,msg)) <= 0) {
printf("%s: %s\n",gl.bus,msg);
close(sd);
return EINVAL;
}
}
else if ((bd = open_aptty(gl.bus)) <= 0) {
printf("%s: %s\n",gl.bus,strerror(eno=errno));
close(sd);
return eno;
}
if (!gl.dbug) {
if ((k = fork()) < 0) {
printf("fork: %s\n",strerror(eno=errno));
close(bd);
close(sd);
return eno;
}
if (k > 0) return 0; /* parent, go away */
/* child continues */
setsid();
close(0);
close(1);
close(2);
}
else {
tio[1] = tio[0];
cfmakeraw(&tio[1]);
tcsetattr(0,TCSANOW,&tio[1]);
printf("hit <Escape> to exit...\r\n");
}
sigsetup();
eno = bridge(bd,sd);
if (gl.dbug) tcsetattr(0,TCSANOW,tio);
close(bd);
close(sd);
return eno;
}

135
aprs485.h Normal file
View File

@ -0,0 +1,135 @@
#ifndef __pabus_h__
#define __pabus_h__
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#define APRS485_PORT 10485
#define EOT 0x04
#define EOT_STR "\004"
#define ENQ 0x05
#define ACK 0x06
#define BEL 0x07
#define NAK 0x15
#define CAN 0x18
#define ESC 0x1b
#define DEL 0x7f
#include <netdb.h>
#include <netinet/in.h>
typedef union {
struct sockaddr sa;
struct sockaddr_in sin;
} soa_t;
#define sin_fmly sin.sin_family
#define sin_port sin.sin_port
#define sin_addr sin.sin_addr.s_addr
#define UDPSIZ 1460 /* UDP package pay load before it gets chopped */
#include <sys/time.h>
#include <time.h>
typedef struct timeval tmv_t;
#define NEL(x) ((int)(sizeof(x)/sizeof(x[0])))
#define MAX(a,b) ((a)>=(b)?(a):(b))
#define MIN(a,b) ((a)<=(b)?(a):(b))
typedef unsigned char u08_t;
typedef unsigned short u16_t;
typedef unsigned long u32_t;
/* Pentair package header
*/
typedef struct {
u08_t lpb;
u08_t sub;
u08_t dst;
u08_t src;
u08_t cfi;
u08_t len;
u08_t dat[0];
} pa5_t;
#define PA5SIZ (32+6+256+2)
/* if you are using this in a program working with aprs485, these may come in handy */
#if APRS485_API == 1
int hub_at(char *ap, char *err)
{
char *p, *r;
fd_set fds;
soa_t soa;
tmv_t tmv;
int hd, k;
u08_t buf[BUFSIZ];
struct hostent *h;
if (ap == 0 || *ap == 0) ap = "localhost";
if (strlen(ap) >= (int)sizeof(buf)) {
if (err) sprintf(err,"name too long");
return -1;
}
strcpy((char *)buf,ap);
bzero(&soa,sizeof(soa));
soa.sin_fmly = AF_INET;
soa.sin_port = htons(APRS485_PORT);
if ((p = strrchr((char *)buf,':'))) {
*p++ = 0;
k = strtol(p,&r,10);
if (r <= p || *r || k <= 0 || k >= 0xffff) {
if (err) sprintf(err,"bad port '%s'",p);
return -1;
}
soa.sin_port = htons(k);
}
if (*(p = (char *)buf) == 0) p = "localhost";
if ((h = gethostbyname(p)) == 0) {
if (err) sprintf(err,"%s %s",p,hstrerror(h_errno));
return -1;
}
bcopy(h->h_addr_list[0],&soa.sin_addr,h->h_length);
if ((hd = socket(soa.sin_fmly,SOCK_DGRAM,0)) <= 0) {
if (err) sprintf(err,"socket: %s",strerror(errno));
return -1;
}
FD_ZERO(&fds); FD_SET(hd,&fds);
tmv.tv_sec = 0; tmv.tv_usec = 250000;
buf[0] = BEL;
buf[1] = 0xff;
if ((k = connect(hd,&soa.sa,sizeof(soa.sa))) ||
(k = write(hd,buf,2)) != 2 ||
(k = select(hd+1,&fds,0,0,&tmv)) <= 0 ||
(k = read(hd,buf,sizeof(buf))) != 2 ||
(buf[0] != ACK)) {
if (err) sprintf(err,"HUB %s",k<0?strerror(errno):k==0?"timeout":"busy");
close(hd);
return -1;
}
if (err) sprintf(err,"hub tab[%d]",buf[1]);
return hd;
}
void hub_dt(int hd)
{
fd_set fds;
tmv_t tmv;
u08_t buf[32];
FD_ZERO(&fds); FD_SET(hd,&fds);
tmv.tv_sec = 0; tmv.tv_usec = 250000;
buf[0] = DEL;
buf[1] = 0;
write(hd,buf,2);
select(hd+1,&fds,0,0,&tmv);
close(hd);
}
#endif
#endif

250
iComII.c Normal file
View File

@ -0,0 +1,250 @@
/*
* iComII - IntelliComII emulator on 'aprs485' hub tab
*
* Extras: pump status report
*/
#define APRS485_API 1
#include "aprs485.h"
#include "pa_iflo.h"
char version[] = "@(#) iComII 0.03";
struct {
u08_t cadr; /* this controllers address */
u08_t padr; /* pump address */
int ctrl;
int poll;
iflsr_t *pstat;
u08_t sbuf[PA5SIZ];
} gl;
pa5_t *pa5_hdr(void *buf, u08_t cfi)
{
pa5_t *ph;
bzero(ph=buf,sizeof(*ph));
ph->lpb = 0xa5;
ph->dst = gl.padr;
ph->src = gl.cadr;
ph->cfi = cfi;
return ph;
}
int pa5_snd(int bd, pa5_t *ps)
{
int n;
u16_t sum;
u08_t *b, *s, pa5[PA5SIZ];
for (b = pa5, n = 3; --n >= 0; *b++ = 0xff); b[-2] = 0;
n = sizeof(*ps) + ps->len;
for (sum = 0, s = &ps->lpb; --n >= 0; b++) sum += *b = *s++;
*b++ = sum>>8;
*b++ = sum;
return write(bd,pa5,b-pa5);
}
pa5_t *pa5_rcv(int bd, void *buf, int tmo)
{
pa5_t *pr;
u08_t *b, *c, *e;
fd_set rfd;
tmv_t tv;
u16_t sum;
int n, k;
FD_ZERO(&rfd); FD_SET(bd,&rfd);
tv.tv_usec = tmo * 1000;
tv.tv_sec = tv.tv_usec/1000000;
tv.tv_usec -= tv.tv_sec*1000000;
if ((k = select(bd+1,&rfd,0,0,&tv)) <= 0) return 0;
if ((n = read(bd,buf,PA5SIZ)) < sizeof(pa5_t)) return 0;
for (e = (b = buf) + n; b < e && *b != 0xa5; b++);
if ((e - b) < sizeof(pa5_t)) return 0;
pr = (pa5_t *)b;
if (((c = &pr->dat[pr->len]) + 2) < e || pr->dst != gl.cadr) return 0;
for (sum = 0; b < c; sum += *b++);
if (sum != ((c[0]<<8)+c[1])) return 0;
return pr;
}
int pump_cmd(int bd, int cmd, int val)
{
pa5_t *ps, *pr;
char snd[sizeof(pa5_t)+4], pa5[PA5SIZ];
ps = pa5_hdr(snd,cmd);
ps->dat[ps->len++] = val;
if (pa5_snd(bd,ps) <= 0) return -1;
if ((pr = pa5_rcv(bd,pa5,500)) == 0) return -2;
if (pr->src != gl.padr) return -3;
if (pr->cfi != ps->cfi || pr->len != ps->len) return -4;
return pr->dat[0] == ps->dat[0] ? pr->dat[0] : -5;
}
int pump_reg(int bd, int adr, int val)
{
pa5_t *ps, *pr;
char snd[sizeof(pa5_t)+4], pa5[PA5SIZ];
ps = pa5_hdr(snd,0x01);
ps->dat[ps->len++] = adr>>8;
ps->dat[ps->len++] = adr>>0;
ps->dat[ps->len++] = val>>8;
ps->dat[ps->len++] = val>>0;
if (pa5_snd(bd,ps) <= 0) return -1;
if ((pr = pa5_rcv(bd,pa5,500)) == 0) return -2;
if (pr->src != gl.padr) return -3;
if (pr->cfi != ps->cfi || pr->len != 2) return -4;
adr = (pr->dat[0]<<8)+pr->dat[1];
return adr == val ? val : -5;
}
/*
* Extra, IntelliComII does not do this !
*/
iflsr_t *pump_stat(int bd, void *buf)
{
pa5_t *ps, *pr;
int ctrl;
char snd[sizeof(pa5_t)+4];
if ((ctrl = gl.ctrl) != IFLO_DSP_REM) {
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_REM);
if (gl.ctrl != IFLO_DSP_REM) return 0;
}
ps = pa5_hdr(snd,IFLO_SRG);
pr = pa5_snd(bd,ps) <= 0 ? 0 : pa5_rcv(bd,buf,500);
if (ctrl != IFLO_DSP_REM)
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_LOC);
if (pr == 0) return 0;
if (pr->src != gl.padr || pr->dst != gl.cadr) return 0;
if (pr->cfi != ps->cfi || pr->len != sizeof(iflsr_t)) return 0;
return (iflsr_t *)&pr->dat;
}
void pr_pstat(iflsr_t *p)
{
if (p == 0) printf("no status!\n");
else {
printf("run=%02x",p->run);
printf(" mod=%02x",p->mod);
printf(" pmp=%02x",p->pmp);
printf(" pwr=%d",ntohs(p->pwr));
printf(" rpm=%d",ntohs(p->rpm));
printf(" gpm=%d",p->gpm);
printf(" ppc=%d",p->ppc);
printf(" err=%02x",p->err);
printf(" tmr=%d",p->tmr);
printf(" %02d:%02d\n",p->clk[0],p->clk[1]);
}
}
typedef struct {
int rep;
int run;
int rr;
} ltc_t;
void ltic0(int bd, ltc_t *l)
{
int k;
if ((gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_REM)) == IFLO_DSP_REM) {
switch (l->rr) {
default: l->rr = 0;
/* FALLTHROUGH */
case 0: k = IFLO_EPRG_P0; break;
case 1: k = IFLO_EPRG_P1; break;
case 2: k = IFLO_EPRG_P2; break;
case 3: k = IFLO_EPRG_P3; break;
case 4: k = IFLO_EPRG_P4; break;
}
if (pump_reg(bd,IFLO_REG_EPRG,k) == k) l->run = l->rr;
if (gl.poll) gl.pstat = pump_stat(bd,&gl.sbuf);
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_LOC);
}
else l->run = l->rr = 0;
}
int uifc(int bd)
{
fd_set rfd;
tmv_t tv;
ltc_t *l, ltc;
int don, tic, n, k;
char buf[64], pa5[PA5SIZ];
bzero(l=&ltc,sizeof(*l));
l->rep = 30;
for (tic = don = 0; !don; ) {
if (--tic < 0) {
tic = 0;
k = gl.ctrl != IFLO_DSP_REM && gl.ctrl != IFLO_DSP_LOC;
if (k || l->run || l->run != l->rr || gl.poll) ltic0(bd,l);
switch (gl.ctrl) {
case IFLO_DSP_REM:
case IFLO_DSP_LOC:
tic = l->rep - 1;
break;
}
}
printf("\r%s ",version+5);
printf(l->run==0?"-- ":"%2d ",tic+1);
printf(gl.ctrl==IFLO_DSP_REM?"R> ":
gl.ctrl==IFLO_DSP_LOC?"%d> ":
"?> ",l->run);
fflush(stdout);
tv.tv_sec = 1; tv.tv_usec = 0;
FD_ZERO(&rfd); FD_SET(0,&rfd); FD_SET(bd,&rfd);
if ((n = select(bd+1,&rfd,0,0,&tv)) < 0 && errno != EINTR)
break;
if (n <= 0) continue;
if (FD_ISSET(bd,&rfd)) {
if ((n = read(bd,pa5,sizeof(pa5))) == 2 && pa5[0] == EOT)
printf("\rEOT from hub \n");
else printf("\rWe are not alone on the bus...\n");
break;
}
if (!FD_ISSET(0,&rfd)) continue;
if (read(0,buf,sizeof(buf)) <= 0) continue;
switch (buf[0]) {
case 'q': don = 1; break;
case 's': pr_pstat(pump_stat(bd,pa5)); break;
case '0': l->rr = 0, tic = 0; break;
case '1': l->rr = 1, tic = 0; break;
case '2': l->rr = 2, tic = 0; break;
case '3': l->rr = 3, tic = 0; break;
case '4': l->rr = 4, tic = 0; break;
default:
printf("q - quit\n");
printf("s - pump status\n");
printf("0 - all inputs off\n");
printf("1 - input 1 active\n");
printf("2 - input 2 active\n");
printf("3 - input 3 active\n");
printf("4 - input 4 active\n");
break;
}
}
return errno;
}
int main(int ac, char **av)
{
int bd;
char msg[128];
bzero(&gl,sizeof(gl));
gl.cadr = 0x10;
gl.padr = 0x60;
gl.ctrl = -1;
gl.poll = 1;
bd = hub_at(ac>1?av[1]:0,msg);
printf("%s: %s\n",av[0],msg);
if (bd < 0) return -1;
uifc(bd);
hub_dt(bd);
return 0;
}

251
iFlow.c Normal file
View File

@ -0,0 +1,251 @@
/*
* iFlow - IntelliFlow simulator on <bus>
*/
#define APRS485_API 1
#include "aprs485.h"
#include "pa_iflo.h"
char version[] = "@(#) iFlow 0.04";
struct {
u08_t adr;
u08_t dsp;
int eprg;
int chg;
u08_t sp_man_gpm;
u08_t sp_gpm;
u08_t sp_epgpm[5];
int sp_eprpm[5];
iflsr_t isr;
} gl;
pa5_t *pa5_rcv(u08_t *buf, int nbu)
{
pa5_t *pr;
u08_t *b, *c, *e;
u16_t sum;
int n;
if ((n = nbu) < sizeof(pa5_t)) return 0;
for (e = (b = buf) + n; b < e && *b != 0xa5; b++);
if ((e - b) < sizeof(pa5_t)) return 0;
pr = (pa5_t *)b;
if (((c = &pr->dat[pr->len]) + 2) < e || pr->dst != gl.adr) return 0;
for (sum = 0; b < c; sum += *b++);
if (sum != ((c[0]<<8)+c[1])) return 0;
return pr;
}
void pa5_snd(int bd, pa5_t *ps)
{
int n, k;
u16_t sum;
u08_t *b, *s, snd[PA5SIZ];
for (b = snd, n = 3; --n >= 0; *b++ = 0xff); b[-2] = 0;
n = sizeof(*ps) + ps->len;
for (sum = 0, s = &ps->lpb; --n >= 0; b++) sum += *b = *s++;
*b++ = sum>>8;
*b++ = sum>>0;
n = b - snd;
k = write(bd,snd,n);
if (k != n) printf("write(%d)=%d!!!\n",n,k);
}
void cmd_in(int bd, pa5_t *pr)
{
pa5_t *ps;
u16_t adr, val;
iflsr_t isn;
u08_t buf[64];
*(ps = (pa5_t *)buf) = *pr;
ps->dst = pr->src;
ps->src = gl.adr;
switch (pr->cfi) {
default: return; break;
case IFLO_REG:
if (pr->len != 4) return;
adr = (pr->dat[0]<<8)+pr->dat[1];
val = (pr->dat[2]<<8)+pr->dat[3];
switch (adr) {
default: return; break;
case IFLO_REG_SPGPM: gl.sp_gpm = val; break;
case IFLO_REG_EPRG:
switch (val) {
default: return; break;
case IFLO_EPRG_P0:
case IFLO_EPRG_P1:
case IFLO_EPRG_P2:
case IFLO_EPRG_P3:
case IFLO_EPRG_P4:
gl.eprg = val;
break;
}
break;
case IFLO_REG_EP1RPM: gl.sp_eprpm[1] = val; break;
case IFLO_REG_EP2RPM: gl.sp_eprpm[2] = val; break;
case IFLO_REG_EP3RPM: gl.sp_eprpm[3] = val; break;
case IFLO_REG_EP4RPM: gl.sp_eprpm[4] = val; break;
}
ps->len = 2;
ps->dat[0] = val>>8;
ps->dat[1] = val>>0;
break;
case IFLO_DSP:
if (pr->len != 1) return;
switch (pr->dat[0]) {
default: return; break;
case IFLO_DSP_LOC:
case IFLO_DSP_REM:
if (gl.dsp != pr->dat[0]) gl.chg = 5;
gl.dsp = ps->dat[0] = pr->dat[0];
break;
}
break;
case IFLO_MOD:
if (pr->len != 1) return;
if (gl.isr.mod != pr->dat[0]) gl.chg = 6;
gl.isr.mod = ps->dat[0] = pr->dat[0];
break;
case IFLO_RUN:
if (pr->len != 1) return;
switch (pr->dat[0]) {
default: return;
case IFLO_RUN_STRT:
case IFLO_RUN_STOP:
if (gl.isr.run != pr->dat[0]) gl.chg = 7;
gl.isr.run = ps->dat[0] = pr->dat[0];
break;
}
break;
case IFLO_SRG:
if (gl.dsp != IFLO_DSP_REM) return;
if (pr->len != 0) return;
gl.isr.gpm = 0;
if (gl.isr.run == IFLO_RUN_STRT) {
switch (gl.isr.mod) {
case IFLO_MOD_MANUAL:
if (gl.isr.gpm != gl.sp_man_gpm) gl.chg = 8;
gl.isr.gpm = gl.sp_man_gpm;
break;
case IFLO_MOD_FILTER:
case IFLO_MOD_EXT_P1:
case IFLO_MOD_EXT_P2:
case IFLO_MOD_EXT_P3:
case IFLO_MOD_EXT_P4:
switch (gl.eprg) {
case IFLO_EPRG_P0: gl.isr.gpm = gl.sp_epgpm[0]; gl.isr.mod = IFLO_MOD_FILTER; break;
case IFLO_EPRG_P1: gl.isr.gpm = gl.sp_epgpm[1]; gl.isr.mod = IFLO_MOD_EXT_P1; break;
case IFLO_EPRG_P2: gl.isr.gpm = gl.sp_epgpm[2]; gl.isr.mod = IFLO_MOD_EXT_P2; break;
case IFLO_EPRG_P3: gl.isr.gpm = gl.sp_epgpm[3]; gl.isr.mod = IFLO_MOD_EXT_P3; break;
case IFLO_EPRG_P4: gl.isr.gpm = gl.sp_epgpm[4]; gl.isr.mod = IFLO_MOD_EXT_P4; break;
}
break;
case 6:
if (gl.isr.gpm != gl.sp_gpm) gl.chg = 9;
gl.isr.gpm = gl.sp_gpm;
break;
}
}
else {
switch (gl.isr.mod) {
case IFLO_MOD_EXT_P1:
case IFLO_MOD_EXT_P2:
case IFLO_MOD_EXT_P3:
case IFLO_MOD_EXT_P4:
gl.chg++;
gl.isr.mod = IFLO_MOD_FILTER;
break;
}
}
if (gl.isr.gpm == 0) gl.isr.rpm = 0;
else gl.isr.rpm = gl.isr.gpm * 50;
gl.isr.pwr = (2000 * gl.isr.rpm) / 3450;
isn = gl.isr;
isn.pwr = htons(isn.pwr);
isn.rpm = htons(isn.rpm);
bcopy(&isn,ps->dat,ps->len=sizeof(isn));
break;
}
pa5_snd(bd,ps);
}
void iflow(int bd)
{
pa5_t *pr;
fd_set pfds, rfds;
tmv_t tc, tv;
int pks, n;
u08_t pkb[PA5SIZ];
struct tm tm;
FD_ZERO(&pfds);
FD_SET(0,&pfds);
FD_SET(bd,&pfds);
gettimeofday(&tc,0);
localtime_r(&tc.tv_sec,&tm);
while (1) {
tv.tv_sec = 30; tv.tv_usec = 0;
rfds = pfds;
if (gl.chg) printf("\n");
printf("\r%02d:%02d:%02d dsp=%02x run=%02x mod=%02x rpm=%-4d pwr=%-4d gpm=%-3d err=%02x ",
tm.tm_hour,tm.tm_min,tm.tm_sec,
gl.dsp,gl.isr.run,gl.isr.mod,gl.isr.rpm,gl.isr.pwr,gl.isr.gpm,gl.isr.err);
fflush(stdout);
gl.chg = 0;
if ((n = select(bd+1,&rfds,0,0,&tv)) < 0 && errno != EINTR) break;
if (n <= 0) continue;
gettimeofday(&tc,0);
localtime_r(&tc.tv_sec,&tm);
gl.isr.clk[0] = tm.tm_hour;
gl.isr.clk[1] = tm.tm_min;
if (FD_ISSET(bd,&rfds)) {
pks = read(bd,pkb,sizeof(pkb));
if (pks == 2 && pkb[0] == EOT) {
printf("EOT from hub\n");
break;
}
if ((pr = pa5_rcv(pkb,pks))) cmd_in(bd,pr);
}
if (FD_ISSET(0,&rfds)) {
n = read(0,pkb,sizeof(pkb));
if (n < 1 || pkb[0] == 'q') break;
if (pkb[0] == 'E') gl.isr.err ^= 0x80;
if (pkb[0] == 'S' && gl.dsp == IFLO_DSP_LOC) {
if (gl.isr.run != IFLO_RUN_STOP)
gl.isr.run = IFLO_RUN_STOP;
else gl.isr.run = IFLO_RUN_STRT;
}
}
}
}
int main(int ac, char **av)
{
int bd;
char msg[128];
bzero(&gl,sizeof(gl));
gl.adr = 0x60;
gl.dsp = IFLO_DSP_LOC;
gl.sp_man_gpm = 30;
gl.sp_epgpm[1] = 20;
gl.sp_epgpm[2] = 25;
gl.sp_epgpm[3] = 30;
gl.sp_epgpm[4] = 36;
gl.sp_eprpm[1] = 1000;
gl.sp_eprpm[2] = 2000;
gl.sp_eprpm[3] = 2500;
gl.sp_eprpm[4] = 3450;
gl.isr.run = IFLO_RUN_STOP;
gl.isr.pmp = IFLO_PMP_READY;
gl.isr.mod = IFLO_MOD_MANUAL;
bd = hub_at(ac>1?av[1]:0,msg);
printf("%s: %s\n",av[0],msg);
if (bd < 0) return -1;
iflow(bd);
hub_dt(bd);
return 0;
}

179
iPmon.c Normal file
View File

@ -0,0 +1,179 @@
/*
* iPmon - IntelliFlow status monitor on 'pabus'
*/
#define APRS485_API 1
#include "aprs485.h"
#include "pa_iflo.h"
char version[] = "@(#) iPmon 0.03";
struct {
u08_t cadr; /* this controllers address */
u08_t padr; /* pump address */
int ctrl;
int poll;
iflsr_t *pstat;
u08_t sbuf[PA5SIZ];
} gl;
pa5_t *pa5_hdr(void *buf, u08_t cfi)
{
pa5_t *ph;
bzero(ph=buf,sizeof(*ph));
ph->lpb = 0xa5;
ph->dst = gl.padr;
ph->src = gl.cadr;
ph->cfi = cfi;
return ph;
}
int pa5_snd(int bd, pa5_t *ps)
{
int n;
u16_t sum;
u08_t *b, *s, pa5[PA5SIZ];
for (b = pa5, n = 3; --n >= 0; *b++ = 0xff); b[-2] = 0;
n = sizeof(*ps) + ps->len;
for (sum = 0, s = &ps->lpb; --n >= 0; b++) sum += *b = *s++;
*b++ = sum>>8;
*b++ = sum;
return write(bd,pa5,b-pa5);
}
pa5_t *pa5_rcv(int bd, void *buf, int tmo)
{
pa5_t *pr;
u08_t *b, *c, *e;
fd_set rfd;
tmv_t tv;
u16_t sum;
int n, k;
FD_ZERO(&rfd); FD_SET(bd,&rfd);
tv.tv_usec = tmo * 1000;
tv.tv_sec = tv.tv_usec/1000000;
tv.tv_usec -= tv.tv_sec*1000000;
if ((k = select(bd+1,&rfd,0,0,&tv)) <= 0) return 0;
if ((n = read(bd,buf,PA5SIZ)) < sizeof(pa5_t)) return 0;
for (e = (b = buf) + n; b < e && *b != 0xa5; b++);
if ((e - b) < sizeof(pa5_t)) return 0;
pr = (pa5_t *)b;
if (((c = &pr->dat[pr->len]) + 2) < e || pr->dst != gl.cadr) return 0;
for (sum = 0; b < c; sum += *b++);
if (sum != ((c[0]<<8)+c[1])) return 0;
return pr;
}
int pump_cmd(int bd, int cmd, int val)
{
pa5_t *ps, *pr;
char snd[sizeof(pa5_t)+4], pa5[PA5SIZ];
ps = pa5_hdr(snd,cmd);
ps->dat[ps->len++] = val;
if (pa5_snd(bd,ps) <= 0) return -1;
if ((pr = pa5_rcv(bd,pa5,500)) == 0) return -2;
if (pr->src != gl.padr) return -3;
if (pr->cfi != ps->cfi || pr->len != ps->len) return -4;
return pr->dat[0] == ps->dat[0] ? pr->dat[0] : -5;
}
iflsr_t *pump_stat(int bd, void *buf)
{
pa5_t *ps, *pr;
int ctrl;
char snd[sizeof(pa5_t)+4];
if ((ctrl = gl.ctrl) != IFLO_DSP_REM) {
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_REM);
if (gl.ctrl != IFLO_DSP_REM) return 0;
}
ps = pa5_hdr(snd,IFLO_SRG);
pr = pa5_snd(bd,ps) <= 0 ? 0 : pa5_rcv(bd,buf,500);
if (ctrl != IFLO_DSP_REM)
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_LOC);
if (pr == 0) return 0;
if (pr->src != gl.padr || pr->dst != gl.cadr) return 0;
if (pr->cfi != ps->cfi || pr->len != sizeof(iflsr_t)) return 0;
return (iflsr_t *)&pr->dat;
}
void pr_pstat(iflsr_t *p)
{
if (p == 0) printf("no status!\n");
else {
printf("%02d:%02d",p->clk[0],p->clk[1]);
printf(" run=%02x",p->run);
printf(" mod=%02x",p->mod);
printf(" pmp=%02x",p->pmp);
printf(" pwr=%d",ntohs(p->pwr));
printf(" rpm=%d",ntohs(p->rpm));
printf(" gpm=%d",p->gpm);
printf(" ppc=%d",p->ppc);
printf(" b09=%02x",p->b09);
printf(" err=%02x",p->err);
printf(" b11=%02x",p->b11);
printf(" tmr=%d\n",p->tmr);
}
}
int uifc(int bd)
{
fd_set rfd;
tmv_t tv;
int don, tic, rep, n;
char buf[64], pa5[PA5SIZ];
rep = 15;
for (tic = 1, don = 0; !don; ) {
if (--tic < 0) {
tic = (gl.pstat = pump_stat(bd,&gl.sbuf)) ? (rep-1) : 0;
pr_pstat(gl.pstat);
}
printf("\r%s ",version+5);
printf("%2d",tic+1);
printf(gl.ctrl==IFLO_DSP_REM?"R> ":
gl.ctrl==IFLO_DSP_LOC?"L> ":
"?> ");
fflush(stdout);
tv.tv_sec = 1; tv.tv_usec = 0;
FD_ZERO(&rfd); FD_SET(0,&rfd); FD_SET(bd,&rfd);
if ((n = select(bd+1,&rfd,0,0,&tv)) < 0 && errno != EINTR) break;
if (n <= 0) continue;
if (FD_ISSET(bd,&rfd)) {
if ((n = read(bd,pa5,sizeof(pa5))) == 2 && pa5[0] == EOT)
printf("\rEOT from hub \n");
else printf("\rWe are not alone on the bus...\n");
break;
}
if (!FD_ISSET(0,&rfd)) continue;
if (read(0,buf,sizeof(buf)) <= 0) continue;
switch (buf[0]) {
case 'q': don = 1; break;
default:
printf("q - quit\n");
break;
}
}
return errno;
}
int main(int ac, char **av)
{
int bd;
char msg[128];
bzero(&gl,sizeof(gl));
gl.cadr = 0x11;
gl.padr = 0x60;
bd = hub_at(ac>1?av[1]:0,msg);
printf("%s: %s\n",av[0],msg);
if (bd < 0) return -1;
uifc(bd);
hub_dt(bd);
return 0;
}

264
iPump.c Normal file
View File

@ -0,0 +1,264 @@
/*
* iPump - experimental controller for IntelliFlow pumps
*/
#define APRS485_API 1
#include "aprs485.h"
#include "pa_iflo.h"
char version[] = "@(#) iPump 0.03";
struct {
u08_t cadr; /* this controllers address */
u08_t padr; /* pump address */
int ctrl;
} gl;
int str2i(char *str, char **rem)
{
int i, k;
while (*str == ' ') str++;
for (i = k = 0; *str >= '0' && *str <= '9'; str++)
i = i * 10 + (*str - '0'), k++;
if (rem) *rem = str;
return k ? i : -1;
}
pa5_t *pa5_hdr(void *buf, u08_t cfi)
{
pa5_t *ph;
bzero(ph=buf,sizeof(*ph));
ph->lpb = 0xa5;
ph->dst = gl.padr;
ph->src = gl.cadr;
ph->cfi = cfi;
return ph;
}
int pa5_snd(int bd, pa5_t *ps)
{
int n;
u16_t sum;
u08_t *b, *s, pa5[PA5SIZ];
for (b = pa5, n = 3; --n >= 0; *b++ = 0xff); b[-2] = 0;
n = sizeof(*ps) + ps->len;
for (sum = 0, s = &ps->lpb; --n >= 0; b++) sum += *b = *s++;
*b++ = sum>>8;
*b++ = sum;
return write(bd,pa5,b-pa5);
}
pa5_t *pa5_rcv(int bd, void *buf, int tmo)
{
pa5_t *pr;
u08_t *b, *c, *e;
fd_set rfd;
tmv_t tv;
u16_t sum;
int n, k;
FD_ZERO(&rfd); FD_SET(bd,&rfd);
tv.tv_usec = tmo * 1000;
tv.tv_sec = tv.tv_usec/1000000;
tv.tv_usec -= tv.tv_sec*1000000;
if ((k = select(bd+1,&rfd,0,0,&tv)) <= 0) return 0;
if ((n = read(bd,buf,PA5SIZ)) < sizeof(pa5_t)) return 0;
for (e = (b = buf) + n; b < e && *b != 0xa5; b++);
if ((e - b) < sizeof(pa5_t)) return 0;
pr = (pa5_t *)b;
if (((c = &pr->dat[pr->len]) + 2) < e || pr->dst != gl.cadr) return 0;
for (sum = 0; b < c; sum += *b++);
if (sum != ((c[0]<<8)+c[1])) return 0;
return pr;
}
int pump_cmd(int bd, int cmd, int val)
{
pa5_t *ps, *pr;
char snd[sizeof(pa5_t)+4], pa5[PA5SIZ];
ps = pa5_hdr(snd,cmd);
ps->dat[ps->len++] = val;
if (pa5_snd(bd,ps) <= 0) return -1;
if ((pr = pa5_rcv(bd,pa5,500)) == 0) return -2;
if (pr->src != ps->dst || pr->dst != ps->src) return -3;
if (pr->cfi != ps->cfi || pr->len != ps->len) return -4;
return pr->dat[0] == ps->dat[0] ? pr->dat[0] : -5;
}
int pump_reg(int bd, int adr, int val)
{
pa5_t *ps, *pr;
char snd[sizeof(pa5_t)+4], pa5[PA5SIZ];
ps = pa5_hdr(snd,0x01);
ps->dat[ps->len++] = adr>>8;
ps->dat[ps->len++] = adr>>0;
ps->dat[ps->len++] = val>>8;
ps->dat[ps->len++] = val>>0;
if (pa5_snd(bd,ps) <= 0) return -1;
if ((pr = pa5_rcv(bd,pa5,500)) == 0) return -2;
if (pr->src != ps->dst || pr->dst != ps->src) return -3;
if (pr->cfi != ps->cfi || pr->len != 2) return -4;
adr = (pr->dat[0]<<8)+pr->dat[1];
return adr == val ? val : -5;
}
iflsr_t *pump_stat(int bd, void *buf)
{
pa5_t *ps, *pr;
int ctrl;
char snd[sizeof(pa5_t)+4];
if ((ctrl = gl.ctrl) != IFLO_DSP_REM) {
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_REM);
if (gl.ctrl != IFLO_DSP_REM) return 0;
}
ps = pa5_hdr(snd,IFLO_SRG);
pr = pa5_snd(bd,ps) <= 0 ? 0 : pa5_rcv(bd,buf,500);
if (ctrl != IFLO_DSP_REM)
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_LOC);
if (pr == 0) return 0;
if (pr->src != gl.padr || pr->dst != gl.cadr) return 0;
if (pr->cfi != ps->cfi || pr->len != sizeof(iflsr_t)) return 0;
return (iflsr_t *)&pr->dat;
}
void pr_pstat(iflsr_t *p)
{
if (p == 0) printf("no status!\n");
else {
printf("run=%02x",p->run);
printf(" mod=%02x",p->mod);
printf(" pmp=%02x",p->pmp);
printf(" pwr=%d",ntohs(p->pwr));
printf(" rpm=%d",ntohs(p->rpm));
printf(" gpm=%d",p->gpm);
printf(" ppc=%d",p->ppc);
printf(" err=%02x",p->err);
printf(" tmr=%d",p->tmr);
printf(" %02d:%02d\n",p->clk[0],p->clk[1]);
}
}
typedef struct {
int rep;
int run;
int rr;
int gpm;
} ltc_t;
void ltic6(int bd, ltc_t *l)
{
iflsr_t *p;
int k;
char pa5[PA5SIZ];
if (l->run == 0 && l->rr == 0) return;
while ((k = 1)) {
if (l->rr != 6) break;
if ((gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_REM)) != IFLO_DSP_REM) break;
if (pump_reg(bd,IFLO_REG_SPGPM,l->gpm) != l->gpm) break;
if (pump_cmd(bd,IFLO_MOD,IFLO_MOD_FEATR1) != IFLO_MOD_FEATR1) break;
if (pump_cmd(bd,IFLO_RUN,IFLO_RUN_STRT) != IFLO_RUN_STRT) break;
if ((p = pump_stat(bd,pa5)) == 0) break;
if (p->err) break;
if (p->run != IFLO_RUN_STRT) break;
l->run = l->rr;
k = 0;
break;
}
if (k) { /* shut it down */
pump_cmd(bd,IFLO_RUN,IFLO_RUN_STOP);
pump_cmd(bd,IFLO_MOD,IFLO_MOD_MANUAL);
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_LOC);
l->run = l->rr = 0;
}
}
int uifc(int bd)
{
fd_set rfd;
tmv_t tv;
ltc_t *l, ltc;
int don, tic, n, k;
char buf[16], pa5[PA5SIZ];
bzero(l=&ltc,sizeof(*l));
l->rep = 16;
l->gpm = 20;
for (tic = don = 0; !don; ) {
if (--tic < 0) {
switch (gl.ctrl) {
case IFLO_DSP_REM:
case IFLO_DSP_LOC:
ltic6(bd,l);
tic = l->run >= 0 ? (l->rep-1) : 0;
break;
default:
gl.ctrl = pump_cmd(bd,IFLO_DSP,IFLO_DSP_LOC);
tic = 0;
break;
}
}
printf("\r%s ",version+5);
printf(l->run==0?"-- ":"%2d ",tic+1);
printf(gl.ctrl==IFLO_DSP_REM?"R> ":
gl.ctrl==IFLO_DSP_LOC?"L> ":
"?> ");
fflush(stdout);
tv.tv_sec = 1; tv.tv_usec = 0;
FD_ZERO(&rfd); FD_SET(0,&rfd); FD_SET(bd,&rfd);
if ((n = select(bd+1,&rfd,0,0,&tv)) < 0 && errno != EINTR)
break;
if (n <= 0) continue;
if (FD_ISSET(bd,&rfd)) {
if ((n = read(bd,pa5,sizeof(pa5))) == 2 && pa5[0] == EOT)
printf("\rEOT from hub \n");
else printf("\rWe are not alone on the bus...\n");
break;
}
if (!FD_ISSET(0,&rfd)) continue;
if (read(0,buf,sizeof(buf)) <= 0) continue;
switch (buf[0]) {
case 'q': don = 1; break;
case 's': pr_pstat(pump_stat(bd,pa5)); break;
case 'S': l->rr = 0, tic = 0; break;
case 'R': l->rr = 6, tic = 0; break;
case 'f':
/* this puposefully suspends tick! */
printf("gpm [%d]: ",l->gpm); fflush(stdout);
if ((n = read(0,buf,sizeof(buf))) <= 0) break;
if ((k = str2i(buf,0)) >= 15 && k <= 130) l->gpm = k;
break;
default:
printf("q - quit\n");
printf("s - pump status\n");
printf("S - stop\n");
printf("R - run\n");
printf("f - set flow rate\n");
break;
}
}
return errno;
}
int main(int ac, char **av)
{
int bd;
char msg[128];
bzero(&gl,sizeof(gl));
gl.cadr = 0x11; /* standard is 0x10 */
gl.padr = 0x60;
gl.ctrl = -1;
bd = hub_at(ac>1?av[1]:0,msg);
printf("%s: %s\n",av[0],msg);
if (bd < 0) return -1;
uifc(bd);
hub_dt(bd);
return 0;
}

95
pa_ctrl.h Normal file
View File

@ -0,0 +1,95 @@
#ifndef __pa_ctrl_h__
#define __pa_ctrl_h__
/* Controllers: EasyTouch, IntelliTouch */
typedef struct { /* status vector 02, the one that gets sent on a regular basis */
u08_t clk[2];
u08_t srly; /* relay status */
u08_t b03;
u08_t b04;
u08_t b05;
u08_t b06;
u08_t b07;
u08_t b08;
u08_t srem; /* remote status */
u08_t b10;
u08_t b11;
u08_t b12;
u08_t b13;
u08_t tpol; /* pool temp */
u08_t tspa; /* spa temp */
u08_t b16;
u08_t b17;
u08_t tair; /* air temp */
u08_t tsol; /* solar temp */
u08_t b20;
u08_t b21;
u08_t b22;
u08_t b23;
u08_t b24;
u08_t b25;
u08_t b26;
u08_t b27;
u08_t b28;
} __attribute__ ((packed)) itv02_t;
typedef struct {
u08_t clk[2];
u08_t b02;
u08_t b03;
u08_t b04;
u08_t b05;
u08_t b06;
u08_t b07;
} __attribute__ ((packed)) itv05_t;
typedef struct {
u08_t tcpol;
u08_t tcspa;
u08_t tcair;
u08_t tspol;
u08_t tsspa;
u08_t b05;
u08_t b06;
u08_t b07;
u08_t tcsol;
u08_t b09;
u08_t b10;
u08_t b11;
u08_t b12;
} __attribute__ ((packed)) itv08_t;
typedef struct {
u08_t b00;
u08_t b01;
u16_t rpm1;
u08_t b04;
u16_t rpm2;
u08_t b07;
u16_t rpm3;
u08_t b10;
u16_t rpm4;
} __attribute__ ((packed)) itv16_t;
typedef struct {
u08_t b00;
u08_t b01;
u08_t b02;
u08_t b03;
u08_t b04;
u08_t b05;
u08_t b06;
u08_t b07;
u08_t b08;
u08_t b09;
u08_t b10;
u08_t b11;
u08_t b12;
u08_t b13;
u08_t b14;
u08_t b15;
} __attribute__ ((packed)) itv17_t;
#endif

61
pa_iflo.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef __pa_iflo_h__
#define __pa_iflo_h__
#define IFLO_REG 1
#define IFLO_REG_SPGPM 0x02e4
#define IFLO_REG_EPRG 0x0321
#define IFLO_EPRG_P0 0x0000
#define IFLO_EPRG_P1 0x0008
#define IFLO_EPRG_P2 0x0010
#define IFLO_EPRG_P3 0x0018
#define IFLO_EPRG_P4 0x0020
#define IFLO_REG_EP1RPM 0x0327 /* 4x160 */
#define IFLO_REG_EP2RPM 0x0328 /* 4x160 */
#define IFLO_REG_EP3RPM 0x0329 /* 4x160 */
#define IFLO_REG_EP4RPM 0x032a /* 4x160 */
#define IFLO_DSP 4
#define IFLO_DSP_LOC 0x00
#define IFLO_DSP_REM 0xff
#define IFLO_MOD 5
#define IFLO_MOD_FILTER 0x00 /* Filter */
#define IFLO_MOD_MANUAL 0x01 /* Manual */
#define IFLO_MOD_BKWASH 0x02
#define IFLO_MOD______3 0x03 /* never seen */
#define IFLO_MOD______4 0x04 /* never seen */
#define IFLO_MOD______5 0x05 /* never seen */
#define IFLO_MOD_FEATR1 0x06 /* Feature 1 */
#define IFLO_MOD______7 0x07 /* never seen */
#define IFLO_MOD______8 0x08 /* never seen */
#define IFLO_MOD_EXT_P1 0x09
#define IFLO_MOD_EXT_P2 0x0a
#define IFLO_MOD_EXT_P3 0x0b
#define IFLO_MOD_EXT_P4 0x0c
#define IFLO_RUN 6
#define IFLO_RUN_STRT 0x0a
#define IFLO_RUN_STOP 0x04
/* IntelliFlow VS status command response */
#define IFLO_SRG 7
typedef struct {
u08_t run;
u08_t mod;
u08_t pmp; /* looks like drive status */
#define IFLO_PMP_READY 0x02
u16_t pwr;
u16_t rpm;
u08_t gpm;
u08_t ppc;
u08_t b09;
u08_t err;
u08_t b11;
u08_t tmr;
u08_t clk[2];
} __attribute__ ((packed)) iflsr_t;
#endif

551
padec.c Normal file
View File

@ -0,0 +1,551 @@
/*
* padec - interpreter for 'palog' datafile
*/
#include "aprs485.h"
#include "pa_iflo.h"
#include "pa_ctrl.h"
#include <stdlib.h>
char version[] = "@(#) padec 0.03";
int pa5pump(u08_t adr) { return (adr & 0xfc) == 0x60; }
int pa5bcst(u08_t adr) { return adr == 0x0f; }
int pa5ctrl(u08_t adr) { return adr == 0x10; }
#define CHG(l,p,m) (!l || p->m != l->m)
void pa5deco(FILE *fo, int ind, pa5_t *pm, pa5_t *pl)
{
char *l;
u08_t cfi;
u16_t adr, val;
cfi = pm->cfi;
if (cfi == 0xff && pm->len == 1) {
fprintf(fo," ERROR(%d)",pm->dat[0]);
return;
}
if (pa5ctrl(pm->dst) && (cfi & 0xc0) == 0xc0 && pm->len == 1) {
fprintf(fo," SEND c=%02x,%02x",cfi&0x3f,pm->dat[0]);
return;
}
if (pa5ctrl(pm->dst) && (cfi & 0xc0) == 0x80 && pm->len > 1) {
fprintf(fo," WRITE c=%02x",cfi&0x3f);
cfi &= 0x3f;
/* FALLTHROUGH */
}
switch (cfi) {
case 0x01:
if (pa5pump(pm->dst) && pm->len == 4) {
adr = (pm->dat[0]<<8) + pm->dat[1];
val = (pm->dat[2]<<8) + pm->dat[3];
l = "";
switch (adr) {
case IFLO_REG_SPGPM: l = " GPM setpoint"; break;
case IFLO_REG_EPRG: l = " Ext.Ctrl"; break;
case IFLO_REG_EP1RPM: l = " P1 RPM setpoint"; break;
case IFLO_REG_EP2RPM: l = " P2 RPM setpoint"; break;
case IFLO_REG_EP3RPM: l = " P3 RPM setpoint"; break;
case IFLO_REG_EP4RPM: l = " P4 RPM setpoint"; break;
}
fprintf(fo," WRITE (%d) to 0x%04x%s",val,adr,l);
break;
}
if (pa5pump(pm->src) && pm->len == 2) {
val = (pm->dat[0]<<8) + pm->dat[1];
fprintf(fo," VALIS (%d)",val);
break;
}
if (pa5ctrl(pm->src) && pm->len == 1) {
fprintf(fo," WRITE c=%02x ACK",pm->dat[0]&0x7f);
break;
}
break;
case 0x02:
if (pa5bcst(pm->dst) && pa5ctrl(pm->src) && pm->len == sizeof(itv02_t)) {
itv02_t *p = (itv02_t *)pm->dat;
itv02_t *l = pl ? (itv02_t *)pl->dat : 0;
if (CHG(l,p,clk[0]) || CHG(l,p,clk[1]))
fprintf(fo,"\n%*sclck %02x%02x %02d:%02d",ind,"",p->clk[0],p->clk[1],p->clk[0],p->clk[1]);
if (CHG(l,p,srly)) fprintf(fo,"\n%*ssrly %02x relay status",ind,"",p->srly);
if (CHG(l,p,b03 )) fprintf(fo,"\n%*s[ 3] %02x ?",ind,"",p->b03);
if (CHG(l,p,b04 )) fprintf(fo,"\n%*s[ 4] %02x ?",ind,"",p->b04);
if (CHG(l,p,b05 )) fprintf(fo,"\n%*s[ 5] %02x ?",ind,"",p->b05);
if (CHG(l,p,b06 )) fprintf(fo,"\n%*s[ 6] %02x ?",ind,"",p->b06);
if (CHG(l,p,b07 )) fprintf(fo,"\n%*s[ 7] %02x ?",ind,"",p->b07);
if (CHG(l,p,b08 )) fprintf(fo,"\n%*s[ 8] %02x ?",ind,"",p->b08);
if (CHG(l,p,srem)) fprintf(fo,"\n%*ssrem %02x remote status",ind,"",p->srem);
if (CHG(l,p,b10 )) fprintf(fo,"\n%*s[10] %02x ?",ind,"",p->b10);
if (CHG(l,p,b11 )) fprintf(fo,"\n%*s[11] %02x ?",ind,"",p->b11);
if (CHG(l,p,b12 )) fprintf(fo,"\n%*s[12] %02x ?",ind,"",p->b12);
if (CHG(l,p,b13 )) fprintf(fo,"\n%*s[13] %02x ?",ind,"",p->b13);
if (CHG(l,p,tpol)) fprintf(fo,"\n%*stpol %02x %dF pool",ind,"",p->tpol,p->tpol);
if (CHG(l,p,tspa)) fprintf(fo,"\n%*stspa %02x %dF spa",ind,"",p->tspa,p->tspa);
if (CHG(l,p,b16 )) fprintf(fo,"\n%*s[16] %02x ?",ind,"",p->b16);
if (CHG(l,p,b17 )) fprintf(fo,"\n%*s[17] %02x ?",ind,"",p->b17);
if (CHG(l,p,tair)) fprintf(fo,"\n%*stair %02x %dF air",ind,"",p->tair,p->tair);
if (CHG(l,p,tsol)) fprintf(fo,"\n%*stsol %02x %dF solar",ind,"",p->tsol,p->tsol);
if (CHG(l,p,b20 )) fprintf(fo,"\n%*s[20] %02x ?",ind,"",p->b20);
if (CHG(l,p,b21 )) fprintf(fo,"\n%*s[21] %02x ?",ind,"",p->b21);
if (CHG(l,p,b22 )) fprintf(fo,"\n%*s[22] %02x ?",ind,"",p->b22);
if (CHG(l,p,b23 )) fprintf(fo,"\n%*s[23] %02x ?",ind,"",p->b23);
if (CHG(l,p,b24 )) fprintf(fo,"\n%*s[24] %02x ?",ind,"",p->b24);
if (CHG(l,p,b25 )) fprintf(fo,"\n%*s[25] %02x ?",ind,"",p->b25);
if (CHG(l,p,b26 )) fprintf(fo,"\n%*s[26] %02x ?",ind,"",p->b26);
if (CHG(l,p,b27 )) fprintf(fo,"\n%*s[27] %02x ?",ind,"",p->b27);
if (CHG(l,p,b28 )) fprintf(fo,"\n%*s[28] %02x ?",ind,"",p->b28);
break;
}
break;
case 0x04:
if (pa5pump(pm->dst) && pm->len == 1) {
if (pm->dat[0] == IFLO_DSP_LOC) fprintf(fo," SETCTRL local");
else if (pm->dat[0] == IFLO_DSP_REM) fprintf(fo," SETCTRL remote");
break;
}
if (pa5pump(pm->src) && pm->len == 1) {
if (pm->dat[0] == IFLO_DSP_LOC) fprintf(fo," CTRL is local");
else if (pm->dat[0] == IFLO_DSP_REM) fprintf(fo," CTRL is remote");
break;
}
break;
case 0x05:
if (pa5pump(pm->dst) && pm->len == 1) {
fprintf(fo," SETMOD %02x",pm->dat[0]);
break;
}
if (pa5pump(pm->src) && pm->len == 1) {
fprintf(fo," MOD is %02x",pm->dat[0]);
break;
}
if (pa5ctrl(pm->src) && pm->len == sizeof(itv05_t)) {
/* status vector 05 */
itv05_t *p = (itv05_t *)pm->dat;
itv05_t *l = pl ? (itv05_t *)pl->dat : 0;
if (CHG(l,p,clk[0]) || CHG(l,p,clk[1]))
fprintf(fo,"\n%*sclck %02x%02x %02d:%02d",ind,"",p->clk[0],p->clk[1],p->clk[0],p->clk[1]);
if (CHG(l,p,b02 )) fprintf(fo,"\n%*s[ 2] %02x ?",ind,"",p->b02);
if (CHG(l,p,b03 )) fprintf(fo,"\n%*s[ 3] %02x ?",ind,"",p->b03);
if (CHG(l,p,b04 )) fprintf(fo,"\n%*s[ 4] %02x ?",ind,"",p->b04);
if (CHG(l,p,b05 )) fprintf(fo,"\n%*s[ 5] %02x ?",ind,"",p->b05);
if (CHG(l,p,b06 )) fprintf(fo,"\n%*s[ 6] %02x ?",ind,"",p->b06);
if (CHG(l,p,b07 )) fprintf(fo,"\n%*s[ 7] %02x ?",ind,"",p->b07);
break;
}
break;
case 0x06:
if (pa5pump(pm->dst) && pm->len == 1) {
fprintf(fo," SETRUN %02x %s",pm->dat[0],
pm->dat[0] == IFLO_RUN_STRT ? "Started" :
pm->dat[0] == IFLO_RUN_STOP ? "Stopped" :
"?");
break;
}
if (pa5pump(pm->src) && pm->len == 1) {
fprintf(fo," RUN is %02x %s",pm->dat[0],
pm->dat[0]==IFLO_RUN_STRT ? "Started" :
pm->dat[0]==IFLO_RUN_STOP ? "Stopped" :
"?");
break;
}
break;
case 0x07:
if (pa5pump(pm->dst) && pm->len == 0) {
fprintf(fo," SEND status");
break;
}
if (pa5pump(pm->src) && pm->len == sizeof(iflsr_t)) {
iflsr_t *p = (iflsr_t *)pm->dat;
iflsr_t *l = pl ? (iflsr_t *)pl->dat : 0;
u16_t v;
if (CHG(l,p,run)) fprintf(fo,"\n%*sRUN %02x %s",ind,"",p->run,
p->run==IFLO_RUN_STRT ? "Started":
p->run==IFLO_RUN_STOP ? "Stopped":
"?");
if (CHG(l,p,mod)) fprintf(fo,"\n%*sMOD %02x %s",ind,"",p->mod,
p->mod==IFLO_MOD_FILTER ? "Filter":
p->mod==IFLO_MOD_MANUAL ? "Manual":
p->mod==IFLO_MOD_BKWASH ? "Backwash":
p->mod==IFLO_MOD_FEATR1 ? "Feature 1":
p->mod==IFLO_MOD_EXT_P1 ? "Ext.Ctrl 1":
p->mod==IFLO_MOD_EXT_P2 ? "Ext.Ctrl 2":
p->mod==IFLO_MOD_EXT_P3 ? "Ext.Ctrl 3":
p->mod==IFLO_MOD_EXT_P4 ? "Ext.Ctrl 4":
"?");
if (CHG(l,p,pmp)) fprintf(fo,"\n%*sPMP %02x %s",ind,"",p->pmp,
p->pmp==IFLO_PMP_READY ? "ready":
"?");
v = ntohs(p->pwr);
if (CHG(l,p,pwr)) fprintf(fo,"\n%*sPWR %04x %d WATT",ind,"",v,v);
v = ntohs(p->rpm);
if (CHG(l,p,rpm)) fprintf(fo,"\n%*sRPM %04x %d RPM",ind,"",v,v);
if (CHG(l,p,gpm)) fprintf(fo,"\n%*sGPM %02x %d GPM",ind,"",p->gpm,p->gpm);
if (CHG(l,p,ppc)) fprintf(fo,"\n%*sPPC %02x %d %%",ind,"",p->ppc,p->ppc);
if (CHG(l,p,b09)) fprintf(fo,"\n%*sb09 %02x ?",ind,"",p->b09);
if (CHG(l,p,err)) {
fprintf(fo,"\n%*sERR %02x ",ind,"",p->err);
if (p->err==0x00) fprintf(fo,"ok");
if (p->err &0x80) fprintf(fo,"!ESTOP");
if (p->err &0x02) fprintf(fo,"!ALERT");
if (p->err &0x7d) fprintf(fo,"(?)");
}
if (CHG(l,p,b11)) fprintf(fo,"\n%*sb11 %02x ?",ind,"",p->b11);
if (CHG(l,p,tmr)) fprintf(fo,"\n%*sTMR %02x %d MIN",ind,"",p->tmr,p->tmr);
if (CHG(l,p,clk[0]) || CHG(l,p,clk[1]))
fprintf(fo,"\n%*sCLK %02x%02x %02d:%02d",ind,"",p->clk[0],p->clk[1],p->clk[0],p->clk[1]);
break;
}
break;
case 0x08:
if (pa5ctrl(pm->src) && pm->len == sizeof(itv08_t)) {
/* status vector 08 */
itv08_t *p = (itv08_t *)pm->dat;
itv08_t *l = pl ? (itv08_t *)pl->dat : 0;
if (CHG(l,p,tcpol)) fprintf(fo,"\n%*stcpol %02x %dF pool",ind,"",p->tcpol,p->tcpol);
if (CHG(l,p,tcspa)) fprintf(fo,"\n%*stcspa %02x %dF spa",ind,"",p->tcspa,p->tcspa);
if (CHG(l,p,tcair)) fprintf(fo,"\n%*stcair %02x %dF air",ind,"",p->tcair,p->tcair);
if (CHG(l,p,tspol)) fprintf(fo,"\n%*stspol %02x %dF pool setpoint",ind,"",p->tspol,p->tspol);
if (CHG(l,p,tspol)) fprintf(fo,"\n%*stsspa %02x %dF spa setpoint",ind,"",p->tsspa,p->tsspa);
if (CHG(l,p,b05 )) fprintf(fo,"\n%*s[ 5] %02x ?",ind,"",p->b05);
if (CHG(l,p,b06 )) fprintf(fo,"\n%*s[ 6] %02x ?",ind,"",p->b06);
if (CHG(l,p,b07 )) fprintf(fo,"\n%*s[ 7] %02x ?",ind,"",p->b07);
if (CHG(l,p,tcsol)) fprintf(fo,"\n%*stcsol %02x %dF solar",ind,"",p->tcsol,p->tcsol);
if (CHG(l,p,b09 )) fprintf(fo,"\n%*s[ 9] %02x ?",ind,"",p->b09);
if (CHG(l,p,b10 )) fprintf(fo,"\n%*s[10] %02x ?",ind,"",p->b10);
if (CHG(l,p,b11 )) fprintf(fo,"\n%*s[11] %02x ?",ind,"",p->b11);
if (CHG(l,p,b12 )) fprintf(fo,"\n%*s[12] %02x ?",ind,"",p->b12);
break;
}
break;
case 0x0a:
if (pa5ctrl(pm->src) && pm->len > 0) {
/* seems to be some kind of button label */
char *t;
int n;
fprintf(fo," \"");
for (t = (char *)pm->dat, n = pm->len; --n >= 0; t++)
fprintf(fo,"%c",*t>=' '&&*t<='~'?*t:'.');
fprintf(fo,"\"");
break;
}
break;
case 0x16:
if (pa5ctrl(pm->src) && pm->len == sizeof(itv16_t)) {
itv16_t *p = (itv16_t *)pm->dat;
itv16_t *l = pl ? (itv16_t *)pl->dat : 0;
if (CHG(l,p,b00 )) fprintf(fo,"\n%*s[ 0] %02x ?",ind,"",p->b00);
if (CHG(l,p,b01 )) fprintf(fo,"\n%*s[ 1] %02x ?",ind,"",p->b01);
if (CHG(l,p,rpm1)) fprintf(fo,"\n%*srpm1 %04x %d RPM P1",ind,"",ntohs(p->rpm1),ntohs(p->rpm1));
if (CHG(l,p,b04 )) fprintf(fo,"\n%*s[ 4] %02x ?",ind,"",p->b04);
if (CHG(l,p,rpm2)) fprintf(fo,"\n%*srpm2 %04x %d RPM P2",ind,"",ntohs(p->rpm2),ntohs(p->rpm2));
if (CHG(l,p,b07 )) fprintf(fo,"\n%*s[ 7] %02x ?",ind,"",p->b07);
if (CHG(l,p,rpm3)) fprintf(fo,"\n%*srpm3 %04x %d RPM P3",ind,"",ntohs(p->rpm3),ntohs(p->rpm3));
if (CHG(l,p,b10 )) fprintf(fo,"\n%*s[10] %02x ?",ind,"",p->b10);
if (CHG(l,p,rpm4)) fprintf(fo,"\n%*srpm4 %04x %d RPM P4",ind,"",ntohs(p->rpm4),ntohs(p->rpm4));
break;
}
break;
case 0x17:
if (pa5ctrl(pm->src) && pm->len == sizeof(itv17_t)) {
itv17_t *p = (itv17_t *)pm->dat;
itv17_t *l = pl ? (itv17_t *)pl->dat : 0;
if (CHG(l,p,b00)) fprintf(fo,"\n%*s[ 0] %02x ?",ind,"",p->b00);
if (CHG(l,p,b01)) fprintf(fo,"\n%*s[ 1] %02x ?",ind,"",p->b01);
if (CHG(l,p,b02)) fprintf(fo,"\n%*s[ 2] %02x ?",ind,"",p->b02);
if (CHG(l,p,b03)) fprintf(fo,"\n%*s[ 3] %02x ?",ind,"",p->b03);
if (CHG(l,p,b04)) fprintf(fo,"\n%*s[ 4] %02x ?",ind,"",p->b04);
if (CHG(l,p,b05)) fprintf(fo,"\n%*s[ 5] %02x ?",ind,"",p->b05);
if (CHG(l,p,b06)) fprintf(fo,"\n%*s[ 6] %02x ?",ind,"",p->b06);
if (CHG(l,p,b07)) fprintf(fo,"\n%*s[ 7] %02x ?",ind,"",p->b07);
if (CHG(l,p,b08)) fprintf(fo,"\n%*s[ 8] %02x ?",ind,"",p->b08);
if (CHG(l,p,b09)) fprintf(fo,"\n%*s[ 9] %02x ?",ind,"",p->b09);
if (CHG(l,p,b10)) fprintf(fo,"\n%*s[10] %02x ?",ind,"",p->b10);
if (CHG(l,p,b11)) fprintf(fo,"\n%*s[11] %02x ?",ind,"",p->b11);
if (CHG(l,p,b12)) fprintf(fo,"\n%*s[12] %02x ?",ind,"",p->b12);
if (CHG(l,p,b13)) fprintf(fo,"\n%*s[13] %02x ?",ind,"",p->b13);
if (CHG(l,p,b14)) fprintf(fo,"\n%*s[14] %02x ?",ind,"",p->b14);
if (CHG(l,p,b15)) fprintf(fo,"\n%*s[15] %02x ?",ind,"",p->b15);
break;
}
break;
}
}
#define NOSYN 0x00000001
#define NOTIM 0x00000002
#define NOHUB 0x00000004
#define NOADR 0x00000008
#define NODEC 0x00000010
#define NOREP 0x00000020
u08_t *findpat(u08_t *b, u08_t *e, u08_t *p, int np)
{
int n, k;
if ((n = (e - p) - np) < 0) return 0;
do for (k = np; --k >= 0 && b[k] == p[k]; );
while (k >= 0 && --n >= 0 && ++b < e);
return k < 0 ? b : 0;
}
typedef struct { /* message cache */
u32_t pos;
u08_t msg[64-4];
} mce_t;
typedef struct {
u32_t pos; /* current record position */
tmv_t tim; /* time stamp */
u32_t flg; /* command line flags */
int afl; /* address match */
int ind; /* print indent for pa5deco() */
int nmce; /* number of messages in cache */
mce_t mces[64];
} ctx_t;
char *tmv2str(tmv_t *tc, char *str)
{
char *s = str;
struct tm tm;
localtime_r(&tc->tv_sec,&tm);
s += sprintf(s,"%02d%02d ",tm.tm_mon+1,tm.tm_mday);
s += sprintf(s,"%02d:%02d:%02d.%03lu",tm.tm_hour,tm.tm_min,tm.tm_sec,tc->tv_usec/1000);
return str;
}
void pr_hdr(FILE *fo, ctx_t *c, char *typ)
{
char tbu[32];
c->ind = (c->flg & NOADR) ? 0 : fprintf(fo,"%08lx:",c->pos);
c->ind += (c->flg & NOTIM) ? fprintf(fo,"%3s: ",typ?typ:"???") : fprintf(fo,"%s ",tmv2str(&c->tim,tbu));
}
mce_t *mce_a5lup(ctx_t *c, u08_t *msg, int msz)
{
mce_t *mc;
int n;
if (msz > sizeof(mc->msg)) return 0;
if (!(c->flg & NOREP)) return 0;
n = (c->flg & NODEC) || c->afl ? 0 : 5;
if (((pa5_t *)msg)->len < n) return 0;
for (mc = c->mces, n = c->nmce; --n >= 0; mc++)
if (!bcmp(msg,mc->msg,sizeof(pa5_t))) return mc;
if (c->nmce < NEL(c->mces)) {
mc->pos = c->pos;
bcopy(msg,mc->msg,msz);
c->nmce++;
}
return 0;
}
int a5xx_msg(FILE *fo, ctx_t *c, u08_t *b, u08_t *e)
{
u08_t *s = b, *t;
pa5_t *pm;
mce_t *mc;
u16_t sum, cks;
int n, msz;
if ((e - b) < 8) return 0;
if (&b[(n = 6 + b[5]) + 2] > e) return 0;
for (sum = 0; --n >= 0; sum += *s++);
cks = (s[0]<<8)+s[1];
if (sum != cks) return 0;
msz = (s - b) + 2;
pm = (pa5_t *)b;
if (c->afl && pm->dst != c->afl && pm->src != c->afl) return msz;
pr_hdr(fo,c,"msg");
fprintf(fo,"%02X%02X ",pm->lpb,pm->sub);
fprintf(fo,"d=%02x s=%02x ",pm->dst,pm->src);
fprintf(fo,"c=%02x l=%02x ",pm->cfi,pm->len);
if ((mc = mce_a5lup(c,b,msz)) && !bcmp(b,mc->msg,msz)) {
fprintf(fo,"REPEAT");
if (!(c->flg & NOADR)) fprintf(fo," %08lx",mc->pos);
}
else {
for (t = pm->dat, n = pm->len; --n >= 0; t++) fprintf(fo,"%02X",*t);
fprintf(fo,"%s<%02X%02X>",pm->len?" ":"",s[0],s[1]);
if (!(c->flg & NODEC)) pa5deco(fo,c->ind,pm,mc?(pa5_t *)mc->msg:0);
if (mc) mc->pos = c->pos, bcopy(b,mc->msg,msz);
}
return msz;
}
int m1090_msg(FILE *fo, ctx_t *c, u08_t *b, u08_t *e)
{
union { char *c; u08_t *b; u16_t *w; u32_t *l; } p;
u08_t *s = b, *t, eom[2], sum;
int msz, n;
/*
* 'palog' context message
* 1090 ..... <cks> 1091
*/
if ((e - b) < 13) return 0;
eom[0] = 0x10; eom[1] = 0x91;
if ((t = b + 64) > e) t = e;
if ((s = findpat(b+2,t,eom,2)) == 0) return 0;
if ((msz = (s - b) + 2) < 13) return 0;
for (sum = 0x12, s--, t = b + 2; t < s; sum += *t++);
if (sum != *s) return 0;
p.b = &b[2];
c->tim.tv_sec = ntohl(*p.l); p.l++;
c->tim.tv_usec = ntohl(*p.l); p.l++;
if (c->flg & NOHUB) return msz;
if (c->afl) return msz;
pr_hdr(fo,c,"pab");
n = s - &p.b[1];
fprintf(fo,"%c %.*s\n",*p.c,n<0?0:n,&p.c[1]);
return msz;
}
int m10xx_msg(FILE *fo, ctx_t *c, u08_t *b, u08_t *e)
{
u08_t *s = b, *t, eom[2], sum;
int msz;
/*
* 1002 ..... <cks> 1003
*/
if ((e - b) < 4) return 0;
eom[0] = 0x10; eom[1] = 0x03;
if ((t = b + 64) > e) t = e;
if ((s = findpat(b+2,t,eom,2)) == 0) return 0;
msz = (s - b) + 2;
for (sum = 0x12, s--, t = b + 2; t < s; sum += *t++);
if (sum != *s) return 0;
if (c->afl) return msz;
pr_hdr(fo,c,"msg");
fprintf(fo,"%02X%02X ",b[0],b[1]);
for (b += 2; b < s; b++) fprintf(fo,"%02X",*b);
fprintf(fo," <%02X> %02X%02X ",b[0],b[1],b[2]);
return msz;
}
int dumplog(int fd, ctx_t *c, FILE *fo)
{
int eof, n, k, syn;
u32_t iop;
u08_t *t, *b, *r, *w, *e, iob[2*BUFSIZ];
for (iop = 0, e = (r = w = iob) + sizeof(iob), eof = 0; !eof || r != w; ) {
if ((n = w - r) <= (NEL(iob)/2)) {
iop += r - iob;
if (n <= 0) r = w = iob;
else if (r > iob) bcopy(r,iob,n), w = (r = iob) + n;
if (!eof) {
if ((n = read(fd,w,k=e-w)) <= 0) eof = 1;
else w += n, eof = n < k;
}
}
/* r->(data), w->(end of data present) */
for (b = r; b < w && (b - r) < 16; b++) {
if (*b == 0xa5) break;
if (*b == 0x10 && (w - b) >= 2) {
if (b[1] == 0x02) break;
if (b[1] == 0x90) break;
}
}
/* r->(data), b->(a potential message), w->(end of data present) */
c->pos = iop + (r - iob);
c->ind = 0;
if ((n = b - r) > 0) {
if ((syn = n >= 3)) {
for (t = r; t < b && *t == 0xff; t++);
syn = (b - t) == 2 && t[0] == 0x00 && t[1] == 0xff;
}
if (!syn) {
for (b = r; ++b < w && (b - r) < 16; )
if (*b == 0xa5 || *b == 0x10 || *b == 0xff) break;
n = b - r;
}
while (1) {
if (syn && (c->flg & NOSYN)) break;
if (c->afl) break;
pr_hdr(fo,c,syn?"syn":"??b");
for (t = r; t < b; t++) fprintf(fo,"%02X",*t);
fprintf(fo,"\n");
break;
}
r += n;
continue;
}
/* r->(data, potential message), w->(end of data present) */
if (*r == 0xa5) n = a5xx_msg(fo,c,r,w);
else if (*r == 0x10) {
if (r[1] == 0x90) {
if ((n = m1090_msg(fo,c,r,w)) > 0) {
r += n;
continue;
}
}
else n = m10xx_msg(fo,c,r,w);
}
else n = 0;
/* n is the number of bytes in a recognized message */
if (n <= 0) {
for (b = r; ++b < w && (b - r) < 16; )
if (*b == 0xa5 || *b == 0x10 || *b == 0xff) break;
n = b - r;
if (c->afl == 0) {
pr_hdr(fo,c,"???");
for (t = r; t < b; t++) fprintf(fo,"%02X",*t);
}
}
r += n;
if (c->ind) fprintf(fo,"\n");
}
return 0;
}
int main(int ac, char **av)
{
FILE *fo = stdout;
ctx_t ctx;
int rv, fd, i, n;
char *p, *r;
if (ac <= 1) goto USAGE;
bzero(&ctx,sizeof(ctx));
ctx.flg = NOSYN|NOTIM|NOHUB|NOADR|NODEC|NOREP;
for (rv = i = 0; !rv && ++i < ac; ) {
if (*(p = av[i]) == '-')
while (*(++p)) switch (*p) {
case 's': ctx.flg ^= NOSYN; break;
case 't': ctx.flg ^= NOTIM; break;
case 'h': ctx.flg ^= NOHUB; break;
case 'a': ctx.flg ^= NOADR; break;
case 'd': ctx.flg ^= NODEC; break;
case 'r': ctx.flg ^= NOREP; break;
case 'f':
if (++i >= ac) goto USAGE;
if ((n = strtol(av[i],&r,16)) <= 0 || n > 0xff || *r) goto USAGE;
ctx.afl = n;
break;
default:
USAGE: fprintf(fo,"%s\n",version);
fprintf(fo,"usage: [-<options>] <datafile>\n");
fprintf(fo,"s - print sync bytes\n");
fprintf(fo,"t - print time stamps\n");
fprintf(fo,"h - print 'palog' messages\n");
fprintf(fo,"a - print record positions in file\n");
fprintf(fo,"d - decode messages\n");
fprintf(fo,"r - print full decode of repeated messages\n");
fprintf(fo,"f <#> - print only messages from/to address <#>\n");
return EINVAL;
break;
}
else if ((fd = open(av[i],0)) < 0)
rv = errno, fprintf(fo,"%s: %s\n",av[i],strerror(errno));
else {
fprintf(fo,"# %s:",version+5);
for (n = 0; ++n <= i; ) fprintf(fo," %s",av[n]);
fprintf(fo,"\n");
rv = dumplog(fd,&ctx,fo);
close(fd);
}
}
return rv;
}