LarkInImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, July 31, 1987 5:58:39 pm PDT
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, InputEventSpec, 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;
};
nothing => LOOP;
This is just a poll to see if we're here. For the moment, we don't insist that anything else be working to respond in the affirmative.
ENDCASE => ERROR;
info.inputQueue.QueueClientAction[QdParseEvent,
NEW[ThSmartsPrivate.InputEventSpecBody ← [parseInfo, 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 ];
IF ThNet.pd.debug THEN DebEvent[info.larkSmartsInfo, sEvent];
info.inputQueue.QueueClientAction[QdParseEvent,
NEW[ThSmartsPrivate.InputEventSpecBody ← [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 ];
IF ThNet.pd.debug THEN DebEvent[info.larkTrunkSmartsInfo, sEvent];
info.inputQueue.QueueClientAction[QdParseEvent,
NEW[ThSmartsPrivate.InputEventSpecBody ← [info.larkTrunkSmartsInfo, sEvent]]];
};
IF event = enabled THEN RingTimeout[info, instance]; -- wait for no-longer-ringing timeout
};
QdParseEvent: PROC[r: REF] = { -- r is an InputEventSpec
inputEventSpec: ThSmartsPrivate.InputEventSpec ← NARROW[r];
smartsInfo: SmartsInfo ← inputEventSpec.smartsInfo;
info: LarkInfo ← IF smartsInfo#NIL THEN smartsInfo.larkInfo ELSE NIL;
IF info=NIL THEN RETURN;
inputEventSpec.sEvent ← DoParseEvent[info, smartsInfo, inputEventSpec.sEvent];
The purpose of DoParseEvent is to analyze the current switchhook and speaker switch states, along with this most recent event, to determine new switch states and to take whatever hardware switching action is required. It must be an Entry procedure.
IF inputEventSpec.sEvent.device=nothing THEN RETURN;
smartsInfo.ParseEvent[inputEventSpec];
The ParseEvent routine that we are calling needn't traffic in REF ANY's any more; the MBQueue point was at one time at this point, so the REF ANY is historical. I did not want to change it, though. DCS June 16, 1987 5:59:47 pm PDT
};
DoParseEvent: ENTRY PROC[
info: LarkInfo, smartsInfo: SmartsInfo, rawEvent: Lark.StatusEvent]
RETURNS[sEvent: Lark.StatusEvent] = {
oldSwitchState: SwitchState ← info.switchState;
newSwitchState: SwitchState;
switchEventType: EvType ← spNone;
sEvent ← rawEvent;
IF info.failed OR smartsInfo.failed THEN { sEvent.device ← nothing; 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
};
};
};
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