<> <> <> <> DIRECTORY IO, Commander USING [ CommandProc, Register ], <> Lark USING [ bStar, bThorp, CommandEvent, CommandEvents, CommandEventSequence, Device, disabled, DTMFEvent, enabled, endNum, Event, Passel, reset, StatusEvent, StatusEvents, SHHH ], LarkOpsRpcControl, LarkSmarts, Nice, RefID USING [ ID, Reseal ], RPC USING [ CallFailed ], <> Rope USING [ ROPE ], Process USING [ Detach, MsecToTicks, SetTimeout ], ThPartyPrivate USING [ SmartsData ], ThSmartsPrivate USING [ click, EnterLarkSt, --flushMarker, --GetSmartsInfo, --indexMarkerEnd, --LarkInfo, --LarkProseQueue, --LarkState, --maxClientMarker, pResetConfirmEnd, ProseControlDone, proseFailure, ReportProseDone, --RingDetState, SmartsInfo, spkrOn, --stopAndFlushEnd, --SwitchState, TonesDone ], Thrush USING[ --ProseSpec,-- ROPE, SHHH, SmartsID ], ThNet USING [ pd ], VoiceUtils USING [ Report ] ; LarkInImpl: CEDAR MONITOR LOCKS info USING info: LarkInfo IMPORTS LarkOpsRpcControl, Commander, --Convert,-- IO, Nice, Process, RefID, --Rope, --RPC, ThNet, ThSmartsPrivate, VoiceUtils EXPORTS LarkSmarts, ThSmartsPrivate= { OPEN IO; Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; }; LarkInfo: TYPE = ThSmartsPrivate.LarkInfo; RingDetState: TYPE = ThSmartsPrivate.RingDetState; SmartsData: TYPE = ThPartyPrivate.SmartsData; SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo; SmartsID: TYPE = Thrush.SmartsID; SwitchState: TYPE = ThSmartsPrivate.SwitchState; ROPE: TYPE = Thrush.ROPE; bStar: Lark.Event = Lark.bStar; bThorp: Lark.Event = Lark.bThorp; enabled: Lark.Event = Lark.enabled; endNum: Lark.Event = Lark.endNum; disabled: Lark.Event = Lark.disabled; reset: Lark.Event = Lark.reset; PD: TYPE = RECORD [ callTimeoutOK: BOOL_FALSE, -- set to keep Thrush alive when debugging a Lark. debounceInterval: INTEGER _ 300, -- ms. spClickInterval: INTEGER _ 300, breakInterval: INTEGER _ 4500, -- ms., 4 sec. nominal, 500 ms. benefit of doubt crowbar: BOOL_FALSE -- TRUE to check Lark command timeout. ]; pd: REF PD _ NEW[PD_[]]; <<***************** External Procedures ********************>> DebEvent: PROC[info: SmartsInfo, ev: Lark.StatusEvent] = INLINE { s: IO.STREAM=IO.ROS[]; s.PutF["[%3B, %3B, ", card[LONG[ IF info#NIL THEN LOOPHOLE[info.larkInfo.netAddress.host,CARDINAL] ELSE LOOPHOLE[777B, CARDINAL]]], card[LONG[LOOPHOLE[ev.device, CARDINAL]]]]; IF ev.event<='z THEN s.PutF["%g] ",char[ev.event]] ELSE s.PutF["<%3B>] ", card[LONG[LOOPHOLE[ev.event, CARDINAL]]]]; VoiceUtils.Report[s.RopeFromROS[], $LarkDetailed, info.larkInfo]; }; RecordEvent: PUBLIC PROC[shh: Thrush.SHHH, smartsID: Thrush.SmartsID, whatHappened: Lark.StatusEvents] RETURNS [ success: BOOL_TRUE ] <> = { smartsInfo: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: smartsID]; parseInfo: SmartsInfo _ smartsInfo; info: LarkInfo; parseSmartsID: SmartsID; IF smartsInfo=NIL OR (info _ smartsInfo.larkInfo)=NIL THEN RETURN[FALSE]; parseSmartsID _ smartsInfo.smartsID; FOR i: Lark.Passel IN [0 .. whatHappened.length) DO sEvent: Lark.StatusEvent _ whatHappened[i]; IF ThNet.pd.debug THEN DebEvent[smartsInfo, sEvent]; IF crowbar OR info.larkState=failed THEN LOOP; <> SELECT sEvent.device FROM speakerSwitch => sEvent _ InterpretSpeakerSwitch[info, sEvent]; ringDetect => { parseSmartsID _ smartsInfo.otherSmartsID; parseInfo _ ThSmartsPrivate.GetSmartsInfo[smartsID: parseSmartsID]; sEvent _ InterpretRingDetect[info, sEvent]; }; tones => { ThSmartsPrivate.TonesDone[smartsInfo.larkInfo, sEvent]; LOOP; }; touchPad => { <> IF sEvent.event=disabled THEN sEvent.event _ smartsInfo.lastTouchpadChar ELSE { smartsInfo.lastTouchpadChar _ sEvent.event; LOOP; -- down transitions of DTMF pad. }; IF sEvent.event='\000 THEN LOOP; }; keyboard => { <<There should probably be a generalized registration for input events, so that other things could register special input from Lark situations. Right now text-to-speech synthesizers form the only example, and there's only one of them, so the registration is limited to this case.>> IF info.keyboardEventHandler#NIL THEN info.keyboardEventHandler[info, sEvent]; LOOP; }; hookSwitch => NULL; ENDCASE => ERROR; IF sEvent.device=nothing THEN LOOP; parseInfo.ParseEvent[ smartsInfo: parseInfo, sEvent: sEvent ]; ENDLOOP; }; EventRope: PUBLIC PROC[ shh: Thrush.SHHH, smartsID: SmartsID, time: CARDINAL, device: Lark.Device, events: ROPE] RETURNS[success: BOOL] = { NULL; }; InterpretSpeakerSwitch: ENTRY PROC[info: LarkInfo, sEvent: Lark.StatusEvent] RETURNS [ processedEvent: Lark.StatusEvent] = { interval: INTEGER = LOOPHOLE[sEvent.time-info.swOnTime]; processedEvent _ sEvent; info.swOnTime _ sEvent.time; SELECT sEvent.event FROM enabled => { TRUSTED { Process.Detach[FORK SpeakerSwitchTimeout[info, info.swOnTime]]; }; processedEvent.device _ nothing; RETURN; }; disabled => IF interval >= 0 AND interval <= pd.spClickInterval THEN processedEvent.event _ ThSmartsPrivate.click; ThSmartsPrivate.spkrOn => RETURN; -- a timed-out ON event, which is now being reported ENDCASE => ERROR; }; SpeakerSwitchTimeout: PROC[info: LarkInfo, onTime: CARDINAL] = { SSTE: ENTRY PROC[info: LarkInfo, onTime: CARDINAL] RETURNS [doIt: BOOL] = TRUSTED { wait: CONDITION; Process.SetTimeout[@wait, Process.MsecToTicks[pd.spClickInterval]]; WAIT wait; RETURN[onTime=info.swOnTime]; }; IF ~SSTE[info, onTime] THEN RETURN; -- click happened or something, forget it. info.larkSmartsInfo.ParseEvent[info.larkSmartsInfo, [onTime+pd.spClickInterval, speakerSwitch, ThSmartsPrivate.spkrOn ] ]; }; InterpretRingDetect: ENTRY PROC[info: LarkInfo, sEvent: Lark.StatusEvent] RETURNS [ processedEvent: Lark.StatusEvent _ [ 0, nothing, Lark.reset ] ] = { <> ENABLE UNWIND=>NULL; interval: INTEGER = LOOPHOLE[sEvent.time-info.ringChangeTime]; event: Lark.Event = sEvent.event; timeout: BOOL _ FALSE; info.ringChangeTime _ sEvent.time; SELECT event FROM enabled => SELECT info.ringDetState FROM idle, between=>NULL; ENDCASE=>RETURN; disabled => SELECT info.ringDetState FROM idle, between=>RETURN; ENDCASE; ENDCASE=>RETURN; SELECT info.ringDetState FROM <> idle => info.ringDetState _ maybe; between => { info.ringDetState _ ring; processedEvent _ sEvent; }; -- generate new offhook maybe => IF interval> ring => info.ringDetState _ between; ENDCASE; info.ringDetInstance _ info.ringDetInstance+1; BROADCAST info.ringDetCondition; -- earlier timeouts zap themselves. IF info.ringDetState = $idle THEN RETURN; TRUSTED { Process.Detach[FORK RingTimeout[info, info.ringDetInstance]]; }; }; RingTimeout: PROC[info: LarkInfo, instance: INT] = { RTEntry: ENTRY PROC[info: LarkInfo] RETURNS[event: Lark.Event_reset] = TRUSTED { -- Process is unsafe ENABLE UNWIND=>NULL; timeout: INTEGER _ pd.breakInterval; IF instance#info.ringDetInstance THEN RETURN; SELECT info.ringDetState FROM $idle => RETURN; $maybe => timeout _ pd.debounceInterval; ENDCASE; Process.SetTimeout[@info.ringDetCondition, Process.MsecToTicks[timeout]]; WAIT info.ringDetCondition; IF info.ringDetInstance#instance THEN RETURN; -- events intervened SELECT info.ringDetState FROM -- timed out $idle => RETURN; -- ?? $maybe => { event _ enabled; info.ringDetState _ $ring; }; -- now ringing ENDCASE => { event _ disabled; info.ringDetState _ $idle; }; -- too long }; event: Lark.Event _ RTEntry[info]; IF event # reset THEN info.larkTrunkSmartsInfo.ParseEvent[ info.larkTrunkSmartsInfo, [info.ringChangeTime, ringDetect, event ] ]; IF event = enabled THEN RingTimeout[info, instance]; -- wait for no-longer-ringing timeout }; InterpretHookState: PUBLIC ENTRY PROC [ info: LarkInfo, rawEvent: Lark.StatusEvent, sInfo: SmartsInfo ] RETURNS [ processedEvent: Lark.StatusEvent ] = { <> <> ENABLE UNWIND=>NULL; event: Lark.Event = rawEvent.event; ev: EvType; newState: SwitchState; oldState: SwitchState_info.switchState; ev _ SELECT rawEvent.device FROM hookSwitch => SELECT event FROM Lark.enabled => tsOn, Lark.disabled => tsOff, ENDCASE=> spNone, speakerSwitch => SELECT event FROM ThSmartsPrivate.spkrOn => spOn, Lark.disabled => spOff, ThSmartsPrivate.click => spClick, ENDCASE => spNone, ENDCASE => spNone; processedEvent _ rawEvent; IF ev = spNone THEN { <> processedEvent.event _ SELECT rawEvent.device FROM touchPad => SELECT event FROM bThorp => endNum, bStar => '*, IN Lark.DTMFEvent => event - (FIRST[Lark.DTMFEvent]-'0), ENDCASE=>event, ENDCASE => event; RETURN; }; newState _ newStates[oldState][ev]; info.switchState _ newState; processedEvent.device _ hookSwitch; IF newState=oldState THEN processedEvent.device_nothing -- no op? ELSE IF newState = $onhook THEN processedEvent.event _ disabled -- really going onhook <> ELSE { IF oldState = $onhook THEN processedEvent.event _ enabled -- really going offhook ELSE processedEvent.device_nothing; ThSmartsPrivate.EnterLarkSt[info, info.larkState]; }; }; CheckHookState: PUBLIC ENTRY PROC [info: LarkInfo ] RETURNS [ onHook: BOOL_TRUE ] = { <> ENABLE RPC.CallFailed => IF pd.callTimeoutOK THEN RESUME ELSE GOTO Failed; reverted, wasReverted: BOOL_FALSE; which: CARDINAL; DO which_0; reverted _ FALSE; DO events: Lark.StatusEvents; [which, events] _ info.interface.WhatIsStatus[info.shh, which]; FOR i: NAT IN [0..events.length) DO SELECT events[i].event FROM enabled => SELECT events[i].device FROM hookSwitch => { onHook_FALSE; info.interface.Commands[shh: info.shh, events: assertARelay]; }; revertHookswitch => IF ~wasReverted THEN reverted _ wasReverted _ TRUE; ENDCASE; ENDCASE; ENDLOOP; IF which=0 THEN EXIT; ENDLOOP; IF reverted THEN { <> info.interface.Commands[shh: info.shh, events: unRevHS]; LOOP; }; EXIT; ENDLOOP; EXITS Failed => onHook_FALSE; }; <> <<>> EvType: TYPE = { tsOff, tsOn, spOff, spOn, spClick, spNone }; <> <> <> <> <<>> newStates: ARRAY SwitchState OF ARRAY EvType OF SwitchState _ [ <> [ $onhook, $telset, $onhook, $sPEAKER, $speaker, $onhook ], -- onhook [ $onhook, $telset, $telset, $mONITOR, $monitor, $telset ], -- telset [ $speaker, $telset, $onhook, $sPEAKER, $onhook, $speaker ], -- speaker [ $sPEAKER, $mONITOR, $onhook, $sPEAKER, $onhook, $sPEAKER ], -- sPEAKER [ $speaker, $monitor, $telset, $mONITOR, $telset, $monitor ], -- monitor [ $sPEAKER, $mONITOR, $telset, $mONITOR, $telset, $mONITOR ] -- mONITOR ]; <> <> <> ViewCmd: Commander.CommandProc = TRUSTED { Nice.View[pd, "Lark In PD"]; }; unRevHS: Lark.CommandEvents _ NEW[Lark.CommandEventSequence[1]]; assertARelay: Lark.CommandEvents _ NEW[Lark.CommandEventSequence[1]]; unRevHS[0] _ [revertHookswitch, disabled]; assertARelay[0] _ [aRelay, enabled]; Commander.Register["VuLarkIn", ViewCmd, "Program Management variables for Lark Input"]; }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> < ID, Log => VoiceUtils>> <> <> <> <> <> <> <> <> < LarkOps>> <> <<>>