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:
BOOL←
TRUE]
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: ROPE ← NIL;
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