LarkInImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, May 25, 1986 10:10:22 pm PDT
Polle Zellweger (PTZ) August 29, 1985 5:46:32 pm PDT
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 ],
LarkOpsRpcControl,
LarkSmarts,
Nice,
RefID USING [ ID, Reseal ],
RPC USING [ CallFailed ],
Rope USING [ Concat, FromChar, ROPE ],
Process USING [ Detach, MsecToTicks, SetTimeout ],
ThPartyPrivate USING [ SmartsData ],
ThSmartsPrivate USING [
click, EnterLarkSt, flushMarker, GetSmartsInfo, indexMarkerEnd, LarkInfo, LarkProseQueue, LarkState, maxClientMarker, pResetConfirmEnd, ProseControlDone, proseFailure, ReportProseDone, RingDetState, SmartsInfo, spkrOn, stopAndFlushEnd, SwitchState, TonesDone ],
Thrush USING[ ProseSpec, ROPE, SHHH, SmartsID ],
ThNet USING [ pd ],
VoiceUtils USING [ Report ]
;
LarkInImpl: CEDAR MONITOR LOCKS info USING info: LarkInfo
IMPORTS
LarkOpsRpcControl, Commander, Convert, IO, Nice, Process, RefID, Rope, RPC, ThNet, ThSmartsPrivate, VoiceUtils
EXPORTS LarkSmarts, ThSmartsPrivate= {
OPEN IO;
Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; };
LarkInfo: TYPE = ThSmartsPrivate.LarkInfo;
RingDetState: TYPE = ThSmartsPrivate.RingDetState;
SmartsData: TYPE = ThPartyPrivate.SmartsData;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
SmartsID: TYPE = Thrush.SmartsID;
SwitchState: TYPE = ThSmartsPrivate.SwitchState;
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: 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
];
pd: REF PDNEW[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]]]];
VoiceUtils.Report[s.RopeFromROS[], $LarkDetailed, info.larkInfo];
};
crowbar: BOOLFALSE; -- TRUE to check basic RPC performance from Lark.
RecordEvent: PUBLIC PROC[shh: Thrush.SHHH, smartsID: Thrush.SmartsID, whatHappened: Lark.StatusEvents]
RETURNS [ success: BOOL←TRUE ]
Handles smarts-independent filtering, events that need timing.
= {
smartsInfo: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: smartsID];
parseInfo: SmartsInfo ← smartsInfo;
info: LarkInfo;
parseSmartsID: SmartsID;
IF smartsInfo=NIL OR (info ← smartsInfo.larkInfo)=NIL THEN RETURN[FALSE];
parseSmartsID ← smartsInfo.smartsID;
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;
Interpret event, possibly filter some out.
SELECT sEvent.device FROM
ringDetect => {
parseSmartsID ← smartsInfo.otherSmartsID;
parseInfo ← ThSmartsPrivate.GetSmartsInfo[smartsID: parseSmartsID];
};
tones => {
ThSmartsPrivate.TonesDone[smartsInfo.larkInfo, sEvent];
LOOP;
};
ENDCASE;
SELECT sEvent.device FROM
speakerSwitch => sEvent ← InterpretSpeakerSwitch[info, sEvent];
ringDetect => sEvent ← InterpretRingDetect[info, sEvent];
touchPad => {
React to upstrokes, not downstrokes. 0-key Rollover is a side-effect. Try it.
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 => ERROR;
IF sEvent.device=nothing THEN LOOP;
parseInfo.ParseEvent[ smartsInfo: parseInfo, sEvent: sEvent ];
ENDLOOP; };
EventRope: PUBLIC PROC[
shh: Thrush.SHHH, smartsID: SmartsID, time: CARDINAL, device: Lark.Device, events: ROPE]
RETURNS[success: BOOL] = {
NULL;
};
Only pass on index marker notifications. Swallow or handle all others.
Assume Prose XON / XOFF disabled.
Assume only get the following responses:
 On pReset: pResetConfirmOK       if everything's okay
    cmdLeader <0-16> 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];
