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 ] = { 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 => { 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 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"]; }. ‚LarkInImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Last modified by D. Swinehart, February 4, 1987 8:34:38 am PST Polle Zellweger (PTZ) August 29, 1985 5:46:32 pm PDT Declarations Lark to LarkIn procedures Seriously consider making most of this an entry procedure. Handles smarts-independent filtering, events that need timing. May not be ready for input events, yet. Interpret event, possibly filter some out. React to upstrokes, not downstrokes. 0-key Rollover is a side-effect. Try it. 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. Smarts-to-LarkIn Procedures Determine if either of telset or speakerphone is activated. We're now serialized with Lark Output, and protected by its failure code Assert aRelay and unrevert hookswitch Utilities Generates one "enabled" for every ring detect (with a delay for possible glitches the first time), and a single "disabled" when the last ring has been gone too long. See ThSmartsPrivate.LarkInfo. Bold states occur only when event is enabled, others only when disabled ELSE: The debounce timeout will soon occur and generate the offhook event Really going offhook Tables SwitchState: TYPE = { onhook, telset, speaker, sPEAKER, monitor, mONITOR }; spOn is transition away from middle on speaker switch that lasts more than a few hundred ms. spOff is transition to middle when spOn. spClick is transition away from and back to middle after a few hundred ms. Note: spNone is used to indicate that no interesting action took place. See ThSmartsPrivate.SwitchState for the legal state transitions; others are made harmless here. Scenario: user picks up telset and talks for a while, then clicks the speaker switch; the speaker comes on but the telset is still active. Hanging up switches to $speaker mode; picking up the telset reverts to $telset mode, whence hanging up would terminate the conversation unless the user clicks the switch again. Scenario: user picks up telset and talks for a while, then turns on the speaker switch; the speaker comes on but the telset is still active. Hanging up switches to $sPEAKER mode; picking up the telset reverts to $mONITOR mode, and hanging up alternates back to sPEAKER mode, since the switch is still on. When calls are terminated from Finch or the other party, the terminal state goes $onhook anyhow, so the distinctions between switch-on modes and click modes are not as great as they once were. Swinehart, August 6, 1985 12:10:40 pm PDT Incorporate PTZ changes changes to: DIRECTORY, LarkInImpl, RecordEvent, HandleProseOutput, InterpretSpeakerSwitch Polle Zellweger (PTZ) August 7, 1985 6:36:35 pm PDT Comments only. changes to: EvType, newStates Polle Zellweger (PTZ) August 22, 1985 5:20:05 pm PDT Handle Prose flushing; remove deadlock from HandleProseOutput. changes to: DIRECTORY, RecordEvent, HandleProseOutput, HandleAndReport Polle Zellweger (PTZ) August 23, 1985 2:10:02 pm PDT Input events from the Prose Lark are being interpreted as smarts-level parseable events. Raise error for odd devices. changes to: RecordEvent Polle Zellweger (PTZ) August 23, 1985 2:34:28 pm PDT changes to: RecordEvent, HandleProseOutput Polle Zellweger (PTZ) August 27, 1985 8:57:45 pm PDT Allow for Prose Reset command. changes to: DIRECTORY, HandleProseOutput Polle Zellweger (PTZ) August 29, 1985 5:46:33 pm PDT changes to: HandleProseOutput Swinehart, October 28, 1985 11:54:20 am PST Handle => ID, Log => VoiceUtils changes to: DIRECTORY, LarkInImpl, Reseal, SmartsID, RecordEvent, EventRope, HandleProseOutput, InterpretSpeakerSwitch, InterpretRingDetect, RingDetProc, InterpretHookState, ProblemBool Swinehart, November 29, 1985 11:17:56 am PST When going on hook, don't react to changes in terminal type, since the terminal will soon be put idle by other actions. changes to: InterpretHookState Swinehart, April 10, 1986 11:55:03 am PST Simplify and improve speaker and telset switch handling, add monitor mode as first-class citizen changes to: DIRECTORY, Reseal, SmartsID, SwitchState, ROPE, PD, InterpretSpeakerSwitch, SpeakerSwitchTimeout, SSTE (local of SpeakerSwitchTimeout), InterpretHookState, EvType, newStates Swinehart, May 25, 1986 10:10:16 pm PDT Lark => LarkOps changes to: DIRECTORY, LarkInImpl Κ ‘˜šœ™Icodešœ Οmœ7™BJšœ>™>K™4J˜—šΟk ˜ Jšžœ˜Jšœ žœ˜*Jšœžœ€žœ˜΅J˜J˜ Jšœžœ˜$J˜Jšœžœžœ˜Jšœžœ%˜2šœžœ˜Jšœ ˜ —Jšœžœžœ ˜Jšœžœ˜Jšœ žœ˜$J˜J˜—šœ ž œžœžœ˜9šž˜Jšœžœ<˜\—Jšžœ˜&—šžœžœ˜J˜—™ J™Jšœ žœ˜*Jšœ žœ˜.Jšœ žœ˜!Jšœ žœ˜0J˜J˜!Jšœ#˜#J˜!Jšœ%˜%J˜J˜šžœžœžœ˜JšœžœžœΟc2˜MJšœžœŸ˜'Jšœžœ˜Jšœžœ Ÿ0˜OJšœ žœžœŸ&˜:J˜J˜—Jš œžœžœžœžœ˜J˜—™J˜šΟn œžœžœž œ=˜fJšžœ ž œ˜"JšΟtœ:‘™JšœK˜KJšœ#˜#Jšœžœ˜J˜š žœ žœžœžœž˜;Jšœ$˜$—šžœžœž˜3J˜+Jš œ žœ žœžœžœ ˜8Jšžœžœ žœ˜Bšžœ žœ˜JšœKžœ˜S—™'Jšžœžœžœžœ˜?—J™*šžœž˜J˜?Jšœžœ˜šœ˜J˜)JšœC˜CJšœ+˜+J˜—šœ ˜ Jšœ7˜7Jšžœ˜J˜—šœ ˜ J™OJšžœžœžœŸ,˜MJšžœžœ+˜Hšžœ˜Jšœ,žœŸ˜MJ˜—Jšžœžœžœ˜ J˜—šœ ˜ Kš‘œ–‘™˜Kšžœžœžœ)˜NKšžœ˜K˜—Jšžœžœ˜—JšœB˜BJšžœ˜—Jšœ˜—J˜š  œž œ˜Jšœ žœžœ$žœ˜]Jšžœ žœ˜JšžœŸ˜$Jšœ˜—J˜—J™™š œžœž˜Jš œžœ žœžœžœ˜UJ˜—š œžœžœž˜$Jšœžœ žœžœ˜4J™;Jš žœžœžœžœžœ˜Jšžœžœžœžœ˜,šœ%˜%Jšžœ7žœ˜A—Jšžœžœ˜J˜J˜—š œžœžœ˜?J™HJšœžœžœ˜"Jšœžœ˜Jšœžœžœ˜Jšžœž‘œ+˜Bšž˜J˜Jšœ žœ˜šž˜J˜J˜?šžœžœžœž˜#šžœž˜šœ žœž˜'šœ˜Jšœ žœ˜Jšœ=˜=J˜—Jšœžœžœžœ˜GJšžœ˜—Jšžœ˜—Jšžœ˜—Jšžœ žœžœ˜Jšžœ˜—šžœ žœ˜J™%Jšœ8˜8Jšžœ˜J˜—Jšžœ˜Jšžœ˜—JšžœžœG˜UK˜—J˜—™ J™š œžœžœ*˜LJšžœ(˜/Jšœ žœžœ˜8J˜J˜šžœž˜˜ Jšžœžœ/˜LJ˜ Jšžœ˜J˜—šœ žœžœ ž˜DJšœ-˜-—JšœžœŸ4˜VJšžœžœ˜—J˜J˜—š œžœž œ˜@šžœžœžœžœžœžœžœ˜SJšœž œ˜JšœC˜CJšžœ˜ Jšžœ˜J˜—Jšœ˜Jš žœžœžœžœŸ*˜NJšœN˜NJ˜&Jšœ1˜1J˜J˜—š œžœžœ*˜IJšžœF˜MJ™ΔJšžœžœžœ˜Jšœ žœžœ"˜>J˜!Jšœ žœžœ˜J˜"šžœžœ˜Jš œ žœžœžœžœžœ˜NJš œ žœžœžœžœ˜IJšžœžœ˜—šžœž˜JšœG™GJšΟbœ˜"Jš’œ<Ÿ˜Zš œ žœžœžœžœ˜QJšžœE™I—Jšœ$˜$Jšžœ˜—J˜.Jšž œ;˜DJšžœžœžœ˜)Jšžœžœ-˜JJšœ˜J˜—š  œžœžœ˜4š  œžœžœžœžœŸ˜eJšžœžœžœ˜Jšœ žœ˜$Jšžœžœžœ˜-šžœž˜Jšœ žœ˜Jšœ(˜(Jšžœ˜—JšœI˜IJšžœ˜JšžœžœžœŸ˜BšžœžœŸ ˜*Jšœ žœŸ˜Jšœ;Ÿ˜IJšžœ6Ÿ ˜H—J˜—Jšœ"˜"šžœžœ˜JšœE˜EJšœ+˜+Jšœ6˜6J˜—JšžœžœŸ%˜ZJšœ˜J˜—š  œžœžœF˜^J˜/J˜J˜!Jšžœžœžœ žœžœ žœžœžœ˜Nšžœž˜˜šœžœž˜*Jšœ˜Jšœ˜Jšœ!˜!Jšžœ ˜—J˜—šœ˜šœžœž˜*Jšœ˜Jšœ˜Jšžœ ˜——šœžœž˜4J˜J˜ šžœ˜Jšœžœ˜*—Jšžœ˜—Jšžœ˜—šžœžœ˜"Jšœ<˜™>Kšœ ¦:™F—™4K™vKšœ ¦ ™—™4Kšœ ¦™*—™4K™Kšœ ¦™(—™4Kšœ ¦™—™+K™Kšœ ¦­™Ή—™,K™wKšœ ¦™—™)K™`Kšœ ¦fœ ¦'™Ή—™'K™Kšœ ¦™!—K™——…—*FIY