LarkSmartsImpl.mesa
Last modified by D. Swinehart, December 13, 1985 10:16:35 am PST
Last Edited by: Pier, May 3, 1984 3:00:22 pm PDT
Polle Zellweger (PTZ) August 23, 1985 2:21:19 pm PDT
DIRECTORY
BasicTime USING [ GMT, Now, Pack, Period, ToNSTime, Unpack, Unpacked, Update ],
Convert USING [ RopeFromCard ],
IO,
Lark,
LarkSmartsMonitorImpl,
MBQueue USING [ QueueClientAction ],
NamesGV USING [ GVSetAttribute ],
RefID USING [ ID, Reseal ],
Rope USING [ Concat, Equal, Fetch, FromChar ],
ThParty USING [ Alert, CreateConversation, GetPartyFromNumber, GetPartyFromFeepNum ],
Thrush USING [
ConversationID, ConvEvent, IntervalSpec, PartyID, NB, nullConvID, nullID, ROPE, StateInConv ],
ThSmartsPrivate USING [
AssessDamage, ChangeState, ConvDesc, DBInfo, Deregister, EnableSmarts, EnterLarkState, ForgetConv, GetConv, InterpretHookState, LarkFailed, LarkInfo, NoteNewState, OpenConversations, ParseState, SmartsInfo ],
TU USING [ RefAddr ],
VoiceUtils USING [ Problem, ProblemFR ]
;
LarkSmartsImpl: CEDAR MONITOR LOCKS root
IMPORTS
BasicTime,
Convert,
IO,
MBQueue,
root: LarkSmartsMonitorImpl,
NamesGV,
RefID,
Rope,
ThParty,
ThSmartsPrivate,
TU,
VoiceUtils
EXPORTS ThSmartsPrivate
SHARES LarkSmartsMonitorImpl = {
OPEN IO;
Copies
CommandEvents: TYPE = Lark.CommandEvents;
ConversationID: TYPE = Thrush.ConversationID;
nullConvID: ConversationID=Thrush.nullConvID;
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
disabled: Lark.Event = Lark.disabled;
enabled: Lark.Event = Lark.enabled;
endNum: Lark.Event = Lark.endNum;
Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; };
IntervalSpec: TYPE = Thrush.IntervalSpec;
LarkInfo: TYPE = ThSmartsPrivate.LarkInfo;
NB: TYPE = Thrush.NB;
nullID: RefID.ID = Thrush.nullID;
OpenConversations: TYPE = ThSmartsPrivate.OpenConversations;
PartyID: TYPE = Thrush.PartyID;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Lark.SHHH; -- Encrypts conv. if first arg to RPC PROC
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
StateInConv: TYPE = Thrush.StateInConv;
StatusEvents: TYPE = Lark.StatusEvents;
Parsing
ParseEvent handles events from the EtherPhone, by queuing them to synchronize with other activities.
EventSpec: TYPE = REF EventSpecBody;
EventSpecBody: TYPE = RECORD [
smartsInfo: SmartsInfo,
sEvent: Lark.StatusEvent
];
LarkParseEvent: PUBLIC PROC[
smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent] = {
smartsInfo.requests.QueueClientAction[
QdLarkParseEvent, NEW[EventSpecBody ← [smartsInfo, sEvent]]];
};
QdLarkParseEvent: ENTRY PROC[r: REF] = { -- r is the EventSpec
eventSpec: EventSpec = NARROW[r];
smartsInfo: SmartsInfo = eventSpec.smartsInfo; {
ENABLE {
UNWIND=>NULL;
ThSmartsPrivate.LarkFailed => { VoiceUtils.ProblemFR["%g: Lark Failed", $Smarts, smartsInfo, TU.RefAddr[smartsInfo]]; GOTO Failed; };
};
sEvent: Lark.StatusEvent ← eventSpec.sEvent;
IF ~LarkEnabled[smartsInfo] THEN RETURN; -- don't interfere with reverted call!
sEvent ← ThSmartsPrivate.InterpretHookState[smartsInfo.larkInfo, sEvent, smartsInfo];
Deal with ring-detect bounce, speaker switch clicks, and the like.
SELECT sEvent.device FROM
nothing => RETURN;
speakerSwitch, touchPad, hookSwitch => NULL;
ENDCASE => ERROR;
DoParse[smartsInfo, sEvent.event]; -- Interpret this event in light of past ones
IF smartsInfo.haveArguments THEN { -- now are ready to execute an action routine
val: INT𡤀
IF smartsInfo.Command=NIL THEN smartsInfo.Command ← CmdCall; -- was a phone number
SELECT smartsInfo.parseState FROM
inNum => val ← IO.GetInt[IO.RIS[smartsInfo.arguments]!
IO.Error => SELECT ec FROM Failure, SyntaxError, Overflow => CONTINUE; ENDCASE];
ENDCASE;
smartsInfo.Command[smartsInfo, val]; -- Execute the action routine
[]←SetParserIdle[smartsInfo, TRUE, TRUE];
};
The rest of this has to be done in the places where the situations arise.
Be sure state of conv. and tones agree with state of parsing.
<<This feels like in the wrong place. Oughta be done by a command proc of some sort?
Or get rid of the command procs?>>
cDesc ← GetConvDesc[smartsInfo];
IF cDesc#NIL THEN {
OPEN now: cDesc.situation.self;
SELECT now.state FROM
# desired.state => RETURN; -- some request already in the works.
reserved => IF smartsInfo.haveOne THEN desired.state←parsing ELSE RETURN;
parsing => IF smartsInfo.haveOne THEN RETURN ELSE {
desired.state←reserved; cDesc.desiredSituation.reason ← NIL; };
ENDCASE=>RETURN;
ThSmartsPrivate.Apprise[smartsInfo];
};
EXITS
Failed => ThSmartsPrivate.Deregister[smartsInfo.smartsID];
}; };
LarkEnabled: INTERNAL PROC[info: SmartsInfo] RETURNS [enabled: BOOL] = INLINE {
RETURN[SELECT info.larkInfo.larkState FROM
none => ThSmartsPrivate.EnableSmarts[info],
failed, recovering => FALSE,
ENDCASE => TRUE];
};
DoParse: INTERNAL PROC[info: SmartsInfo, event: Lark.Event] = {
In the name of unexcess generality, this procedure parses all of the commands: call placement, message handling, call management, and so on. Could change to call sequential registered procedures and like that, if more flexibility is needed.
IF info.haveOne=FALSE AND event#disabled AND GetSIC[info] = $reserved THEN
[--nb--] ← ThSmartsPrivate.ChangeState[GetConvDesc[info], $parsing];
See Log notes, November 29, 1985 11:18:59 am PST
info.haveOne ← TRUE;
SELECT event FROM
On/Offhook
disabled => { info.haveArguments ← TRUE; info.Command ← CmdOnhook; RETURN;};
enabled => { info.haveArguments ← TRUE; info.Command ← CmdOffhook; RETURN;};
Termination of arguments, DEL
endNum => { info.haveArguments ← TRUE; RETURN; }; -- '#
IO.DEL, IO.ESC => { []←SetParserIdle[info, TRUE, TRUE]; RETURN; };
Command or recipient initiation
'* => {
IF info.cmdOrRecip THEN info.offset�
info.cmdOrRecip ← TRUE; info.parseState ← idle;
RETURN;
};
Valid argument event
ENDCASE => NULL; -- continues below
IF info.cmdOrRecip THEN { -- begins command or feepName recipient specification
info.cmdOrRecip ← FALSE;
event𡤎vent+info.offset;
info.offset𡤀
IF event = '0 THEN { -- feepName recipient specification
[]←SetParserIdle[info, FALSE, FALSE]; info.parseState ← getFeep; RETURN; };
[]←SetParserIdle[info, TRUE, FALSE];
SELECT event FROM
Here are the commands
'1, 'H, 'h => -- hold << for now, toggle "radio" state >>
{ info.Command ← CmdRadio; info.haveArguments ← TRUE; };
'3, 'C, 'c => -- conference << for now, toggle "hot line" state >>
{ info.Command ← CmdAutoAnswer; info.haveArguments ← TRUE; };
'7, 'F, 'f => -- forward << for now, toggle "monitored" state >>
{ info.Command ← CmdMonitor; info.haveArguments ← TRUE; };
'*,   -- esc
endNum -- del -- => []←SetParserIdle[info, TRUE, TRUE];
'0+10, 'T, 't => { info.Command ← CmdBackDoor; info.haveArguments ← TRUE; };
**0
'1+10, 'O, 'o => { info.Command ← CmdRingOff; info.haveArguments ← TRUE; };
'2+10, 'N, 'n => { info.Command ← CmdRingOn; info.haveArguments ← TRUE; };
'3+10, 'L, 'l => { info.Command ← CmdRingOffTimed; info.parseState ← getNum; };
'4+10, 'A, 'a => { info.Command ← CmdRingOnce; info.haveArguments ← TRUE; };
'5+10, 'B, 'b => { info.Command𡤌mdRingOnceTimed; info.parseState ← getNum; };
ENDCASE => -- unassigned same as DEL -- NULL;
RETURN;
};
A letter, digit, or other non-activation character.
info.arguments ← Rope.Concat[info.arguments, Rope.FromChar[event]];
info.argLength ← info.argLength + 1; -- appends new event to argument string
info.parseState ← SELECT event FROM
IN ['0..'9] => SELECT info.parseState FROM
idle => inSeq,
getFeep => inFeep,
getNum => inNum,
getStr => inStr,
ENDCASE => info.parseState,
ENDCASE => SELECT info.parseState FROM
idle, getStr => inStr,
getFeep, getNum, inFeep, inNum => inTossStr,
inSeq => SetParserIdle[info, TRUE],
ENDCASE => info.parseState;
IF info.parseState = inSeq THEN {
Sequence of digits whose length is determined by the values of the first character or two
e1: Lark.Event = info.arguments.Fetch[0];
SELECT info.argLength FROM
1 => SELECT event FROM
'0 => info.haveArguments←TRUE; -- PARC Operator
'4, '5, '6, '8, '9 => NULL; -- seq-type number continues
ENDCASE => info.parseState ← inNum;
Require explicit termination of calls beginning with unrecognized prefixes
2 => IF info.arguments.Equal["90"] THEN info.parseState ← inNum;
Require explicit termination of international calls, calling card calls, calls to Telco Operator
3 => IF info.arguments.Equal["911"] THEN info.haveArguments←TRUE;
4 => SELECT e1 FROM
'9 => IF info.arguments.Equal["9911"] THEN info.haveArguments ← TRUE;
'4, '5, '6 => info.haveArguments ← TRUE; -- local extensions
ENDCASE;
8 => IF info.arguments.Fetch[2] > '1 THEN info.haveArguments ← TRUE;
Local or Intelnet
11 => info.haveArguments ← TRUE; -- DDD call, via ATT or Intelnet
ENDCASE; -- keep on going
};
};
SetParserIdle: INTERNAL PROC[info: SmartsInfo, clearCmd: BOOL, reallyIdle: BOOLTRUE]
RETURNS [ ThSmartsPrivate.ParseState ] = {
info.haveArguments ← info.haveOne ← FALSE;
info.parseState ← idle; info.arguments ← NIL;
info.offset ← 0; info.cmdOrRecip ← FALSE;
info.argLength ← 0; IF clearCmd THEN info.Command ← NIL;
IF reallyIdle THEN SELECT GetSIC[info] FROM
$parsing => [] ← ThSmartsPrivate.ChangeState[GetConvDesc[info], $reserved]; -- error?
ENDCASE;
RETURN[idle];
};
User-invoked actions
CmdCall: INTERNAL PROC[info: SmartsInfo, val: INT] = {
User has specified feep name or number;
cDesc: ConvDesc; {
OPEN now: cDesc.situation.self;
partyID: PartyID;
otherPartyID: PartyID;
nb: NB;
IF info.argLength=0 OR info.parseState=$inTossStr THEN RETURN;
SELECT GetSIC[info] FROM
$reserved, $parsing => NULL; ENDCASE => RETURN;
cDesc ← GetConvDesc[info];
IF cDesc=NIL THEN RETURN; --  report error, enter error state?
partyID ← now.partyID;
[nb, otherPartyID] ← GetPartiesForCall[info, partyID, info.parseState, info.arguments];
SELECT nb FROM
$success => NULL;
$noSuchParty, -- Serious business; analyze this!
$noSuchParty2, $narcissism =>
{ [--nb--] ← ThSmartsPrivate.ChangeState[cDesc, $failed, $notFound]; RETURN; };
ENDCASE => GOTO Failed;
IF (nb ← ThSmartsPrivate.ChangeState[cDesc, $initiating]) = $success THEN
nb←ThParty.Alert[credentials: now, calledPartyID: otherPartyID];
IF nb # $success THEN GOTO Failed; -- enter error state?
EXITS
Failed => [--nb--] ← ThSmartsPrivate.ChangeState[cDesc, $failed, $notFound];
System failure or unexpected case; analyze!! 
See LarkSmartsSupImpl.AssessDamage for the kinds of errors that occur here. Nobody's catching them, so they have to be dealt with here.
}; };
CmdBackDoor: INTERNAL PROC[info: SmartsInfo, val: INT] = {
IF GetSIC[info]=$active AND info.larkInfo.larkState = $trunkTalking THEN { -- Hack
ThSmartsPrivate.EnterLarkState[info.larkInfo, $trunkFlashing, info]; RETURN; };
info.arguments ← NIL;
info.parseState ← $inSeq;
info.argLength ← 1;
CmdCall[info, val];
};
CmdOnhook: INTERNAL PROC[info: SmartsInfo, val: INT] = {
cDesc: ConvDesc;
SELECT GetSIC[info] FROM
$idle, $notified, $ringing, $neverWas => RETURN; -- could still be offhook from last call.
ENDCASE => NULL;
cDesc ← GetConvDesc[info];
[--nb--] ← ThSmartsPrivate.ChangeState[cDesc, $idle, $terminating];
};
CmdOffhook: INTERNAL PROC[info: SmartsInfo, val: INT] = {
desiredState: StateInConv ← $active;
nb: NB;
cDesc: ConvDesc;
SELECT GetSIC[info] FROM
$idle => {
convEvent: Thrush.ConvEvent;
cDesc ← ThSmartsPrivate.GetConv[info, nullConvID, TRUE];
[nb, convEvent] ← ThParty.CreateConversation[
credentials: cDesc.situation.self,
state: $reserved
];
IF nb # $success THEN {
ThSmartsPrivate.AssessDamage[nb, cDesc, convEvent];
ThSmartsPrivate.ForgetConv[cDesc];
No idea what to do!
}
ELSE ThSmartsPrivate.NoteNewState[cDesc, convEvent];
};
$ringing => {
cDesc ← GetConvDesc[info];
IF cDesc=NIL THEN RETURN; -- Is this an error?
[--nb--]←ThSmartsPrivate.ChangeState[cDesc, desiredState]; -- Complain on error?
};
ENDCASE
};
Data base changes -- ringing specifications and so on.
CmdRingOff: INTERNAL PROC[info: SmartsInfo, val: INT] = {
DoRingSpec[info, "O", $timedringmode]};
CmdRingOn: INTERNAL PROC[info: SmartsInfo, val: INT] = {
DoRingSpec[info,"R", $ringmode]};
CmdRingOffTimed: INTERNAL PROC[info: SmartsInfo, val: INT] = {
DoRingSpec[info,"O",$timedringmode, val]};
CmdRingOnce: INTERNAL PROC[info: SmartsInfo, val: INT] = {
DoRingSpec[info,"S", $ringmode]; };
CmdRingOnceTimed: INTERNAL PROC[info: SmartsInfo, val: INT] ={
DoRingSpec[info,"S",$timedringmode, val]};
RingSpec: TYPE = REF RingObj;
RingObj: TYPE = RECORD [
info: SmartsInfo,
ringMode: ROPE,
ringModeAttribute: ATOM,
timeInMinutes: INT
];
DoRingSpec: INTERNAL PROC[info: SmartsInfo, ringMode: ROPE,
ringModeAttribute: ATOM, timeInMinutes: INT←1441-- default one day and one second--] = {
Queue it to release user interface
IF timeInMinutes=0 THEN timeInMinutes ← 30; -- default one day and one second
info.requests.QueueClientAction[QdRing,
NEW[RingObj ← [info, ringMode, ringModeAttribute, timeInMinutes]]];
};
QdRing: ENTRY PROC[r: REF] = {
ringSpec: RingSpec ← NARROW[r];
partyID: PartyID;
rName: ROPE;
info: SmartsInfo = ringSpec.info;
cDesc: ConvDesc = ThSmartsPrivate.GetConv[info, info.currentConvID, FALSE];
now: BasicTime.GMT;
unp: BasicTime.Unpacked;
interval: INT ← ringSpec.timeInMinutes*60;
IF cDesc=NIL THEN RETURN; -- should make error signal, but that's too hard.
rName ← ThSmartsPrivate.DBInfo[partyID ← cDesc.situation.self.partyID].dbRname;
IF rName=NIL THEN RETURN; -- should make error signals
Set timed or standard ring mode attribute
NamesGV.GVSetAttribute[rName, ringSpec.ringModeAttribute, ringSpec.ringMode];
If timed, set time to minimum of now+(ringSpec.time)*60 and midnight in minutes
IF ringSpec.ringModeAttribute # $timedringmode THEN RETURN;
unp ← BasicTime.Unpack[now ← BasicTime.Now[]];
unp.hour ← 23;
unp.minute ← 59;
unp.second ← 59; -- midnight
interval ← MIN[interval, BasicTime.Period[from: now, to: BasicTime.Pack[unp]]+1];
NamesGV.GVSetAttribute[rName, $expiration,
Convert.RopeFromCard[BasicTime.ToNSTime[BasicTime.Update[now, interval]]]];
};
CmdRadio: INTERNAL PROC[info: SmartsInfo, val: INT] = {
info.larkInfo.radio ← NOT info.larkInfo.radio;
};
CmdAutoAnswer: INTERNAL PROC[info: SmartsInfo, val: INT] = {
info.larkInfo.autoAnswer ← NOT info.larkInfo.autoAnswer;
};
CmdMonitor: INTERNAL PROC[info: SmartsInfo, val: INT] = {
 info.larkInfo.monitor ← NOT info.larkInfo.monitor;
};
Connection Management Utilities
GetConvDesc: PUBLIC INTERNAL PROC[info: SmartsInfo] RETURNS [ cDesc: ConvDesc ] = {
RETURN[IF info.currentConvID = nullConvID THEN NIL
ELSE ThSmartsPrivate.GetConv[ info, info.currentConvID, FALSE ]];
};
GetSIC: PUBLIC INTERNAL PROC[info: SmartsInfo]
RETURNS [ state: StateInConv ] = {
cDesc: ConvDesc = GetConvDesc[info];
state ← IF cDesc=NIL THEN $idle ELSE cDesc.situation.self.state;
};
GetPartiesForCall: PROC[
info: SmartsInfo, partyID: PartyID, parseState: ThSmartsPrivate.ParseState, args: ROPE]
RETURNS[nb: NB← $noSuchParty, calledPartyID: PartyID ← nullID ] = {
IF partyID = nullID THEN RETURN;
SELECT parseState FROM
inSeq, inNum => [nb, calledPartyID] ← ThParty.GetPartyFromNumber[
partyID: partyID, phoneNumber: args];
inFeep =>
[nb, calledPartyID] ← ThParty.GetPartyFromFeepNum[partyID: partyID, feepNum: args];
inStr => complain — see below;
ENDCASE => VoiceUtils.Problem["Don't know how to look for called party", $Smarts, info];
};
}.
Swinehart, May 22, 1985 12:14:29 pm PDT
hotLine => autoAnswer
changes to: DoParse, CmdHotline, }
Polle Zellweger (PTZ) August 23, 1985 2:21:19 pm PDT
Consistency checking on devices.
changes to: LarkParseEvent, DIRECTORY
Swinehart, October 28, 1985 9:51:23 am PST
Handle => ID, Log => VoiceUtils, pull ProblemHandle, ...
changes to: DIRECTORY, LarkSmartsImpl, ConversationID, Reseal, nullID, PartyID, LarkParseEvent, CmdCall, CmdOffhook, CmdRingDoProc, GetCDesc, GetPartiesForCall, ProblemID
Swinehart, November 29, 1985 11:19:02 am PST
When event is disabled (hanging up), don't transition into $parsing because there will soon be a transition into $idle
changes to: DoParse