summaryrefslogtreecommitdiff
path: root/appl/cmd/fone.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/fone.b')
-rw-r--r--appl/cmd/fone.b560
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;
+}