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:
parent
0f048d61dd
commit
606770f64e
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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=<c,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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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=<c,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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue