LarkInImpl.mesa
Last modified by D. Swinehart, November 26, 1984 10:49:21 am PST
Actual hardware interface to Lark; includes process to keep tones going and eventually to deal with failure.
DIRECTORY
IO,
Commander USING [ CommandProc, Register ],
Lark USING [ bStar, bThorp, CommandEvent, CommandEvents, CommandEventSequence, Device, disabled, DTMFEvent, enabled, endNum, Event, Passel, reset, StatusEvent, StatusEvents, SHHH ],
LarkSmarts,
Log USING [ ProblemBool, Report, SLOG ],
Nice,
RPC USING [ CallFailed ],
Rope USING [ ROPE ],
Process USING [ Detach, MsecToTicks, SetTimeout, Ticks ],
ThPartyPrivate USING [ SmartsData ],
ThSmartsPrivate
USING [
click, EnterLarkSt, GetSmartsInfo, HookState, LarkInfo, LarkState, RingDetState, SmartsInfo, TerminalType, TonesDone ],
Thrush USING[ H, pERROR, ROPE, SHHH, SmartsHandle, ThHandle ],
ThNet USING [ pd ]
;
LarkInImpl:
CEDAR MONITOR
LOCKS info
USING info: LarkInfo
IMPORTS
Commander, IO, Log, Nice, Process, RPC, ThNet, Thrush, ThSmartsPrivate
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]]]];
Log.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.
Log.SLOG[];
SELECT sEvent.device
FROM
ringDetect =>
{
parseSmarts ← smartsInfo.otherSmarts;
parseInfo ← ThSmartsPrivate.GetSmartsInfo[smarts: parseSmarts]; };
tones =>
SELECT sEvent.event
FROM
'F=>
-- Feeping complete
{
parseSmarts ← smartsInfo.otherSmarts;
parseInfo ← ThSmartsPrivate.GetSmartsInfo[smarts: parseSmarts];
};
ENDCASE; -- Other tones finished
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;
};
ENDCASE;
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;
};
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 ]
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;
IF rawEvent.device = tones
THEN {
ThSmartsPrivate.TonesDone[info, rawEvent, NIL];
RETURN;
};
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 ←
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=> IF Log.ProblemBool[, $Lark, , info] THEN std ELSE std;
IF oldType#info.terminalType
THEN
ThSmartsPrivate.EnterLarkSt[info, info.larkState, NIL];
};
unRevCommands: Lark.CommandEvents ← NEW[Lark.CommandEventSequence[2]];
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;
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: unRevCommands];
LOOP;
};
EXIT;
ENDLOOP;
EXITS
Failed => onHook←FALSE;
};
EvType: TYPE = { tsOn, tsOff, spOn, spOff, spClick, spMon, spNone };
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
[ bOth, sPkr, sPkr, onhook, spkr, bOth, onhook ], -- sPkr
[ bOth, sPkr, sPkr, onhook, onhook, bOth, onhook ], -- spKr (sPkr, but click turns off.)
[ both, spkr, both, telset, telset, both, onhook ], -- both
[ bOth, sPkr, bOth, telset, both, bOth, onhook ], -- bOth
[ monitor, onhook, bOth, sPkr, monitor, monitor, onhook ] ]; -- monitor
ViewCmd: Commander.CommandProc =
TRUSTED {
Nice.View[pd, "Lark In PD"];
};
unRevCommands[0] ← [aRelay, enabled];
unRevCommands[1] ← [revertHookswitch, disabled];
Commander.Register["VuLarkIn", ViewCmd, "Program Management variables for Lark Input"];
}.