LarkTrunkSmartsImpl.mesa
Smarts for the Lark "Back Door"
Last modified by D. Swinehart, August 6, 1985 3:44:50 pm PDT
Note: Incoming calls are those from the Etherphone to the outside party, whom this system represents. Outgoing calls are those from the outside world to an Etherphone. Remember; trunk party is a representative of people in the public telephone system.
DIRECTORY
Commander USING [ CommandProc, Register ],
IO,
Lark
-- USING [
-- CommandEvents, ConnectionSpec, Event, LarkModel, Passel, SHHH, StatusEvents ]--,
LarkSmartsMonitorImpl,
Nice,
Log USING [ Problem, ProblemFR, ProblemHandle, Report, ReportFR ],
Process USING [ SecondsToTicks, SetTimeout ],
Rope USING [ Concat ],
ThParty USING [ Advance, Alert, CreateParty ],
ThPartyPrivate
USING [
DehandleParty, DehandleSmarts, LocalSmartsInterface, RegisterLocal, SmartsBody, SmartsData ],
Thrush
USING [
ConversationHandle, ConvEvent, PartyHandle, H, NB, nullConvHandle, nullHandle, pERROR, Reason, ROPE, SmartsHandle, StateInConv, ThHandle ],
ThSmartsPrivate
USING [
Apprise, ConvDesc, Deregister, EnterLarkState, GetConv, GetConvDesc, GetSIC, GetSmartsInfo, LarkFailed, LarkProgress, OpenConversations, QueueFeeps, SmartsInfo, SmartsInfoBody ],
ThSmartsRpcControl,
Triples USING [Any, Make, Select ],
TU
;
LarkTrunkSmartsImpl:
CEDAR MONITOR
LOCKS root
IMPORTS
Commander,
root: LarkSmartsMonitorImpl,
IO,
Log,
Nice,
Process,
Rope,
Thrush,
ThParty,
ThPartyPrivate,
ThSmartsPrivate,
ThSmartsRpcControl,
Triples,
TU
EXPORTS ThSmartsPrivate --, ThSmarts via Interface record --
SHARES LarkSmartsMonitorImpl = { OPEN IO;
Types and Constants
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; };
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Lark.SHHH; -- Encrypts conv. if first arg to RPC PROC
PartyHandle: TYPE = Thrush.PartyHandle;
nullHandle: Thrush.ThHandle = Thrush.nullHandle;
nullConvHandle: Thrush.ConversationHandle = Thrush.nullConvHandle;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
StateInConv: TYPE = Thrush.StateInConv;
PD:
TYPE =
RECORD [
timeoutNoAction: INT,
cSp: REF Ctr←NEW[Ctr←[0,1000000]],
cRs: REF Ctr←NEW[Ctr←[0,1000000]],
cZp: REF Ctr←NEW[Ctr←[0,1000000]],
doReports: BOOL←FALSE,
doNice: BOOL←FALSE
];
pd: REF PD ← NEW[PD←[]];
Ctr:
TYPE =
RECORD[
count: INT,
stop: INT
];
Report:
PROC[what:
ROPE, info: SmartsInfo, ctr:
REF Ctr] = {
IF NOT pd.doReports THEN RETURN;
Log.Report[what, $Lark, info.larkInfo];
ctr.count𡤌tr.count+1;
IF ctr.count>ctr.stop THEN { Log.Problem["Report overflow", $Lark, info.larkInfo]; };
};
ReportFR:
PROC[what:
ROPE, info: SmartsInfo, ctr:
REF Ctr, a1, a2:
IO.Value←rope[
NIL]] = {
IF NOT pd.doReports THEN RETURN;
Log.ReportFR[what, $Lark, info.larkInfo, a1, a2];
ctr.count𡤌tr.count+1;
IF ctr.count>ctr.stop THEN { Log.Problem["Report overflow", $Lark, info.larkInfo]; };
};
BeNice:
PROC[r:
REF, d:
INT, info: SmartsInfo] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Lark, info.larkInfo];
};
Initialization
RegisterTrunk:
PUBLIC
ENTRY PROC[
hostPartyID: PartyHandle, hostSmarts: ThPartyPrivate.SmartsData, hostInfo: SmartsInfo ]
RETURNS [ smartsID: Thrush.SmartsHandle ] = { {
ENABLE UNWIND => NULL;
There's no provision here at all for reregistration. Yet.
localSmarts: ThPartyPrivate.LocalSmartsInterface;
info: SmartsInfo;
partyID: PartyHandle = ThParty.CreateParty[type: trunk];
hostParty: REF = ThPartyPrivate.DehandleParty[hostPartyID];
party: REF = ThPartyPrivate.DehandleParty[partyID];
smarts: ThPartyPrivate.SmartsData;
IF hostParty=
NIL
OR party =
NIL
THEN
RETURN[Log.ProblemHandle["TrunkSmarts didn't register", $Party, nullHandle, hostParty]];
TU.MakeUnique[$TrunkParty, hostParty, party];
localSmarts ← ThSmartsRpcControl.NewInterfaceRecord[];
localSmarts.clientStubProgress ← ThSmartsPrivate.LarkProgress;
smartsID ← ThPartyPrivate.RegisterLocal [
partyID: partyID,
interface: localSmarts,
properties: hostSmarts.properties
];
IF smartsID = nullHandle THEN GOTO NotSmart;
smarts ← ThPartyPrivate.DehandleSmarts[smartsID, TRUE];
info ←
NEW[ThSmartsPrivate.SmartsInfoBody ← [
smarts: smarts,
otherSmarts: hostSmarts,
ParseEvent: LarkTrunkParseEvent,
Supervise: TrunkSupervise,
larkInfo: hostInfo.larkInfo ]];
Triples.Make[$SmartsData, smarts, info];
EXITS
NotSmart => RETURN[nullHandle]; }; };
Events
RecordTrunkEvent handles events from the EtherPhone Trunk connection.
The only interesting events at present are "ring" (place trunk->station call) and "tones F" (station->trunk call being "answered")
The upstream code produces a single "enabled" event when the line starts ringing, and a single "disabled" event when it appears that ringing has stopped. This "disabled" event must be anticipated and ignored if the station answers (calling trunk can't hang up on completed call!)
LarkTrunkParseEvent:
ENTRY
PROC[
smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent] = {
ENABLE {
UNWIND=>NULL;
ThSmartsPrivate.LarkFailed => { Problem["Lark Failed", smartsInfo]; GOTO Failed; };
};
SELECT sEvent.device
FROM
ringDetect =>
SELECT sEvent.event
FROM
Lark.enabled => CmdCall[smartsInfo];
Lark.disabled => CmdOnhook[smartsInfo];
ENDCASE => ERROR Thrush.pERROR;
ENDCASE;
EXITS
Failed =>
ThSmartsPrivate.Deregister[ThSmartsPrivate.GetSmartsInfo[smarts: smartsInfo.otherSmarts]];
};
CmdCall:
INTERNAL
PROC[info: SmartsInfo] = {
calledPartyID: PartyHandle;
cDesc: ConvDesc;
SELECT ThSmartsPrivate.GetSIC[info]
FROM
idle => NULL;
ENDCASE => RETURN; -- not a valid time for this call. Ignore. Host system will handle.
cDesc ← ThSmartsPrivate.GetConv[info, nullConvHandle, TRUE];
calledPartyID ←
H[Triples.Select[$TrunkParty,
--owner--,
ThPartyPrivate.DehandleParty[cDesc.cState.credentials.partyID]]]; --<<UGH!>>
IF calledPartyID = nullHandle
THEN
RETURN;
Ignore the incoming call. It will be handled as unanswered by the host telephone system.
cDesc.desiredState ← active;
cDesc.desiredPartyID ← calledPartyID;
cDesc.desiredReason ← wontSay;
cDesc.desiredComment ← NIL;
cDesc.cState.state ← any; -- meaning "This is a new conversation to be made."
ThSmartsPrivate.Apprise[info];
};
CmdOnhook:
INTERNAL
PROC[info: SmartsInfo] = {
cDesc: ConvDesc=ThSmartsPrivate.GetConvDesc[info];
IF cDesc=NIL THEN RETURN;
SELECT cDesc.cState.state
FROM
initiating, maybe => NULL; -- ringing stopped before called party answered.
ENDCASE => RETURN; -- already active or something; ignore end of ringing
cDesc.desiredState←idle;
cDesc.desiredReason ← wontSay;
cDesc.desiredComment ← NIL;
ThSmartsPrivate.Apprise[info];
};
Supervision
TrunkSupervise:
ENTRY
PROC[info: SmartsInfo ] = {
TRUSTED {
Process.SetTimeout[@info.thAction, Process.SecondsToTicks[pd.timeoutNoAction]]; };
IF info.apprise
THEN
DO
ENABLE {
UNWIND => NULL;
ThSmartsPrivate.LarkFailed => { Problem["Lark Failed", info]; GOTO Failing; };
};
prevL: ThSmartsPrivate.OpenConversations ← NIL;
nb: Thrush.NB←success;
convL: ThSmartsPrivate.OpenConversations;
trans: TrkTransition;
info.apprise ← FALSE;
FOR convL ← info.conversations,
convL.rest WHILE convL#NIL DO
cDesc: ConvDesc = convL.first;
stateNow: StateInConv = cDesc.cState.state;
ours: BOOL ← ( cDesc.cState.credentials.convID = info.currentConvID );
IF pd.doReports
THEN Report[
Rope.Concat[
IO.PutFR["**** TkSup: %t(%d) %g %g->%g",
time[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID],
TU.RefAddr[info], refAny[NEW[StateInConv←stateNow]], refAny[NEW[StateInConvsc.desiredState]]],
IO.PutFR[" %g%g\n",
refAny[NEW[TrkTransition←trkTransForStates[stateNow][cDesc.desiredState]]],
rope[IF ours THEN " (ours)" ELSE ""]]],
info, pd.cSp];
BeNice[cDesc, 6, info];
IF NOT cDesc.descValid THEN LOOP;
IF info.currentConvID = nullConvHandle
AND stateNow#idle
THEN {
<<This stuff feels like it's in the wrong place. Will feel more so when multi-calls
are dealt with. >>
ours←TRUE; info.currentConvID ← cDesc.cState.credentials.convID;
};
IF stateNow#idle
AND (
NOT ours)
THEN
Conv isn't the one we're interested in, and doesn't look like it's going idle: idle it.
Should probably consult transForStates for validity.
nb ← ThParty.Advance[
credentials: cDesc.cState.credentials,
state: idle,
reason: busy,
comment: "One conversation at a time, please."
]
ELSE {
State and substate (below) and desired state indicate there's something to do. Do it:
SELECT (trans←trkTransForStates[stateNow][cDesc.desiredState])
FROM
noop => NULL;
elim => {
Conv. is now idle: forget about it.
Remove dead conversation from list.
IF pd.doReports
THEN Report[
IO.PutFR[" ** Zap: %t\n",
time[cDesc.cState.credentials.convID]], info, pd.cZp];
IF prevL#
NIL
THEN {
prevL.rest ← convL.rest; convL ← prevL; }
ELSE info.conversations𡤌onvL.rest;
IF ours
THEN {
info.currentConvID ← nullConvHandle;
IF info.larkInfo.larkState=trunkForwarding
THEN
ThSmartsPrivate.EnterLarkState[info.larkInfo, idle, info];
};
};
alrt => {
-- placing call
convID: Thrush.ConversationHandle;
info.phoneNumber ← NIL;
[nb, convID] ← ThParty.Alert [
credentials: cDesc.cState.credentials,
calledPartyID: cDesc.desiredPartyID,
state: initiating
];
IF nb=success THEN {
cDesc.cState.credentials.convID ← info.currentConvID ← convID;
ours←TRUE;
cDesc.cState.credentials.stateID ← 0;
};
};
idle, actv =>
-- Simple transitions
nb ← ThParty.Advance [
credentials: cDesc.cState.credentials,
state: cDesc.desiredState,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
spvs => {
IF cDesc.newEvent
THEN {
IF
NOT cDesc.signallingStarted
THEN {
cDesc.signallingStarted ← TRUE;
ThSmartsPrivate.EnterLarkState[info.larkInfo,
IF info.phoneNumber=NIL THEN trunkTalking ELSE trunkSignalling,
info, info.phoneNumber];
}
ELSE
IF cDesc.newProses#
NIL
AND cDesc.newProses.first.type=request
THEN
ThSmartsPrivate.QueueFeeps[info, cDesc.newProses];
};
cDesc.newIntervals ← NIL; -- Ignore interval Specs.
cDesc.newProses ← NIL; -- Ignore prose Specs.
};
pend => {
-- Handle an incoming (from Etherphone) call.
info.phoneNumber ← cDesc.cState.address;
cDesc.desiredStatetive;
info.apprise ← TRUE;
};
invl => {
Problem["LarkSmarts: Invalid state transition request.", info];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Invalid state transition";
};
ntiy => {
Problem["State transition not yet implemented.", info];
info.apprise←TRUE;
cDesc.desiredState ← idle;
cDesc.desiredReason ← error;
cDesc.desiredComment ← "Unimplemented state transition";
};
ENDCASE => Thrush.pERROR;
};
Analyze any results of trying to reach a different state.
IF nb#success
THEN
ReportFR[" ** LKResults: nb=%g\n", info, pd.cRs, refAny[NEW[Thrush.NB←nb]]];
SELECT nb
FROM
success, stateMismatch => NULL;
noSuchParty2, narcissism => {
-- Have to get to error tone here. No such party.
Problem["Called party not found, or calling self", info];
cDesc.cState.comment ← NIL;
cDesc.cState.reason ← notFound;
cDesc.desiredState←idle;
info.apprise←TRUE;
};
partyNotEnabled => {
-- See old version; vamp until enabled.
Problem["Party not yet enabled, ignore as much as possible.", info];
};
invalidTransition, convNotActive, convStillActive => {
Complain and transition to idle
comment: ROPE="Party-level detected invalid state transition request";
Problem[comment, info];
cDesc.desiredState ← idle;
cDesc.cState.comment ← comment;
cDesc.cState.reason ← error;
info.apprise←TRUE;
};
notInConv, noSuchConv => {
-- Complain, zap, go idle and repeat.
comment: ROPE="NotInConv or NoSuchConv";
Problem[comment, info];
IF ours THEN info.currentConvID ← nullConvHandle;
cDesc.cState.credentials.convID ← nullConvHandle;
cDesc.cState.credentials.stateID ← 0;
cDesc.desiredState ← idle;
cDesc.cState.comment ← comment;
cDesc.cState.reason ← error;
info.apprise←TRUE;
};
noSuchParty, noSuchSmarts => {
-- Complain, deregister, we gone! Needs tuning.
Problem[
"LarkTrunkSmarts: NoSuchParty or NoSuchSmarts reported, must try to go away", info];
GOTO Failing;
};
ENDCASE => Thrush.pERROR;
prevL ← convL;
ENDLOOP;
IF NOT info.apprise THEN WAIT info.thAction;
IF info.conversations = NIL THEN EXIT;
REPEAT Failing => ThSmartsPrivate.Deregister[info]; -- now Failed
ENDLOOP;
info.thProcess ← NIL;
};
Problem:
PROC[comment:
ROPE, info: SmartsInfo] = {
Log.ProblemFR[Rope.Concat["LarkKSmarts(%g): ", comment], $Smarts, info, TU.RefAddr[info]];
};
TrkTransition: TYPE = {
Just codes to dispatch on in Supervisor; explained there
noop, elim, idle, alrt, pend, actv, spvs, invl, ntiy
};
trkTransForStates:
ARRAY StateInConv
OF
ARRAY StateInConv
OF TrkTransition ← [
idle resrv pars init pend mayb ring canAc activ inact any -- desired
[ elim, invl, invl, invl, invl, invl, invl, invl, elim, ntiy, elim ], -- idle (current)
[ invl, invl, invl, invl, invl, invl, invl, invl, invl, invl, invl ], -- reserved
[ invl, invl, invl, invl, invl, invl, invl, invl, invl, invl, invl ], -- parsing
[ idle, invl, invl, noop, invl, invl, invl, invl, noop, ntiy, invl ], -- initiating
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, pend ], -- pending
[ idle, invl, invl, invl, invl, noop, invl, invl, noop, ntiy, invl ], -- maybe
[ invl, invl, invl, invl, invl, invl, invl, invl, invl, ntiy, invl ], -- ringing
[ ntiy, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, invl ], -- canActivate
[ idle, invl, invl, invl, invl, invl, invl, invl, spvs, ntiy, invl ], -- active
[ ntiy, invl, invl, invl, invl, invl, invl, invl, ntiy, noop, invl ], -- inactive
[ elim, invl, invl, invl, invl, invl, invl, invl, alrt, ntiy, invl ] -- any (nonex)
];
Debugging nonsense
ViewCmd: Commander.CommandProc =
TRUSTED {
Nice.View[pd, "Lark Trunk PD"];
};
Commander.Register["VuLarkTrunkSmarts", ViewCmd, "Program Management variables for Lark Trunk Smarts"];
}.
<< Comments left over from previous system. Think about it all sometime. was in section involving analysis of new pending state. >>
<<This is here to deal with the case where we saw ringing, launched a call, were rebuffed by the selected station, and thus went idle. Now we look available even though the phone is still ringing. Alternative to having a hook state for the back door is to enter the reserved state on rebuff until the disabled event comes in (caller gives up or is transferred.)>>
nb ← ChangeState[info, busy, "Sorry, trunk in use"]; RETURN; };