DIRECTORY
Atom USING [ DottedPair, DottedPairNode ],
IO,
Commander USING [ CommandProc, Register ],
Lark USING [ bStar, bThorp, ConnectionSpec, ConnectionSpecRec, CommandEvent, CommandEvents, CommandEventSequence, Device, disabled, EchoParameters, EchoParameterRecord, enabled, endNum, Event, KeyTable, Milliseconds, o3i1, o2i2, o1i1, Passel, SHHH, StatusEvent, Tone, ToneSpec, ToneSpecRec, VoiceBuffer ],
LarkOps USING [ LarkParameters, LarkParametersRec, ParamCode ],
LarkPlay USING [ ToneList, ToneSpec, ToneSpecRec ],
LarkRemoteControl USING [ BootLark ],
LarkOpsRpcControl,
LarkSmarts,
MBQueue USING [ QueueClientAction ],
Multicast USING [ HandleMulticast, StopHandlingMulticast ],
Nice,
Process USING [ Detach, EnableAborts, MsecToTicks, SetTimeout ],
Pup USING [ nullSocket ],
Rope USING [ Concat, Fetch, Length, ROPE ],
RPC USING [ CallFailed, SetMaxTransmissions ],
ThNet USING [ pd ],
ThParty USING [ PartyInfo ],
ThPartyPrivate USING [ SmartsData ],
Thrush USING[ NetAddress, ROPE, SHHH, SmartsID ],
ThSmarts USING [ noneScheduled ],
ThSmartsPrivate
USING [
ConvDesc, Deregister, FlashDone, LarkCall, LarkInfo, LarkState, LSwitches, LState, ProgressTones, SmartsInfo, SwitchState ],
TU USING [ RefAddr ],
VoiceUtils USING [ ProblemFR, Report, ReportFR ]
;
Declarations
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
LarkInfo: TYPE = ThSmartsPrivate.LarkInfo;
LarkState: TYPE = ThSmartsPrivate.LarkState;
SmartsData: TYPE = ThPartyPrivate.SmartsData;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
SmartsID: TYPE = Thrush.SmartsID;
ROPE: TYPE = Thrush.ROPE;
firstTone: LarkState = FIRST[ThSmartsPrivate.ProgressTones];
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;
TimeoutPolicy: TYPE = { never, always, unlessDebugging};
PD:
TYPE =
RECORD [
waitForTelco: CARDINAL ← 500, -- default values for initial pause, on time, and off time
telcoMinOn: CARDINAL ← 60, -- when generating touch-tones.
telcoMinOff: CARDINAL ← 60,
flashWaitTime: CARDINAL ← 1200,
idleWaitTime: CARDINAL ← 20000, -- time for supervisor to wait for more events.
keySynchTime: CARDINAL ← 3000, -- time for keys to be distributed, else give up
blinkWaitTime: CARDINAL ← 500, -- if LED is blinking, don't wait so long.
backDoorOH: BOOL←FALSE,
toLarkTimeoutOK: TimeoutPolicy ← unlessDebugging,
Whether to let RPC calls from server to Lark time out. The default is to do so iff the database indicates the Lark is not in operational mode.
informLarkOK: TimeoutPolicy ← unlessDebugging,
When asked to boot the Lark during Lark failure, determines the conditions under which the request is granted: always, never, or only unless debugging, as with timeouts.
toLarkTimeoutTransmissions: CARDINAL ← 4, -- How long to give a lark to respond
fromLarkTimeoutOK: TimeoutPolicy ← unlessDebugging,
Determines what to tell the Lark to do about server timeouts.
fromLarkTimeoutTransmissions: CARDINAL ← 5, -- how long to give the server.
larkEventTimer: CARDINAL ← 6000,
larkTimeoutEvents: TimeoutPolicy ← unlessDebugging,
tonesInvalid: BOOL←TRUE,
noisyFeeps: BOOL←FALSE -- TRUE if caller is to hear automatically-generated touchtones.
];
pd: REF PD ← NEW[PD←[]];
dialTone: LarkPlay.ToneSpec ← NIL;
busyTone: LarkPlay.ToneSpec ← NIL;
errorTone: LarkPlay.ToneSpec ← NIL;
ringbackTone: LarkPlay.ToneSpec ← NIL;
quenchSpec: Lark.ToneSpec ← NEW[Lark.ToneSpecRec ←[volume: 0, totalTime: 0, tones: LIST[[0,0,0,0]]]];
External Procedures
EnterLarkState:
PUBLIC
ENTRY
PROC[
info: LarkInfo, newState: LarkState, data: LIST OF REF ANY←NIL ] = {
ENABLE UNWIND=>NULL;
EnterLarkSt[info, newState, data];
};
EnterLarkSt:
PUBLIC
INTERNAL
PROC[
info: LarkInfo, newState: LarkState, data: LIST OF REF ANY ] = {
trans: LSTrans ← lsTrans[info.larkState][newState];
oldState: LarkState = info.larkState;
sw: BOOL←FALSE;
otherAction: REF←NIL;
connectionSpec: ThParty.PartyInfo;
toneSpec: LarkPlay.ToneSpec;
ropeSpec: Rope.ROPE;
keyTable: Lark.KeyTable;
feepSpecs: REF AFeepType←NIL;
keyTablesToDate: INT;
larkCall: ThSmartsPrivate.LarkCall ← NIL; -- See ThSmartsPrivate.LarkCall
IF info.failed THEN { FailInt[info, "Additional requests disregarded", FALSE]; RETURN; };
IF pd.tonesInvalid THEN SetTones[];
FOR dL:
LIST
OF
REF
ANYta, dL.rest
WHILE dL#
NIL
DO
WITH dL.first
SELECT
FROM
cS: ThParty.PartyInfo => connectionSpec ← cS;
tS: LarkPlay.ToneSpec => toneSpec ← tS;
kT: Lark.KeyTable => keyTable ← kT;
lC: ThSmartsPrivate.LarkCall => larkCall ← lC; -- see ThSmartsPrivate.LarkCall
dP: Atom.DottedPair =>
SELECT dP.key
FROM
$audioSource => {info.audioSource ← NARROW[dP.val]; sw←TRUE; };
$transmitOnly => {info.transmitOnly ← dP.val=$true;sw←TRUE; };
$textToSpeech => info.textToSpeech ← dP.val=$true;
$phoneNumber => ropeSpec ← NARROW[dP.val];
ENDCASE; -- unknown
ENDCASE; -- unknown
ENDLOOP;
IF connectionSpec #
NIL
AND info.forwardedCall
THEN
TRUSTED {
IF info.blinkProcess=NIL THEN Process.Detach[info.blinkProcess ← FORK BlinkLED[info]];
};
Relevant only for trunk calls. The hosts are different, so the local trunk is communicating to a remote Etherphone. See LarkSmartsSupImpl for more discussion of these connections. We don't handle conferences involving trunks, thus the numParties=2 test. Higher level code assures this. In this state, the LED flashes to let the user know why the switch hook and attempts to place calls don't work.
Each select statement combines cases to execute a subset of the required actions efficiently.
SELECT trans
FROM
nop => NULL; -- Certifiably nothing at all to do, unless there's data or some submode has changed.
set => { info.larkState←newState; RETURN; };
X => {info.audioSource ←
NIL; info.transmitOnly ←
FALSE;
FailInt[info, "Invalid LarkState Transition", TRUE]; RETURN;};
zap, zpu, zpn => { info.audioSource ← NIL; info.transmitOnly ← FALSE; };
ENDCASE;
IF tClrFwd[trans] = clrFwd THEN info.forwardedCall ← FALSE;
IF tSetFwd[trans] = setFwd
THEN {
If trunkTalking was requested, we must enter either trunkTalking or trunkForwarding, depending on whether the other end of the connection is on the same machine.
newState ← IF info.forwardedCall THEN trunkForwarding ELSE trunkTalking;
trans ← lsTrans[info.larkState][newState];
};
info.larkState←newState;
When leaving talking state but not going idle, must explicitly take down Ethernet connection.
Does this ever happen?
IF tDisconn[trans] = $disconnect
THEN {
Queue up request to eliminate connections.
IF ThNet.pd.debug THEN Deb[ info, "Rq disconnect" ];
QueueLarkAction[info, aDisconnect];
};
Select tone/tune specifications.
toneSpec ←
SELECT trans
FROM
dia, diu => IF toneSpec#NIL THEN toneSpec ELSE dialTone, -- to handset receiver
rbk, rbu => IF toneSpec#NIL THEN toneSpec ELSE ringbackTone,
bzy, bzu => busyTone,
err, eru => errorTone,
rng, rgu => toneSpec,
ENDCASE => NIL
;
SELECT tDoTones[trans]
FROM
doTones => {
IF ThNet.pd.debug THEN Deb[ info, "Rq tones" ];
IF toneSpec#NIL THEN QueueLarkAction[info, toneSpec];
};
stopTones => { IF ThNet.pd.debug THEN Deb[ info, "Rq stop tones" ]; QueueLarkAction[info, aNoTones]; };
ENDCASE;
Go offhook and enter speakerphone mode if noises need to be heard and phone isn't offhook
Go back onhook when idling from spkr (not sPkr) or monitor mode.
SELECT tHookState[trans]
FROM
reset => {
SELECT info.switchState
FROM
$monitor, $speaker => info.switchState←$onhook; -- going totally idle
ENDCASE;
};
spkrTrans =>
IF info.switchState=$onhook
THEN
-- dialtone, direct connect or ringback
info.switchState ← $speaker;
ENDCASE; -- no state change cases.
IF info.switchState#info.lastSwitchState THEN sw←TRUE;
SELECT tSwitch[trans]
FROM
switch => sw←TRUE;
switchIfDiff => IF oldState<firstTone OR newState<firstTone THEN sw←TRUE;
ENDCASE;
SELECT trans
FROM
sgl, sgn => {
IF ThNet.pd.debug THEN Deb[info, "Rq feeping"];
feepSpecs ← RopeToDTMF[ropeSpec];
pd.noisyFeeps ← feepSpecs.audible AND ~info.forwardedCall;
};
ENDCASE;
IF sw
THEN
TRUSTED {
command: ROPE ← larkCommands[info.switchState][newState];
IF ThNet.pd.debug THEN Deb[info, "Rq switching", rope[command]];
otherAction ← QueueCommandSequence[info, command, @info.lState, info.scratchEv];
IF trans=fls
THEN {
Process.Detach[FORK FlashWait[info, oldState]];
info.larkState ← oldState; -- never really enter fls state, stay in forwarding or trunkTalking.
};
info.lastSwitchState ← info.switchState;
};
IF feepSpecs#
NIL
THEN QueueLarkAction[info, feepSpecs];
Must follow switching, but calculations must precede it.
IF keyTable#
NIL
THEN {
IF ThNet.pd.debug THEN Deb[info, "Rq key distribution"];
keyTablesToDate ← (info.keyTableDistrsRequested ← info.keyTableDistrsRequested+1);
QueueLarkAction[info, keyTable];
};
IF connectionSpec#
NIL
THEN
{
IF ThNet.pd.debug THEN Deb[info, "Rq connection"];
SELECT trans
FROM
sup, spn, tlk, frd, frn =>
QueueLarkAction[info, connectionSpec];
sgl, sgn, ksp =>
IF info.forwardedCall THEN QueueLarkAction[info, connectionSpec];
Otherwise we'd be transmitting stuff for no reason.
ENDCASE;
};
IF larkCall#
NIL
THEN
SELECT tLarkCall[trans]
FROM
call => QueueLarkAction[info, larkCall];
ENDCASE;
IF otherAction#
NIL
THEN {
IF ThNet.pd.debug THEN Deb[info, "Rq other action"];
QueueLarkAction[info, otherAction];
};
IF keyTable#
NIL
THEN
WHILE keyTablesToDate > info.keyTablesDistributed
DO
WAIT info.keySynch; -- Be sure keys have really been delivered
ENDLOOP;
};
TonesDone:
PUBLIC
ENTRY
PROC[info: LarkInfo, commandEvent: Lark.StatusEvent ] = {
Deb[info, "Tones done detected"];
SELECT commandEvent.event
FROM
'F=>
-- Feeping complete
{
IF info.larkState#trunkSignalling THEN RETURN;
EnterLarkSt[info, trunkTalking, NIL];
};
ENDCASE =>
-- other tones finished.
QueueLarkAction[info, NEW[ATonesDoneType ← [commandEvent.event]]];
};
FlashWait:
PUBLIC
ENTRY
PROC[info: LarkInfo, oldState: LarkState] =
TRUSTED {
ENABLE UNWIND => NULL;
flashWait: CONDITION;
command: ROPE;
IF info=NIL OR info.failed THEN RETURN;
Process.SetTimeout[@flashWait, Process.MsecToTicks[pd.flashWaitTime]]; -- 600 ms or so.
Deb[info, "Waiting for flash timeout"];
WAIT flashWait;
command ← larkCommands[info.switchState][info.larkState];
Deb[info, "Flash timeout complete, restoring switching", rope[command]];
IF info.larkState#oldState THEN RETURN; -- something has gone wrong
[]←QueueCommandSequence[info, command, @info.lState, info.scratchEv];
Revert to the previous switching condition
info.inputQueue.QueueClientAction[ThSmartsPrivate.FlashDone, info];
};
BlinkLED:
PUBLIC
ENTRY
PROC[info: LarkInfo] =
TRUSTED {
ENABLE UNWIND => NULL;
blinkWait: CONDITION;
IF info=NIL THEN RETURN;
Deb[info, "Rq Begin blinking"];
WHILE info.forwardedCall
AND info.larkState >= trunkSignalling
AND info.larkState <= trunkFlashing
AND info.larkProcess#
NIL
AND ~info.failed
DO
[]←QueueLarkAction[info, aToggleLED];
Process.SetTimeout[@blinkWait, Process.MsecToTicks[pd.blinkWaitTime]]; -- 500 ms or so.
WAIT blinkWait;
ENDLOOP;
Deb[info, "Rq End blinking"];
info.blinkProcess ← NIL;
};
QueueFeeps:
PUBLIC
PROC[sInfo: SmartsInfo, feeps: Rope.
ROPE ] = {
EnterLarkState[sInfo.larkInfo, trunkSignalling,
LIST[NEW[Atom.DottedPairNode←[$phoneNumber, feeps]]]];
};
prServerWDT: LarkOps.ParamCode=LOOPHOLE[10]; -- Both of these should go into LarkOps.
prServerReps: LarkOps.ParamCode=LOOPHOLE[11];
larkRepsParam: LarkOps.LarkParameters ←
NEW[LarkOps.LarkParametersRec[1]];
SetupTimeouts:
PUBLIC
ENTRY
PROC[info: LarkInfo, debugging:
BOOL ] = {
larkParams: LarkOps.LarkParameters ← NEW[LarkOps.LarkParametersRec[5]];
Server to Lark
RPC.SetMaxTransmissions[
conversation: info.shh,
maxTransmissions: pd.toLarkTimeoutTransmissions,
timeoutEnable:
SELECT pd.toLarkTimeoutOK
FROM
always => always, never => never, unlessDebugging =>
IF debugging THEN never ELSE always, ENDCASE=>dontCare];
Lark to Server
larkParams[0] ← [prDefaultMaxTransmissions, pd.fromLarkTimeoutTransmissions];
larkParams[1] ← [prSignalTimeout,
LOOPHOLE[SELECT pd.fromLarkTimeoutOK
FROM
always => TRUE, never => FALSE, unlessDebugging => ~debugging, ENDCASE=>ERROR]];
larkParams[2] ← [prEventTimer, pd.larkEventTimer];
larkParams[3] ← [prTimeoutEvents,
LOOPHOLE[SELECT pd.larkTimeoutEvents
FROM
always => TRUE, never => FALSE, unlessDebugging => ~debugging, ENDCASE=>ERROR]];
larkParams[4] ← [prServerWDT, 5000]; -- enabled by setting prServerReps, see below
larkParams.numParams ← 5;
[]←QueueLarkAction[info, larkParams];
larkRepsParam.numParams ← 1; -- preparation for later true settings, see below.
larkRepsParam[0] ← [prServerReps, 0]; -- preparation for later true settings, see below.
};
Internal Procedures
Fail:
PUBLIC
ENTRY
PROC[info: LarkInfo, reason:
ROPE, informLark:
BOOL←
FALSE] = {
ENABLE UNWIND => NULL;
FailInt[info, reason, informLark];
};
FailInt:
PUBLIC INTERNAL
PROC[info: LarkInfo, reason:
ROPE, informLark:
BOOL←
FALSE] = {
IF info=
NIL
OR info.failed
THEN {
VoiceUtils.ReportFR[Rope.Concat["%g Lark failed: ", reason], $Lark, info, TU.RefAddr[info.larkSmartsInfo]];
RETURN;
};
VoiceUtils.ProblemFR[Rope.Concat["%g Lark failed: ", reason], $Lark, info, TU.RefAddr[info.larkSmartsInfo]];
info.keyTablesDistributed ← info.keyTableDistrsRequested;
BROADCAST info.keySynch;
info.larkProcess ← NIL;
info.audioSource ← NIL;
info.transmitOnly ← FALSE;
info.failed ← TRUE;
NOTIFY info.stateChange; -- Be sure process notices failure and disappears.
IF informLark
AND (
SELECT pd.informLarkOK
FROM
never =>FALSE, always =>TRUE, unlessDebugging => ~info.debugging, ENDCASE=>ERROR)
THEN []←LarkRemoteControl.BootLark[info.netAddress]; -- best efforts only
Issue an input "failed" event.
info.inputQueue.QueueClientAction[ThSmartsPrivate.Deregister, info];
};
LarkSupervisor:
PROCEDURE[ info: LarkInfo ] = {
In a loop, keep the state of the Lark up to date. Awakens itself whenever any pending
tone is otherwise likely to time out. Awakened by EnterLarkState whenever the state
of tones, switches, and the like might have to change.
ENABLE UNWIND => NULL;
req: REF;
WaitForAction:
ENTRY
PROC[info: LarkInfo]
RETURNS [ref:
REF←
NIL] =
TRUSTED {
ENABLE UNWIND=>NULL;
DO
elt: LIST OF REF ← info.newActions;
IF info.failed THEN RETURN[NIL];
IF elt #
NIL
THEN {
ref𡤎lt.first;
info.newActions ← elt.rest;
RETURN[ref];
};
Process.SetTimeout[@info.stateChange, Process.MsecToTicks[pd.idleWaitTime]];
IF info.larkState#$idle THEN WAIT info.stateChange;
IF info.newActions =
NIL
THEN {
IF info.nextToneList#NIL THEN FailInt[info, "Tones done notification was late", TRUE]; -- Notification is late.
RETURN[NIL];
};
ENDLOOP;
};
IF info.larkToneSpec=
NIL
THEN
info.larkToneSpec ← NEW[Lark.ToneSpecRec ← [volume: 0, totalTime: 0, tones: NIL]];
IF info.cSpec=
NIL
THEN
info.cSpec ←
NEW[Lark.ConnectionSpecRec ← [
protocol: interactive,
encoding: muLaw,
sampleRate: 8000,
packetSize: 160,
buffer: in1,
keyIndex: IF ThNet.pd.encryptVoice THEN 1 ELSE 0,
localSocket: [[0],[0], Pup.nullSocket],
remoteSocket: [[0],[0], Pup.nullSocket]
]];
TRUSTED {
Process.EnableAborts[@info.stateChange];
Process.EnableAborts[@info.keySynch];
Process.SetTimeout[@info.stateChange, Process.MsecToTicks[pd.keySynchTime]];
};
When initializing the supervisor process, multicasting should already be off for this host. But be as careful as possible here: stop any that's detectably going on, and in any case, turn off multicast forwarding for this host.
IF ~StopMulticast[info].failed THEN -- If not, Fail has been called
WHILE (req←WaitForAction[info])#
NIL
DO
-- Deal with communications failure.
ENABLE {
RPC.CallFailed => { Fail[info, "Call Timed Out", TRUE]; GOTO Failed; };
ABORTED => { Fail[info, "LarkSupervisor aborted", TRUE]; GOTO Failed; };
};
DoTones:
PROC[newTones:
BOOL] = {
-- Does one tone from current list of tones
IF info.nextToneList=NIL THEN RETURN;
info.larkToneSpec.volume ← info.toneSpec.volume;
info.expectedNotification ←
IF info.expectedNotification='z THEN 'a ELSE info.expectedNotification+1;
info.larkToneSpec.notification ← [tones, info.expectedNotification];
info.larkToneSpec.tones ← info.nextToneList.first;
[]←info.interface.SpecifyTones[shh: info.shh, queueIt: ~newTones, tones: info.larkToneSpec];
};
WITH req
SELECT
FROM
d:
REF ADisconnectType =>
IF info.spec#
NIL
THEN {
buf: Lark.VoiceBuffer ← out1;
info.interface.Disconnect[ shh: info.shh, buffer: in1 ]; -- always stop in1 (tx1)
FOR i:
NAT
IN [1..info.spec.conversationInfo.numActive)
DO
Deb[info, "Do disconnect per request", card[i]];
info.interface.Disconnect[ shh: info.shh, buffer: buf ]; -- out1 (tx1) through outn (txn)
buf ← SUCC[buf];
ENDLOOP;
IF info.multicasting AND StopMulticast[info].failed THEN GOTO Failed;
info.spec ← NIL;
};
spec: ThParty.PartyInfo => {
cSpec: Lark.ConnectionSpec = info.cSpec;
numActive: NAT ← spec.conversationInfo.numActive;
conference: BOOL ← numActive>=3;
IF ~conference AND info.multicasting AND StopMulticast[info].failed THEN GOTO Failed;
IF info.spec#
NIL
THEN {
-- Duplicate of ADisconnectType stuff, for now
buf: Lark.VoiceBuffer ← out1;
Deb[info, "Do disconnect transmit to connect"];
info.interface.Disconnect[ shh: info.shh, buffer: in1 ]; -- always stop in1 (tx1)
FOR i:
NAT
IN [1..info.spec.conversationInfo.numActive)
DO
Deb[info, "Do disconnect receive prior to connect", card[i]];
info.interface.Disconnect[ shh: info.shh, buffer: buf ];
buf ← SUCC[buf];
ENDLOOP;
};
IF numActive>=2
THEN {
If conferencing, set up multicasting for this host, first.
info.spec ← spec;
IF conference THEN IF ([conference, ] ← StartMulticast[info, spec.conversationInfo.conferenceHost]).failed THEN GOTO Failed;
Transmit buffer
cSpec.buffer ← in1;
cSpec.remoteSocket ← spec[1].socket;
cSpec.remoteSocket.socket ← spec[0].socket.socket;
IF conference THEN cSpec.remoteSocket.host ← spec.conversationInfo.conferenceHost.host;
cSpec.localSocket ← cSpec.remoteSocket; -- superstition
Deb[info, "Connect transmit buf", card[cSpec.remoteSocket.host]];
info.interface.Connect[shh: info.shh, specs: cSpec ];
Receive buffers
cSpec.buffer ← out1;
FOR i:
NAT
IN [1..numActive)
DO
cSpec.localSocket ← spec[i].socket;
IF conference THEN cSpec.localSocket.host ← spec.conversationInfo.conferenceHost.host;
cSpec.remoteSocket ← cSpec.localSocket; -- superstition
Deb[info, "Connect receive buf", card[i], card[cSpec.localSocket.host]];
info.interface.Connect[shh: info.shh, specs: cSpec ];
cSpec.buffer ← SUCC[cSpec.buffer];
ENDLOOP;
};
};
keyTable: Lark.KeyTable => {
UKT:
ENTRY
PROC[info: LarkInfo] =
INLINE {
info.keyTable ← keyTable;
info.keyTablesDistributed ← info.keyTablesDistributed + 1;
BROADCAST info.keySynch;
};
info.interface.SetKeyTable[shh: info.shh, table: keyTable];
UKT[info];
};
ts: LarkPlay.ToneSpec => {
info.toneSpec←ts;
info.nextToneList ← info.toneSpec.tones;
DoTones[TRUE];
};
lC: ThSmartsPrivate.LarkCall => lC.proc[info, lC.clientData];
See ThsmartsPrivate.LarkCall
td:
REF ATonesDoneType =>
IF info.toneSpec#
NIL
THEN {
IF td.event#info.expectedNotification
THEN {
Fail[info, "Tones notifications out of order", TRUE]; GOTO Failed; };
IF info.nextToneList#
NIL
AND info.nextToneList.rest#
NIL
THEN
info.nextToneList ← info.nextToneList.rest
ELSE IF NOT info.toneSpec.repeatIndefinitely THEN { info.toneSpec←NIL; info.nextToneList←NIL }
ELSE info.nextToneList ← info.toneSpec.tones;
IF info.nextToneList#NIL THEN DoTones[FALSE];
};
a:
REF ANoTonesType => {
info.toneSpec←NIL;
info.nextToneList←NIL;
quenchSpec.tones.first.on ← 0;
[]←info.interface.SpecifyTones[shh: info.shh, tones: quenchSpec, queueIt: FALSE];
};
led:
REF AToggleLEDType => {
info.lState.lSw[led] ← IF info.lState.lSw[led] = disabled THEN enabled ELSE disabled;
ledToggler[0] ← [led, info.lState.lSw[led]];
[]←info.interface.Commands[info.shh, ledToggler];
};
resetAction:
REF ResetActionType => {
[]←info.interface.Reset[shh: info.shh, rName: "Don't revert"];
<<NIL => revert. Remember to change 2d parameter next time Lark.mesa changes.>>
IF info.multicasting AND StopMulticast[info].failed THEN GOTO Failed;
IF info.keyboardResetHandler#NIL THEN info.keyboardResetHandler[info];
};
larkParameters: LarkOps.LarkParameters =>
[]←info.interface.SetParameters[shh: info.shh, parameters: larkParameters];
keepAlive:
REF
INT => {
Just pings the Lark; will fail if the call times out.
IF keepAlive^=ThSmarts.noneScheduled OR keepAlive^=0 THEN LOOP;
IF keepAlive^#info.nextScheduledCheck
THEN {
info.nextScheduledCheck←keepAlive^;
larkRepsParam[0] ← [prServerReps, (info.nextScheduledCheck+4)/5];
prServerWDT is already 5000
[]←info.interface.SetParameters[shh: info.shh, parameters: larkRepsParam];
};
[] ← info.interface.WhatAreTones[shh: info.shh]; -- Actually do the ping.
};
echoParameters: Lark.EchoParameters =>
[]←info.interface.EchoSupression[shh: info.shh, echo: echoParameters];
commands: Lark.CommandEvents =>
IF commands#NIL THEN info.interface.Commands[ info.shh, commands ];
feepSpecs:
REF AFeepType => {
IF feepSpecs=NIL THEN LOOP;
IF (quenchSpec.tones.first.on ← feepSpecs.initialDelay)#0
THEN
[]←info.interface.SpecifyTones[shh: info.shh, tones: quenchSpec, queueIt: FALSE];
[]←info.interface.Feep[
shh: info.shh, on: feepSpecs.on, off: feepSpecs.off, notify: [tones, 'F],
waveTable: ThNet.pd.feepVolume, queueIt: TRUE, events: feepSpecs.digits];
}
ENDCASE;
REPEAT Failed => NULL;
ENDLOOP;
info.larkProcess ← NIL;
};
StartMulticast:
PROC[info: LarkInfo, hostAddress: Thrush.NetAddress]
RETURNS[ok: BOOL←FALSE, failed: BOOL←FALSE] = {
ENABLE {
RPC.CallFailed => { Fail[info, "Call Timed Out", TRUE]; GOTO Failed; };
ABORTED => { Fail[info, "LarkSupervisor aborted", TRUE]; GOTO Failed; };
};
IF info.multicasting AND StopMulticast[info].failed THEN RETURN[FALSE, TRUE];
IF info.spec=NIL OR info.spec.conversationInfo.numActive <=2 THEN RETURN[FALSE, FALSE];
ok ← Multicast.HandleMulticast[shh: info.shh, net: info.netAddress.net,
realHost: info.netAddress.host, listeningTo: hostAddress.host];
IF ~ok
THEN {
VoiceUtils.ProblemFR["Couldn't set multicasting [%g => %g]", $Lark, info,
int[info.netAddress.host], int[hostAddress.host]];
RETURN[ok, FALSE];
};
info.interface.SetHostNumber[shh: info.shh, host: [hostAddress.net, hostAddress.host]];
info.multicasting ← TRUE;
EXITS
Failed => { ok ← FALSE; failed ← TRUE; };
};
StopMulticast:
PROC[info: LarkInfo, force:
BOOL←
FALSE]
RETURNS[failed: BOOL←FALSE] = {
ENABLE {
RPC.CallFailed => { Fail[info, "Call Timed Out", TRUE]; GOTO Failed; };
ABORTED => { Fail[info, "LarkSupervisor aborted", TRUE]; GOTO Failed; };
};
IF ~info.multicasting THEN RETURN[FALSE];
info.interface.SetHostNumber[shh: info.shh, host: [info.netAddress.net, info.netAddress.host]];
Multicast.StopHandlingMulticast[shh: info.shh, realHost: info.netAddress.host];
info.multicasting ← FALSE;
EXITS
Failed => failed ← TRUE;
};
Queue is a FIFO list of REFs, with a lastAction pointer to aid in rapid enqueuing.
QueueLarkAction:
PUBLIC INTERNAL
PROC[info: LarkInfo, ref:
REF] = {
elt: LIST OF REF = LIST[ref];
lst: LIST OF REF = info.lastAction;
IF info.failed THEN FailInt[info, "Additional output requests disregarded", FALSE];
IF info.newActions=
NIL
THEN info.newActions ← elt
ELSE
{
IF lst=NIL THEN ERROR;
WITH ref
SELECT
FROM
x: REF BOOL =>
IF info.newActions#
NIL
THEN
RETURN;
ENDCASE;
Ref BOOL's are just keep-alive polls. Don't add them to a non-empty queue.
lst.rest ← elt;
};
info.lastAction ← elt;
IF info.larkProcess=
NIL
THEN
TRUSTED {
Process.Detach[info.larkProcess ← FORK LarkSupervisor[ info ]]; };
NOTIFY info.stateChange;
};
QueueCommandSequence:
INTERNAL
PROC[
info: LarkInfo, commands: ROPE,
lState: LONG POINTER TO LState, scratchEv: Lark.CommandEvents]
RETURNS[otherAction: REF←NIL]= TRUSTED {
eventIndex: INTEGER←-1;
c: CHAR;
i: NAT;
index: INTEGER;
event: Lark.Event;
events: Lark.CommandEvents ← scratchEv;
nextState: LState ← [];
len: NAT;
IF commands=NIL THEN RETURN; -- status quo
len ← commands.Length[];
See Hardware Switching Tables below for interpretations of these command sequences.
FOR i
IN [0..len)
DO
SELECT (c𡤌ommands.Fetch[i]) FROM 'J, 'j => IF ~pd.backDoorOH THEN LOOP; ENDCASE;
SELECT c
FROM
'\040 => LOOP; -- ignore spaces
'Z => { lState^← []; otherAction ← resetAction; }; -- Zap; reset hardware to full idle
'X, 'x => {
-- Crossbar switch specification, possibly parameterized by next character
param: CHAR ← commands.Fetch[i+1];
outputs: PACKED ARRAY [0..8) OF BOOLEAN;
row: NAT;
wasParam, doIt: BOOL←TRUE;
SELECT param
FROM
IN ['0..'9] => wasParam ← FALSE; -- no parameter, just do it.
Parameters determine whether this crossbar specification should be obeyed depending on outside influences, like "line(A/B) mode" or global indications of whether automatically-generated touch-tones should be heard by the caller.
'A => IF info.audioSource#$linea THEN doIt←FALSE;
'B => IF info.audioSource#$lineb THEN doIt←FALSE;
'r => IF info.audioSource=$linea OR info.audioSource=$lineb THEN doIt←FALSE;
't => IF info.transmitOnly THEN doIt←FALSE; -- inhibit all receive switching.
'F => IF ~pd.noisyFeeps THEN doIt←FALSE; -- caller hears auto-feeping
ENDCASE => doIt←FALSE; -- unrecognized parameter, don't do it;
IF wasParam THEN { i←i+1; param ← commands.Fetch[i+1]; };
row ← Digit[param];
outputs ← LOOPHOLE[nextState.xbar[row]];
IF doIt THEN outputs[Digit[commands.Fetch[i+2]]] ← (c='X);
nextState.xbar[row] ← LOOPHOLE[outputs];
i←i+2;
LOOP;
};
'E, 'e => {
nextState.echoStyle ← commands.Fetch[i←i+1]-'0;
otherAction ← echosOn[nextState.echoStyle];
};
'M, 'm => {
nextState.voiceMode ←
SELECT commands.Fetch[i←i+1]
FROM
'0 => Lark.o3i1,
'1 => Lark.o2i2,
'2 => Lark.o1i1,
ENDCASE => ERROR;
};
ENDCASE=> {
IF c
IN ['a..'z]
THEN { event ← Lark.disabled; c𡤌-('a-'A); }
ELSE event ← Lark.enabled;
IF lStateForLetter[c]#none THEN nextState.lSw[lStateForLetter[c]] ← event;
};
ENDLOOP;
IF nextState=lState^ THEN RETURN;
IF nextState.echoStyle#lState.echoStyle
AND otherAction=
NIL
THEN
otherAction ← echosOff[lState.echoStyle];
FOR iteration:
NAT
IN [0..1]
DO
-- 0: compute size; 1: fill in result sequence.
index←-1;
IF nextState.voiceMode#lState.voiceMode
THEN
events[index←index+1] ← [voiceMode, nextState.voiceMode];
FOR i: LSwitches
DECREASING IN LSwitches
DO
IF nextState.lSw[i]#lState.lSw[i]
THEN
events[index←index+1] ← [lDevs[i], nextState.lSw[i]]; ENDLOOP;
FOR i:
NAT
IN [0..8)
DO
IF nextState.xbar[i]#lState.xbar[i]
THEN {
outputs: PACKED ARRAY[0..8) OF BOOLEAN = LOOPHOLE[lState.xbar[i]];
nxtOutputs: PACKED ARRAY[0..8) OF BOOLEAN = LOOPHOLE[nextState.xbar[i]];
FOR j:
NAT
IN [0..8)
DO
IF outputs[j]#nxtOutputs[j]
THEN
events[index←index+1] ← [
Lark.Device[LOOPHOLE[IF nxtOutputs[j] THEN 23 ELSE 22]],
LOOPHOLE[i*16+j] ];
ENDLOOP;
}; ENDLOOP;
IF index=-1 THEN { events←NIL; EXIT; };
IF iteration=0 THEN events ← NEW[Lark.CommandEventSequence[index+1]]; ENDLOOP;
QueueLarkAction[info, events];
lState^ ← nextState;
lState.lSw[xBarAll] ← Lark.disabled;
};
Digit: PROC[c: CHAR] RETURNS [digit: NAT] = INLINE { RETURN[c-'0]; };
RopeToDTMF:
PROC [r: Thrush.
ROPE]
RETURNS [fs:
REF AFeepType] = {
len: INT ← MIN[Lark.Passel.LAST, r.Length[]];
ce: Lark.CommandEvents;
ceIndex: INT𡤀
number: INTEGER;
fs ← NEW[AFeepType←[]];
FOR rIndex:
INT
IN [0..len)
DO
c: CHAR = r.Fetch[rIndex];
SELECT c
FROM
IN ['0..'9], IN ['a..'d], '#, '* => ceIndex ← ceIndex+1;
'P => IF ceIndex#0 THEN ceIndex ← ceIndex+1 ELSE fs.initialDelay ← number*100;
'A => fs.audible ← TRUE;
'O => fs.on ← number*10;
'F => fs.off ← number*10;
IN ['\001..'\037] => number ← c-'\000;
ENDCASE; -- unrecognized, so ignore it.
ENDLOOP;
ce ← NEW[Lark.CommandEventSequence[ceIndex]];
fs.digits ← ce;
ceIndex ← 0;
FOR rIndex:
INT
IN [0..len)
DO
c: CHAR = r.Fetch[rIndex];
val: CHAR ← '\000;
SELECT c
FROM
IN ['0..'9] => val ← LOOPHOLE[128 + c - '0];
IN ['a..'d] => val ← LOOPHOLE[138 + c - 'a];
'* => val ← Lark.bStar;
'# => val ← Lark.bThorp;
IN ['\001..'\037] => number ← c-'\000;
'P => IF ceIndex#0 THEN val ← LOOPHOLE[number*10];
ENDCASE;
IF val = '\000 THEN LOOP;
ce[ceIndex] ← [touchPad, val];
ceIndex ← ceIndex+1;
ENDLOOP;
};
SetTones:
INTERNAL PROC[] = {
IF ~pd.tonesInvalid THEN RETURN;
pd.tonesInvalid ← FALSE;
dialTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume, tones:
LIST[LIST[
[f1: 350, f2: 440, on: 5000, off: 0],
[f1: 350, f2: 440, on: 5000, off: 0]]]]];
busyTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume, tones:
LIST[LIST[
[f1: 480, f2: 620, on: 500, off: 500, repetitions: 5],
[f1: 480, f2: 620, on: 500, off: 500, repetitions: 5]]]]];
errorTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume, tones:
LIST[LIST[
[f1: 480, f2: 620, on: 250, off: 250, repetitions: 10],
[f1: 480, f2: 620, on: 250, off: 250, repetitions: 10]]]]];
ringbackTone ←
NEW[LarkPlay.ToneSpecRec ← [repeatIndefinitely:
TRUE,
volume: ThNet.pd.tonesVolume,
tones:
LIST[LIST[
[f1: 440, f2: 480, on: 2000, off: 4000],
[f1: 440, f2: 480, on: 2000, off: 4000]]]]];
};
Deb:
PUBLIC
PROC[info: LarkInfo, r:
ROPE, p1, p2, p3, p4:
IO.Value←[null[]]] = {
s: IO.STREAM;
IF ~ThNet.pd.debug THEN RETURN;
s←IO.ROS[];
s.PutF["<%g", rope[r]];
IF p1#[null[]] THEN s.PutF["-- %g", p1];
IF p2#[null[]] THEN s.PutF[", %g", p2];
IF p3#[null[]] THEN s.PutF[", %g", p3];
IF p4#[null[]] THEN s.PutF[", %g", p4];
s.PutRope[">\r"];
VoiceUtils.Report[s.RopeFromROS[], $LarkDetailed, info];
};
QuickState:
ARRAY LarkState
OF
IO.Value = [
rope["none"], rope["idle"], rope["talking"], rope["trunkSignalling"],
rope["trunkTalking"], rope["trunkForwarding"], rope["trunkFlashing"], rope["ringing"], rope["silence"],
rope["dialTone"], rope["ringBack"], rope["busyTone"], rope["errorTone"]];
Hardware Switching Tables
Interpretation of the command string characters:
En-- echo mode; n IN [0..3); selects echoStyleStd (default), ...FD, ...BD, or ...Fwd
F -- T/R lead reversion
G -- A/A1 hookswitch control reversion
H -- Assert Telewall hookswitch
I -- Assert A/A1 to Telewall
J -- Assert Telewall hookswitch, maybe (depends on boolean variable)
L -- Lights led
l -- Flashes led if trunk is forwarding a call.
R -- overrides volume control for ringing through speaker
S -- enables telset sidetone
T -- spMode; configures codecs for electronic trunk action
Xij -- connects crossbar input i to output j
Xmij -- connects crossbar input i to output j, conditionally
Conditions:
A -- only in lineA mode (input comes from line 1 in instead of telset or microphone)
B -- only in lineB mode (input comes from line 2 in instead of telset or microphone)
r -- only in non-radio (standard, non-lineA/B) mode
t -- inhibit in transmit-only mode (server stuff)
F -- only if feeping (touch-tone signalling) is to be audible.
Mn -- voiceMode; n IN [0..2); selects program O3I1 (default), O2I2, or O1I1
Z -- Resets hardware and crossbar
Radio mode: input comes from line 1 in instead of telset or microphone
Crossbar Connections:
Port Input Output
0 dec1 co1
1 Xmtr Rcvr
2 from Telewall to Telewall
3 mike speaker
4 silence DTMF receiver
5 dec2 co2
6 line 1 in line 1 out
7 line 2 in line 2 out
CommandsForState: TYPE = REF CommandsForStateArray;
CommandsForStateArray: TYPE = ARRAY LarkState OF ROPE;
larkCommands: ARRAY ThSmartsPrivate.SwitchState OF CommandsForState ← [
telsetCommands, -- good as any for "onhook" condition
telsetCommands, -- telset
speakerCommands, -- speaker
speakerCommands, -- sPEAKER
monitorCommands, -- monitor
monitorCommands -- mONITOR
];
telsetCommands: CommandsForState ←
NEW[CommandsForStateArray ←
Telset
[
NIL,
-- none
"Z", -- idle
"S Xt01 Xt06 Xr10 XA60 XB70 X14", -- talking
"HI X02 X06 X14 XF01", -- trunkSignalling
"HIST X02 Xr10 XA60 XB70 Xt21 Xt26 X14", -- trunkTalking, codec-assisted electronic mode
"HIE3 X02 X20 X24", -- trunkForwarding, trunk to remote Lark connection.
"ST X02 Xr10 XA60 XB70 Xt21 Xt26 X14",
-- trunkFlashing, on-hook but otherwise trunkTalking.
"Z", -- trunkFlashing, on-hook for a second.
"R X03 X06 X14", -- ringing
"S X14", -- silence
toneStdCommand, toneStdCommand, toneStdCommand, toneStdCommand -- tones
]];
toneStdCommand: ROPE = "S X01 X14 X06";
monitorCommands: CommandsForState ←
NEW[CommandsForStateArray ←
Monitoring Telset
[
NIL,
-- none
"Z", -- idle
"LS Xt01 Xt06 Xt03 Xr10 XA60 XB70 X14", -- talking
"HIL X02 X06 X03 X14 XF01", -- trunkSignalling
"HILST X02 Xr10 XA60 XB70 Xt21 Xt26 Xt23 X14", -- trunkTalking, codec-assisted electronic mode
"HILE3 X02 X20 X24", -- trunkForwarding, trunk to remote Lark connection.
"LST X02 Xr10 XA60 XB70 Xt21 Xt26 Xt23 X14",
--trunkFlashing, on-hook but otherwise trunkTalking.
"Z", -- trunkFlashing, on-hook for a second.
"R X03 X06 X14", -- ringing
"LS X14", -- silence
toneMonCommand, toneMonCommand, toneMonCommand, toneMonCommand -- tones
]];
toneMonCommand: ROPE = "LS X01 X03 X14 X06";
speakerCommands: CommandsForState ←
NEW[CommandsForStateArray ←
Speakerphone
[
NIL,
-- none
"Z", -- idle
"E1L Xt03 Xt06 Xr30 XA60 XB70 X14", -- talking
"HIL X02 X06 XF03", -- trunkSignalling
"HIE2M1TL Xr30 X02 Xt25 Xt26 X24 Xt53 XA60 XB70", -- trunkTalking, gain-controlled digital mode
"HILE3 X02 X20 X24", -- trunkForwarding, trunk to remote Lark connection. (need gain setting for echo?)
"E2M1TL Xr30 X02 Xt25 Xt26 X24 Xt53 XA60 XB70", --trunkFlashing, on-hook but otherwise trunkTalking.
"Z", -- trunkFlashing, on-hook for a second.
"R X03 X06", -- ringing
"L X14", -- silence
toneSpkrCommand, toneSpkrCommand, toneSpkrCommand, toneSpkrCommand -- tones
]];
toneSpkrCommand: ROPE = "L X03 X14 X06";
larkCommands:
ARRAY ThSmartsPrivate.SwitchState
OF CommandsForState ← [
telsetCommands, -- good as any for "onhook" condition
telsetCommands, -- telset
speakerCommands, -- speaker
speakerCommands, -- sPEAKER
monitorCommands, -- monitor
monitorCommands -- mONITOR
];
echoStyleFD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Front Door call using Speakerphone
buffer: out1,
buffer2Controlled: FALSE,
buffer1Controlled: TRUE,
decayTime: 5,
gain: [ 1024, 2048, 2048, 2048, 32767 ]
]];
echoStyleFwd: REF ← echoStyleFD;
echoStyleBD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Back Door call using Speakerphone
buffer: in2,
buffer2Controlled: FALSE,
buffer1Controlled: TRUE,
decayTime: 10,
gain: [ 2048, 4096, 8192, 16384, 32767 ]
]];
echoStyleNoFD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Standard FD or BD handset mode, no forwarding
buffer: out1, -- not interesting
buffer2Controlled: FALSE,
buffer1Controlled: TRUE, -- FALSE, compensates for a Lark bug; restore when fixed
decayTime: 5,
gain: [ 32767, 32767, 32767, 32767, 32767 ]
]];
echoStyleNoFwd: REF ← echoStyleNoFD;
echoStyleNoBD:
REF ←
NEW[Lark.EchoParameterRecord ←[
Standard FD or BD handset mode, no forwarding
buffer: in2, -- not interesting
buffer2Controlled: FALSE,
buffer1Controlled: TRUE, -- FALSE, compensates for a Lark bug; restore when fixed
decayTime: 5,
gain: [ 32767, 32767, 32767, 32767, 32767 ]
]];
echosOn: ARRAY [0..3] OF REF←[ NIL, echoStyleFD, echoStyleBD, echoStyleFwd ];
echosOff: ARRAY [0..3] OF REF←[ NIL, echoStyleNoFD, echoStyleNoBD, echoStyleNoFwd ];
State tables
LSwitches: TYPE = ThSmartsPrivate.LSwitches;
LState: TYPE = ThSmartsPrivate.LState;
lDevs:
ARRAY LSwitches
OF Lark.Device = [
crossBar, offHookRelay, aRelay, sideTone, ringEnable, revertRelay, revertHookswitch, led, spMode, crossBar--random...not used-- ];
lStateForLetter:
ARRAY
CHAR['A..'Z]
OF LSwitches = [
none, none, none, none, none, -- A to E
revert, revertHook, hook, aSwitch, hook, none, led, -- F to L
none, none, none, none, none, -- M to Q
ringO, sideTone, spMode, none, none, none, none, none, -- R to Y
none -- Z --
];
LSTrans:
TYPE = {
nop, -- nothing to do
set, -- enter specified state (usu. step to recovery) without taking any other actions.
zap, zpu, zpn, -- reset Lark hardware (u means unconnect first, n means silence tones first)
trk, tkn, -- Set for electronic phone connection (n means silence tones first)
frd, frn, -- Trunk-to-network forwarding versions of trk, tkn (frn probably doesn't exist; wrong end <<Sep. FD/BD>>)
tlk, -- like supervision, but must also adjust switching.
sup, spn, -- supervision, OK to change connection, key table. (n means silence tones first)
ksp, -- key supervision, OK to change key table.
sgl, sgn, -- Do trunk signalling (n means silence tones first)
fls, -- Flash the phone line
rng, rgu, -- Set for ringing (u means unconnect first, r means repeating tone)
dia, diu, -- Set for dial tone (tones should be more generic and user-programmable than this!)
rbk, rbu, -- Set for ring back
bzy, bzu, -- Set for busy tone
err, eru, -- Set for error tone
sil, -- silence tones, ksp obtains
X -- invalid transition; complain, then remain in present state (go idle?)
};
lsTrans:
ARRAY LarkState
OF
ARRAY LarkState
OF LSTrans = [[
non idl tlk sig trk fwd fls rng shh dia rbk bzy err ←new old \/
nop, zap, X, X, X, X, X, X, X, X, X, X, err ],[--non (none)
X, nop, spn, sgl, trk, frd, X, rng, nop, dia, rbk, bzy, err ],[--idl (idle)
X, zpu, sup, X, X, X, X, rgu, nop, diu, rbu, bzu, eru ],[--tlk (talking)
X, zpn, X, ksp, trk, frd, X, X, nop, X, X, X, X ],[--sig (trunkSignling)
X, zap, X, sgl, ksp, X, fls,X, nop, X, X, X, X ],[--trk (trunkTalking)
X, zpu, X, sgl, X, sup, fls, X, nop, diu, rbu, bzu, eru ],[--fwd (trunkFwding)
X, zap, X, X, trk, X, sup,X, nop, X, X, X, X ],[--fls (trunkFlash)
X, zpn, spn, sgn, tkn, frn, X, ksp, sil, dia, rbk, bzy, err ],[--rng (ringing)
X, zap, tlk, sgl, trk, frd, X, rng, ksp, dia, rbk, bzy, err ],[--shh (silence)
X, zpn, spn, sgn, tkn, frn, X, rng, sil, ksp, rbk, bzy, err ],[--dia (dialTone)
X, zpn, spn, sgn, tkn, frn, X, rng, sil, dia, ksp, bzy, err ],[--rbk (ringBack)
X, zpn, spn, sgn, tkn, frn, X, rng, sil, dia, rbk, ksp, err ],[--bzy (busyTone)
X, zpn, spn, sgn, tkn, frn, X, rng, sil, dia, rbk, bzy, ksp ] --err (errTone)
];
Subtransition codes and tables
TDisconn: TYPE = { X, disconnect };
tDisconn: ARRAY LSTrans OF TDisconn = [ -- zpu, rgu, diu, rbu, bzu, eru
X, X, X, disconnect, X, X, X, X, X, X, X, X, X, X, X, X, X, disconnect, X, disconnect, X, disconnect, X, disconnect, X, disconnect, X, X ];
TDoTones: TYPE = { X, doTones, stopTones };
tDoTones: ARRAY LSTrans OF TDoTones = [ -- rng, rgu, dia, diu, rbk, rbu, bzy, bzu, err, eru; zpn, tkn, frn, spn, sgn, sil
X, X, X, X, stopTones, X, stopTones, X, stopTones, X, X, stopTones, X, X, stopTones, X, doTones, doTones, doTones, doTones, doTones, doTones, doTones, doTones, doTones, doTones, stopTones, X ];
THookState: TYPE = { X, reset, spkrTrans };
tHookState: ARRAY LSTrans OF THookState = [ -- zap, zpu, zpn; trk, tkn, tlk, spn, fls, dia, diu, rbk, rbu, bzy, bzu, err, eru, sil
X, X, reset, reset, reset, spkrTrans, spkrTrans, X, X, spkrTrans, X, spkrTrans, X, X, X, spkrTrans, X, X, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, spkrTrans, X ];
TSwitch: TYPE = { X, switch, switchIfDiff };
tSwitch: ARRAY LSTrans OF TSwitch = [
zap, zpu, zpn, trk, tkn, frd, frn, tlk, spn, sgl, sgn, fls; rng, rgu, dia, diu, rbk, rbu, bzy, bzu, err, eru
X, X, switch, switch, switch, switch, switch, switch, switch, switch, X, switch, X, switch, switch, switch, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switchIfDiff, switch, X ];
TSetFwd: TYPE = { X, setFwd };
tSetFwd: ARRAY LSTrans OF TSetFwd = [
trk, tkn
X, X, X, X, X, setFwd, setFwd, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X ];
TClrFwd: TYPE = { X, clrFwd };
tClrFwd: ARRAY LSTrans OF TClrFwd = [
all but nop, set, trk, tkn, frd, frn, sup, spn, ksp, sgl, sgn, fls
X, X, clrFwd, clrFwd, clrFwd, X, X, X, X, clrFwd, X, X, X, X, X, X, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd, clrFwd ];
TLarkCall: TYPE = { X, call };
tLarkCall:
ARRAY LSTrans
OF TLarkCall = [
nop, trk, tkn, frd, frn, tlk, sup, spn, ksp
call, X, X, X, X, call, call, call, call, call, call, call, call, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X ];