LarkSmartsSupImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, December 10, 1985 9:44:42 am PST
Polle Zellweger (PTZ) August 27, 1985 9:07:50 pm PDT
DIRECTORY
Commander USING [ CommandProc, Register ],
Convert USING [ CardFromRope ],
IO,
LarkPlay USING [ MergeToneSpecs, PlayString, ToneSpec, ToneSpecRec ],
LarkSmartsMonitorImpl,
MBQueue USING [ QueueClientAction ],
NamesGV USING [ GVGetAttribute ],
Nice,
RefID USING [ ID, Reseal, Unseal ],
RefTab USING [ Create, Fetch, Ref, Store ],
Rope USING [ Concat, ROPE ],
SafeStorage USING [ NarrowRefFault ],
ThNet USING [ pd ],
ThParty USING [ Advance, ConversationInfo, DescribeParty, GetConversationInfo, GetKeyTable, GetPartyInfo, PartyInfo ],
ThPartyPrivate USING [ GetCurrentParty ],
Thrush USING [
ConvEvent, ConversationID, PartyID, PartyType, NB, notReallyInConv, nullConvID, nullID, Reason, ROPE, SHHH, SmartsID, StateID, StateInConv ],
ThSmartsPrivate
USING [
ConvDesc, ConvDescBody, ConvRequest, ConvRequestBody, EnterLarkState, LarkState, OpenConversations, ParseState, SmartsInfo ],
ThSmartsRpcControl,
Triples USING [ Select ],
TU,
VoiceUtils USING [ MakeAtom, ProblemFR, Report, ReportFR ]
;
LarkSmartsSupImpl:
CEDAR MONITOR
LOCKS root
IMPORTS
Commander,
Convert,
IO,
LarkPlay,
root: LarkSmartsMonitorImpl,
MBQueue,
NamesGV,
Nice,
RefID,
RefTab,
Rope,
SafeStorage,
ThNet,
ThParty,
ThPartyPrivate,
ThSmartsPrivate,
Triples,
TU,
VoiceUtils
EXPORTS ThSmartsPrivate
SHARES LarkSmartsMonitorImpl = {
OPEN IO;
Definitions
ConversationID:
TYPE = Thrush.ConversationID;
nullConvID: ConversationID=Thrush.nullConvID;
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
ConvEvent: TYPE = Thrush.ConvEvent;
Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; };
NB: TYPE = Thrush.NB;
OpenConversations: TYPE = ThSmartsPrivate.OpenConversations;
PartyID:
TYPE = Thrush.PartyID;
nullID: RefID.ID = Thrush.nullID;
ROPE: TYPE = Thrush.ROPE;
SmartsID: TYPE = Thrush.SmartsID;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
StateInConv: TYPE = Thrush.StateInConv;
PD:
TYPE =
RECORD [
timeoutNoAction: INTEGER ← 5,
doReports: BOOL←FALSE,
larkRegistry: ROPE ← ".lark",
doNice: BOOL←FALSE
];
pd: REF PD ← NEW[PD←[]];
Party-invoked actions
LarkProgress:
PUBLIC
ENTRY
PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
convEvent: Thrush.ConvEvent
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
Some party has changed state in a conversation we know about.
info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID];
IF info=NIL THEN { Report["No info at Progress", NIL]; RETURN; };
info.requests.QueueClientAction[QdLarkProgress, convEvent];
};
Queued procedures — via request or party->smarts report
All have prefix "Qd" (queued request)
QdLarkProgress:
ENTRY
PROC[r:
REF] = {
Three cases:
Another smarts initiated a state change for this party, and we're not receptive.
(For now, when we don't know about that conversation and already in another)
Another smarts initiated a change for this party, and we're receptive.
(New conversation and we're idle or a conversation we're in already)
Another party changed state in a conversation we know about.
ENABLE UNWIND => NULL; -- RestoreInvariant;
convEvent: Thrush.ConvEvent = NARROW[r];
info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID];
cDesc: ConvDesc;
convID: ConversationID = convEvent.self.convID;
whatNeedsDoing: WhatNeedsDoing;
IF info=NIL THEN { Report["Smarts Info Missing at Progress", NIL]; RETURN; };
cDesc ← GetConv[ info, convID, TRUE ];
IF convEvent.self.partyID = convEvent.other.partyID
THEN {
-- own state changed
NoteNewState[cDesc, convEvent];
SELECT info.currentConvID
FROM
convID, nullConvID =>
NULL;
(new conversation and we're idle) or (this is the one we like)
ENDCASE => DoRejectCall[cDesc, convEvent];
We have to reject this, since we're already dealing with another conv.
We are (still) willing to seriously consider only one conversation at a time.
RETURN;
};
IF convID#info.currentConvID
THEN {
Situation is that someone else reported a change of theirs concerning a conversation we've expressed no interest in. I don't believe this should happen.
Report["Strange progress report", cDesc.info];
ForgetConv[cDesc];
RETURN;
};
Someone else's state changed in a conv. we're interested in; see if it means anything to us!
whatNeedsDoing ← whatNeedsDoingIf[cDesc.situation.self.state][convEvent.other.state];
SELECT whatNeedsDoing
FROM
$noop, $ntiy => NULL; -- No action is needed, or we don't know what one to take.
$imp => ERROR; -- This is supposed to be impossible!
$xrep => Report["Didn't expect state change report", info];
We don't expect reports of state changes like $notified and $ringback to be reported to us unless it's us.
$frgt => ForgetConv[cDesc];
We were told something about a conversation we didn't particularly care to know about. Forget it.
$invl => {
We don't know a valid interpretation of this transition on someone else's part when we're in the state we're in.
Report["Invalid state transition", info];
[]𡤌hangeState[cDesc: cDesc, state: $failed,
reason: $error, comment: "System Error: Invalid state transition"];
};
$idle => {
If everybody else is gone, go too. I'm sure I've got the dial-tone stuff messed up.
nb2: NB;
reason: Thrush.Reason;
cInfo: ThParty.ConversationInfo;
[nb2, cInfo] ← ThParty.GetConversationInfo[convID: convEvent.self.convID];
reason ← IF nb2 # $success THEN $error ELSE convEvent.reason;
IF nb2 # $success
OR (cInfo.numParties-cInfo.numIdle) <= 1
THEN
[]𡤌hangeState[cDesc, IF reason=$terminating THEN $idle ELSE $failed, reason];
};
$rback => []𡤌hangeState[cDesc, $ringback]; -- Provide ring-back signal
$actv => []𡤌hangeState[cDesc, $active]; -- Go active
$reac => NULL; -- Will be used to reactivate when somebody else leaves inactive
$deac => NULL; -- Will be used to deactivate when everybody else leaves active;
ENDCASE => ERROR;
};
QdNotification:
ENTRY
PROC [r:
REF] ~ {
We have been invited to join a conversation, and we were not otherwise occupied. Decide how to deal with it. r is the updated cDesc
ENABLE UNWIND => NULL; -- RestoreInvariant;
cDesc: ConvDesc = NARROW[r];
{
OPEN now: cDesc.situation.self;
The only question is whether to ring or to accept right away.
autoAnswer:
BOOL =
VoiceUtils.MakeAtom[DBInfo[now.partyID, $autoanswer].value] = $true;
IF now.state # $notified THEN RETURN; -- Nothing to do any more??
[]𡤌hangeState[cDesc, IF autoAnswer THEN $active ELSE $ringing];
};
};
Action procedures: change state, queue up interval request, and so on.
All have prefix "Do"
DoAdvance:
INTERNAL
PROC[convRequest: ThSmartsPrivate.ConvRequest]
RETURNS [nb: NB] = {
cDesc: ConvDesc = convRequest.cDesc;
DO -- for possible stateMismatch loop
OPEN now: cDesc.situation.self, desired: convRequest.desiredSituation.self;
convEvent: Thrush.ConvEvent;
IF now.state=$idle
OR now.state = desired.state
THEN
RETURN;
More comfortable if this were a complete check for reasonable transitions. For now, if the initial request wasn't a transition to $idle and we got a state mismatch, we've given up, assuming that what our other smarts did was as reasonable as what we wanted to do. Later queued actions, and later actions in LarkSmartsImpl (synchronous with user actions) will have to detect any anomalies and deal with them.
[nb, convEvent] ← ThParty.Advance[
credentials: now,
state: desired.state,
reason: convRequest.desiredSituation.reason,
comment: convRequest.desiredSituation.comment,
reportToAll: shouldReportToAll[desired.state]
];
SELECT nb
FROM
$success => cDesc.info.NoteNewStateP[cDesc, convEvent];
$stateMismatch => {
We and some other smarts acting on our behalf requested something at the same time. The other one won. If we were trying to hang up, we must persist. Otherwise, assume the winner was right.
cDesc.info.NoteNewStateP[cDesc, convEvent];
IF desired.state = $idle THEN LOOP;
};
$noSuchConv => Report["Conv doesn't exist at Advance", cDesc.info];
Unusual, but possible if idle report took a real long time to get here.
$partyAlreadyActive => NULL; -- Deal with $canActivate here some time
ENDCASE => AssessDamage[nb, cDesc, convEvent];
EXIT; ENDLOOP;
};
DoRejectCall:
INTERNAL
PROC[cDesc: ConvDesc, convEvent: ConvEvent] = {
[]𡤌hangeState[cDesc, $idle, $busy];
};
Update procedures: record state changes, take local actions based on what they are, and so on.
All Update procedures have prefix "Note". These procedures should never result directly in another ThParty request, to avoid uncontrolled recursions. (It might be necessary for them to queue another -- thinking particularly of the "going idle" case.)
NoteNewState:
PUBLIC INTERNAL
PROC[cDesc: ConvDesc, convEvent: ConvEvent] ~ {
OPEN now: cDesc.situation.self;
nb: NB;
larkStateData: REF←NIL;
state: StateInConv ← convEvent.self.state;
savedCurrentConvID: ConversationID;
cDesc hasn't been updated with convEvent, yet, so comparisons of previous and current state are possible here if desirable.
cDesc.situation ← convEvent^;
IF cDesc.info.currentConvID=nullConvID
THEN
cDesc.info.currentConvID ← cDesc.situation.self.convID;
savedCurrentConvID ← cDesc.info.currentConvID; -- ForgetConv might clear it.
SELECT state
FROM
$notified => {
-- Somebody else did the notifying. We have to respond.
This is the only time a change in our own state has to be reacted to? Could possibly do the ChangeState here, but feel more comfortable queueing it.
cDesc.info.requests.QueueClientAction[QdNotification, cDesc];
};
$ringing, $ringback => larkStateData ← SetupRingTunes[cDesc];
$idle, $neverWas => {
If off-hook or speakerphone on, schedule a new dial-tone? I'm sure there's not enough info around to know why the other party quit, at this time.
ForgetConv[cDesc];
};
$active => larkStateData ← ComputeConnection[cDesc];
ENDCASE;
IF state > Thrush.notReallyInConv
AND cDesc.keyTable=
NIL
THEN {
Earliest opportunity to get keys
[nb, cDesc.keyTable] ← ThParty.GetKeyTable[credentials: now];
IF nb # $success THEN cDesc.keyTable←NIL;
};
IF state> Thrush.notReallyInConv
AND cDesc.keyTable#cDesc.info.larkInfo.keyTable
AND
larkStateData=NIL THEN larkStateData ← cDesc.keyTable; -- which might still be NIL: OK
IF now.convID = savedCurrentConvID
THEN SetLarkState[cDesc, larkStateData];
The test prevents changes in lark state for a conversation that we're participating in but don't have the voice terminal connected to.
};
AssessDamage:
PUBLIC INTERNAL
PROC [nb:
NB, cDesc: ConvDesc, convEvent: ConvEvent] ~ {
nb # $success after a call to ThParty. It was also not one of the failures that one expects in normal discourse. Decide how bad it is.
SELECT nb FROM
};
Connection Management Utilities
ChangeState:
PUBLIC
INTERNAL
PROC[
cDesc: ConvDesc,
state: StateInConv ← idle,
reason: Thrush.Reason ← NIL, -- $wontSay
comment: ROPE←NIL
] RETURNS [nb: NB] = {
convRequest: ThSmartsPrivate.ConvRequest;
IF cDesc = NIL THEN RETURN;
convRequest ← NEW[ThSmartsPrivate.ConvRequestBody ← [cDesc: cDesc]];
convRequest.desiredSituation.self.state ← state;
convRequest.desiredSituation.reason ← reason;
convRequest.desiredSituation.comment ← comment;
nb ← DoAdvance[convRequest];
};
GetConv:
PUBLIC INTERNAL
PROC[info: SmartsInfo, convID: ConversationID, createOK:
BOOLLSE
] RETURNS [ cDesc: ConvDesc←NIL ] = --INLINE-- {
FOR convs: OpenConversations ← info.conversations, convs.rest
WHILE convs#
NIL
DO
IF convs.first.situation.self.convID = convID THEN RETURN[convs.first];
ENDLOOP;
IF ~createOK THEN RETURN;
cDesc ← NEW[ThSmartsPrivate.ConvDescBody←[]];
cDesc.situation.self ← [ convID: convID, smartsID: info.smartsID,
partyID: ThPartyPrivate.GetCurrentParty[smartsID: info.smartsID].partyID ]; -- nb check?
cDesc.info ← info;
info.conversations ← CONS[cDesc, info.conversations];
IF pd.doReports
THEN
ReportFR[" ** NewConv %t %g\n", info, time[convID], TU.RefAddr[info] ];
};
ForgetConv:
PUBLIC INTERNAL
PROC[cDesc: ConvDesc] = {
Just remove its cDesc from info.conversations; also clear info.currentConvID if it's us.
info: SmartsInfo = cDesc.info;
prev: OpenConversations←NIL;
IF cDesc.situation.self.convID = info.currentConvID THEN info.currentConvID ← nullConvID;
FOR convs: OpenConversations ← info.conversations, convs.rest
WHILE convs#
NIL
DO
IF convs.first # cDesc THEN { prev𡤌onvs; LOOP; };
IF prev#NIL THEN prev.rest ← convs.rest ELSE info.conversations ← convs.rest;
RETURN;
ENDLOOP;
};
ComputeConnection:
PUBLIC INTERNAL
PROC[cDesc: ConvDesc]
RETURNS [pInfo: ThParty.PartyInfo ← NIL] = {
Socket assignment: Each transmitting host gets an assigned socket (ThParty makes them) and ThParty.GetConversationInfo returns a list of them (self first). One transmits voice to a socket comprised of the other host's address combined with one's OWN socket number. In a conference, the other host is the multicast host. One listens to some number of sockets, comprised of the other host's (or multicast) address, and their own socket numbers. This makes the multicast case consistent.
A ConnectionSpec, when sent to the lark, includes:
buffer: {in1, in2, out1, out2, out3}. A buffer to which analog-to-ethernet (ini, corresponding to txi) or ethernet-to-analog (outj, corresponding to rxj) samples for this connection will be queued. When running the standard program O3I1 (1 in, 3 out) only in1 can be used, and any ethernet-to-analog connection can be assigned to any of the outj buffers. This is the conference mode. When running O2I2 (2 in, 2 out), in1 and out1 are paired, as are in2 and out2; out3 is not used. This is the "simultaneous back door and front door call" mode.
localSocket: for ini (txi) buffers, this is ignored by the Lark Pup code. For outj (rxj) buffers, this specifies the socket on which to listen -- see above.
remoteSocket: ignored for outj (rxj) buffers. For in1 (tx1) buffers, specifies the socket to send to.
This call now simply gets the PartyInfo from ThParty; LarkOutImpl produces the actual connection specs and sends them on to the lark.
nb: NB;
[nb, pInfo] ←
ThParty.GetPartyInfo[credentials: cDesc.situation.self, nameReq: $none, allParties: TRUE];
IF nb # $success
OR pInfo[0].partyID=0
THEN {
Problem["No conversation info, or incomplete", cDesc.info]; pInfo←NIL; RETURN; }; -- This is really bad!
};
Other Utilities
GetSmartsInfo:
PUBLIC
PROC[smartsID: SmartsID]
RETURNS [info: SmartsInfo←
NIL] = {
sd: REF ← RefID.Unseal[smartsID];
IF sd#NIL THEN RETURN[NARROW[Triples.Select[$SmartsData, sd, --info--]]];
};
DBInfo:
PUBLIC
PROC[partyID: Thrush.PartyID, attribute:
ATOM←
NIL]
RETURNS [dbRname: ROPE←NIL, dbAtom: ATOM←NIL, value: ROPE←NIL] = {
nb: NB;
rName: ROPE;
IF partyID=nullID THEN RETURN;
[nb, rName] ← ThParty.DescribeParty[partyID: partyID, nameReq: $current];
IF nb # $success OR rName=NIL THEN RETURN;
dbRname ← rName.Concat[pd.larkRegistry];
dbAtom ← VoiceUtils.MakeAtom[rName: rName, case: FALSE];
IF attribute#NIL THEN value ← NamesGV.GVGetAttribute[dbRname, attribute, NIL];
};
Problem:
PROC[comment:
ROPE, info: SmartsInfo] = {
VoiceUtils.ProblemFR[Rope.Concat["LarkSmarts(%g): ", comment], $Smarts, info, TU.RefAddr[info]];
};
Report:
PROC[what:
ROPE, info: SmartsInfo] = {
IF NOT pd.doReports THEN RETURN;
VoiceUtils.Report[what, $Lark, info.larkInfo];
};
ReportFR:
PROC[what:
ROPE, info: SmartsInfo, a1, a2, a3:
IO.Value←rope[NIL]] = {
IF NOT pd.doReports THEN RETURN;
VoiceUtils.ReportFR[what, $Lark, info.larkInfo, a1, a2, a3];
};
BeNice:
PROC[r:
REF, d:
INT, info: SmartsInfo] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Lark, info.larkInfo];
};
Ring Tune Stuff — but for GFI's, would move to another file.
ringTunes: RefTab.Ref←RefTab.Create[59];
ringTone: LarkPlay.ToneSpec ← NIL;
subduedRingTone: LarkPlay.ToneSpec ← NIL;
outsideRingTone: LarkPlay.ToneSpec ← NIL;
outsideRingTune: PUBLIC LarkPlay.ToneSpec ← NIL;
outsideRingTuneRope: ROPE ← "@300;G%>G<%G%>G<%G%>G<%G%>G<%G%>*C";
ringTuneDelay: NAT ← 2400; -- ms. delay between tune starts.
SetupRingTunes:
PROC[cDesc: ConvDesc]
RETURNS [toneSpec: LarkPlay.ToneSpec] = {
OPEN self: cDesc.situation.self;
partyID: Thrush.PartyID = self.partyID;
dbName: ROPE = DBInfo[partyID].dbRname;
otherPartyID: Thrush.PartyID←nullID;
otherDbName: ROPE;
otherTune: LarkPlay.ToneSpec←NIL;
otherType: Thrush.PartyType;
defaultSpec: LarkPlay.ToneSpec; -- outside or inside ringing
nb: NB;
pInfo: ThParty.PartyInfo;
divisor: NAT ← 1;
rope: ROPE;
now: BasicTime.GMT = BasicTime.Now[];
expiration: BasicTime.GMT ← now;
ringMode: ATOM;
ringDo: ATOM;
[nb, pInfo] ←
ThParty.GetPartyInfo[credentials: [convID: self.convID, partyID: partyID], allParties: TRUE, nameReq: $none];
IF pInfo.numParties=2
THEN {
Doesn't handle conference situations: look for a party in ringing or ringBack state!
otherPartyID ← pInfo[1].partyID;
otherType ← pInfo[1].type;
otherDbName← DBInfo[otherPartyID].dbRname;
};
IF ThNet.pd.ringsInvalid THEN MakeDefaultRingTunes[];
SELECT self.state
FROM
$ringback => {
toneSpec ← GetRingTune[otherPartyID];
IF toneSpec=NIL THEN RETURN;
toneSpec ← NEW[LarkPlay.ToneSpecRec ← toneSpec^];
toneSpec.volume ← ThNet.pd.tonesVolume+2;
toneSpec.repeatIndefinitely ← TRUE;
RETURN;
};
$ringing => NULL; -- main case continues below
ENDCASE =>
RETURN;
-- what are we doing here anyhow?
We are being called. Figure out how to respond.
If there's an expiration for a temporary ringing mode, and it hasn't expired yet, use it. If there isn't one or it has expired, use the permanent ringing mode.
defaultSpec ← SELECT otherType FROM $trunk => outsideRingTone, ENDCASE => ringTone;
rope ← NamesGV.GVGetAttribute[dbName, $expiration, NIL];
IF rope#NIL THEN expiration ← BasicTime.FromNSTime[Convert.CardFromRope[rope]];
rope ← NamesGV.GVGetAttribute[dbName,
IF BasicTime.Period[from: now, to: expiration] > 0 THEN $timedringmode ELSE $ringmode, NIL];
ringMode ← IF rope=NIL THEN $r ELSE VoiceUtils.MakeAtom[rName: rope, case: FALSE];
SELECT ringMode
FROM
-- $o, $s, $r
$o => RETURN[NIL];
$s => RETURN[subduedRingTone];
$r => NULL; -- largest case continues below
ENDCASE => RETURN[defaultSpec]; -- unknown case; act vanilla
We're ringing. Figure out how to do it.
rope ← NamesGV.GVGetAttribute[dbName, $dotune, NIL];
ringDo ← IF rope=NIL THEN $standard ELSE VoiceUtils.MakeAtom[rName: rope, case: FALSE];
SELECT ringDo FROM
$false, $standard => RETURN[defaultSpec];
$both => {
SELECT otherType
FROM
$individual => {
divisor ← 2;
rope ← NamesGV.GVGetAttribute[otherDbName, $dotune, NIL];
ringDo ← IF rope=NIL THEN $standard ELSE VoiceUtils.MakeAtom[rName: rope, case: FALSE];
IF ringDo=$both THEN otherTune ← GetRingTune[otherPartyID];
This produces a second ring tune, compatible with the callee's; they will be played together.
};
$trunk => otherTune ← outsideRingTune;
ENDCASE;
};
$true, $tune => NULL; -- continue below
ENDCASE => RETURN[defaultSpec];
toneSpec ← GetRingTune[partyID];
IF toneSpec=NIL THEN RETURN[defaultSpec];
IF otherTune#NIL THEN toneSpec ← LarkPlay.MergeToneSpecs[toneSpec, otherTune, divisor, ringTuneDelay];
};
GetRingTune:
PROC[party: PartyID]
RETURNS [ringTune: LarkPlay.ToneSpec←
NIL] = {
Returns the ring tune, if any, associated with the party. If the caller wants to change anything, it must copy the results, since GetRingTune doesn't.
nameAtom: ATOM;
name: ROPE;
ringTuneRope: ROPE;
IF party = nullID THEN RETURN;
[name, nameAtom, ringTuneRope] ← DBInfo[party, $ringtune];
IF ringTuneRope=NIL THEN RETURN;
ringTune ← NARROW[ringTunes.Fetch[nameAtom].val];
IF ringTune#NIL AND ringTune.asRope=ringTuneRope THEN RETURN;
ringTune ← LarkPlay.PlayString[music: ringTuneRope, file: FALSE, volume: ThNet.pd.defaultRingVolume];
[]←ringTunes.Store[nameAtom, ringTune]
MakeDefaultRingTunes:
PROC = {
ringTone ←
NEW[LarkPlay.ToneSpecRec ← [
repeatIndefinitely: TRUE, volume: ThNet.pd.defaultRingVolume,
tones:
LIST[
LIST[ [f1: 440, f2: 480, on: 2000, off: 4000],
[f1: 440, f2: 480, on: 2000, off: 4000]]]]];
outsideRingTone ←
NEW[LarkPlay.ToneSpecRec ← [
repeatIndefinitely: TRUE, volume: ThNet.pd.defaultRingVolume,
tones:
LIST[
LIST[ [f1: 440, f2: 480, on: 500, off: 500],
[f1: 440, f2: 480, on: 500, off: 4500]]]]];
subduedRingTone ←
NEW[LarkPlay.ToneSpecRec ← [
repeatIndefinitely: FALSE,
volume: ThNet.pd.defaultRingVolume+ThNet.pd.subduedVolumeInterval,
tones: LIST[LIST[[f1: 440, f2: 480, on: 500, off: 0]]]
]];
outsideRingTune ←
LarkPlay.PlayString[outsideRingTuneRope, FALSE, ThNet.pd.defaultRingVolume];
ThNet.pd.ringsInvalid ← FALSE;
};
State Transition Tables
larkStateForState: ARRAY StateInConv OF ThSmartsPrivate.LarkState ← [
neverWas, idle, failed, reserved, parsing, initiating, pending, maybe, ringing,
silence, idle, errorTone, dialTone, silence, silence, silence, ringBack, ringing,
canActivate, active, inActive
silence, talking, silence
];
SetLarkState:
INTERNAL
PROC[cDesc: ConvDesc, data:
REF] = {
larkState: ThSmartsPrivate.LarkState ← larkStateForState[cDesc.situation.self.state];
SELECT larkState
FROM
$talking => {
If connection is to the back door on the same machine, Trunk smarts has already set state to trunkTalking, or is about to. Don't change it.
spec: ThParty.PartyInfo;
spec ← NARROW[data!SafeStorage.NarrowRefFault=>CONTINUE];
IF spec=NIL THEN ERROR;
IF spec.numParties=2
AND spec[0].socket.host = spec[1].socket.host
THEN
See the discussion of sockets — they'll be equal only when sender and receiver is same host. We're disallowing conferences in this case.
larkState ← cDesc.info.larkInfo.larkState; -- don't change
};
$errorTone => larkState ←
SELECT cDesc.situation.reason
FROM
$busy, $notImportantEnough => $busyTone,
$absent, $noCircuits, $noParticular, $notFound, $error => $errorTone,
ENDCASE => $errorTone;
ENDCASE;
ThSmartsPrivate.EnterLarkState[cDesc.info.larkInfo, larkState, data];
};
Should we report this transition to everyone, or just to our own smartses?
shouldReportToAll:
ARRAY StateInConv
OF
BOOL = [
never idle error reserved parsing init notified ringback ringing canAc active inactive
FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE ];
WhatNeedsDoing:
TYPE =
ATOM;
-- {
Just codes to dispatch on in Supervisor; explained there
$noop, $idle, $actv, $rback, $reac, $deac, $frgt, -- cases explained in code
$invl, -- considered an invalid request
$xrep, -- we got a report we feel we shouldn't have got
$ntiy, -- not implemented yet
$imp -- this situation should not arise even in the face of invalid requests
};
whatNeedsDoingIf:
ARRAY StateInConv
OF
ARRAY StateInConv
OF WhatNeedsDoing ← [
If we're in the state identified by the row, and someone else in the conversation reports a transition onto the state identified by the column, what should we do?
never idle failed resrv pars init notif rback ring canAc activ inact -- ← (other)
[ $imp, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt],
--neverWas
(Clip this table to view without these comments.)
This situation arises when we've forgotten about the conversation that somebody else is still reporting on.
[ $imp, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $ntiy ], -- idle
[ $imp, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $ntiy ], -- failed
[ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ],
-- reserved
The actions of other parties are not of interest to us yet, since they're not in this conv.
[ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ],
-- parsing
Ditto.
[ $imp,
$idle,$noop, $invl, $invl, $invl, $xrep, $xrep,
$rback,
$actv,
$actv,
$actv ],
-- initiating
They can either enter ringing to indicate interest, or go active without ringing
[ $imp,
$idle,$noop, $invl, $invl, $invl, $xrep, $noop, $noop, $ntiy, $noop, $ntiy ],
-- notified
We don't expect to hear from others while we're deciding whether to play
[ $imp,
$idle,$noop,$invl, $invl, $invl, $noop, $xrep, $noop, $ntiy,
$actv, $ntiy ],
-- ringback
They have earlier expressed interest noopringing), and are now joining the fray
[ $imp,
$idle,$noop, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $noop, $ntiy ],
-- ringing
The only thing that interests us here is everybody else quitting.
[ $imp, $idle,$noop, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $noop, $ntiy ], -- canActivate
[ $imp, $idle,$noop, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $reac, $deac ], -- active
[ $imp, $idle,$invl, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ntiy, $ntiy ] -- inactive (current ^)
];
Debugging nonsense
ViewCmd: Commander.CommandProc =
TRUSTED {
Nice.View[pd, "Lark PD"];
};
Commander.Register["VuLarkSmarts", ViewCmd, "Program Management variables for Lark Smarts"];
}.
Swinehart, May 15, 1985 11:05:22 am PDT
Cedar 6.0, add Prose stuff as direct copy of Interval stuff.
changes to: IntervalSpecs, ProseSpec, ProseSpecs, LarkState, LarkProgress, LarkSupervise, EnqueueProses
Swinehart, May 22, 1985 12:15:11 pm PDT
hotLine => autoAnswer
changes to: LarkSupervise
Swinehart, July 2, 1985 10:09:14 am PDT
Fixing up trunkTalking stuff
changes to: LarkSupervise, LarkStateForState
Polle Zellweger (PTZ) July 11, 1985 6:15:01 pm PDT
changes to: DIRECTORY, EnqueueProses, ReportProseDone, LarkSupervise
Polle Zellweger (PTZ) July 16, 1985 9:42:51 pm PDT
changes to: LarkProgress, LarkSupervise, ReportProseDone
Polle Zellweger (PTZ) July 30, 1985 4:41:39 pm PDT
Handle copying of newProses correctly - give Lark its own copy.
changes to: LarkSupervise
Swinehart, August 6, 1985 2:26:39 pm PDT
Merge PTZ ProseSpec changes
changes to: DIRECTORY, LarkProgress, LarkSupervise, EnqueueProses
Polle Zellweger (PTZ) August 14, 1985 6:12:13 pm PDT
Prose flushing, also fix undesired speakerphone hangup for intervals, proseSpecs.
changes to: LarkProgress, LarkSupervise
Polle Zellweger (PTZ) August 22, 1985 2:04:45 pm PDT
changes to: LarkProgress -- comment out some optimizing queue flushing
Swinehart, October 28, 1985 9:58:08 am PST
Handle => ID, H => Reseal, Log => VoiceUtils
changes to: DIRECTORY, LarkSmartsSupImpl, ConversationID, Reseal, IntervalSpec, NB, nullID, PartyID, SmartsID, LarkProgress, LarkSupervise, GetConv, GetSmartsInfo
Swinehart, November 9, 1985 5:03:39 pm PST
Major revision: MBQueue replaces supervisor process. More responsibility for own state transitions, socket calculations, key maintenance, ...
changes to: LarkProgress, NoteProgress, LarkSupervise, GetConv, larkStateForState, transForStates, ConvEvent, NoteProgress, RejectCall, NoteNewState, LarkSupervise, whatNeedsDoingIf, pairedState, WhatNeedsDoing, ]