LarkSmartsSupImpl.mesa
Copyright Ó 1985, 1986, 1987 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, April 6, 1987 8:23:23 am PDT
Polle Zellweger (PTZ) August 27, 1985 9:07:50 pm PDT
DIRECTORY
Atom USING [ DottedPairNode, GetPName ],
Commander USING [ CommandProc, Register ],
Convert USING [ IntFromRope ],
GList USING [ DRemove ],
IO,
LarkPlay USING [ MergeToneSpecs, PlayString, ToneSpec, ToneSpecRec ],
LarkSmartsMonitorImpl,
MBQueue USING [ QueueClientAction ],
NameDB USING [ GetAttribute, SetAttribute ],
Nice,
Process USING [ Detach, SecondsToTicks, SetTimeout ],
RefID USING [ ID, Reseal, Unseal ],
RefTab USING [ Create, Fetch, Ref, Store ],
Rope USING [ Concat, Fetch, Length, ROPE ],
ThNet USING [ pd ],
ThParty USING [ Advance, ConversationInfo, DescribeParty, GetConversationInfo, GetKeyTable, GetPartyInfo, PartyInfo ],
ThPartyPrivate USING [ GetCurrentParty ],
Thrush USING [
ActionReport, ConvEvent, ConversationID, PartyID, PartyType, NB, notReallyInConv, nullConvID, nullID, Reason, ROPE, SHHH, SmartsID, StateID, StateInConv ],
ThSmartsPrivate USING [
ConvDesc, ConvDescBody, ConvRequest, ConvRequestBody, EnterLarkState, Fail, LarkInfo, LarkState, OpenConversations, ParseState, SmartsInfo ],
ThSmartsRpcControl,
Triples USING [ Select ],
TU,
VoiceUtils USING [ MakeAtom, ProblemFR, ReportFR ]
;
LarkSmartsSupImpl: CEDAR MONITOR LOCKS root
IMPORTS
Atom,
Commander,
Convert,
GList,
IO,
LarkPlay,
root: LarkSmartsMonitorImpl,
MBQueue,
NameDB,
Nice,
Process,
RefID,
RefTab,
Rope,
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;
LORA: TYPE = LIST OF REF ANY;
PD: TYPE = RECORD [
timeoutNoAction: INTEGER ← 5,
doReports: BOOLFALSE,
doNice: BOOLFALSE
];
pd: REF PDNEW[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.
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 are already in another)
Another smarts initiated a change for this party, and we're receptive.
(New conversation and we're idle or it's a conversation we're in already)
Another party changed state in a conversation we know about.
convID: ConversationID = convEvent.self.convID;
info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID];
cDesc: ConvDesc;
reason: Thrush.Reason;
cInfo: REF ThParty.ConversationInfo;
whatNeedsDoing: WhatNeedsDoing;
IF info=NIL THEN { Problem["No Smarts at LarkProgress for SmartsID %g", NIL, card[convEvent.self.smartsID]]; RETURN; };
cDesc ← GetConv[ info, convID, TRUE ];
IF convEvent.self.partyID = convEvent.other.partyID THEN { -- own state changed
NoteNewState[cDesc, convEvent]; -- Report on existing conv. or notification of new one.
RETURN;
};
IF convID#info.currentConvID AND info.currentConvID#nullConvID THEN {
Situation is that someone else reported a change of theirs concerning a conversation we've expressed no interest in. This occurs when we've decided not to participate in a multi-party conversation. It will occur repeatedly until everybody has quit. This situation can arise normally, for example, when a visitor or visitee is busy when a call comes in.
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 => Problem["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.
Problem["Invalid state transition", info];
[]𡤌hangeState[cDesc: cDesc, state: $failed,
reason: $error, comment: "System Error: Invalid state transition"];
};
$idle => {
Somebody just quit. If everybody else has quit, quit too.
IF (cInfo ← GetConversationInfo[cDesc]) = NIL
THEN RETURN; -- have already complained
reason ← IF convEvent.reason#NIL THEN convEvent.reason ELSE $terminating;
IF (cInfo.numParties-cInfo.numIdle) <= 1 THEN
[]𡤌hangeState[cDesc, IF reason=$terminating THEN $idle ELSE $failed, reason]
ELSE IF cDesc.situation.self.state=$active THEN ResetLarkState[cDesc];
};
$idlerg => {
We're ringing. Somebody just quit. If it was the originator, quit too.
IF (cInfo ← GetConversationInfo[cDesc]) = NIL
THEN RETURN; -- have already complained
reason ← IF convEvent.reason # NIL THEN convEvent.reason ELSE $terminating;
IF cInfo.originator = convEvent.other.partyID 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
$ckrg => CheckRing[cDesc]; -- We're ringing; Some other party that was ringing has now become active; determine if we should continue ringing.
$reac, -- Someone else has entered or returned to active state, while we are active
$deac -- Someone else has entered inactive state.
=> ResetLarkState[cDesc];
ENDCASE => ERROR;
};
LarkSubstitution: PUBLIC ENTRY PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
convEvent: Thrush.ConvEvent,
oldPartyID: Thrush.PartyID,
newPartyID: Thrush.PartyID
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
A poacher has substituted for a poachee, or the other way around. Update state so that if it's us, we know who we are.
No conversation state has changed, so just update our notion of our state and quit.
info: SmartsInfo = GetSmartsInfo[smartsID: convEvent.self.smartsID];
cDesc: ConvDesc;
IF info=NIL THEN { Problem["No Smarts at LarkSubstitution for SmartsID %g", NIL, card[convEvent.self.smartsID]]; RETURN; };
IF (cDesc ← GetConv[ info, convEvent.self.convID, FALSE ]) = NIL THEN RETURN;
NoteNewState[cDesc, convEvent];
};
LarkReportAction: PUBLIC ENTRY PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
report: Thrush.ActionReport
] = {
ENABLE UNWIND => NULL;
nb: NB;
info: SmartsInfo = GetSmartsInfo[smartsID: report.self.smartsID];
cDesc: ConvDesc;
IF info=NIL THEN { Problem["No Smarts at LarkSubstitution for SmartsID %g", NIL, card[report.self.smartsID]]; RETURN; };
IF (cDesc ← GetConv[ info, report.self.convID, FALSE ]) = NIL THEN RETURN;
SELECT report.actionClass FROM
$keyDistribution => {
SELECT report.actionType FROM $newKeys => NULL; ENDCASE => RETURN;
[nb, cDesc.keyTable] ← ThParty.GetKeyTable[credentials: report.self];
IF nb#$success THEN { cDesc.keyTable ← NIL; RETURN; };
Assume problem analyzed elsewhere
IF cDesc.situation.self.state <= Thrush.notReallyInConv THEN RETURN;
SetLarkState[cDesc, LIST[cDesc.keyTable]];
};
$recording, $playback, $synthesizer =>
If the handset and speaker switches are in their "off" positions, terminate the connection when all recording or synthesis activity has completed. N.B. There's a faint possibility that the $recording service and the $synthesizer service will generate non-disjoint ID's. This could cause the connection to terminate prematurely.
SELECT report.actionType FROM
$scheduled => cDesc.lastActionID ← report.actionID;
$started => NULL; -- always follows $scheduled
$finished, $flushed => {
IF cDesc.lastActionID = report.actionID AND
cDesc.situation.self.state > Thrush.notReallyInConv AND
(SELECT cDesc.info.larkInfo.switchState FROM
speaker, monitor =>TRUE, ENDCASE =>FALSE)
THEN [] ← ChangeState[cDesc, $idle]
};
ENDCASE;
ENDCASE;
};
Queued procedures — via request or party->smarts report
All have prefix "Qd" (queued request)
The only ones left are those that avoid recursive calls on ThParty.Advance and on NoteNewState. Even those could probably be eliminated with a bit more thought. Like maybe Loop with the resulting ConvEvent. 
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;
rNAtom: ATOM;
rName: ROPE;
val: ROPE;
The only question is whether to ring or to accept right away.
IF cDesc.info.failed THEN { Problem["Notification abandoned", cDesc.info]; RETURN; };
IF now.state # $notified THEN RETURN; -- Nothing to do any more??
Now test whether Manager (workstation program) has assumed the responsibility for determining the incoming calls.
[rName, rNAtom, val] ← DBInfo[now.partyID, $deferanswer];
IF VoiceUtils.MakeAtom[val]=$true THEN TRUSTED {
Process.Detach[FORK DeferAnswer[cDesc, rName]]; RETURN; };
This works even if what we're planning to do is to reject due to multiple simultaneous conversations! Manager can thus override the prohibition.
IF cDesc.info.currentConvID#cDesc.situation.self.convID THEN GOTO Reject;
IF ChangeState[cDesc,
IF VoiceUtils.MakeAtom[DBInfo[now.partyID, $autoanswer, rNAtom].value] = $true THEN $active
ELSE $ringing] = $success
AND now.state=$ringing THEN CheckRing[cDesc];
EXITS Reject =>
[]𡤌hangeState[cDesc, $idle, $busy]; -- Couldn't accept call, for whatever reason.
};
};
DeferAnswer: ENTRY PROC [cDesc: ConvDesc, rName: ROPE] ~ TRUSTED {
c: CONDITION;
Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutNoAction]];
WAIT c;
IF cDesc.situation.self.state # $notified THEN RETURN;
We were told not to automatically accept this call, but no one has done anything about it for some time; notified is supposed to be a transitory state. Cancel the restraining order and let the call ring through. Whoever issued the restraining order will have to issue another one.
This can also be the home of something that refuses to stay in the state "initiating" for too long. To date, it's been something that dealt with the state "notified". 
NameDB.SetAttribute[rName, $deferanswer, NIL];
cDesc.info.requests.QueueClientAction[QdNotification, cDesc];
};
Action procedures: change state, queue up interval request, and so on.
All have prefix "Do"
DoAdvance: INTERNAL PROC[convRequest: ThSmartsPrivate.ConvRequest, secondTry: BOOLFALSE]
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[$success];
[nb, convEvent] ← ThParty.Advance[
credentials: now,
state: desired.state,
reason: convRequest.desiredSituation.reason,
comment: convRequest.desiredSituation.comment,
reportToAll: shouldReportToAll[desired.state],
bilateral: convRequest.bilateral
];
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 got there first.
Try the philosophy that the last one in wins. Unless the previous transition was to the $idle state, now attempt to attain the one requested here. (Not sure about active => ringing, but let's see)
IF secondTry THEN ERROR; -- Don't loop forever
cDesc.info.NoteNewStateP[cDesc, convEvent];
secondTry ← TRUE;
IF now.state # $idle THEN LOOP; -- Attempt the transition, based on new information.
A return of stateMismatch from this routine indicates a failure based on the party having become idle during the attempt.
};
$partyAlreadyActive, $conferenceConv,
$bilateralConv, $voiceTerminalUnavailable,
The transition didn't occur. These things can happen. We're postulating that the proper response is always to attempt to reach an idle state, before returning the original error code. Caller must take any additional cleanup action, but usually none is required.
$interfaceError => {
We requested a state later than the current state. This is fatal. Try to get out cleanly.
IF secondTry THEN ERROR; -- Don't loop forever
IF nb=$interfaceError THEN Problem["Party state-advance yields interface error", cDesc.info];
cDesc.info.NoteNewStateP[cDesc, convEvent];
desired.state ← $idle;
IF now.state # $idle THEN [] ← DoAdvance[convRequest, TRUE];
Return the original error code.
};
ENDCASE => { -- $convIdle, $noSuchSmarts, $noSuchParty, $noSuchConv, $notInConv
msg: Rope.ROPE =
IO.PutFR["Unexpected party state-advance failure, nb=%g", atom[nb]];
IF cDesc#NIL AND cDesc.info#NIL AND cDesc.info.larkInfo#NIL THEN
ThSmartsPrivate.Fail[cDesc.info.larkInfo, msg, TRUE]
Callers should attempt minimal activity after one of these major errors.
It would probably be acceptable to crash on these errors, since they should have been reported to us under normal Lark failure conditions.
ELSE { Problem[msg, cDesc.info]; ForgetConv[cDesc]; };
};
EXIT; ENDLOOP;
};
DoAdvance.nb~$success -- questionable
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: LORA NIL;
state: StateInConv ← convEvent.self.state;
previousState: StateInConv = cDesc.situation.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 state = previousState THEN RETURN; -- No conceivable value in acting.
IF cDesc.info.currentConvID=nullConvID THEN
cDesc.info.currentConvID ← cDesc.situation.self.convID;
ELSE IF cDesc.info.currentConvID#cDesc.situation.self.convID THEN ERROR;
We're going to have to learn to deal with multiple conversations. Soon. Right now they sail right through under some circumstances. See Notes.
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. Still queued up because otherwise the state-management is confusing. Or maybe it isn't.
cDesc.info.requests.QueueClientAction[QdNotification, cDesc];
};
$ringing, $ringback => larkStateData ← LIST[SetupRingTunes[cDesc]];
$idle, $neverWas => ForgetConv[cDesc];
$active => {
value: ROPE; rNAtom: ATOM;
larkStateData ← LIST[ComputeConnection[cDesc]];
[, rNAtom, value] ← DBInfo[now.partyID, $audiosource];
larkStateData ← CONS[NEW[Atom.DottedPairNode←[$audioSource, VoiceUtils.MakeAtom[value]]], larkStateData]; --[[$audioSource,audioSource],...]
value ← DBInfo[now.partyID, $transmitonly, rNAtom].value;
larkStateData ← CONS[NEW[Atom.DottedPairNode←[$transmitOnly, VoiceUtils.MakeAtom[value]]], larkStateData];
};
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 cDesc.keyTable#NIL THEN larkStateData ← CONS[cDesc.keyTable, larkStateData];
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.
};
CheckRing: PUBLIC INTERNAL PROC [cDesc: ConvDesc] ~ {
nb: NB;
cInfo: ThParty.ConversationInfo;
[nb, cInfo] ← ThParty.GetConversationInfo[convID: cDesc.situation.self.convID];
IF nb # $success THEN {
[]𡤌hangeState[cDesc, $failed, $error, "System Error: can't monitor ringing situation"];
RETURN;
};
IF cDesc.ringCheckProcess#NIL OR -- Already handling
cInfo.numActive = 0 THEN RETURN; -- Not in the interesting situation yet.
Call is in progress, but we haven't joined it yet. Decide how long to stay around.
TRUSTED { Process.Detach[FORK RingProcess[cDesc]]; };
};
RingProcess: ENTRY PROC[cDesc: ConvDesc] = {
ENABLE UNWIND => { cDesc.ringCheckProcess ← NIL; };
numWaited: INT𡤀
numToWait: INT𡤀
condition: CONDITION;
TRUSTED { Process.SetTimeout[@condition, Process.SecondsToTicks[1]]; };
WHILE TRUE DO
value: ROPENIL;
valueAtom: ATOM;
IF cDesc.info.failed OR cDesc.situation.self.state # $ringing THEN EXIT;
value ← DBInfo[cDesc.situation.self.partyID, $multiring].value;
valueAtom ← VoiceUtils.MakeAtom[value];
SELECT TRUE FROM
valueAtom=NIL, value.Length[]=0, valueAtom=$false => numToWait←numWaited; -- quit
valueAtom=$true => { numToWait←numWaited+1; }; -- forever
value.Fetch[0] IN ['0..'9] => numToWait𡤌onvert.IntFromRope[value];
ENDCASE => numToWait ← numWaited; -- invalid database
IF numWaited >= numToWait THEN GOTO Idle;
WAIT condition;
numWaited ← numWaited+1;
REPEAT
Idle => []𡤌hangeState[cDesc, $idle]; -- We're a third wheel.
ENDLOOP;
cDesc.ringCheckProcess ← NIL;
};
Connection Management Utilities
ChangeState: PUBLIC INTERNAL PROC[
cDesc: ConvDesc,
state: StateInConv ← $idle,
reason: Thrush.Reason ← NIL, -- $wontSay
comment: ROPENIL,
bilateral: BOOLFALSE
] RETURNS [nb: NB ← $noSuchConv] = {
convRequest: ThSmartsPrivate.ConvRequest;
IF cDesc = NIL THEN { Problem["No cDesc supplied at ChangeState", NIL]; RETURN; };
IF state=$failed AND cDesc.info.larkInfo.textToSpeech THEN state ← $idle;
Don't send t-t-s service into failed state. Just quit!
convRequest ← NEW[ThSmartsPrivate.ConvRequestBody ← [cDesc: cDesc, bilateral: bilateral]];
convRequest.desiredSituation.self.state ← state;
convRequest.desiredSituation.reason ← reason;
convRequest.desiredSituation.comment ← comment;
nb ← DoAdvance[convRequest];
};
ChangeState.nb~$ a variety of nb-errors.
On return, all errors have been handled as well as possible, though; callers should just
do minimum additional work when errors have occurred.
GetConv: PUBLIC INTERNAL PROC[info: SmartsInfo, convID: ConversationID, createOK: BOOL�LSE
] RETURNS [ cDesc: ConvDesc←NIL ] = --INLINE-- {
nb: NB;
partyID: PartyID;
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 {
VoiceUtils.ProblemFR["Couldn't find referenced conversation, id= %g",
$System, NIL, time[convID]];
RETURN;
};
[nb, partyID] ← ThPartyPrivate.GetCurrentParty[smartsID: info.smartsID];
SELECT nb FROM
$success => NULL;
$noSuchSmarts, $noSuchParty => {
VoiceUtils.ProblemFR["Party has apparently failed; damage control.", $System, NIL];
RETURN;
};
ENDCASE => ERROR;
cDesc ← NEW[ThSmartsPrivate.ConvDescBody←[]];
cDesc.situation.self ← [ convID: convID, smartsID: info.smartsID, partyID: partyID ];
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;
IF cDesc.situation.self.convID = info.currentConvID THEN info.currentConvID ← nullConvID;
info.conversations ← NARROW[GList.DRemove[cDesc, info.conversations]];
};
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.GetPartyInfo 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: ATOMNIL, prevRNAtom: ATOMNIL]
RETURNS [rName: ROPENIL, rNAtom: ATOM←NIL, value: ROPENIL] = {
nb: NB ← $success;
rNAtom ← prevRNAtom;
IF rNAtom#NIL THEN rName ← Atom.GetPName[rNAtom]
ELSE IF partyID#nullID THEN
[nb, rName] ← ThParty.DescribeParty[partyID: partyID, nameReq: $current];
IF nb # $success OR rName=NIL THEN RETURN;
IF rNAtom=NIL THEN rNAtom ← VoiceUtils.MakeAtom[rName: rName, case: FALSE];
IF attribute#NIL THEN value ← NameDB.GetAttribute[rName, attribute];
};
GetConversationInfo: INTERNAL PROC[cDesc: ConvDesc]
RETURNS [cInfo: REF ThParty.ConversationInfo] = {
nb: NB;
cInfoRecord: ThParty.ConversationInfo;
[nb, cInfoRecord] ← ThParty.GetConversationInfo[convID: cDesc.situation.self.convID];
SELECT nb FROM
$success => NULL;
$noSuchConv => {
Problem["Conversation disappeared, can't get ConversationInfo", cDesc.info];
ForgetConv[cDesc];
RETURN[NIL];
};
ENDCASE => ERROR;
RETURN[NEW[ThParty.ConversationInfo ← cInfoRecord]];
};
Problem:
PROC[comment: ROPE, info: SmartsInfo, v1: IO.Value ← [null[]]] = {
VoiceUtils.ProblemFR[Rope.Concat["LarkSmarts(%g): ", comment], $Smarts, info, TU.RefAddr[info], v1];
};
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;
rName: ROPE;
originatingPartyID: Thrush.PartyID←nullID;
otherRName: ROPE;
otherTune: LarkPlay.ToneSpec←NIL;
otherType: Thrush.PartyType←$service; -- default value: not telephone, individual, trunk
defaultSpec: LarkPlay.ToneSpec; -- outside or inside ringing
nb: NB;
pInfo: ThParty.PartyInfo;
divisor: NAT ← 1;
rope: ROPE;
now: BasicTime.GMT = BasicTime.Now[];
ringMode: ATOM;
ringDo: ATOM;
IF ThNet.pd.ringsInvalid THEN MakeDefaultRingTunes[];
[nb, pInfo] ←
ThParty.GetPartyInfo[credentials: [convID: self.convID, partyID: partyID], allParties: TRUE, nameReq: $current];
IF nb#$success OR pInfo.numParties=0 THEN RETURN[ringTone]; -- not worth messing about
IF self.state=$ringing THEN originatingPartyID ← pInfo.conversationInfo.originator;
rName ← pInfo[0].intendedName; -- self
FOR i: NAT IN [1..pInfo.numParties) DO
SELECT self.state FROM
ringback: choose original called party's ring tune for ringback tune. Or if more than one party is being called, use the first one that's still ringing or notified.
ringback => SELECT pInfo[i].state FROM
ringing, notified => {
toneSpec ← GetRingTune[pInfo[i].intendedName];
IF toneSpec=NIL THEN RETURN[ringTone];
toneSpec ← NEW[LarkPlay.ToneSpecRec ← toneSpec^];
toneSpec.volume ← ThNet.pd.tonesVolume+2;
toneSpec.repeatIndefinitely ← TRUE;
RETURN;
};
ENDCASE;
ringing: find the originator's party, in case we're going to play a duet.
ringing => IF pInfo[i].partyID = originatingPartyID THEN {
otherType ← pInfo[i].type;
otherRName ← pInfo[i].intendedName;
EXIT;
};
ENDCASE=>ERROR;
ENDLOOP;
IF self.state= ringback THEN RETURN[ringTone]; -- Didn't find another party above.
We are being called. Figure out how to respond. Obtain the ringing mode.
defaultSpec ← SELECT otherType FROM $trunk => outsideRingTone, ENDCASE => ringTone;
rope ← NameDB.GetAttribute[rName, $ringmode];
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 ← NameDB.GetAttribute[rName, $dotune];
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, $telephone => {
divisor ← 2;
rope ← NameDB.GetAttribute[otherRName, $dotune];
ringDo ← IF rope=NIL THEN $standard ELSE VoiceUtils.MakeAtom[rName: rope, case: FALSE];
IF ringDo=$both THEN otherTune ← GetRingTune[otherRName];
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[rName];
IF toneSpec=NIL THEN RETURN[defaultSpec];
IF otherTune#NIL THEN toneSpec ← LarkPlay.MergeToneSpecs[toneSpec, otherTune, divisor, ringTuneDelay];
};
GetRingTune: PROC[name: ROPE] 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;
ringTuneRope: ROPE;
IF name=NIL THEN RETURN;
ringTuneRope ← NameDB.GetAttribute[name, $ringtune];
IF ringTuneRope=NIL THEN RETURN;
nameAtom ← VoiceUtils.MakeAtom[name];
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: LORA ] = {
larkState: ThSmartsPrivate.LarkState ← larkStateForState[cDesc.situation.self.state];
larkInfo: ThSmartsPrivate.LarkInfo ← cDesc.info.larkInfo;
IF larkInfo.forwardedCall THEN RETURN -- don't even heed data
ELSE IF larkState # larkInfo.larkState THEN 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;
FOR dL: LORA ← data, dL.rest WHILE dL#NIL DO WITH dL.first SELECT FROM
pI: ThParty.PartyInfo => spec ← pI;
ENDCASE; ENDLOOP;
IF spec=NIL THEN ERROR;
IF spec.conversationInfo.numActive < 2 OR
(spec.conversationInfo.bilateralConv 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 ← larkInfo.larkState; -- don't change
};
$idle => IF larkInfo.forwardedCall THEN larkState ← larkInfo.larkState;
Telset doesn't control Lark
$errorTone => larkState ← IF larkInfo.forwardedCall THEN larkInfo.larkState -- ??
ELSE SELECT cDesc.situation.reason FROM
$busy, $notImportantEnough => $busyTone,
$absent, $noCircuits, $noParticular, $notFound, $error => $errorTone,
ENDCASE => $errorTone;
ENDCASE;
ThSmartsPrivate.EnterLarkState[cDesc.info.larkInfo, larkState, data];
};
ResetLarkState: INTERNAL PROC[cDesc: ConvDesc] = {
SetLarkState[cDesc, LIST[ComputeConnection[cDesc]]];
};
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, $idlerg, $actv, $rback, $reac, $deac, $ckrg, $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, $idlerg,$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, $idlerg,$noop, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ckrg, $ntiy ], -- ringing
The only thing that interests us here is everybody else quitting.
[ $imp, $idle,$noop, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ckrg, $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, ]
Swinehart, June 1, 1986 7:33:23 pm PDT
Test hypothesis that Smarts supervisory actions don't have to be queued, since progress reports are queued at the Party level. Will vastly simplify Smartses. Keep the MBQueue around for things that might take a while and that can be done out of sequence. In fact, can keep LarkSmartsImpl's use of the MBQueue around for a while. It's harmless, says here. Left in also to help with QdNotification, although that's considered eliminatable.
changes to: LarkProgress, LarkSubstitution, QdLarkProgress, QdSubstitution, QdNotification, NoteNewState, LarkReportAction
Swinehart, January 1, 1987 9:54:27 pm PST
Still working on FD/BD conflict stuff. Pushing it back to Party level.
changes to: DIRECTORY, LarkProgress, QdNotification, NoteNewState
Swinehart, January 27, 1987 5:05:49 pm PST
Updating error management (NB-handling)
changes to: QdNotification, DoAdvance, ChangeState, GetConversationInfo, SetupRingTunes
Swinehart, April 6, 1987 8:07:48 am PDT
Cedar 7.0; accommodate NamesGV => NameDB
changes to: DIRECTORY, LarkSmartsSupImpl, PD, DeferAnswer, DBInfo, SetupRingTunes, GetRingTune
changes to: QdNotification, NoteNewState