LarkInImpl.mesa
Copyright © 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
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;
Declarations
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: BOOLFALSE, -- 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: BOOLFALSE -- TRUE to check Lark command timeout.
];
pd: REF PDNEW[PD←[]];
Lark to LarkIn procedures
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.
Handles smarts-independent filtering, events that need timing.
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; };
May not be ready for input events, yet.
IF info.larkState=$none AND ~CheckHookStateE[info] THEN RETURN;
Interpret event, possibly filter some out.
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 => {
React to upstrokes, not downstrokes. 0-key Rollover is a side-effect. Try it.
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.
};
Smarts-to-LarkIn Procedures
CheckHookStateE: ENTRY PROC
[ info: LarkInfo ] RETURNS [ onHook: BOOLTRUE ] = { RETURN[CheckHookState[info]]; };
CheckHookState: PUBLIC INTERNAL PROC
[ info: LarkInfo ] RETURNS [ onHook: BOOLTRUE ] = {
Determine if either of telset or speakerphone is activated.
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] = {
We're now serialized with Lark Output, and protected by its failure code
reverted, wasReverted: BOOLFALSE;
which: CARDINAL;
onHook: BOOLTRUE;
IF ThNet.pd.debug THEN ThSmartsPrivate.Deb[info, "Ck hook state"];
DO
which𡤀
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 {
Assert aRelay and unrevert hookswitch
info.interface.Commands[shh: info.shh, events: unRevHS];
LOOP;
};
EXIT;
ENDLOOP;
IF onHook THEN info.inputQueue.QueueClientAction[ThSmartsPrivate.EnableSmarts, info];
};
Utilities
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 ] ] = {
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.
ENABLE UNWIND=>NULL;
interval: INTEGER = LOOPHOLE[sEvent.time-info.ringChangeTime];
event: Lark.Event = sEvent.event;
timeout: BOOLFALSE;
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
Bold states occur only when event is enabled, others only when disabled
idle => info.ringDetState ← maybe;
between => { info.ringDetState ← ring; processedEvent ← sEvent; }; -- generate new offhook
maybe => IF interval<pd.debounceInterval THEN info.ringDetState←idle ELSE RETURN;
ELSE: The debounce timeout will soon occur and generate the offhook event
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
Really going offhook
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];
};
Tables
SwitchState: TYPE = { onhook, telset, speaker, sPEAKER, monitor, mONITOR };
EvType: TYPE = { tsOff, tsOn, spOff, spOn, spClick, spNone };
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.
newStates: ARRAY SwitchState OF ARRAY EvType OF SwitchState ← [
See ThSmartsPrivate.SwitchState for the legal state transitions; others are made harmless here.
[ $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
];
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.
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"];
}.
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