diff options
Diffstat (limited to 'appl/cmd/fone.b')
| -rw-r--r-- | appl/cmd/fone.b | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/appl/cmd/fone.b b/appl/cmd/fone.b new file mode 100644 index 00000000..51bbede6 --- /dev/null +++ b/appl/cmd/fone.b @@ -0,0 +1,560 @@ +implement fone; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + stdout: ref Sys->FD; + logfd: ref Sys->FD; + +include "draw.m"; + draw: Draw; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "sh.m"; + smtp: Command; + +#include "keyring.m"; + +include "daytime.m"; + daytime: Daytime; + +TIMEGRAN: con 60000; +debug := 0; +logflag := 0; +logfile := ""; # name of log file +Nphones := 0; # number of telephone sets configured +voicefile := ""; # name of serial port to DECTalk +voice: ref sys->FD; +mailhost := ""; + +person: adt { + mailaddr: string; + name: string; # name pronounced by the voice + lineno: string; # 4 digit extension + time: string; + orignum: string; # originating number + origname: string; # originating name + state: int; + flags: int; +}; + +# states +ONHOOK: con 0; +RING: con 1; +DISPLAY: con 2; +OFFHOOK: con 3; + +# flags +LOG: con 1; +MAIL: con 2; +ANNOUNCE: con 4; + +telset: adt { + devfile: string; # file name of interface to phone set + apprfile: string; + apprtime: int; # time appearance file is read + phonefd: ref sys->FD; # open FD for this set + numappr: int; # number of appearances on this set + people: array of person; # appearance data for this set + version: string; # telephone set version +}; + +phone:= array[4] of telset; + +months:= array[13] of { 0 => "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug","Sep", "Oct", "Nov", "Dec"}; + +fone: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) { + + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + daytime = load Daytime Daytime->PATH; + smtp = load Command "smtp.dis"; +# keyring := load Keyring Keyring->PATH; + + stdout = sys->fildes(1); + logfd = stdout; + stderr = sys->fildes(2); + voicechan := chan of string; + timechan := chan of string; + +# +# set up name space. According to tradition this is done +# outside of the program. Needs to be here so debugging +# is not so tedious. +# + if (sys->pctl(sys->FORKNS, nil) < 0) { + sys->fprint(stderr, "pctl(FORKNS) failed: %r\n"); + exit; + } + if (sys->bind("#t", "/dev", sys->MAFTER) < 0) { + sys->fprint(stderr, "bind #t failed: %r\n"); + exit; + } + if (sys->bind("#p", "/prog", sys->MAFTER) < 0) { + sys->fprint(stderr, "bind #p failed: %r\n"); + exit; + } + + if (sys->bind("#C", "/", sys->MAFTER) < 0) { + sys->fprint(stderr, "bind #C failed: %r\n"); + exit; + } + + argv = tl argv; + while(argv != nil && len hd argv && (arg := hd argv)[0] == '-' && len arg > 1){ + case arg[1] { + 'd' => + debug = 1; + logflag = 1; + } + argv = tl argv; + } + configfile("fone.cfg"); +# +# Sound Blaster using sbtalker and read +# +# voice = SBsetup(); +# +# DECtalk using second serial port + voice = DTsetup(voicefile); + + sys->fprint(voice, "hello.\r"); + + + spawn timekeeper(timechan); + for (phoneid := 0; phoneid < Nphones; phoneid++) + spawn watchphone(phoneid, voicechan); + for (;;) alt { + mesg := <- voicechan => + sys->fprint(voice, "%s", mesg); + tmesg := <- timechan => + case tmesg { + "filecheck" => + for (i:=0; i<Nphones; i++) { + (r, f) := sys->stat(phone[i].apprfile); + if (r < 0) { + sys->fprint(stderr, "cannot stat %s: %r\n", phone[i].apprfile); + continue; + } + if (f.mtime > phone[i].apprtime) + getcallapprinfo(i); + } + } + } +} + +# +# read in the configuration file which tells the program which +# files and devices to use. +# +configfile(cfgname: string): int { + line, errstr: string; + + cfgfd := sys->open(cfgname, sys->OREAD); + if (cfgfd == nil) { + sys->fprint(stderr, "open %s failed, %r\n", cfgname); + bye(); + } + do { + (line, errstr) = getline(cfgfd); + if (errstr != nil) { + sys->fprint(stderr, "error reading config file: %r\n"); + return -1; + } + if (line != nil) { + (i, t) := sys->tokenize(line, ": \t\r\n"); + if ((hd t)[0] == '#') continue; + case hd t { + "logfile" => + if (i < 2) { + sys->fprint(stderr, "no log file name found. %d\n", i); + sys->fprint(stderr, "logfile: log_file_name\n"); + return -1; + } + t = tl t; + logfile = hd t; + if (logfile != nil) { + if ((logfd = sys->open(logfile, sys->OWRITE)) == nil) { + sys->fprint(stderr, "open log file %s failed\n", logfile); + continue; + } + logflag = 1; + } + "mailhost" => + if (i < 2) { + sys->fprint(stderr, "no mailhost found."); + sys->fprint(stderr, "mailhost: host_name\n"); + return -1; + } + t = tl t; + mailhost = hd t; + "voice" => + if (i < 2) { + sys->fprint(stderr, "no log file name found."); + sys->fprint(stderr, "voice: serial_port\n"); + return -1; + } + t = tl t; + voicefile = hd t; + "phone" => + if (i < 3) { + sys->fprint(stderr, "not enough fields for phone attendance line\n"); + sys->fprint(stderr, "attend: serial_port phone_appearance_file_name\n"); + return -1; + } + t = tl t; + phonefile := hd t; + t = tl t; + apprfile := hd t; + phone[Nphones].devfile = phonefile; + phone[Nphones].apprfile = apprfile; + phone[Nphones].phonefd = sys->open(phonefile, sys->ORDWR); + if (phone[Nphones].phonefd == nil) { + sys->fprint(stderr, "open %s failed, %r\n", phonefile); + return -1; + } + (numappr, version) := phoneinit(Nphones); + if (numappr == 0) continue; + phone[Nphones].numappr = numappr; + phone[Nphones].people = array[numappr + 1] of person; + phone[Nphones].version = version; + if (debug) sys->fprint(stderr, "phone %d initialized\n", Nphones); + getcallapprinfo(Nphones); + ++Nphones; + * => + sys->fprint(stderr, "bad keyword <%s> in configuration file\n", hd t); + return -1; + } + } + } while (line != nil); + return 0; +} + +# +# +# +timekeeper(tchan: chan of string) { + for(;;) { + sys->sleep(TIMEGRAN); + tchan <- = sys->sprint("filecheck"); + } +} + +# +# monitor the status messages of the phone(s). +# look for ring indications and subsequent display data to send +# to users if they do not answer their phones. +# If display data is received and the phone is not answered, +# a mail message is sent. +# +watchphone(pindex: int, voicechan: chan of string) { + buf, errbuf: string; + + do { + (buf, errbuf) = getline(phone[pindex].phonefd); + if (errbuf != nil) { + sys->fprint(stderr, "%s\n", errbuf); + return; + } + if (debug) sys->fprint(stderr, "phone %d: %s\n", pindex, buf); + (resultcode, info) := str->splitl(buf, ":"); + if (resultcode == nil) continue; + + # get rid of colon + info = info[1:]; + + (i, t) := sys->tokenize(info, ","); + appr := int hd t; + t = tl t; + --i; + case resultcode { + "RING" or "02" => + if ((phone[pindex].people[appr].flags & ANNOUNCE)) + voicechan <- = sys->sprint("phone call for, %s.\r", phone[pindex].people[appr].name); + phone[pindex].people[appr].state = RING; + phone[pindex].people[appr].time = ""; + phone[pindex].people[appr].orignum = ""; + phone[pindex].people[appr].origname = ""; + "DISPLAY" or "06" => + if (i <= 0) { + sys->fprint(stderr, "not enough args for DISPLAY result code\n"); + continue; + } + displaydata := hd t; + (displaytype, s) := str->toint(displaydata[0:2], 16); + case displaytype { + 16r03 => + # originating number + phone[pindex].people[appr].orignum = displaydata[2:]; + 16r05 => + # originating name + phone[pindex].people[appr].origname = displaydata[2:]; + 16r0a => + correct24hr: int; + + # date and time + if (displaydata[13:15] == "pm") + correct24hr = 12; + else + correct24hr = 0; +# hour := int displaydata[8:10] + correct24hr; + phone[pindex].people[appr].time = sys->sprint("%s %2d %2d:%.2d", months[int displaydata[2:4]], int displaydata[5:7], int displaydata[8:10] % 12 + correct24hr, int displaydata[11:13]); + phone[pindex].people[appr].state = DISPLAY; + if (logflag && (phone[pindex].people[appr].flags & LOG)) + sys->fprint(logfd, "%s: x%s %s (%s)\n", phone[pindex].people[appr].time, phone[pindex].people[appr].lineno, phone[pindex].people[appr].orignum, phone[pindex].people[appr].origname); + } + "SIGNAL" or "13" => + signalcode := hd t; + t = tl t; + --i; + case signalcode { + "4F" => + if (i <= 0) { + if (phone[pindex].people[appr].state == DISPLAY) { + phone[pindex].people[appr].state = OFFHOOK; + } + continue; + } + causecode := hd t; + case causecode { + "10" => + case phone[pindex].people[appr].state { + DISPLAY => + if ((phone[pindex].people[appr].flags & MAIL) && phone[pindex].people[appr].mailaddr != "-") { + mailmesg := sys->sprint("From: phoneca\nTo: %s\nSubject: Phone call from %s\n\n from: %s\n phone: %s\n time: %s\n", phone[pindex].people[appr].mailaddr, phone[pindex].people[appr].orignum, phone[pindex].people[appr].origname, phone[pindex].people[appr].orignum, phone[pindex].people[appr].time); + + spawn smtp->init(nil, "smtp" :: mailhost :: "phoneca" :: phone[pindex].people[appr].mailaddr :: mailmesg :: nil); + } + } + phone[pindex].people[appr].state = ONHOOK; + } + } + } + } while(errbuf == nil); +} + +usage() { + sys->fprint(stderr, "usage: fone -d phone_dev\n"); + bye(); +} + +# +# wait for an OK from a particular phone, part of Hayes protocol +OK(phonefd: ref sys->FD): int { + buf, err: string; + + do { + (buf, err) = getline(phonefd); + if (err != nil) { + sys->fprint(stderr, "%s\n", err); + return(0); + } + if (debug) sys->fprint(stderr, "%s\n", buf); + } while (buf != "OK" && buf != "0"); + return(1); +} + +bye() { + exit; +} + +phoneinit(pindex: int): (int, string) { + buf, err: string; + i: int; + t: list of string; + + phonefd := phone[pindex].phonefd; +# E0=echo OFF, V0=verbal return codes ON/OFF, &D0=ignore DTR transition + if (debug) sys->fprint(stderr, "initialize phone %d serial port...", pindex); + sys->fprint(phonefd, "ATE0V1&D0\r"); + if (!OK(phonefd)) return (0, "cannot initialize phone"); + +# &&I=init phone, I3=report phone type + if (debug) sys->fprint(stderr, "get phone version..."); + sys->fprint(phonefd, "AT&&II3\r"); + do { + (buf, err) = getline(phonefd); + if (err != nil) { + sys->fprint(stderr, "%s\n", err); + return (0, "cannot get phone version"); + } + (i, t) = sys->tokenize(buf, " \n\r"); + } while (i != 4 || hd t != "03-"); + t = tl t; + if (!OK(phonefd)) return (0, "cannot get phone version"); + version := hd t; + if (debug) sys->fprint(stderr, "version <%s>\n", version); + numappr := int version[2:4]; + +# %A0=3 channel assigned to control voice + if (debug) sys->fprint(stderr, "control phone's voice channel..."); + sys->fprint(phonefd, "AT%%A0=3\r"); + if (!OK(phonefd)) return (0, "cannot control voice channel"); + return (numappr, version); +} + +# +# get a line of text (up to a newline or carriage return) +# throw away initial newlines or carriage returns +# +getline(fd: ref sys->FD): (string, string) { + c := array[1] of byte; + s := ""; + i := 0; + + loop: while(i < 4096) { + r := sys->read(fd, c, 1); + if(r < 0) + return (s, sys->sprint("%r")); + if(r == 0) + return (nil, nil); + case int c[0] { + '\r' or + '\n' => + if(i != 0) + break loop; + * => + s[i++] = int c[0]; + } + + } + return (s, nil); +} +# +# read in names and mail addresses for appearances on each phone +# +getcallapprinfo(pindex: int) { + name : string; + filename := phone[pindex].apprfile; + + if (debug) sys->fprint(stderr, "getting call appearance data from %s\n", filename); + who := bufio->open(filename, sys->OREAD); + if (who == nil) { + sys->fprint(stderr, "open %s failed, %r\n", filename); + bye(); + } + phone[pindex].apprtime = daytime->now(); + while ((s := who.gets('\n')) != nil) { + if ((array of byte(s))[0] == byte '#') continue; + (i, t) := sys->tokenize(s, " \t\n\r"); + if(i < 5) { + sys->fprint(stderr, "Error in %s. The line was:\n%s\n", filename, s); + continue; + } + appr := int hd t; + t = tl t; + phone[pindex].people[appr].lineno = hd t; + t = tl t; + flags := hd t; + phone[pindex].people[appr].flags = 0; + for (n:=0; n<len flags; n++) { + case int (array of byte flags)[n] { + 'l' => + phone[pindex].people[appr].flags |= LOG; + 'm' => + phone[pindex].people[appr].flags |= MAIL; + 'a' => + phone[pindex].people[appr].flags |= ANNOUNCE; + * => + sys->fprint(stderr, "unknown flag %c\n", int (array of byte flags)[n]); + } + } + t = tl t; + phone[pindex].people[appr].mailaddr = hd t; + t = tl t; + name = ""; + while(t != nil) { + name += " " + hd t; + t = tl t; + } + phone[pindex].people[appr].name = name; +# if (debug) sys->fprint(stderr, "added user %s at %d\n", phone[pindex].people[appr].name, appr); + } +} + +# +# Setup connection to use READ.EXE command in SounBlaster software +# +SBsetup(): ref sys->FD { + cmd := sys->open("/cmd/clone", sys->ORDWR); + if (cmd == nil) { + sys->fprint(stderr, "open %s failed, %r\n", "/cmd/clone"); + bye(); + } + cmdno := array[32] of byte; + if ((n:=sys->read(cmd, cmdno, 32)) <= 0) { + sys->fprint(stderr, "read error: %r\n"); + bye(); + } + cmddirname := "/cmd/" + string cmdno[0:n]; + + if (debug) sys->fprint(stderr, "exec'ing command\n"); + if ((n=sys->fprint(cmd, "exec command")) < 0) { + sys->fprint(stderr, "fprint of cmd failed:%r\n"); + bye(); + } + + cmddata := sys->open(cmddirname + "/data", sys->ORDWR); + if (cmddata == nil) { + sys->fprint(stderr, "open %s:%r\n", cmddirname + "/data"); + bye(); + } + + buf := array[128] of byte; +# sys->fprint(stderr, "sending sbtalker\n"); + if ((n=sys->fprint(cmddata, "sbtalker /dBLASTER\r")) < 0) { + sys->fprint(stderr, "fprint of cmddata failed:%r\n"); + bye(); + } + n = sys->read(cmddata, buf, 128); + if (n < 0) { + sys->fprint(stderr, "read /cmd/n/data failed:%r\n"); + bye(); + } + sys->fprint(stderr, "%*s\n", n, string buf[0:n]); + +# sys->fprint(stderr, "sending read\n"); + if ((n=sys->fprint(cmddata, "read\r")) < 0) { + sys->fprint(stderr, "fprint of cmddata failed:%r\n"); + bye(); + } + n = sys->read(cmddata, buf, 128); + if (n < 0) { + sys->fprint(stderr, "read /cmd/n/data failed:%r\n"); + bye(); + } + sys->fprint(stderr, "%*s\n", n, string buf[0:n]); + return cmddata; +} + +# +# setup connection to DECTalk +# +DTsetup(voicedev: string): ref sys->FD { + voicel := sys->open(voicedev, sys->ORDWR); + if (voicel == nil) { + sys->fprint(stderr, "open %s failed, %r\n", voicedev); + bye(); + } + voicectl := sys->open(voicedev+"ctl", sys->OWRITE); + if (voicectl == nil) { + sys->fprint(stderr, "open %s failed, %r\n", voicedev+"ctl"); + bye(); + } + if (sys->fprint(voicectl, "B1200") != 5) { + sys->fprint(stderr, "write %s failed, %r\n", voicedev+"ctl"); + bye(); + } + return voicel; +} |
