<<>> <> <> <> <> <<>> <> <> <<>> <<>> DIRECTORY Atom, Commander, CommanderOps, IO, LoganBerryEntry, <> PhSmarts, PhSwitch, Process, RefID, Rope, ThParty, Thrush, ThSmarts, VoiceUtils ; PhSmartsImpl: CEDAR MONITOR LOCKS PhSmarts.lock IMPORTS Atom, Commander, CommanderOps, IO, LoganBerryEntry, --MacawProc,-- PhSmarts, PhSwitch, Process, Rope, ThParty, VoiceUtils EXPORTS ThSmarts, PhSmarts = { OPEN pd: PhSmarts.pd, phoenixInfo: PhSmarts.phoenixInfo; <> ROPE: TYPE = Thrush.ROPE; ConvDesc: TYPE = PhSmarts.ConvDesc; ConvEvent: TYPE = Thrush.ConvEvent; ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID=Thrush.nullConvID; NB : TYPE = Thrush.NB; OpenConversations: TYPE = PhSmarts.OpenConversations; PhoenixInfo: TYPE = PhSmarts.PhoenixInfo; SmartsID: TYPE = Thrush.SmartsID; StateInConv: TYPE = Thrush.StateInConv; PartyInfo: TYPE ~ ThParty.PartyInfo; PartyID: TYPE = Thrush.PartyID; nullID: RefID.ID = Thrush.nullID; PD: TYPE = PhSmarts.PD; serverInstance: Rope.ROPE ¬ NIL; WhatNeedsDoing: TYPE = ATOM; whatNeedsDoingIf: ARRAY Thrush.StateInConv OF ARRAY Thrush.StateInConv OF WhatNeedsDoing ¬ [ <> <<>> << never idle failed resrv pars init notif rback ring canAc activ inact >> [ $imp, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt], --neverWas <<(Clip this table to view without these comments.)>> <> [ $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 <> [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- parsing <> [ $imp, $idle, $noop, $invl, $invl, $invl, $xrep, $xrep, $rback,$actvd, $actvd,$actvd ], -- initiating <> [ $imp, $idlerg,$noop,$invl, $invl, $invl, $xrep, $noop, $noop, $ntiy, $noop, $ntiy ], -- notified <> [ $imp, $idle, $noop, $invl, $invl, $invl, $noop, $xrep, $noop, $ntiy, $actv, $actvd ], -- ringback <> [ $imp, $idlerg,$noop,$invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ckrg, $ntiy ], -- ringing <> [ $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, $noop, $invl, $invl, $noop, $noop, $noop, $noop, $ntiy, $reac, $deac ], -- active [ $imp, $idle, $invl, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ntiy, $ntiy ] -- inactive (current ­) ]; <> actionID: INT ¬ 0; Substitution: PUBLIC ENTRY PROC [shh: Thrush.SHHH, convEvent: Thrush.ConvEvent, oldPartyID: Thrush.PartyID, newPartyID: Thrush.PartyID] = {}; CheckIn: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, credentials: Thrush.Credentials, voicePath: BOOL, reason: Thrush.Reason, remark: ROPE, nextScheduledCheck: INT ] = { ENABLE UNWIND => NULL; localRemark: ROPE¬NIL; connected: BOOL ¬ TRUE; enabled: BOOL ¬ phoenixInfo.enabled; phoenixInfo.nextScheduledCheck ¬ nextScheduledCheck; SELECT reason FROM $goodbye => { enabled ¬ connected ¬ FALSE; localRemark ¬ "Permanent disconnect requested by server"; }; $trylater => { localRemark ¬ "Temporary disconnect requested by server"; connected ¬ FALSE; }; $welcome, $hello => connected ¬ TRUE; ENDCASE; []¬PhSmarts.RecordSystemStateFromSmartsReport[ remark: localRemark, connected: connected, enabled: enabled, voicePath: voicePath, remoteRemark: remark]; <> }; Progress: PUBLIC ENTRY PROC [shh: Thrush.SHHH, convEvent: Thrush.ConvEvent] = { <<>> <> <> <> <<(For now, when we don't know about that conversation and are already in another)>> <> <<(New conversation and we're idle or it's a conversation we're in already)>> <> ENABLE UNWIND => NULL; partyInfo: ThParty.PartyInfo; whatNeedsDoing: WhatNeedsDoing; nb: Thrush.NB; reason: Thrush.Reason; refAny: REF ANY; cInfo: ThParty.ConversationInfoRec; numParties: INT; numActive: INT; convID: ConversationID = convEvent.self.convID; cDesc: ConvDesc; info: PhoenixInfo ¬ PhSmarts.phoenixInfo; VoiceUtils.ReportFR["Progress Called: myState=%g, otherState=%g, %g", $Smarts, info, [rope[stateRope[convEvent.self.state]]], [rope[stateRope[convEvent.other.state]]], [rope[IF convEvent.self.partyID=convEvent.other.partyID THEN "(me)" ELSE "(somebody else)"]]]; IF info=NIL THEN { Problem["No Smarts for SmartsID %g", NIL, IO.card[convEvent.self.smartsID]]; RETURN; }; [nb, cDesc] ¬ GetInfo[convEvent.self]; IF nb#$success THEN { IF cDesc#NIL THEN NoteNewState[cDesc, convEvent]; RETURN; }; <> cInfo ¬ cDesc.cInfo; partyInfo ¬ cDesc.partyInfo; numParties ¬ cInfo.numParties; numActive ¬ cInfo.numActive; cDesc.convMedia ¬ FetchAtom[cInfo.convAttributes, $Media]; cDesc.doingAudio ¬ cDesc.convMedia#$Video; cDesc.doingVideo ¬ cDesc.convMedia#$Audio; cDesc.expectedMedia ¬ FetchAtom[cInfo.convAttributes, $ExpectedMedia]; cDesc.actualMedia ¬ FetchAtom[cInfo.convAttributes, $ActualMedia]; cDesc.isConference ¬ numParties > 2; <> <> <> <> <> <> <<}>> <> <<>> IF convEvent.self.partyID = convEvent.other.partyID THEN { -- own state changed NoteNewState[cDesc, convEvent]; -- Existing conv. or notif. of new one. RETURN; }; <> <<>> whatNeedsDoing ¬ whatNeedsDoingIf[convEvent.self.state][convEvent.other.state]; SELECT whatNeedsDoing FROM $noop, $ntiy => NULL; $imp => ERROR; $xrep => Problem["Didn't expect state change report", info]; <> $frgt => PhSmarts.ForgetConv[cDesc]; $invl => { Problem["Invalid State Transition", info]; ChangeState[cDesc: cDesc, desiredState: $failed, reason: $error, comment: "System Error: Invalid State Transition" ]; }; $idle => { <> <<>> reason ¬ IF convEvent.reason # NIL THEN convEvent.reason ELSE $terminating; IF (cInfo.numParties - cInfo.numIdle) <= 1 OR (cInfo.convType = $meeting AND convEvent.other.partyID = cInfo.moderator) THEN <> ChangeState[cDesc: cDesc, desiredState: IF reason=$terminating THEN $idle ELSE $failed, reason: reason, reportToAll: reason=$terminating]; }; $idlerg => { <> <> reason ¬ IF convEvent.reason#NIL THEN convEvent.reason ELSE $terminating; IF cInfo.originator = convEvent.other.partyID THEN ChangeState[cDesc: cDesc, desiredState: IF reason=$terminating THEN $idle ELSE $failed, reason: reason, comment: "Originator left", reportToAll: reason=$terminating]; }; $rback => ChangeState[cDesc: cDesc, desiredState: $ringback]; $actv, $actvd => ChangeState[cDesc: cDesc, desiredState: $active, reportToAll:TRUE]; $ckrg => NULL; <> $reac => <> IF convEvent.other.partyID # partyInfo.parties[partyInfo.ixOriginator].partyID THEN { NoteNewState[cDesc, convEvent]; -- make sure we're up to date in compliance. }; $deac => <> NoteNewState[cDesc, convEvent]; -- assure compliance. <> ENDCASE => ERROR; }; CheckInitiation: ENTRY PROC [cDesc: ConvDesc] ~ TRUSTED { ENABLE UNWIND => NULL; nb: NB; c: CONDITION; Process.SetTimeout[@c, Process.SecondsToTicks[pd.timeoutInitiating]]; WAIT c; IF cDesc.situation.self.state # $initiating THEN RETURN; <> ChangeState[cDesc: cDesc, desiredState: $idle, comment: "Initial Connection Timeout: Party can't be reached", reportToAll: TRUE]; }; ReportAction: PUBLIC ENTRY PROC [shh: Thrush.SHHH, report: Thrush.ActionReport] = { ENABLE UNWIND => NULL; nb: NB; cDesc: ConvDesc; myName: Rope.ROPE; IF PhSmarts.phoenixInfo=NIL THEN { Problem["No Smarts for SmartsID %g", NIL, IO.card[report.self.smartsID]]; RETURN; }; [nb, cDesc] ¬ GetInfo[credentials: report.self]; IF nb#$success OR cDesc=NIL THEN RETURN; SELECT report.actionClass FROM $conferenceParticipation => { IF nb=$success THEN SELECT report.actionType FROM $conferenceEstablished => <> IF report.requestingParty = cDesc.cInfo.originator AND report.self.state # $active THEN <> ChangeState[cDesc: cDesc, desiredState: $idle, comment: "Conference Established, and we're not in it", reportToAll:TRUE]; $requestToLeave => { myName ¬ cDesc.partyInfo[cDesc.partyInfo.ixSelf].intendedName; IF report.requestingParty = cDesc.cInfo.originator AND Rope.Equal[myName, report.actionInfo] THEN <> ChangeState[cDesc: cDesc, desiredState: $idle, reportToAll:TRUE]; }; ENDCASE; }; $PriorityChange => <> cDesc.myPriority ¬ report.actionInfo; $ParticipationChange => NULL; -- Perhaps must manually ensure realworld compliance ENDCASE; }; <> ChangeState: INTERNAL PROC [cDesc: ConvDesc, desiredState: StateInConv, reason: Thrush.Reason¬NIL, comment: ROPE¬NIL, reportToAll: BOOL ¬ FALSE] ~ { nb: NB; convEvent: Thrush.ConvEvent; [nb, convEvent] ¬ ThParty.Advance[shhh: phoenixInfo.shh, credentials: cDesc.situation.self, state: desiredState, reason: reason, comment: comment, reportToAll: reportToAll ]; VoiceUtils.ReportFR["ChangeState: desired=%g, actual=%g, nb=%g", $Smarts, PhSmarts.phoenixInfo, [rope[stateRope[desiredState]]], [rope[stateRope[convEvent.self.state]]], [atom[nb]]]; WHILE TRUE DO -- loop permits stateMismatch to repeat. SELECT nb FROM $success => { NoteNewState[cDesc, convEvent]; EXIT; }; $stateMismatch => { <> <<-- Two progress reports were received by the smarts.>> <<-- The reply to ThParty.Advance was lost and the smarts timed out and retransmitted the request.>> currentState: StateInConv ¬ convEvent.self.state; Report["State mismatch reported.", PhSmarts.phoenixInfo]; NoteNewState[cDesc, convEvent]; IF currentState = desiredState THEN RETURN; [nb, convEvent] ¬ ThParty.Advance[shhh: phoenixInfo.shh, credentials: convEvent.self, state: desiredState --, checkConflict: TRUE --]; }; ENDCASE => { <> Problem["Unexpected party state-advance failure, nb=%g", cDesc.info, IO.atom[nb]]; convEvent.self.state ¬ $idle; NoteNewState[cDesc, convEvent]; RETURN; }; ENDLOOP; }; NoteNewState: INTERNAL PROC[cDesc:ConvDesc, convEvent:Thrush.ConvEvent] ~ { nb: NB; nextState: StateInConv; state: StateInConv ¬ convEvent.self.state; previousState: StateInConv ¬ cDesc.situation.self.state; <<>> <> <> cDesc.situation ¬ convEvent­; IF state = previousState THEN RETURN; SELECT state FROM $notified => { videoDeviceInUse: BOOL ¬ FALSE; <> nextState ¬ $ringing; IF cDesc.convMedia = $AudioVisual AND videoDeviceInUse AND cDesc.expectedMedia = $BestEffort THEN { <> [] ¬ ThParty.AddAttributes[credentials: convEvent.self, convAttributes: LIST[[$ActualMedia, "Audio"]]]; -- Where else is ActualMedia set? Tested? cDesc.convMedia ¬ $Audio; cDesc.doingVideo ¬ FALSE; }; <> <> <> <<};>> IF cDesc.doingAudio THEN <> nextState ¬ $ringing; ChangeState[cDesc: cDesc, desiredState: nextState, reportToAll: TRUE]; RETURN; }; $initiating => { <> Process.Detach[FORK CheckInitiation[cDesc]]; <> <<~MacawProc.InitiatingProc[cDesc.isConference] AND cDesc.doingVideo THEN {>> <> <<}>> <> }; $ringing, $reserved, $ringback, $failed, $active, $inactive => NULL; $idle, $neverWas => PhSmarts.ForgetConv[cDesc]; ENDCASE; IF cDesc.doingAudio THEN []¬PhSwitch.Comply[cDesc]; -- Make physical world agree with logical. }; GetInfo: PROC[credentials: Thrush.Credentials] RETURNS [nb: NB¬$NoSuchConv, cDesc: PhSmarts.ConvDesc¬NIL] ~ { <> cInfo: ThParty.ConversationInfo; cDesc ¬ PhSmarts.GetConv[PhSmarts.phoenixInfo, credentials, credentials.state >= $failed]; <> IF cDesc = NIL THEN RETURN; [nb, cInfo] ¬ ThParty.GetConversationInfo[shh: phoenixInfo.shh, convID: credentials.convID]; IF nb = $success THEN { partyInfo: ThParty.PartyInfo; cDesc.myPriority ¬ LoganBerryEntry.GetAttr[cInfo.convAttributes, $Priority]; IF cDesc.partyInfo=NIL OR cDesc.situation.self.state#credentials.state OR cDesc.cInfo.numParties#cInfo.numParties OR cDesc.cInfo.numActive#cInfo.numActive OR cDesc.cInfo.numIdle#cInfo.numIdle THEN { [nb, partyInfo] ¬ ThParty.GetPartyInfo[shh: phoenixInfo.shh, credentials: credentials, allParties: TRUE]; -- expensive on every transition! cDesc.partyInfo ¬ MergeAttributes[cDesc.partyInfo, -- into -- partyInfo]; }; cDesc.cInfo ¬ cInfo­; } ELSE { msg: Rope.ROPE = IO.PutFR1["Unexpected Server Information failure, nb=%g", IO.atom[nb]]; Problem[msg, cDesc.info]; }; }; MergeAttributes: PROC[oldInfo: PartyInfo, newInfo: PartyInfo] RETURNS [mergedInfo: PartyInfo] ~ { <> mergedInfo ¬ newInfo; IF oldInfo=NIL THEN RETURN; IF newInfo=NIL THEN ERROR; FOR ixNew: NAT IN [1..newInfo.numParties] DO FOR ixOld:NAT IN [1..oldInfo.numParties] DO IF newInfo[ixNew].partyID=oldInfo[ixOld].partyID THEN { newAttrs: ThParty.Attributes ¬ newInfo[ixNew].partyAttributes; FOR attributes: ThParty.Attributes ¬ oldInfo[ixOld].partyAttributes, attributes.rest WHILE attributes#NIL DO IF LoganBerryEntry.GetAttr[newAttrs, attributes.first.type]=NIL THEN newInfo[ixNew].partyAttributes ¬ newAttrs ¬ CONS[attributes.first, newAttrs]; ENDLOOP; }; ENDLOOP; ENDLOOP; }; <> SendActionReport: INTERNAL PROC [myCredentials: Thrush.Credentials, actionClass: Thrush.ActionClass, actionType: Thrush.ActionType, actionInfo: Rope.ROPE ¬ NIL, reportToAll: BOOL ¬ TRUE] ~ { actionReport: Thrush.ActionReport ¬ [other: myCredentials, requestingParty: myCredentials.partyID, actionID: actionID, actionClass: actionClass, actionType: actionType, actionInfo: actionInfo]; actionID ¬ actionID + 1; [] ¬ ThParty.ReportAction[shhh: phoenixInfo.shh, report: actionReport, reportToAll: reportToAll]; }; <> <<>> Problem: PUBLIC PROC[comment: ROPE, info: PhoenixInfo, v1: IO.Value ¬ [null[]]] = { IF v1=[null[]] THEN VoiceUtils.Problem[Rope.Concat["PhoenixSmarts: ", comment], $Smarts, info] ELSE VoiceUtils.ProblemFR[Rope.Concat["PhoenixSmarts(%g): ", comment], $Smarts, info, v1]; }; Report: PUBLIC PROC[comment: ROPE, info: PhoenixInfo¬PhSmarts.phoenixInfo, v1: IO.Value ¬ [null[]], where: ATOM¬$Smarts] = { IF v1=[null[]] THEN VoiceUtils.Report[Rope.Concat["PhoenixSmarts: ", comment], $Smarts, info] ELSE VoiceUtils.ReportFR[Rope.Concat["PhoenixSmarts(%g): ", comment], $Smarts, info, v1]; }; FetchAtom: PROC[attributes: ThParty.Attributes, attribute: ATOM, default: ROPE¬NIL] RETURNS[atom: ATOM ¬ NIL] ~ { value: ROPE ¬ LoganBerryEntry.GetAttr[attributes, attribute]; IF value = NIL THEN value ¬ default; IF value # NIL THEN atom ¬ Atom.MakeAtom[value]; }; stateRope: ARRAY Thrush.StateInConv OF ROPE ¬ [ "$neverWas", "$idle", "$failed", "$reserved", "$parsing", "$initiating", "$pending", "$ringback", "$ringing", "$canActivate", "$active", "$inactive" ]; << >> <> PhoenixCmd: Commander.CommandProc ~ { serverInstance ¬ CommanderOps.NextArgument[cmd]; StartPhoenix[]; }; UnPhoenixCmd: Commander.CommandProc = { StopPhoenix[]; }; StartPhoenix: PROC ~ { enabled, connected: BOOL; [enabled, connected] ¬ PhSmarts.PhoenixIsRunning[]; SELECT TRUE FROM connected => Report["Already running . . ."]; enabled => { Report["Connecting . . ."]; PhSmarts.Poke[]; }; ENDCASE => { Report[ comment: "Connecting . . ."]; Report[ comment: "Phoenix Initialization in progress ... ", where: $DefaultWindow]; PhSmarts.InitPhoenixSmarts[serverInstance]; Report[ comment: "done", where: $DefaultWindow]; Report[ comment: "done"]; }; }; StopPhoenix: PROC[disable: BOOL¬TRUE] = { Report[ comment: "Disconnecting . . ."]; Report[ comment: "Phoenix Termination in progress ... ", where: $DefaultWindow]; PhSmarts.UnInitPhoenixSmarts[disable]; Report[ comment: "Done"]; Report[ comment: "done", where: $DefaultWindow]; }; Commander.Register["Phoenix", PhoenixCmd]; Commander.Register["UnPhoenix", UnPhoenixCmd]; }. <>