LarkSmartsImpl.mesa
Copyright Ó 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, June 11, 1987 1:55:02 pm 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, GetConvDesc, InputEventSpec, GetConv, HardwareRequired, 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, $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;
IF (cDesc ← GetConvDesc[info])#NIL THEN
[] ← 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, [], TRUE];
[nb, convEvent] ← ThParty.CreateConversation[
credentials: cDesc.situation.self,
state: $reserved,
checkConflict: TRUE
];
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;
};
$voiceTerminalBusy => {
VoiceUtils.Problem["Lost an off-hook race?"];
ThSmartsPrivate.ForgetConv[cDesc]; -- I claim you need this line!!
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.GetConvDesc[info];
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←NIL ] = {
Request is to obtain the conversation descriptor for the most relevant current conversation. We define that to be the cDesc for which we have the Lark hardware reserved, if any, else NIL. Assertion: there is never more than one such cDesc; check it here, since that's convenient.
FOR convs: OpenConversations ← info.conversations, convs.rest WHILE convs#NIL DO
IF ThSmartsPrivate.HardwareRequired[convs.first] THEN {
IF cDesc=NIL THEN cDesc ← convs.first ELSE VoiceUtils.Problem[
"Two conversations involving same smarts have Lark hardware reserved"];
};
ENDLOOP;
};
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
Swinehart, May 29, 1987 2:21:52 pm PDT
EngageParty (checkConflict) stuff incorporated
changes to: CmdOffhook