<> <> <> DIRECTORY IO, Lark USING [ bStar, bThorp, CommandEvent, CommandEvents, CommandEventSequence, Device, disabled, DTMFEvent, enabled, endNum, Event, Passel, reset, StatusEvent, StatusEvents, SHHH ], LarkSmarts, Log USING [ ProblemBool, Report, SLOG ], Nice, RPC USING [ CallFailed ], Rope USING [ ROPE ], Process USING [ Detach, MsecToTicks, SetTimeout, Ticks ], ThPartyPrivate USING [ SmartsData ], ThSmartsPrivate USING [ click, EnterLarkSt, GetSmartsInfo, HookState, LarkInfo, LarkState, RingDetState, SmartsInfo, TerminalType ], Thrush USING[ H, pERROR, ROPE, SHHH, SmartsHandle, ThHandle ], ThNet USING [ pd ] ; LarkInImpl: CEDAR MONITOR LOCKS info USING info: LarkInfo IMPORTS IO, Log, Nice, Process, 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, ",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; <> Log.SLOG[]; SELECT sEvent.device FROM ringDetect, tones => { parseSmarts _ smartsInfo.otherSmarts; parseInfo _ ThSmartsPrivate.GetSmartsInfo[smarts: parseSmarts]; }; ENDCASE; SELECT sEvent.device FROM speakerSwitch => sEvent _ InterpretSpeakerSwitch[info, sEvent]; ringDetect => sEvent _ InterpretRingDetect[info, H[parseSmarts], parseInfo, sEvent]; touchPad => IF sEvent.event=disabled THEN LOOP; -- Up transitions of DTMF pad. ENDCASE; 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; }; 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 ] RETURNS [ processedEvent: Lark.StatusEvent ] = { <> <> ENABLE UNWIND=>NULL; event: Lark.Event = rawEvent.event; ev: EvType _ 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; newState: HookState; oldType: TerminalType_info.terminalType; 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, NIL]; }; unRevCommands: Lark.CommandEvents _ NEW[Lark.CommandEventSequence[2]]; 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, speakerSwitch => onHook_FALSE; 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: unRevCommands]; 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 [ bOth, sPkr, sPkr, onhook, spkr, bOth, onhook ], -- sPkr [ bOth, sPkr, sPkr, onhook, onhook, bOth, onhook ], -- spKr (sPkr, but click turns off.) [ both, spkr, both, telset, telset, both, onhook ], -- both [ bOth, sPkr, bOth, telset, both, bOth, onhook ], -- bOth [ monitor, onhook, bOth, sPkr, monitor, monitor, onhook ] ]; -- monitor unRevCommands[0] _ [aRelay, enabled]; unRevCommands[1] _ [revertHookswitch, disabled]; Nice.View[pd, "Lark In PD"]; }.