From 606770f64e5b129b135471ec75d11ddad8da9522 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 23 May 2023 16:57:22 -0700 Subject: [PATCH] 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. --- Makefile | 42 ++++ README.txt | 331 +++++++++++++++++++++++++ aprs485.c | 698 +++++++++++++++++++++++++++++++++++++++++++++++++++++ aprs485.h | 135 +++++++++++ iComII.c | 250 +++++++++++++++++++ iFlow.c | 251 +++++++++++++++++++ iPmon.c | 179 ++++++++++++++ iPump.c | 264 ++++++++++++++++++++ pa_ctrl.h | 95 ++++++++ pa_iflo.h | 61 +++++ padec.c | 551 ++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 2857 insertions(+) create mode 100644 Makefile create mode 100644 README.txt create mode 100644 aprs485.c create mode 100644 aprs485.h create mode 100644 iComII.c create mode 100644 iFlow.c create mode 100644 iPmon.c create mode 100644 iPump.c create mode 100644 pa_ctrl.h create mode 100644 pa_iflo.h create mode 100644 padec.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..53f139a --- /dev/null +++ b/Makefile @@ -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 + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..0f4e158 --- /dev/null +++ b/README.txt @@ -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 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 [-] +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. + + can be either a 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 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 Creates log files in directory . 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: + + [] + + - leading packet byte, 0xa5 + - ? + - destination address + - source address + - command/function/instruction + - size of data field (may be 0!) + + - most significant byte of checksum + - 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 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 and 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 depends on the destination of a message, + meaning a 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 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 to a controller seems to work like this: + bits <76543210> + 00xxxxxx - ? + 01xxxxxx - ? + 10xxxxxx - transfer(write) &0x3f to controller + controller acknowledges the write with <0x01><0x01> + 11xxxxxx - request &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 [-] +INFO Offline binary decoder. Prints to standard output. + The may be a raw dump of traffic from a Pentair + RS485 bus or the 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 + -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 [] +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 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 [] +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 [] +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 [] +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. + diff --git a/aprs485.c b/aprs485.c new file mode 100644 index 0000000..e385e3a --- /dev/null +++ b/aprs485.c @@ -0,0 +1,698 @@ +/* + * aprs485 - RS485 access point/bridge/hub + */ +#include "aprs485.h" +#include +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 ACK client request to attach to hub tab + * NAK <#tabs> error, no tab available + * + * DEL ACK client detach request + * NAK 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 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 + +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: [-] \n"); + printf("-p - server port (default: %d)\n",APRS485_PORT); + printf("-l - log directory\n"); + printf("-d - debug, don't fork\n"); + printf("-D - log partial reads from \n"); + printf(" - or \n"); + printf("-c - send 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 to exit...\r\n"); + } + sigsetup(); + eno = bridge(bd,sd); + if (gl.dbug) tcsetattr(0,TCSANOW,tio); + close(bd); + close(sd); + return eno; +} + diff --git a/aprs485.h b/aprs485.h new file mode 100644 index 0000000..1e3a750 --- /dev/null +++ b/aprs485.h @@ -0,0 +1,135 @@ +#ifndef __pabus_h__ +#define __pabus_h__ + +#include +#include +#include +#include +#include +#include + +#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 +#include +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 +#include +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 + diff --git a/iComII.c b/iComII.c new file mode 100644 index 0000000..4768769 --- /dev/null +++ b/iComII.c @@ -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; +} + diff --git a/iFlow.c b/iFlow.c new file mode 100644 index 0000000..4601298 --- /dev/null +++ b/iFlow.c @@ -0,0 +1,251 @@ +/* + * iFlow - IntelliFlow simulator on + */ +#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; +} + diff --git a/iPmon.c b/iPmon.c new file mode 100644 index 0000000..337e68a --- /dev/null +++ b/iPmon.c @@ -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; +} + + diff --git a/iPump.c b/iPump.c new file mode 100644 index 0000000..18b9f26 --- /dev/null +++ b/iPump.c @@ -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; +} + diff --git a/pa_ctrl.h b/pa_ctrl.h new file mode 100644 index 0000000..8a717ba --- /dev/null +++ b/pa_ctrl.h @@ -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 + diff --git a/pa_iflo.h b/pa_iflo.h new file mode 100644 index 0000000..8639edd --- /dev/null +++ b/pa_iflo.h @@ -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 + diff --git a/padec.c b/padec.c new file mode 100644 index 0000000..5f03fcd --- /dev/null +++ b/padec.c @@ -0,0 +1,551 @@ +/* + * padec - interpreter for 'palog' datafile + */ +#include "aprs485.h" +#include "pa_iflo.h" +#include "pa_ctrl.h" +#include + +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 ..... 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 ..... 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: [-] \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; +} +