<> <> <> <> 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, MBQueue USING [ QueueClientAction ], Nice, Rope USING [ ROPE ], Process USING [ Detach, MsecToTicks, SetTimeout ], ThSmartsPrivate USING [ click, Deb, EnableSmarts, EnterLarkSt, InputEventSpecBody, GetSmartsInfo, LarkCallBody, LarkInfo, QueueLarkAction, SmartsInfo, spkrOn, SwitchState, TonesDone ], Thrush USING[ SHHH, SmartsID ], ThNet USING [ pd ], VoiceUtils USING [ Problem, Report ] ; LarkInImpl: CEDAR MONITOR LOCKS info USING info: LarkInfo IMPORTS LarkOpsRpcControl, Commander, IO, MBQueue, Nice, Process, ThNet, ThSmartsPrivate, VoiceUtils EXPORTS LarkSmarts, ThSmartsPrivate= { OPEN IO; <> <<>> LarkInfo: TYPE = ThSmartsPrivate.LarkInfo; SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo; SmartsID: TYPE = Thrush.SmartsID; SwitchState: TYPE = ThSmartsPrivate.SwitchState; 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_[]]; <> RecordEvent: PUBLIC PROC[shh: Thrush.SHHH, smartsID: Thrush.SmartsID, whatHappened: Lark.StatusEvents] RETURNS [ success: BOOL_TRUE ] = { <<Seriously consider making most of this an entry procedure.>> <> smartsInfo: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: smartsID]; parseInfo: SmartsInfo _ smartsInfo; info: LarkInfo_NIL; parseSmartsID: SmartsID; IF smartsInfo#NIL AND (info _ smartsInfo.larkInfo)#NIL THEN parseSmartsID _ smartsInfo.smartsID; FOR i: Lark.Passel IN [0 .. whatHappened.length) DO sEvent: Lark.StatusEvent _ whatHappened[i]; cantRecord: BOOL= pd.crowbar OR info=NIL OR info.failed; IF ThNet.pd.debug OR cantRecord THEN DebEvent[smartsInfo, sEvent]; IF cantRecord THEN { VoiceUtils.Problem["Input event cannot be reported", $LarkDetailed, info]; LOOP; }; <> IF info.larkState=$none AND ~CheckHookStateE[info] THEN RETURN; <> SELECT sEvent.device FROM speakerSwitch => sEvent _ InterpretSpeakerSwitch[info, sEvent]; hookSwitch => NULL; ringDetect => { parseSmartsID _ smartsInfo.otherSmartsID; parseInfo _ ThSmartsPrivate.GetSmartsInfo[smartsID: parseSmartsID]; sEvent _ InterpretRingDetect[info, sEvent]; }; tones => { ThSmartsPrivate.TonesDone[smartsInfo.larkInfo, sEvent]; LOOP; }; touchPad => { <> IF info.forwardedCall THEN LOOP; -- stray input event from forwarding phone!! IF sEvent.event=disabled THEN sEvent.event _ smartsInfo.lastTouchpadChar ELSE { smartsInfo.lastTouchpadChar _ sEvent.event; LOOP; -- down transitions of DTMF }; 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; }; ENDCASE => ERROR; DoParseEvent[ info: info, smartsInfo: parseInfo, sEvent: sEvent ]; ENDLOOP; }; EventRope: PUBLIC PROC[ shh: Thrush.SHHH, smartsID: SmartsID, time: CARDINAL, device: Lark.Device, events: Rope.ROPE] RETURNS[success: BOOL] = { NULL; -- Larks don't yet issue them. }; <> <<>> CheckHookStateE: ENTRY PROC [ info: LarkInfo ] RETURNS [ onHook: BOOL_TRUE ] = { RETURN[CheckHookState[info]]; }; CheckHookState: PUBLIC INTERNAL PROC [ info: LarkInfo ] RETURNS [ onHook: BOOL_TRUE ] = { <> IF info=NIL THEN RETURN[FALSE]; IF info.larkState # $none THEN RETURN[TRUE]; ThSmartsPrivate.QueueLarkAction[info, NEW[ThSmartsPrivate.LarkCallBody _ [ReallyCheckHookState, NIL]]]; RETURN[FALSE]; }; ReallyCheckHookState: PROC[info: LarkInfo, clientData: REF] = { <> reverted, wasReverted: BOOL_FALSE; which: CARDINAL; onHook: BOOL_TRUE; IF ThNet.pd.debug THEN ThSmartsPrivate.Deb[info, "Ck hook state"]; 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; IF onHook THEN info.inputQueue.QueueClientAction[ThSmartsPrivate.EnableSmarts, info]; }; <> <<>> 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]; }; sEvent: Lark.StatusEvent; IF ~SSTE[info, onTime] THEN RETURN; -- click happened or something, forget it. sEvent _ [onTime+pd.spClickInterval, speakerSwitch, ThSmartsPrivate.spkrOn ]; DebEvent[info.larkSmartsInfo, sEvent]; DoParseEvent[info, info.larkSmartsInfo, sEvent ]; }; 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 { sEvent: Lark.StatusEvent _ [info.ringChangeTime, ringDetect, event ]; DebEvent[info.larkTrunkSmartsInfo, sEvent]; DoParseEvent[info, info.larkTrunkSmartsInfo, sEvent ]; }; IF event = enabled THEN RingTimeout[info, instance]; -- wait for no-longer-ringing timeout }; DoParseEvent: ENTRY PROC[info: LarkInfo, smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent] = { oldSwitchState: SwitchState _ info.switchState; newSwitchState: SwitchState; switchEventType: EvType _ spNone; IF info=NIL OR smartsInfo=NIL OR info.failed OR smartsInfo.failed THEN RETURN; SELECT sEvent.device FROM speakerSwitch => { switchEventType _ SELECT sEvent.event FROM ThSmartsPrivate.spkrOn => spOn, Lark.disabled => spOff, ThSmartsPrivate.click => spClick, ENDCASE => spNone; }; hookSwitch => switchEventType _ SELECT sEvent.event FROM Lark.enabled => tsOn, Lark.disabled => tsOff, ENDCASE=> spNone; touchPad => sEvent.event _ (SELECT sEvent.event FROM bThorp => endNum, bStar => '*, IN Lark.DTMFEvent => sEvent.event - (FIRST[Lark.DTMFEvent]-'0), ENDCASE=> sEvent.event); ENDCASE; IF switchEventType # spNone THEN { newSwitchState _ newStates[oldSwitchState][switchEventType]; info.switchState _ newSwitchState; IF newSwitchState=oldSwitchState THEN RETURN -- No effect ELSE IF newSwitchState = $onhook THEN sEvent.event _ disabled -- really going onhook ELSE { IF oldSwitchState = $onhook THEN sEvent.event _ enabled <> ELSE sEvent.device_nothing; ThSmartsPrivate.EnterLarkSt[info, info.larkState]; -- React to new switch state }; }; IF sEvent.device=nothing THEN RETURN; info.inputQueue.QueueClientAction[smartsInfo.ParseEvent, NEW[ThSmartsPrivate.InputEventSpecBody _ [smartsInfo, sEvent]]]; }; DebEvent: PROC[info: SmartsInfo, ev: Lark.StatusEvent] = INLINE { s: IO.STREAM=IO.ROS[]; larkInfo: LarkInfo _ IF info=NIL THEN NIL ELSE info.larkInfo; s.PutF["[%3B, %3B, ", card[LONG[ IF larkInfo#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, larkInfo]; }; <> <> <<>> 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>> <> <<>>