DIRECTORY Atom USING [ DottedPairNode ], BasicTime USING [ GMT, Now, Pack, Period, ToNSTime, Unpack, Unpacked, Update ], Convert USING [ RopeFromCard ], IO, Lark, LarkSmartsMonitorImpl, MBQueue USING [ QueueClientAction ], NamesGV USING [ GVSetAttribute ], RefID USING [ ID, Reseal ], Rope USING [ Concat, Equal, Fetch, FromChar, Length ], ThParty USING [ Alert, CreateConversation, GetPartyFromNumber, GetPartyFromFeepNum ], Thrush USING [ ConversationID, ConvEvent, IntervalSpec, PartyID, NB, nullConvID, nullID, ROPE, StateInConv ], ThSmartsPrivate USING [ AssessDamage, ChangeState, ConvDesc, DBInfo, Deregister, EnableSmarts, EnterLarkState, ForgetConv, GetConv, InterpretHookState, LarkFailed, LarkInfo, NoteNewState, OpenConversations, ParseState, SmartsInfo ], TU USING [ RefAddr ], VoiceUtils USING [ MakeAtom, Problem, ProblemFR ] ; LarkSmartsImpl: CEDAR MONITOR LOCKS root IMPORTS BasicTime, Convert, IO, MBQueue, root: LarkSmartsMonitorImpl, NamesGV, RefID, Rope, ThParty, ThSmartsPrivate, TU, VoiceUtils EXPORTS ThSmartsPrivate SHARES LarkSmartsMonitorImpl = { OPEN IO; CommandEvents: TYPE = Lark.CommandEvents; ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID=Thrush.nullConvID; ConvDesc: TYPE = ThSmartsPrivate.ConvDesc; disabled: Lark.Event = Lark.disabled; enabled: Lark.Event = Lark.enabled; endNum: Lark.Event = Lark.endNum; Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; }; IntervalSpec: TYPE = Thrush.IntervalSpec; LarkInfo: TYPE = ThSmartsPrivate.LarkInfo; NB: TYPE = Thrush.NB; nullID: RefID.ID = Thrush.nullID; OpenConversations: TYPE = ThSmartsPrivate.OpenConversations; PartyID: TYPE = Thrush.PartyID; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Lark.SHHH; -- Encrypts conv. if first arg to RPC PROC SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo; StateInConv: TYPE = Thrush.StateInConv; StatusEvents: TYPE = Lark.StatusEvents; EventSpec: TYPE = REF EventSpecBody; EventSpecBody: TYPE = RECORD [ smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent ]; LarkParseEvent: PUBLIC PROC[ smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent] = { smartsInfo.requests.QueueClientAction[ QdLarkParseEvent, NEW[EventSpecBody _ [smartsInfo, sEvent]]]; }; QdLarkParseEvent: ENTRY PROC[r: REF] = { -- r is the EventSpec eventSpec: EventSpec = NARROW[r]; smartsInfo: SmartsInfo = eventSpec.smartsInfo; { ENABLE { UNWIND=>NULL; ThSmartsPrivate.LarkFailed => { VoiceUtils.ProblemFR["%g: Lark Failed", $Smarts, smartsInfo, TU.RefAddr[smartsInfo]]; GOTO Failed; }; }; sEvent: Lark.StatusEvent _ eventSpec.sEvent; IF ~LarkEnabled[smartsInfo] THEN RETURN; -- don't interfere with reverted call! sEvent _ ThSmartsPrivate.InterpretHookState[smartsInfo.larkInfo, sEvent, smartsInfo]; SELECT sEvent.device FROM nothing => RETURN; speakerSwitch, touchPad, hookSwitch => NULL; ENDCASE => ERROR; DoParse[smartsInfo, sEvent.event]; -- Interpret this event in light of past ones IF smartsInfo.haveArguments THEN { -- now are ready to execute an action routine val: INT_0; IF smartsInfo.Command=NIL THEN smartsInfo.Command _ CmdCall; -- was a phone number SELECT smartsInfo.parseState FROM inNum => val _ IO.GetInt[IO.RIS[smartsInfo.arguments]! IO.Error => SELECT ec FROM Failure, SyntaxError, Overflow => CONTINUE; ENDCASE]; ENDCASE; smartsInfo.Command[smartsInfo, val]; -- Execute the action routine []_SetParserIdle[smartsInfo, TRUE, TRUE]; }; EXITS Failed => ThSmartsPrivate.Deregister[smartsInfo.smartsID]; }; }; LarkEnabled: INTERNAL PROC[info: SmartsInfo] RETURNS [enabled: BOOL] = INLINE { RETURN[SELECT info.larkInfo.larkState FROM none => ThSmartsPrivate.EnableSmarts[info], failed, recovering => FALSE, ENDCASE => TRUE]; }; DoParse: INTERNAL PROC[info: SmartsInfo, event: Lark.Event] = { IF info.haveOne=FALSE AND event#disabled AND GetSIC[info] = $reserved THEN [--nb--] _ ThSmartsPrivate.ChangeState[GetConvDesc[info], $parsing]; info.haveOne _ TRUE; SELECT event FROM disabled => { info.haveArguments _ TRUE; info.Command _ CmdOnhook; RETURN;}; enabled => { info.haveArguments _ TRUE; info.Command _ CmdOffhook; RETURN;}; endNum => { info.haveArguments _ TRUE; RETURN; }; -- '# IO.DEL, IO.ESC => { []_SetParserIdle[info, TRUE, TRUE]; RETURN; }; '* => { IF info.cmdOrRecip THEN info.offset_10; info.cmdOrRecip _ TRUE; info.parseState _ idle; RETURN; }; ENDCASE => NULL; -- continues below IF info.cmdOrRecip THEN { -- begins command or feepName recipient specification info.cmdOrRecip _ FALSE; event_event+info.offset; info.offset_0; IF event = '0 THEN { -- feepName recipient specification []_SetParserIdle[info, FALSE, FALSE]; info.parseState _ getFeep; RETURN; }; []_SetParserIdle[info, TRUE, FALSE]; SELECT event FROM '*, -- esc endNum -- del -- => []_SetParserIdle[info, TRUE, TRUE]; IN ['1..'9] => { []_SetParserIdle[info, FALSE, FALSE]; info.parseState _ getFeep; GOTO Continue; }; '0+10, 'T, 't => { info.Command _ CmdBackDoor; info.haveArguments _ TRUE; }; '1+10 => { info.Command _ CmdRingOff; info.haveArguments _ TRUE; }; '2+10 => { info.Command _ CmdRingOn; info.haveArguments _ TRUE; }; '3+10 => { info.Command _ CmdRingOffTimed; info.parseState _ getNum; }; '4+10 => { info.Command _ CmdRingOnce; info.haveArguments _ TRUE; }; '5+10 => { info.Command_CmdRingOnceTimed; info.parseState _ getNum; }; '7+10 => { info.Command_ CmdAudioSource; info.haveArguments _ TRUE; info.arguments _ "telset"; }; '8+10 => { info.Command_ CmdAudioSource; info.haveArguments _ TRUE; info.arguments _ "lineA"; }; '9+10 => { info.Command_ CmdAudioSource; info.haveArguments _ TRUE; info.arguments _ "lineB"; }; ENDCASE => -- unassigned same as DEL -- NULL; RETURN; EXITS Continue => NULL; }; info.arguments _ Rope.Concat[info.arguments, Rope.FromChar[event]]; info.parseState _ SELECT event FROM IN ['0..'9] => SELECT info.parseState FROM idle => inSeq, getFeep => inFeep, getNum => inNum, getStr => inStr, ENDCASE => info.parseState, ENDCASE => SELECT info.parseState FROM idle, getStr => inStr, getFeep, getNum, inFeep, inNum => inTossStr, inSeq => SetParserIdle[info, TRUE], ENDCASE => info.parseState; IF info.parseState = inSeq THEN { e1: Lark.Event = info.arguments.Fetch[0]; SELECT info.arguments.Length[] FROM 1 => SELECT event FROM '0 => info.haveArguments_TRUE; -- PARC Operator '4, '5, '6, '8, '9 => NULL; -- seq-type number continues ENDCASE => info.parseState _ inNum; 2 => IF info.arguments.Equal["90"] THEN info.parseState _ inNum; 3 => IF info.arguments.Equal["911"] THEN info.haveArguments_TRUE; 4 => SELECT e1 FROM '9 => IF info.arguments.Equal["9911"] THEN info.haveArguments _ TRUE; '4, '5, '6 => info.haveArguments _ TRUE; -- local extensions ENDCASE; 8 => IF info.arguments.Fetch[2] > '1 THEN info.haveArguments _ TRUE; 11 => info.haveArguments _ TRUE; -- DDD call, via ATT or Intelnet ENDCASE; -- keep on going }; }; SetParserIdle: INTERNAL PROC[info: SmartsInfo, clearCmd: BOOL, reallyIdle: BOOL_TRUE] RETURNS [ ThSmartsPrivate.ParseState ] = { info.haveArguments _ info.haveOne _ FALSE; info.parseState _ idle; info.arguments _ NIL; info.offset _ 0; info.cmdOrRecip _ FALSE; IF clearCmd THEN info.Command _ NIL; IF reallyIdle THEN SELECT GetSIC[info] FROM $parsing => [] _ ThSmartsPrivate.ChangeState[GetConvDesc[info], $reserved]; -- error? ENDCASE; RETURN[idle]; }; CmdCall: INTERNAL PROC[info: SmartsInfo, val: INT] = { cDesc: ConvDesc; { OPEN now: cDesc.situation.self; partyID: PartyID; otherPartyID: PartyID; nb: NB; IF info.arguments=NIL OR info.parseState=$inTossStr THEN RETURN; SELECT GetSIC[info] FROM $reserved, $parsing => NULL; ENDCASE => RETURN; cDesc _ GetConvDesc[info]; IF cDesc=NIL THEN RETURN; --  report error, enter error state? partyID _ now.partyID; [nb, otherPartyID] _ GetPartiesForCall[info, partyID, info.parseState, info.arguments]; SELECT nb FROM $success => NULL; $noSuchParty, -- Serious business; analyze this! $noSuchParty2 => { [--nb--] _ ThSmartsPrivate.ChangeState[cDesc, $failed, $notFound]; RETURN; }; $narcissism => { [--nb--] _ ThSmartsPrivate.ChangeState[cDesc, $failed, $notFound, "Attempt to call self rejected on philisophical grounds"]; RETURN; }; $voiceTerminalBusy => -- front/back door thing { [--nb--] _ ThSmartsPrivate.ChangeState[cDesc, $failed, $busy, "Called Etherphone's phone line is in use"]; RETURN; }; ENDCASE => GOTO Failed; IF (nb _ ThSmartsPrivate.ChangeState[cDesc, $initiating]) = $success THEN nb_ThParty.Alert[credentials: now, calledPartyID: otherPartyID]; IF nb # $success THEN GOTO Failed; -- enter error state? EXITS Failed => [--nb--] _ ThSmartsPrivate.ChangeState[cDesc, $failed, $notFound]; }; }; CmdBackDoor: INTERNAL PROC[info: SmartsInfo, val: INT] = { IF GetSIC[info]=$active AND info.larkInfo.larkState = $trunkTalking THEN { -- Hack ThSmartsPrivate.EnterLarkState[info.larkInfo, $trunkFlashing]; RETURN; }; info.arguments _ ""; -- non-nil, but no value either. info.parseState _ $inSeq; CmdCall[info, val]; }; CmdOnhook: INTERNAL PROC[info: SmartsInfo, val: INT] = { cDesc: ConvDesc; SELECT GetSIC[info] FROM $idle, $notified, $ringing, $neverWas => RETURN; -- could still be offhook from last call. ENDCASE => NULL; cDesc _ GetConvDesc[info]; [--nb--] _ ThSmartsPrivate.ChangeState[cDesc, $idle, $terminating]; }; CmdOffhook: INTERNAL PROC[info: SmartsInfo, val: INT] = { desiredState: StateInConv _ $active; nb: NB; cDesc: ConvDesc; SELECT GetSIC[info] FROM $idle => { convEvent: Thrush.ConvEvent; cDesc _ ThSmartsPrivate.GetConv[info, nullConvID, TRUE]; [nb, convEvent] _ ThParty.CreateConversation[ credentials: cDesc.situation.self, state: $reserved ]; IF nb # $success THEN { ThSmartsPrivate.AssessDamage[nb, cDesc, convEvent]; ThSmartsPrivate.ForgetConv[cDesc]; } ELSE ThSmartsPrivate.NoteNewState[cDesc, convEvent]; }; $ringing => { cDesc _ GetConvDesc[info]; IF cDesc=NIL THEN RETURN; -- Is this an error? [--nb--]_ThSmartsPrivate.ChangeState[cDesc, desiredState]; -- Complain on error? }; ENDCASE }; CmdRingOff: INTERNAL PROC[info: SmartsInfo, val: INT] = { DoRingSpec[info, "O", $timedringmode]}; CmdRingOn: INTERNAL PROC[info: SmartsInfo, val: INT] = { DoRingSpec[info,"R", $ringmode]}; CmdRingOffTimed: INTERNAL PROC[info: SmartsInfo, val: INT] = { DoRingSpec[info,"O",$timedringmode, val]}; CmdRingOnce: INTERNAL PROC[info: SmartsInfo, val: INT] = { DoRingSpec[info,"S", $ringmode]; }; CmdRingOnceTimed: INTERNAL PROC[info: SmartsInfo, val: INT] ={ DoRingSpec[info,"S",$timedringmode, val]}; RingSpec: TYPE = REF RingObj; RingObj: TYPE = RECORD [ info: SmartsInfo, ringMode: ROPE, ringModeAttribute: ATOM, timeInMinutes: INT ]; DoRingSpec: INTERNAL PROC[info: SmartsInfo, ringMode: ROPE, ringModeAttribute: ATOM, timeInMinutes: INT_1441-- default one day and one second--] = { IF timeInMinutes=0 THEN timeInMinutes _ 30; -- default one day and one second info.requests.QueueClientAction[QdRing, NEW[RingObj _ [info, ringMode, ringModeAttribute, timeInMinutes]]]; }; QdRing: ENTRY PROC[r: REF] = { ringSpec: RingSpec _ NARROW[r]; partyID: PartyID; rName: ROPE; info: SmartsInfo = ringSpec.info; cDesc: ConvDesc = ThSmartsPrivate.GetConv[info, info.currentConvID, FALSE]; now: BasicTime.GMT; unp: BasicTime.Unpacked; interval: INT _ ringSpec.timeInMinutes*60; IF cDesc=NIL THEN RETURN; -- should make error signal, but that's too hard. rName _ ThSmartsPrivate.DBInfo[partyID _ cDesc.situation.self.partyID].dbRname; IF rName=NIL THEN RETURN; -- should make error signals NamesGV.GVSetAttribute[rName, ringSpec.ringModeAttribute, ringSpec.ringMode]; IF ringSpec.ringModeAttribute # $timedringmode THEN RETURN; unp _ BasicTime.Unpack[now _ BasicTime.Now[]]; unp.hour _ 23; unp.minute _ 59; unp.second _ 59; -- midnight interval _ MIN[interval, BasicTime.Period[from: now, to: BasicTime.Pack[unp]]+1]; NamesGV.GVSetAttribute[rName, $expiration, Convert.RopeFromCard[BasicTime.ToNSTime[BasicTime.Update[now, interval]]]]; }; CmdAudioSource: INTERNAL PROC[info: SmartsInfo, val: INT] = { IF info.arguments.Length[]=0 THEN info.arguments_"telset"; IF info.larkInfo=NIL THEN RETURN; ThSmartsPrivate.EnterLarkState[info.larkInfo, info.larkInfo.larkState, LIST[NEW[Atom.DottedPairNode_[$audioSource, VoiceUtils.MakeAtom[info.arguments]]]]]; }; GetConvDesc: PUBLIC INTERNAL PROC[info: SmartsInfo] RETURNS [ cDesc: ConvDesc ] = { RETURN[IF info.currentConvID = nullConvID THEN NIL ELSE ThSmartsPrivate.GetConv[ info, info.currentConvID, FALSE ]]; }; GetSIC: PUBLIC INTERNAL PROC[info: SmartsInfo] RETURNS [ state: StateInConv ] = { cDesc: ConvDesc = GetConvDesc[info]; state _ IF cDesc=NIL THEN $idle ELSE cDesc.situation.self.state; }; GetPartiesForCall: PROC[ info: SmartsInfo, partyID: PartyID, parseState: ThSmartsPrivate.ParseState, args: ROPE] RETURNS[nb: NB_ $noSuchParty, calledPartyID: PartyID _ nullID ] = { IF partyID = nullID THEN RETURN; SELECT parseState FROM inSeq, inNum => [nb, calledPartyID] _ ThParty.GetPartyFromNumber[ partyID: partyID, phoneNumber: args]; inFeep => [nb, calledPartyID] _ ThParty.GetPartyFromFeepNum[partyID: partyID, feepNum: args]; ENDCASE => VoiceUtils.Problem["Don't know how to look for called party", $Smarts, info]; }; }. LarkSmartsImpl.mesa Last modified by D. Swinehart, May 1, 1986 1:54:17 pm PDT Last Edited by: Pier, May 3, 1984 3:00:22 pm PDT Polle Zellweger (PTZ) August 23, 1985 2:21:19 pm PDT Copies Parsing ParseEvent handles events from the EtherPhone, by queuing them to synchronize with other activities. Deal with ring-detect bounce, speaker switch clicks, and the like. The rest of this has to be done in the places where the situations arise. Be sure state of conv. and tones agree with state of parsing. <> cDesc _ GetConvDesc[smartsInfo]; IF cDesc#NIL THEN { OPEN now: cDesc.situation.self; SELECT now.state FROM # desired.state => RETURN; -- some request already in the works. reserved => IF smartsInfo.haveOne THEN desired.state_parsing ELSE RETURN; parsing => IF smartsInfo.haveOne THEN RETURN ELSE { desired.state_reserved; cDesc.desiredSituation.reason _ NIL; }; ENDCASE=>RETURN; ThSmartsPrivate.Apprise[smartsInfo]; }; In the name of unexcess generality, this procedure parses all of the commands: call placement, message handling, call management, and so on. Could change to call sequential registered procedures and like that, if more flexibility is needed. See Log notes, November 29, 1985 11:18:59 am PST On/Offhook Termination of arguments, DEL Command or recipient initiation Valid argument event Here are the commands Continue to collect event as first character of feep name! **0 A letter, digit, or other non-activation character. Sequence of digits whose length is determined by the values of the first character or two Require explicit termination of calls beginning with unrecognized prefixes Require explicit termination of international calls, calling card calls, calls to Telco Operator Local or Intelnet User-invoked actions User has specified feep name or number; System failure or unexpected case; analyze!!  See LarkSmartsSupImpl.AssessDamage for the kinds of errors that occur here. Nobody's catching them, so they have to be dealt with here. No idea what to do! Data base changes -- ringing specifications and so on. Queue it to release user interface Set timed or standard ring mode attribute If timed, set time to minimum of now+(ringSpec.time)*60 and midnight in minutes Connection Management Utilities inStr => complain  see below; Swinehart, May 22, 1985 12:14:29 pm PDT hotLine => autoAnswer changes to: DoParse, CmdHotline, } Polle Zellweger (PTZ) August 23, 1985 2:21:19 pm PDT Consistency checking on devices. changes to: LarkParseEvent, DIRECTORY Swinehart, October 28, 1985 9:51:23 am PST Handle => ID, Log => VoiceUtils, pull ProblemHandle, ... changes to: DIRECTORY, LarkSmartsImpl, ConversationID, Reseal, nullID, PartyID, LarkParseEvent, CmdCall, CmdOffhook, CmdRingDoProc, GetCDesc, GetPartiesForCall, ProblemID Swinehart, November 29, 1985 11:19:02 am PST When event is disabled (hanging up), don't transition into $parsing because there will soon be a transition into $idle changes to: DoParse Κo˜Jšœ™šœ9™9J™0Icode™4J™—šΟk œ˜ Jšœœ˜Jšœ œœ:˜OJšœœ˜Jšœ˜Jšœ˜J˜Jšœœ˜$Jšœœ˜!Jšœœœ ˜Jšœœ,˜6JšœœH˜Ušœœ˜Jšœ2œœ˜^—šœœ˜JšœΠ˜Π—Jšœœ ˜Jšœ œ!˜1J˜J˜—šœœœœ˜(š˜Jšœ ˜ J˜Jšœ˜J˜J˜Jšœ˜J˜J˜J˜J˜Jšœ˜J˜ —Jšœ˜Jšœ˜ Jšœœ˜J˜—šœ™J˜Jšœœ˜)šœœ˜-Jšœ-˜-—Jšœ œ˜*Jšœ%˜%Jšœ#˜#Jšœ!˜!JšΟnœœœœœœœ˜LJšœœ˜)Jšœ œ˜*Jšœœ œ˜J˜!Jšœœ%˜Jšœœ˜!Jšœ0˜0šœ˜Jšœœ˜ Jšœ]œœ ˜…J˜—J˜,JšœœœŸ&˜OšœU˜UJ™B—šœ˜Kšœ œ˜Kšœ'œ˜,Kšœœ˜—Jšœ#Ÿ-˜PšœœŸ-˜PJšœœ˜ JšœœœŸ˜Ršœ˜!šœœœœ˜6Jš œ œœ#œœ˜P—Jšœ˜—Jšœ%Ÿ˜BJšœ œ˜)Jšœ˜—J™IJšœ=™=J™UJ™"J™ šœœœ™Jšœ™šœ ™JšœœŸ%™@Jš œ œœœœ™Iš œ œœœœ™3Jšœ8œ™?—Jšœœ™—J™$J™—š˜Jšœ:˜:—Jšœ˜J˜—š ž œœœœ œœ˜Ošœœ˜*J˜+Jšœœ˜Jšœœ˜—J˜J˜—šžœœœ)˜?J™ρš œœœœ˜JJšœŸœ=˜DJ™0—Jšœœ˜šœ˜J™J™ Jšœ#œœ˜MJšœ"œœ˜MJ˜J™Jšœ!œœŸ˜9Jš œœœœ œœ˜BJ˜J™šœ˜Jšœœ˜'Jšœœ˜/Jšœ˜Jšœ˜—J˜J™JšœœŸ˜#—šœœŸ5˜OJšœœ˜J˜J˜šœ œŸ#˜8Jšœœœœ˜K—Jšœœœ˜$šœ˜J™JšœŸ˜ JšœŸ œœœ˜7J˜šœ&œœœ ˜cJ™:—šœDœ˜LJ™—Jšœ;œ˜CJšœ:œ˜BJšœG˜GJšœD˜DJšœF˜FJšœ>œ˜aJšœ>œ˜`Jšœ>œ˜`JšœŸœœ˜-—Jšœ˜Jšœ œ˜Jšœ˜J˜—J™3JšœC˜Cšœœ˜#šœ œ˜*J˜J˜J˜J˜Jšœ˜—šœœ˜&J˜J˜,Jšœœ˜#Jšœ˜——šœœ˜!JšœY™YJšœ)˜)šœ˜#šœœ ˜JšœœŸ˜/JšœœŸ˜8šœ˜#JšŸJ™J——šœœœ˜@Jšœ`™`—Jšœœœœ˜Ašœœ˜Jšœœœœ˜EJšœ#œŸ˜Jšœ*˜*—J˜šž œ œœ˜:Jšœ#˜#—J˜šžœ œœ˜>Jšœ*˜*—J˜Jšœ œœ ˜šœ œœ˜J˜Jšœ œ˜Jšœœ˜Jšœ˜J˜J˜—šž œœœœ˜;JšœœœŸ!œ˜XJ™"JšœœŸ!˜Mšœ'˜'Jšœ@˜C—J˜J˜—šžœœœœ˜Jšœœ˜J˜Jšœœ˜ J˜!JšœDœ˜KJšœœ˜J˜Jšœ œ˜*J˜Jš œœœœŸ3˜MJ˜OJš œœœœŸ˜8J™)J˜MJ™OJšœ-œœ˜;J˜Jšœ.˜.J˜J˜J˜Jšœ œC˜Q˜*J˜K—J˜—J˜šžœ œœ˜=Jšœœ˜:Jšœœœœ˜!˜GJšœœL˜T—J˜—J˜—™J˜š ž œœœœœ˜Sšœœ!œ˜2Jšœ4œ˜A—J˜J˜—šžœœœœ˜.Jšœ˜"J˜$Jš œœœœœ˜@Jšœ˜—J˜šžœœ˜JšœRœ˜WJšœœ5˜CJšœœœ˜ šœ ˜šœA˜AJšœ%˜%—˜ JšœS˜S—Jšœ Ÿ™JšœQ˜X—Jšœ˜—J˜—J˜™'K™Kšœ Οr™"—™4K™ Kšœ ‘™%—K™™*K™8Kšœ ‘ž™ͺ—K™™,K™vKšœ ‘™—K™—…—3N