LarkInImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, May 17, 1986 4:16:22 pm PDT
Polle Zellweger (PTZ) December 11, 1985 11:56:06 pm PST
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,
RPC USING [ CallFailed ],
Rope USING [ Concat, FromChar, ROPE ],
Process USING [ Detach, MsecToTicks, SetTimeout, Ticks ],
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[ H, pERROR, ProseSpec, ROPE, SHHH, SmartsHandle, ThHandle ],
ThNet USING [ pd ],
VoiceUtils USING [ Problem, Report ]
;
LarkInImpl:
CEDAR MONITOR
LOCKS info
USING info: LarkInfo
IMPORTS
LarkRpcControl, Commander, Convert, IO, Nice, Process, Rope, RPC, ThNet, Thrush, ThSmartsPrivate, VoiceUtils
EXPORTS LarkSmarts, ThSmartsPrivate= {
OPEN IO;
H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; };
HookState: TYPE = ThSmartsPrivate.HookState;
LarkInfo: TYPE = ThSmartsPrivate.LarkInfo;
RingDetState: TYPE = ThSmartsPrivate.RingDetState;
SmartsData: TYPE = ThPartyPrivate.SmartsData;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
SmartsHandle: TYPE = Thrush.SmartsHandle;
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.
];
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.SmartsHandle, 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;
parseSmarts: SmartsData;
IF smartsInfo=NIL OR (info ← smartsInfo.larkInfo)=NIL THEN RETURN[FALSE];
parseSmarts ← smartsInfo.smarts;
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 =>
{
parseSmarts ← smartsInfo.otherSmarts;
parseInfo ← ThSmartsPrivate.GetSmartsInfo[smarts: parseSmarts]; };
tones => {
ThSmartsPrivate.TonesDone[smartsInfo.larkInfo, sEvent, smartsInfo];
LOOP;
};
ENDCASE;
SELECT sEvent.device
FROM
speakerSwitch => sEvent ← InterpretSpeakerSwitch[info, sEvent];
ringDetect => sEvent ← InterpretRingDetect[info, H[parseSmarts], parseInfo, 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 => Thrush.pERROR;
IF sEvent.device=nothing THEN LOOP;
parseInfo.ParseEvent[ smartsInfo: parseInfo, sEvent: sEvent ];
ENDLOOP; };
EventRope:
PUBLIC PROC[
shh: Thrush.SHHH, smartsID: SmartsHandle, 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, sInfo];
info.flushJustFinished ← TRUE;
};
ThSmartsPrivate.indexMarkerEnd => {
marker: INT ← Convert.IntFromRope[info.proseResponse];
IF marker > ThSmartsPrivate.maxClientMarker
THEN
ThSmartsPrivate.ProseControlDone[info, marker, sInfo]
ELSE {
pQ: ThSmartsPrivate.LarkProseQueue ← info.proseQueue;
IF pQ=NIL THEN Thrush.pERROR;
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, sInfo]; -- fail!!!
};
};
ThSmartsPrivate.pResetConfirmEnd => {
IF Convert.IntFromRope[info.proseResponse] # 0 THEN
ThSmartsPrivate.ProseControlDone[info, ThSmartsPrivate.proseFailure, sInfo] -- fail!!!;
ELSE {
ThSmartsPrivate.ProseControlDone[info, ThSmartsPrivate.flushMarker, sInfo];
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 Thrush.pERROR;
IF interval < 0 OR interval > spClickInterval THEN RETURN;
processedEvent.event ← ThSmartsPrivate.click; };
spClickInterval: INTEGER ← 500;
InterpretRingDetect:
ENTRY
PROC[info: LarkInfo, smartsID: SmartsHandle, smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent]
RETURNS [ processedEvent: Lark.StatusEvent ← [ 0, nothing, Lark.reset ] ] = --<<INLINE>>-- {
ENABLE UNWIND=>NULL;
interval: INTEGER = LOOPHOLE[sEvent.time-info.ringChangeTime];
event: Lark.Event = sEvent.event;
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 =>
TRUSTED {
-- known enabled --
info.ringDetState ← maybe;
info.ringDetWaitState←idle;
info.ringDetInstance ← info.ringDetInstance+1;
Process.Detach[FORK RingDetProc[smartsID, smartsInfo, info.ringDetInstance]]; };
maybe => {
-- known disabled --
IF interval<debounceInterval
THEN info.ringDetState←idle
ELSE {
info.ringDetState ← between;
processedEvent ← sEvent; processedEvent.event ← enabled; }; };
ring1 => {
-- known disabled --
info.ringDetState ← between; };
ring => {
-- known disabled --
info.ringDetState ← IF interval > ringInterval THEN between ELSE idle; };
between => {
-- known enabled --
info.ringDetState ← IF interval < breakInterval THEN ring ELSE idle; };
ENDCASE;
IF info.ringDetState#info.ringDetWaitState THEN NOTIFY info.ringDetCondition; };
debounceInterval: INTEGER ← 300; -- ms.
ringInterval: INTEGER = 1700; -- ms., 2 sec. nominal, 300 ms. benefit of doubt
breakInterval: INTEGER = 4500; -- ms., 4 sec. nominal, 500 ms. benefit of doubt
debounceIntTicks: Process.Ticks = Process.MsecToTicks[debounceInterval];
ringIntTicks: Process.Ticks = Process.MsecToTicks[ringInterval];
breakIntTicks: Process.Ticks = Process.MsecToTicks[breakInterval];
RingDetProc:
PROC[smartsID: SmartsHandle, smartsInfo: SmartsInfo, instance:
CARDINAL] = {
info: LarkInfo = smartsInfo.larkInfo;
event: Lark.Event;
RingDetProcEntry:
ENTRY
PROC[info: LarkInfo]
RETURNS[stillRunning:
BOOL] =
--INLINE-- {
ENABLE UNWIND=>NULL;
newState: RingDetState = IF info.ringDetInstance#instance THEN idle ELSE info.ringDetState;
event ← reset;
IF newState#idle
AND newState=info.ringDetWaitState
THEN
-- timed out
SELECT info.ringDetState
FROM
idle => RETURN[FALSE];--??--
maybe => { info.ringDetState ← ring1; event ← enabled; RETURN[TRUE]; };
ring1, between => info.ringDetState ← idle;
ring => info.ringDetState ← between; --??--
ENDCASE;
newly entered state
info.ringDetWaitState ← info.ringDetState;
TRUSTED {
SELECT info.ringDetState
FROM
idle => { event ← disabled; RETURN[FALSE]; };
maybe => Process.SetTimeout[@info.ringDetCondition, debounceIntTicks];
ring1 => Process.SetTimeout[@info.ringDetCondition, ringIntTicks+breakIntTicks];
ring => Process.SetTimeout[@info.ringDetCondition, ringIntTicks];
between => Process.SetTimeout[@info.ringDetCondition, breakIntTicks];
ENDCASE;
};
WAIT info.ringDetCondition;
RETURN[ TRUE ]; };
DO
stillRunning:BOOL← RingDetProcEntry[info];
IF event#reset
THEN
smartsInfo.ParseEvent[smartsInfo, [info.ringChangeTime, ringDetect, event ] ];
IF ~stillRunning THEN RETURN; ENDLOOP; };
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;
processedEvent.device ← nothing;
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
keyboard =>
SELECT event
FROM
'[ => enabled,
'] => disabled,
'\n => endNum,
ENDCASE => event,
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;
info.hookState ← newState;
info.terminalType ← std;
SELECT newState
FROM
onhook => NULL;
telset, both, bOth => IF info.radio THEN info.terminalType ← radio;
spkr, sPkr, spKr => info.terminalType ← IF info.radio THEN radio ELSE spkr;
monitor => info.terminalType ← monitor;
ENDCASE=> VoiceUtils.Problem[, $Lark, info];
IF oldType#info.terminalType
THEN
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
Polle Zellweger (PTZ) December 11, 1985 11:50:38 pm PST
changes to: HandleProseOutput
Swinehart, May 16, 1986 4:14:03 pm PDT
Cedar 6.1
changes to: DIRECTORY, LarkInImpl, DebEvent, InterpretHookState