<> <> <> <> DIRECTORY IO, Commander USING [ CommandProc, Register ], Convert USING [ IntFromRope ], Lark USING [ bStar, bThorp, CommandEvent, CommandEvents, CommandEventSequence, Device, disabled, DTMFEvent, enabled, endNum, Event, Passel, reset, StatusEvent, StatusEvents, SHHH ], LarkRpcControl, LarkSmarts, Log USING [ ProblemBool, Report ], Nice, RPC USING [ CallFailed ], Rope USING [ Concat, FromChar, ROPE ], Process USING [ Detach, MsecToTicks, SetTimeout, Ticks ], ThPartyPrivate USING [ SmartsData ], ThSmartsPrivate USING [ click, EnterLarkSt, flushMarker, GetSmartsInfo, HookState, indexMarkerEnd, LarkInfo, LarkProseQueue, LarkState, maxClientMarker, pResetConfirmEnd, ProseControlDone, proseFailure, ReportProseDone, RingDetState, SmartsInfo, stopAndFlushEnd, TerminalType, TonesDone ], Thrush USING[ H, pERROR, ProseSpec, ROPE, SHHH, SmartsHandle, ThHandle ], ThNet USING [ pd ] ; LarkInImpl: CEDAR MONITOR LOCKS info USING info: LarkInfo IMPORTS LarkRpcControl, Commander, Convert, IO, Log, Nice, Process, Rope, RPC, ThNet, Thrush, ThSmartsPrivate EXPORTS LarkSmarts, ThSmartsPrivate= { OPEN IO; H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; }; HookState: TYPE = ThSmartsPrivate.HookState; LarkInfo: TYPE = ThSmartsPrivate.LarkInfo; RingDetState: TYPE = ThSmartsPrivate.RingDetState; SmartsData: TYPE = ThPartyPrivate.SmartsData; SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo; SmartsHandle: TYPE = Thrush.SmartsHandle; TerminalType: TYPE = ThSmartsPrivate.TerminalType; 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. ]; 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]]]]; Log.Report[s.RopeFromROS[], $LarkDetailed, info.larkInfo]; }; crowbar: BOOL_FALSE; -- TRUE to check basic RPC performance from Lark. RecordEvent: PUBLIC PROC[shh: Thrush.SHHH, smartsID: Thrush.SmartsHandle, whatHappened: Lark.StatusEvents] RETURNS [ success: BOOL_TRUE ] <> = { smartsInfo: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: smartsID]; parseInfo: SmartsInfo _ smartsInfo; info: LarkInfo; parseSmarts: SmartsData; IF smartsInfo=NIL OR (info _ smartsInfo.larkInfo)=NIL THEN RETURN[FALSE]; parseSmarts _ smartsInfo.smarts; 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 OR info.larkState=recovering THEN LOOP; <> SELECT sEvent.device FROM ringDetect => { parseSmarts _ smartsInfo.otherSmarts; parseInfo _ ThSmartsPrivate.GetSmartsInfo[smarts: parseSmarts]; }; tones => { ThSmartsPrivate.TonesDone[smartsInfo.larkInfo, sEvent, smartsInfo]; LOOP; }; ENDCASE; SELECT sEvent.device FROM speakerSwitch => sEvent _ InterpretSpeakerSwitch[info, sEvent]; ringDetect => sEvent _ InterpretRingDetect[info, H[parseSmarts], parseInfo, sEvent]; 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 => { IF info.textToSpeech THEN HandleAndReport[info, sEvent, smartsInfo]; LOOP; }; hookSwitch => NULL; ENDCASE => Thrush.pERROR; IF sEvent.device=nothing THEN LOOP; parseInfo.ParseEvent[ smartsInfo: parseInfo, sEvent: sEvent ]; ENDLOOP; }; EventRope: PUBLIC PROC[ shh: Thrush.SHHH, smartsID: SmartsHandle, time: CARDINAL, device: Lark.Device, events: ROPE] RETURNS[success: BOOL] = { NULL; }; < pResetConfirmEnd otherwise Index marker response: Undetermined problem: BEL>> HandleProseOutput: ENTRY PROC[info: LarkInfo, commandEvent: Lark.StatusEvent, sInfo: SmartsInfo] RETURNS [pS: Thrush.ProseSpec _ NIL] = { c: CHAR _ commandEvent.event; SELECT c FROM IO.BEL => NULL; -- take some error action? IO.ESC, ';, '\\ => info.proseResponse _ ""; -- start of some response '[ => NULL; -- peel this character off ThSmartsPrivate.stopAndFlushEnd => { ThSmartsPrivate.ProseControlDone[info, ThSmartsPrivate.flushMarker, sInfo]; info.flushJustFinished _ TRUE; }; ThSmartsPrivate.indexMarkerEnd => { marker: INT _ Convert.IntFromRope[info.proseResponse]; IF marker > ThSmartsPrivate.maxClientMarker THEN ThSmartsPrivate.ProseControlDone[info, marker, sInfo] ELSE { pQ: ThSmartsPrivate.LarkProseQueue _ info.proseQueue; IF pQ=NIL THEN Thrush.pERROR; IF info.flushJustFinished THEN { <> FOR pSkip: ThSmartsPrivate.LarkProseQueue _ pQ, pSkip.rest WHILE pSkip#NIL DO IF pSkip.first.proseMarker = marker THEN pQ _ pSkip; ENDLOOP; info.flushJustFinished _ FALSE; }; IF pQ.first.proseMarker = marker THEN { pS _ pQ.first.proseSpec; info.proseQueue _ pQ.rest; -- Flush any skipped items. IF info.proseQueue=NIL THEN info.pTail _ NIL; } ELSE ThSmartsPrivate.ProseControlDone[info, ThSmartsPrivate.proseFailure, sInfo]; -- fail!!! }; }; ThSmartsPrivate.pResetConfirmEnd => { <> <> <> ThSmartsPrivate.ProseControlDone[info, ThSmartsPrivate.flushMarker, sInfo]; info.flushJustFinished _ TRUE; <<};>> }; ENDCASE => info.proseResponse _ Rope.Concat[info.proseResponse, Rope.FromChar[c]]; }; HandleAndReport: PROC [info: LarkInfo, commandEvent: Lark.StatusEvent, sInfo: SmartsInfo] ~ { <> pS: Thrush.ProseSpec _ HandleProseOutput[info, commandEvent, sInfo]; IF pS#NIL THEN ThSmartsPrivate.ReportProseDone[sInfo, pS]; }; InterpretSpeakerSwitch: PROC[info: LarkInfo, sEvent: Lark.StatusEvent] RETURNS [ processedEvent: Lark.StatusEvent] = --<>-- { interval: INTEGER = LOOPHOLE[sEvent.time-info.swOnTime]; processedEvent _ sEvent; SELECT sEvent.event FROM enabled => info.swOnTime _ sEvent.time; disabled => NULL; ENDCASE => ERROR Thrush.pERROR; IF interval < 0 OR interval > spClickInterval THEN RETURN; processedEvent.event _ ThSmartsPrivate.click; }; spClickInterval: INTEGER _ 500; InterpretRingDetect: ENTRY PROC[info: LarkInfo, smartsID: SmartsHandle, smartsInfo: SmartsInfo, 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; 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 => TRUSTED { -- known enabled -- info.ringDetState _ maybe; info.ringDetWaitState_idle; info.ringDetInstance _ info.ringDetInstance+1; Process.Detach[FORK RingDetProc[smartsID, smartsInfo, info.ringDetInstance]]; }; maybe => { -- known disabled -- IF interval { -- known disabled -- info.ringDetState _ between; }; ring => { -- known disabled -- info.ringDetState _ IF interval > ringInterval THEN between ELSE idle; }; between => { -- known enabled -- info.ringDetState _ IF interval < breakInterval THEN ring ELSE idle; }; ENDCASE; IF info.ringDetState#info.ringDetWaitState THEN NOTIFY info.ringDetCondition; }; debounceInterval: INTEGER _ 300; -- ms. ringInterval: INTEGER = 1700; -- ms., 2 sec. nominal, 300 ms. benefit of doubt breakInterval: INTEGER = 4500; -- ms., 4 sec. nominal, 500 ms. benefit of doubt debounceIntTicks: Process.Ticks = Process.MsecToTicks[debounceInterval]; ringIntTicks: Process.Ticks = Process.MsecToTicks[ringInterval]; breakIntTicks: Process.Ticks = Process.MsecToTicks[breakInterval]; RingDetProc: PROC[smartsID: SmartsHandle, smartsInfo: SmartsInfo, instance: CARDINAL] = { info: LarkInfo = smartsInfo.larkInfo; event: Lark.Event; RingDetProcEntry: ENTRY PROC[info: LarkInfo] RETURNS[stillRunning: BOOL] = --INLINE-- { ENABLE UNWIND=>NULL; newState: RingDetState = IF info.ringDetInstance#instance THEN idle ELSE info.ringDetState; event _ reset; IF newState#idle AND newState=info.ringDetWaitState THEN -- timed out SELECT info.ringDetState FROM idle => RETURN[FALSE];--??-- maybe => { info.ringDetState _ ring1; event _ enabled; RETURN[TRUE]; }; ring1, between => info.ringDetState _ idle; ring => info.ringDetState _ between; --??-- ENDCASE; <> info.ringDetWaitState _ info.ringDetState; TRUSTED { SELECT info.ringDetState FROM idle => { event _ disabled; RETURN[FALSE]; }; maybe => Process.SetTimeout[@info.ringDetCondition, debounceIntTicks]; ring1 => Process.SetTimeout[@info.ringDetCondition, ringIntTicks+breakIntTicks]; ring => Process.SetTimeout[@info.ringDetCondition, ringIntTicks]; between => Process.SetTimeout[@info.ringDetCondition, breakIntTicks]; ENDCASE; }; WAIT info.ringDetCondition; RETURN[ TRUE ]; }; DO stillRunning:BOOL_ RingDetProcEntry[info]; IF event#reset THEN smartsInfo.ParseEvent[smartsInfo, [info.ringChangeTime, ringDetect, event ] ]; IF ~stillRunning THEN RETURN; ENDLOOP; }; 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: HookState; oldType: TerminalType_info.terminalType; processedEvent.device _ nothing; ev _ SELECT rawEvent.device FROM hookSwitch => SELECT event FROM Lark.enabled => IF info.monitor THEN spMon ELSE tsOn, Lark.disabled => tsOff, ThSmartsPrivate.click => spClick, ENDCASE=> spNone, speakerSwitch => SELECT event FROM Lark.enabled => spOn, Lark.disabled => spOff, ThSmartsPrivate.click => spClick, ENDCASE => spNone, ENDCASE => spNone; processedEvent _ rawEvent; IF ev = spNone THEN { <> processedEvent.event _ SELECT rawEvent.device FROM keyboard => SELECT event FROM '[ => enabled, '] => disabled, '\n => endNum, ENDCASE => event, touchPad => SELECT event FROM bThorp => endNum, bStar => '*, IN Lark.DTMFEvent => event - (FIRST[Lark.DTMFEvent]-'0), ENDCASE=>event, ENDCASE => event; RETURN; }; newState _ newStates[info.hookState][ev]; processedEvent.device _ IF newState=onhook OR info.hookState=onhook THEN hookSwitch ELSE nothing; IF event=ThSmartsPrivate.click THEN processedEvent.event_disabled; info.hookState _ newState; info.terminalType _ SELECT newState FROM onhook => std, telset, both, bOth => IF info.radio THEN radio ELSE std, spkr, sPkr, spKr => IF info.radio THEN radio ELSE spkr, monitor => monitor, ENDCASE=> IF Log.ProblemBool[, $Lark, , info] THEN std ELSE std; IF oldType#info.terminalType THEN ThSmartsPrivate.EnterLarkSt[info, info.larkState, sInfo]; }; 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 = { tsOn, tsOff, spOn, spOff, spClick, spMon, spNone }; <> <> <> <> newStates: ARRAY HookState OF ARRAY EvType OF HookState _ [ [ telset, onhook, sPkr, onhook, onhook, monitor, onhook ], -- onhook [ telset, onhook, bOth, telset, telset, monitor, onhook ], -- telset [ telset, spkr, spKr, onhook, onhook, monitor, onhook ], -- spkr (speakerphone mode but switch in middle) [ bOth, sPkr, sPkr, onhook, spkr, bOth, onhook ], -- sPkr (speaker switch is up) [ bOth, sPkr, sPkr, onhook, onhook, bOth, onhook ], -- spKr (sPkr, but click turns off - speaker switch in middle or up.) Scenario goes like this: we were in spkr mode and we saw an spOn. Next we might see an spClick, in which case this is a click off (=down+up) so we go onhook. However, it might also be a click on (=up), in which case this situation could persist and has to be considered the same as sPkr. [ both, spkr, both, telset, telset, both, onhook ], -- both (handset offhook, speaker switch in middle or up; click reverts to telset) [ bOth, sPkr, bOth, telset, both, bOth, onhook ], -- bOth (handset offhook, speaker switch up) [ monitor, onhook, bOth, sPkr, monitor, monitor, onhook ] ]; -- 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"]; }. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>>