LarkInImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, December 8, 1985 4:02:38 pm PST
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 ],
LarkRpcControl,
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, HookState, indexMarkerEnd, LarkInfo, LarkProseQueue, LarkState, maxClientMarker, pResetConfirmEnd, ProseControlDone, proseFailure, ReportProseDone, RingDetState, SmartsInfo, stopAndFlushEnd, TerminalType, TonesDone ],
Thrush USING[ ProseSpec, ROPE, SHHH, SmartsID ],
ThNet USING [ pd ],
VoiceUtils USING [ Problem, Report ]
;
LarkInImpl:
CEDAR MONITOR
LOCKS info
USING info: LarkInfo
IMPORTS
LarkRpcControl, 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]]; };
HookState: TYPE = ThSmartsPrivate.HookState;
LarkInfo: TYPE = ThSmartsPrivate.LarkInfo;
RingDetState: TYPE = ThSmartsPrivate.RingDetState;
SmartsData: TYPE = ThPartyPrivate.SmartsData;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
SmartsID: TYPE = Thrush.SmartsID;
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.
debounceInterval: INTEGER ← 300, -- ms.
breakInterval: INTEGER ← 4500 -- ms., 4 sec. nominal, 500 ms. benefit of doubt
];
pd: REF PD ← NEW[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: BOOL←FALSE; -- 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:
PROC[info: LarkInfo, sEvent: Lark.StatusEvent]
RETURNS [ processedEvent: Lark.StatusEvent] = --<<INLINE>>-- {
interval: INTEGER = LOOPHOLE[sEvent.time-info.swOnTime];
processedEvent ← sEvent;
SELECT sEvent.event
FROM
enabled => info.swOnTime ← sEvent.time;
disabled => NULL;
ENDCASE => ERROR;
IF interval < 0 OR interval > spClickInterval THEN RETURN;
processedEvent.event ← ThSmartsPrivate.click; };
spClickInterval: INTEGER ← 500;
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: 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
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: HookState;
oldType: TerminalType←info.terminalType;
ev ←
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;
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[info.hookState][ev];
processedEvent.device
← IF newState=onhook
OR info.hookState=onhook
THEN hookSwitch ELSE nothing;
IF event=ThSmartsPrivate.click
THEN processedEvent.event𡤍isabled;
That one needs explaining. In fact, clicks need explaining somewhere central.
info.hookState ← newState;
{
ProblemBool: PROC RETURNS[TerminalType] = {VoiceUtils.Problem[,$Lark,info]; RETURN[$std];};
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=> ProblemBool[];
};
IF newState#onhook
AND oldType#info.terminalType
THEN
When newState is onhook, the lark state will soon be idle, and this is just a distraction.
ThSmartsPrivate.EnterLarkSt[info, info.larkState, sInfo];
};
CheckHookState:
PUBLIC
ENTRY
PROC
[info: LarkInfo ] RETURNS [ onHook: BOOL←TRUE ] = {
Determine if either of telset or speakerphone is activated.
ENABLE RPC.CallFailed => IF pd.callTimeoutOK THEN RESUME ELSE GOTO Failed;
reverted, wasReverted: BOOL←FALSE;
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;
};
EvType:
TYPE = { tsOn, tsOff, spOn, spOff, spClick, spMon, spNone };
spOn is transition away from middle on speaker switch
spOff is transition to middle if it's been more than a few hundred milliseconds
spClick is transition to middle if it's been less than a few hundred milliseconds -- spClick is always preceded by spOn.
Note: spNone transitions are never taken.
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 (speakerphone mode but switch in middle)
[ bOth, sPkr, sPkr, onhook, spkr, bOth, onhook ], -- sPkr (speaker switch is up)
[ bOth, sPkr, sPkr, onhook, onhook, bOth, onhook ], -- spKr (sPkr, but click turns off - speaker switch in middle or up.) Scenario goes like this: we were in spkr mode and we saw an spOn. Next we might see an spClick, in which case this is a click off (=down+up) so we go onhook. However, it might also be a click on (=up), in which case this situation could persist and has to be considered the same as sPkr.
[ both, spkr, both, telset, telset, both, onhook ], -- both (handset offhook, speaker switch in middle or up; click reverts to telset)
[ bOth, sPkr, bOth, telset, both, bOth, onhook ], -- bOth (handset offhook, speaker switch up)
[ monitor, onhook, bOth, sPkr, monitor, monitor, onhook ] ]; -- 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"];
}.
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