info.flushJustFinished ← TRUE;
};
ThSmartsPrivate.indexMarkerEnd => {
marker: INT ← Convert.IntFromRope[info.proseResponse];
IF marker > ThSmartsPrivate.maxClientMarker THEN
ThSmartsPrivate.ProseControlDone[info, marker]
ELSE {
pQ: ThSmartsPrivate.LarkProseQueue ← info.proseQueue;
IF pQ=NIL THEN ERROR;
IF info.flushJustFinished THEN {
It's okay to skip ahead in the queue. Except for 1st item in a conversation, which follows an initial RESET, should be to some entry with queueIt=FALSE. Don't report skipped entries as finished; FinchSmarts will take care of skipping ahead in its queue. (May want to add a proseSpec.type = flushed?)
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]; -- fail!!!
};
};
ThSmartsPrivate.pResetConfirmEnd => {
IF Convert.IntFromRope[info.proseResponse] # 0 THEN
ThSmartsPrivate.ProseControlDone[info, ThSmartsPrivate.proseFailure] -- fail!!!;
ELSE {
ThSmartsPrivate.ProseControlDone[info, ThSmartsPrivate.flushMarker];
info.flushJustFinished ← TRUE;
};
};
ENDCASE => info.proseResponse ← Rope.Concat[info.proseResponse, Rope.FromChar[c]];
};
HandleAndReport: PROC [info: LarkInfo, commandEvent: Lark.StatusEvent, sInfo: SmartsInfo] ~ {
Avoid deadlock - shouldn't call Smarts-level entry procs while holding the LarkIn/Out lock.
pS: Thrush.ProseSpec ← HandleProseOutput[info, commandEvent, sInfo];
IF pS#NIL THEN ThSmartsPrivate.ReportProseDone[sInfo, pS];
};
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];
};
IF ~SSTE[info, onTime] THEN RETURN; -- click happened or something, forget it.
info.larkSmartsInfo.ParseEvent[info.larkSmartsInfo,
[onTime+pd.spClickInterval, speakerSwitch, ThSmartsPrivate.spkrOn ] ];
};
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
info.larkTrunkSmartsInfo.ParseEvent[
info.larkTrunkSmartsInfo, [info.ringChangeTime, ringDetect, event ] ];
IF event = enabled THEN RingTimeout[info, instance]; -- wait for no-longer-ringing timeout
};
InterpretHookState: PUBLIC ENTRY PROC
[ info: LarkInfo, rawEvent: Lark.StatusEvent, sInfo: SmartsInfo ]
RETURNS [ processedEvent: Lark.StatusEvent ] = {
Filters only events whose device codes are hookswitch and speakerSwitch
Implements another state machine via SELECT
ENABLE UNWIND=>NULL;
event: Lark.Event = rawEvent.event;
ev: EvType;
newState: SwitchState;
oldState: SwitchState←info.switchState;
ev ← SELECT rawEvent.device FROM
hookSwitch => SELECT event FROM
Lark.enabled => tsOn,
Lark.disabled => tsOff,
ENDCASE=> spNone,
speakerSwitch => SELECT event FROM
ThSmartsPrivate.spkrOn => spOn,
Lark.disabled => spOff,
ThSmartsPrivate.click => spClick,
ENDCASE => spNone,
ENDCASE => spNone;
processedEvent ← rawEvent;
IF ev = spNone THEN {
Translate characters from various devices into common set
processedEvent.event ← SELECT rawEvent.device FROM
touchPad => SELECT event FROM
bThorp => endNum,
bStar => '*,
IN Lark.DTMFEvent => event - (FIRST[Lark.DTMFEvent]-'0),
ENDCASE=>event,
ENDCASE => event;
RETURN;
};
newState ← newStates[oldState][ev];
info.switchState ← newState;
processedEvent.device ← hookSwitch;
IF newState=oldState THEN processedEvent.device←nothing -- no op?
ELSE IF newState = $onhook THEN processedEvent.event ← disabled -- really going onhook
When newState is onhook, the lark state will soon be idle, and this is just a distraction.
ELSE {
IF oldState = $onhook THEN processedEvent.event ← enabled -- really going offhook
ELSE processedEvent.device←nothing;
ThSmartsPrivate.EnterLarkSt[info, info.larkState];
};
};
CheckHookState: PUBLIC ENTRY PROC
[info: LarkInfo ] RETURNS [ onHook: BOOLTRUE ] = {
Determine if either of telset or speakerphone is activated.
ENABLE RPC.CallFailed => IF pd.callTimeoutOK THEN RESUME ELSE GOTO Failed;
reverted, wasReverted: BOOLFALSE;
which: CARDINAL;
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;
EXITS
Failed => onHook←FALSE;
};
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