<> <> <> <> <> <<>> DIRECTORY Atom USING [ DottedPairNode ], BasicTime USING [ GMT, Now, Pack, Period, ToNSTime, Unpack, Unpacked, Update ], IO, Lark, LarkFeepRpcControl, LarkSmartsMonitorImpl, MBQueue USING [ QueueClientAction ], NamesGV USING [ AttributeSeq, AttributeSeqRec, GVSetAttribute, GVSetAttributeSeq ], 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, InputEventSpec, GetConv, LarkInfo, NoteNewState, OpenConversations, ParseState, SmartsInfo ], VoiceUtils USING [ MakeAtom, Problem ] ; LarkSmartsImpl: CEDAR MONITOR LOCKS root IMPORTS BasicTime, IO, MBQueue, LarkFeepRpcControl, root: LarkSmartsMonitorImpl, NamesGV, RefID, Rope, SafeStorage, ThParty, ThSmartsPrivate, VoiceUtils EXPORTS ThSmartsPrivate SHARES LarkSmartsMonitorImpl = { OPEN IO; <> 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; <<>> <> <<>> <> 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_0; 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] = { <> 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! <> info.haveOne _ TRUE; SELECT event FROM <<>> <> disabled => { info.haveArguments _ TRUE; info.Command _ CmdOnhook; RETURN;}; enabled => { info.haveArguments _ TRUE; info.Command _ CmdOffhook; RETURN;}; <> endNum => { info.haveArguments _ TRUE; RETURN; }; -- '# IO.DEL, IO.ESC => { []_SetParserIdle[info, TRUE, TRUE]; RETURN; }; <> '* => { IF info.cmdOrRecip THEN info.offset_10; info.cmdOrRecip _ TRUE; info.parseState _ idle; RETURN; }; <> ENDCASE => NULL; -- continues below IF info.cmdOrRecip THEN { -- begins command or feepName recipient specification info.cmdOrRecip _ FALSE; event_event+info.offset; info.offset_0; IF event = '0 THEN { -- feepName recipient specification []_SetParserIdle[info, FALSE, FALSE]; info.parseState _ getFeep; RETURN; }; []_SetParserIdle[info, TRUE, FALSE]; SELECT event FROM <> '*, -- esc endNum -- del -- => []_SetParserIdle[info, TRUE, TRUE]; IN ['1..'9] => { []_SetParserIdle[info, FALSE, FALSE]; info.parseState _ getFeep; GOTO Continue; }; <> '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_CmdRingOnceTimed; 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; }; <> 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 { <> 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; <> 2 => IF info.arguments.Equal["90"] THEN info.parseState _ inNum; <> 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; <> 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]; }; <> CmdCall: INTERNAL PROC[info: SmartsInfo, val: INT] = { <> 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, $voiceTerminalBusy, $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.>> <> 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; cDesc _ GetConvDesc[info]; [] _ 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, nullConvID, TRUE]; [nb, convEvent] _ ThParty.CreateConversation[ credentials: cDesc.situation.self, state: $reserved ]; 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; }; 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 }; <> <<>> 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--] = { <> 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.GetConv[info, info.currentConvID, FALSE]; now: BasicTime.GMT; unp: BasicTime.Unpacked; 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].dbRname; IF rName=NIL THEN RETURN; -- should make error signals <> SELECT ringSpec.ringModeAttribute FROM $ringmode => NamesGV.GVSetAttribute[rName, $ringmode, ringSpec.ringMode]; $timedringmode => { as: NamesGV.AttributeSeq _ NEW[NamesGV.AttributeSeqRec[2]]; as.length _ 2; <> 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]; as[0] _ [$timed, IO.PutFR["%g", card[BasicTime.ToNSTime[BasicTime.Update[now, interval]]]]]; as[1] _ [$unspec, ringSpec.ringMode]; NamesGV.GVSetAttributeSeq[rName, $ringmode, as]; }; 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]]]]]; }; <> GetConvDesc: PUBLIC INTERNAL PROC[info: SmartsInfo] RETURNS [ cDesc: ConvDesc ] = { RETURN[IF info.currentConvID = nullConvID THEN NIL ELSE ThSmartsPrivate.GetConv[ info, info.currentConvID, FALSE ]]; }; 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]; < 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"]; }; <> <> <> <> <> <> <> }. <> < autoAnswer>> <> <> <> <> <<>> <> < ID, Log => VoiceUtils, pull ProblemHandle, ...>> <> <<>> <> <> <> <<>> <> <> <> <<>> <> <> <> <> <> <> <<>>