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; +} +