LarkSmartsImpl.mesa
Copyright Ó 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, April 6, 1987 8:24:04 am PDT
Last Edited by: Pier, May 3, 1984 3:00:22 pm PDT
Polle Zellweger (PTZ) August 23, 1985 2:21:19 pm PDT
DIRECTORY
Atom USING [ DottedPairNode ],
BasicTime USING [ GMT, Now, Pack, Period, Unpack, Unpacked ],
IO,
Lark,
LarkFeepRpcControl,
LarkSmartsMonitorImpl,
MBQueue USING [ QueueClientAction ],
NameDB USING [ SetAttribute, SetAttributeTimed ],
RefID USING [ ID, Reseal, Unseal ],
Rope USING [ Concat, Equal, Fetch, FromChar, Length ],
SafeStorage USING [ NarrowRefFault ],
ThParty USING [ Alert, CreateConversation, GetPartyFromNumber, GetPartyFromFeepNum, GetPartyInfo, LookupServiceInterface, PartyInfo ],
Thrush USING [
ConversationID, ConvEvent, InterfaceSpec, PartyID, NB, none, nullConvID, nullID, ROPE, StateInConv ],
ThSmartsPrivate USING [
ChangeState, ConvDesc, DBInfo, EnterLarkState, ForgetConv, InputEventSpec, GetConv, LarkInfo, NoteNewState, OpenConversations, ParseState, SmartsInfo ],
VoiceUtils USING [ MakeAtom, Problem ]
;
LarkSmartsImpl: CEDAR MONITOR LOCKS root
IMPORTS
BasicTime,
IO,
MBQueue,
LarkFeepRpcControl,
root: LarkSmartsMonitorImpl,
NameDB,
RefID,
Rope,
SafeStorage,
ThParty,
ThSmartsPrivate,
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]]; };
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.
LarkParseEvent: PUBLIC ENTRY PROC[r: REF] = { -- r is the InputEventSpec
ENABLE UNWIND=>NULL;
eventSpec: ThSmartsPrivate.InputEventSpec = NARROW[r];
smartsInfo: SmartsInfo = eventSpec.smartsInfo;
sEvent: Lark.StatusEvent ← eventSpec.sEvent;
IF smartsInfo=NIL OR smartsInfo.failed THEN RETURN; -- Events directed at dead smarts
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];
};
};
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. Hairy touchpad user interfaces aren't a major goal of this work.
IF info.haveOne=FALSE AND event#disabled AND GetSIC[info] = $reserved THEN
IF ThSmartsPrivate.ChangeState[GetConvDesc[info], $parsing] # $success THEN
{ []←SetParserIdle[info, TRUE, FALSE]; RETURN; }; -- just get out!
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
'*,   -- esc
endNum -- del -- => []←SetParserIdle[info, TRUE, TRUE];
IN ['1..'9] => { []←SetParserIdle[info, FALSE, FALSE]; info.parseState ← getFeep; GOTO Continue; };
Continue to collect event as first character of feep name!
'0+10, 'T, 't => { info.Command ← CmdBackDoor; info.haveArguments ← TRUE; };
**0
'1+10 => { info.Command ← CmdRingOff; info.haveArguments ← TRUE; };
'2+10 => { info.Command ← CmdRingOn; info.haveArguments ← TRUE; };
'3+10 => { info.Command ← CmdRingOffTimed; info.parseState ← getNum; };
'4+10 => { info.Command ← CmdRingOnce; info.haveArguments ← TRUE; };
'5+10 => { info.Command𡤌mdRingOnceTimed; info.parseState ← getNum; };
'7+10 => { info.Command← CmdAudioSource; info.haveArguments ← TRUE; info.arguments ← "telset"; };
'8+10 => { info.Command← CmdAudioSource; info.haveArguments ← TRUE; info.arguments ← "lineA"; };
'9+10 => { info.Command← CmdAudioSource; info.haveArguments ← TRUE; info.arguments ← "lineB"; };
ENDCASE => -- unassigned same as DEL -- NULL;
RETURN;
EXITS Continue => NULL;
};
A letter, digit, or other non-activation character.
info.arguments ← Rope.Concat[info.arguments, Rope.FromChar[event]];
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.arguments.Length[] FROM
1 => SELECT event FROM
'0 => info.haveArguments←TRUE; -- PARC Operator
'4, '5, '6, '7, '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;
5 => SELECT e1 FROM
'7 => info.haveArguments ← TRUE; -- Xerox Sunnyvale 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;
IF clearCmd THEN info.Command ← NIL;
IF reallyIdle THEN SELECT GetSIC[info] FROM
$parsing => IF ThSmartsPrivate.ChangeState[GetConvDesc[info], $reserved] # $success
THEN info.Command ← NIL; -- Massive failure, control damage.
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;
nb: NB;
partyID: PartyID;
otherPartyID: PartyID;
IF info.arguments=NIL OR info.parseState=$inTossStr THEN RETURN;
SELECT GetSIC[info] FROM
$reserved, $parsing => NULL;
$active => RETURN; -- ignore further DTMF signalling activity after call is active
ENDCASE => {
VoiceUtils.Problem["LarkSmarts placing call from wrong state"]; RETURN; };
cDesc ← GetConvDesc[info];
IF cDesc=NIL THEN RETURN; -- Error already reported by GetConvDesc.
partyID ← now.partyID;
[nb, otherPartyID] ← GetPartiesForCall[info, partyID, info.parseState, info.arguments];
SELECT nb FROM
$success => NULL;
$noSuchParty2 =>
{ [] ← ThSmartsPrivate.ChangeState[cDesc, $failed, $notFound]; RETURN; };
$voiceTerminalBusy => -- front/back door thing
{ [] ← ThSmartsPrivate.ChangeState[cDesc, $failed, $busy, "Only available trunk line is in use"]; RETURN; }; -- Unlikely to happen.
ENDCASE => RETURN; -- Problems already reported, nothing more can be done.
IF ThSmartsPrivate.ChangeState[cDesc, $initiating] = $success THEN {
comment: ROPENIL;
SELECT (nb←ThParty.Alert[credentials: now, calledPartyID: otherPartyID]) FROM
$success => RETURN;
$narcissism => comment ← "Attempt to call self rejected on philosophical grounds";
$noSuchParty2, $voiceTerminalBusy, $alreadyInConv =>
comment ← "Called party cannot be contacted";
$noSuchParty, $noSuchSmarts, $noSuchConv, $notInConv, $convIdle, $stateMismatch, $interfaceError => {
VoiceUtils.Problem["Serious Party error detected by LarkSmarts"];
ThSmartsPrivate.ForgetConv[cDesc]; RETURN;
};
Consider removing some of these if other reports always preceed them.
Might have to do ForgetConversation and/or ThSmarts.Fail. See what Advance does with these things, first.
ENDCASE => ERROR; -- Unexpected
[] ← ThSmartsPrivate.ChangeState[cDesc, $failed, $busy, comment];
};
}; };
CmdBackDoor: INTERNAL PROC[info: SmartsInfo, val: INT] = {
To work properly, this would have to be a communication to the "Feep" interface of the other party. Check out how LarkSmarts.Feep works. Fix Feep in the forwarded case.
cDesc: ConvDesc ← GetConvDesc[info];
IF cDesc#NIL AND cDesc.situation.self.state=$active THEN {
pInfo: ThParty.PartyInfo;
interfaceSpec: Thrush.InterfaceSpec;
interface: LarkFeepRpcControl.InterfaceRecord←NIL;
nb: NB;
[nb, pInfo] ←
ThParty.GetPartyInfo[credentials: cDesc.situation.self, nameReq: $none, allParties: TRUE];
IF nb # $success OR pInfo[0].partyID=0 THEN {
VoiceUtils.Problem["No conversation info, or incomplete"]; RETURN; }; -- This is really bad!
IF pInfo.conversationInfo.numActive#2 OR ~pInfo.conversationInfo.bilateralConv
THEN RETURN;
[nb, interfaceSpec] ←
ThParty.LookupServiceInterface[
credentials: cDesc.situation.self, serviceParty: pInfo[1].partyID, type: "LarkFeep"];
IF nb#$success THEN RETURN;
interface ← NARROW[RefID.Unseal[interfaceSpec.interfaceID]!
SafeStorage.NarrowRefFault => CONTINUE]; -- Insist on a local implementation.
IF interface=NIL THEN RETURN; -- flashing has no meaning in this conversation.
IF interface.Flash[shhh: Thrush.none, serviceID: interfaceSpec.serviceID,
convID: cDesc.situation.self.convID, requestingParty: cDesc.situation.self.partyID,
actionID: 0, extraTime: 177777B] # $success THEN
VoiceUtils.Problem["Switchhook flash failed"];
RETURN; };
info.arguments ← ""; -- non-nil, but no value either.
info.parseState ← $inSeq;
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];
[] ← 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
];
SELECT nb FROM
$success => NULL;
$noSuchSmarts, $noSuchParty => {
VoiceUtils.Problem["Serious party error noted by LarkSmarts"];
ThSmartsPrivate.ForgetConv[cDesc]; -- see above about possible Fail, Forget actions.
RETURN;
};
ENDCASE => ERROR; -- Unexpected
ThSmartsPrivate.NoteNewState[cDesc, convEvent];
};
$ringing => {
cDesc ← GetConvDesc[info];
IF cDesc=NIL THEN RETURN; -- Error has been reported
[] ← ThSmartsPrivate.ChangeState[cDesc, $active];
};
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];
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].rName;
IF rName=NIL THEN RETURN; -- should make error signals
Set timed or standard ring mode attribute
SELECT ringSpec.ringModeAttribute FROM
$ringmode => NameDB.SetAttribute[rName, $ringmode, ringSpec.ringMode];
$timedringmode => {
now: BasicTime.GMT;
unp: BasicTime.Unpacked;
If timed, set time to minimum of now+(ringSpec.time)*60 and midnight in minutes
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];
NameDB.SetAttributeTimed[
rName: rName, attribute: $ringmode, value: ringSpec.ringMode, interval: interval];
};
ENDCASE => ERROR;
};
CmdAudioSource: INTERNAL PROC[info: SmartsInfo, val: INT] = {
IF info.arguments.Length[]=0 THEN info.arguments←"telset";
IF info.larkInfo=NIL THEN RETURN;
ThSmartsPrivate.EnterLarkState[info.larkInfo, info.larkInfo.larkState,
LIST[NEW[Atom.DottedPairNode←[$audioSource, VoiceUtils.MakeAtom[info.arguments]]]]];
};
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];
SELECT nb FROM
$success, $voiceTerminalBusy, $noSuchParty2 => RETURN;
ENDCASE;
VoiceUtils.Problem["Serious Party problem detected at LarkSmarts"];
};
GetPartiesForCall.nb~$noSuchParty
GetPartiesForCall.GetPartyFromNumber/FeepNum.nb~~$noSuchParty
GetPartiesForCall.GetRelatedParty.nb~~$voiceTerminalBusy
GetPartiesForCall.GetPartyFromNumber/FeepNum.nb~~$noSuchParty2
GetPartiesForCall.GetRelatedParty.nb~~$convStillActive
GetPartiesForCall.GetIdleParty/GetRnameFromParty.nb~~$noNameAvailable
GetPartyFromFeepNum.GetActiveParty.nb~~$noIdentSupplied
}.
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
Swinehart, December 29, 1986 3:37:31 pm PST
Remove obsolete $voiceTerminalBusy nb test
changes to: CmdCall, GetPartiesForCall, CmdOffhook
Swinehart, January 1, 1987 9:49:59 pm PST
Still working on FD/BD conflict code.
changes to: CmdCall, CmdAudioSource
Swinehart, January 28, 1987 8:13:00 am PST
A pass for error management, esp. nb-processing
changes to: StatusEvents, LarkParseEvent, DoParse, SetParserIdle, CmdCall, CmdBackDoor, CmdOnhook, CmdOffhook, GetPartiesForCall
Swinehart, April 6, 1987 8:14:37 am PDT
Cedar 7.0; accommodate NamesGV => NameDB
changes to: QdRing, DIRECTORY, LarkSmartsImpl