// Copyright © 1994-1995 Apple Computer, Inc. All rights reserved constant kAppSymbol := '|ACME Serial:PIEDTS|; // Asynchronous Connection Manager Example for Serial endpoints constant kAppName := "ACME Serial"; constant kMaxAppWidth := 240; // original MP width constant kMaxAppHeight := 336; // original MP height constant kPort_A := 'Main; constant kPort_B := 'SerialIR; constant kRetryMaxCount:= 0; // max number of times MConnect will try again to establish a connection constant kState_Disconnected := 0; // ready-to-go (default state) constant kState_Listen := 1; // preparation for (asynchronous) listen constant kState_Listening := 2; // in-process of (asynchronous) listen constant kState_Connect := 3; // preparation for (asynchronous) connect constant kState_Connecting := 4; // in-process of (asynchronous) connect constant kState_Connected := 5; // connected (requires disconnect) constant kState_Disconnecting := 6; // in-process of (asynchronous) disconnect constant kError_EndpointInUse := -666; constant kMessage_EndpointInUse := "Another application seems to be using the communications port."; constant kMessage_Disconnected := "Ready to connectÉ"; constant kMessage_Listening := "Waiting for connection..."; constant kMessage_Connecting := "Connecting..."; constant kMessage_Connected_A := "Connected to MAIN serial port, awaiting disconnect..."; constant kMessage_Connected_B := "Connected to INFRA-RED serial port, awaiting disconnect..."; constant kMessage_Disconnecting := "Disconnecting, please wait..."; constant kMessage_Retry := "No response... Will try again..."; constant kMessage_BufferOverrun := "A parity or bit-frame error has occured, or the communication data buffer was overrun and has been reset."; // the following constants (kDA_StatusFrame, kDA_ActionFunc, kDA_AddDeferredAction) // are used to implement "killable" deferred actions --> see MAddDeferredAction for more info // DefConst( 'kDA_StatusFrame, { fRunIt: true, fRanIt: nil, RanIt: func() fRanIt, KillIt: func() begin fRunIt := nil; fRanIt; end, } ); DefConst( 'kDA_ActionFunc, func(fn, args, status) begin status.fRanIt := true; if status.fRunIt then Apply(fn, args); end ); DefConst( 'kDA_AddDeferredAction, func(fn, args) begin local status := {_proto: kDA_StatusFrame}; AddDeferredAction(kDA_ActionFunc, [fn, args, status]); status; end ); // ---- End Project Data ---- // ---- File ACME Serial.t ---- // Before Script for "vMainApp" // Copyright © 1994-1995 Apple Computer, Inc. All rights reserved. vMainApp := { MAddDeferredAction: /* use this method like you would use AddDeferredAction the difference is that this routine returns a frame containing closures which can be used to "cancel" the deferred action before it executes and to query whether or not the deferred action has executed Example: local x := :MAddDeferredAction( func() GetRoot():SysBeep(), [] ); if x:RanIt() then print("already executed"); else print("waiting to run"); if x:KillIt() then print("already executed"); else print("canceled"); obviously you'll keep x around in a slot somewhere if you ever want to cancel the deferred action... */ kDA_AddDeferredAction // (fn, args), viewSetupDoneScript: func() begin AddPowerOffHandler(SELF); :MSetEndPointState(fEndPointState); // do NOT change the endpoint state -- just update any views that depend on it if fEndPointState = kState_Disconnected then :MMessage(kMessage_Disconnected); else :MMessage(""); end, MMessage: func(message) // this routine can be called regardless of the value of SELF begin local appBaseView := GetRoot().(kAppSymbol); if call kViewIsOpenFunc with (appBaseView) then begin SetValue(appBaseView.vMessage, 'text, Clone(message)); RefreshViews(); end end, viewFormat: 83951953, viewQuitScript: func() begin fEndPoint.fQuitting := true; :MDisconnect(); RemovePowerOffHandler(SELF); end, MConnect: func() begin if fEndPointState <> kState_Disconnected then return; if fEndPointPort = kPort_A then fEndPoint.configOptions := fEndPointConfig; else if fEndPointPort = kPort_B then begin fEndPoint.configOptions := Clone(fEndPointConfig); AddArraySlot(fEndPoint.configOptions, { label: kCMOSerialHardware, type: 'option, opCode: opSetRequired, data: call kGetSCCSideBFunc with () }); // funky voodoo magic end; else return; :MSetEndPointState(kState_Connect); fEndPoint.fPort := fEndPointPort; fEndPoint.fRetryCount := kRetryMaxCount; fEndPoint.fRetryWhat := 'MConnect; fEndPoint.fFlushLevel := 0; fEndPoint.fDeferredObj := :MAddDeferredAction(MConnectAction, [fEndPoint]); end, MDisconnectCompProc: func() // this routine is called from the MDisconnectAction deferred action begin // SELF will be the endpoint frame if fDisconnectSlip then begin fDisconnectSlip:Close(); fDisconnectSlip := NIL; end; fLastError := 0; :MMessage(kMessage_Disconnected); :MSetEndPointState(kState_Disconnected); if fQuitting and (self = fEndPoint) then fEndPoint := nil; else if fRetryWhat and fRetryCount > 0 then begin local retryCount := fRetryCount; Perform(SELF, fRetryWhat, []); fRetryCount := retryCount - 1; end; end, fEndPointOptions: // this options slot is set immediately after a successful Connect // see MConnectCompProc for usage NIL, MDisconnectAction: func(ep, state) // this routine is called as a deferred action from MDisconnect begin if state = kState_Connected then ep:Disconnect(); if ep.fPort = kPort_B then call kEnableIRModuleFunc with (nil); // funky voodoo magic ep:Dispose(); ep:MDisconnectCompProc(); // set up SELF to be the endpoint frame end, fEndPointConfig: [ { label: kCMSAsyncSerial, type: 'service, opCode: opSetRequired }, { label: kCMOSerialIOParms, type: 'option, opCode: opSetNegotiate, data: { bps: k9600bps, parity: kNoParity, dataBits: k8DataBits, stopBits: k1StopBits, } }, { label: kCMOInputFlowControlParms, type: 'option, opCode: opSetNegotiate, data: { useHardFlowControl: nil, useSoftFlowControl: nil, xonChar: unicodeDC1, xoffChar: unicodeDC3, } }, { label: kCMOOutputFlowControlParms, type: 'option, opCode: opSetNegotiate, data: { useHardFlowControl: nil, useSoftFlowControl: nil, xonChar: unicodeDC1, xoffChar: unicodeDC3, } }, ], viewBounds: {left: 0, top: 2, right: 236, bottom: 326}, MExceptionHandler: func(exceptionFrame) // SELF is the endpoint frame when this routine is called begin if exceptionFrame.data exists and exceptionFrame.data <> -16005 // exception generated by ep:Abort() while connected -- really not an error and exceptionFrame.data <> -16013 then begin // exception generated by ep:Abort() during connect -- really not an error if exceptionFrame.data <> fLastError then begin // this prevents a flood of identical exceptions from beating us to death fLastError := exceptionFrame.data; if exceptionFrame.data = -18003 then begin // I/O buffer overrun AddDeferredAction(func(ep) ep:MResetConnection(), [self]); :MNotifyError(exceptionFrame.data); end; else begin // handle all other (unexptected) exceptions by disconnecting the endpoint AddDeferredAction(func(ep) ep:MDisconnect(), [self]); :MNotifyError(exceptionFrame.data); end; end; end; TRUE; end, _proto: protoApp, MNotifyError: func(error) begin if error = kError_EndpointInUse then :MNotify(kMessage_EndpointInUse); else if error = -18003 then :MNotify(kMessage_BufferOverrun); else :MNotify("An unexpected error has occured. Error code =" && NumberStr(error)); end, fEndPointPort: kPort_A, MResetConnection: func() begin fEndPoint.nextInputSpec := NIL; fEndPoint:SetInputSpec(NIL); fEndPoint:Abort(); AddDelayedAction( func(ep) begin ep:SetInputSpec(ep.fInputHandler); end, [fEndPoint], 2500); end, title: "", powerOffScript: func(what) begin if what = 'okToPowerOff and fEndPointState = kState_Disconnected then TRUE else NIL; end, fEndPointState: kState_Disconnected, MDisconnect: func() // this routine may or may not be called from a deferred action begin // SELF will be some frame which eventually inherits the base app frame if not fEndPoint then return; if fEndPoint.fFlushLevel <> 0 then return AddDelayedAction(func(base) base:MDisconnect(), [self], 500); if fEndPoint.fDeferredObj and not fEndPoint.fDeferredObj:KillIt() then :MSetEndPointState(kState_Disconnected); if fEndPointState <> kState_Connected and fEndPointState <> kState_Connecting and fEndPointState <> kState_Listening then return; local currentState := fEndPointState; :MSetEndPointState(kState_Disconnecting); :MMessage(kMessage_Disconnecting); fEndPoint.fDisconnectSlip := BuildContext(pt_protoDisconnectSlip); if fEndPoint.fDisconnectSlip then fEndPoint.fDisconnectSlip:Open(); fEndPoint.fRetryCount := 0; fEndPoint.nextInputSpec := NIL; fEndPoint:SetInputSpec(NIL); fEndPoint:Abort(); AddDelayedAction(MDisconnectAction, [fEndPoint, currentState], 2500); end, fEndPoint: NIL, fEndPointInputSpec: { inputForm: 'string, endCharacter: unicodeCR, discardAfter: 256, inputScript: func(ep, data) begin ep:MInput(data); // be sure to set up SELF as the endpoint frame for MInput() end, }, viewSetupFormScript: func() begin // make view no bigger than the original MP local b := GetAppParams() ; viewBounds := RelBounds( b.appAreaLeft, b.appAreaTop, MIN( b.appAreaWidth, kMaxAppWidth ), MIN( b.appAreaHeight, kMaxAppHeight )); SELF.fEndPoint := { _proto: protoEndPoint, _parent: SELF, exceptionHandler: MExceptionHandler, fInputHandler: fEndPointInputSpec, configOptions: fEndPointConfig, fConnectOptions: fEndPointOptions, fConnectAddress: DeepClone(fEndPointAddress), fDisconnectSlip: NIL, fLastError: 0, fRetryCount: 0, fRetryWhat: NIL, fPort: kPort_A, fDeferredObj: NIL, fQuitting: NIL, fFlushLevel: 0, // see :MOutput() for more info... }; end, MListen: func() begin :MConnect(); // simple serial connections don't implement the listen routine, so just re-route it to the connect routine end, MOutput: func(data) // SELF can be any frame that inherits to the base app view begin if fEndPointState <> kState_Connected then begin :MNotify("Not connected."); return; end; fEndPoint:Output(data & unicodeCR, NIL); fEndPoint.fFlushLevel := fEndPoint.fFlushLevel + 1; // set this flag to indicate we're entering a kind of "nested event loop" fEndPoint:FlushOutput(); // an unfortunate side-effect of this is that inputSpecs may trigger (YIKES!) fEndPoint.fFlushLevel := fEndPoint.fFlushLevel - 1; // clear the flag end, MSetEndpointState: func(newState) // this routine can be called regardless of the value of SELF begin local appBaseView := GetRoot().(kAppSymbol); // NOTE: We must be absolutely certain fEndPointState gets created/overridden in the app base view frame! appBaseView.fEndPointState := newState; if not call kViewIsOpenFunc with (appBaseView) then return; if appBaseView.fEndPointState = kState_Disconnected then begin appBaseView.vConnect:Show(); appBaseView.vListen:Show(); appBaseView:MSetButtons("Connect", "Listen"); end; else if appBaseView.fEndPointState = kState_Listen then begin appBaseView.vConnect:Hide(); appBaseView:MSetButtons("Listening", NIL); end; else if appBaseView.fEndPointState = kState_Listening then appBaseView:MSetButtons("Stop Listening", NIL); else if appBaseView.fEndPointState = kState_Connect then begin appBaseView.vListen:Hide(); appBaseView:MSetButtons("Connecting", NIL); end; else if appBaseView.fEndPointState = kState_Connecting then appBaseView:MSetButtons("Stop Connecting", NIL); else if appBaseView.fEndPointState = kState_Connected then appBaseView:MSetButtons("Disconnect", NIL); else if appBaseView.fEndPointState = kState_Disconnecting then appBaseView:MSetButtons("Disconnecting", NIL); else appBaseView:MSetButtons("I Am Confused", NIL); RefreshViews(); end, fEndPointAddress: NIL, MInput: func(data) // SELF is the endpoint frame begin if fFlushLevel = 0 then // see :MOutput() for more info... PlaySound(ROM_SimpleBeep); // inputSpec triggered normally else PlaySound(ROM_PlinkBeep); // inputSpec triggered as side-effect of a FlushOutput call :MMessage(data); // :SetInputSpec(fInputHandler); // it is only necessary to set the inputSpec again if the inputSpec has changed since the last trigger end, MConnectCompProc: func(err) // this routine is called from the MConnectAction deferred action begin // SELF is the endpoint frame if not err then begin fRetryCount := 0; if fConnectOptions then :SetOptions(fConnectOptions); :MSetEndPointState(kState_Connected); :MMessage(if fPort = kPort_A then kMessage_Connected_A else kMessage_Connected_B); :SetInputSpec(fInputHandler); end; else if err <> -10039 then begin // -10039 means :Abort() was called to kill an in-progress (asynchronous) :Connect() // so do NOT call :MDisconnect() when this error occurs(!) Just ignore it... local retryCount := fRetryCount; :MDisconnect(); if retryCount > 0 then begin fRetryCount := retryCount; GetRoot():SysBeep(); :MMessage(kMessage_Retry); end; else :MNotifyError(err); end; end, MConnectAction: func(ep) // this routine is called as a deferred action from MConnect begin local err := kError_EndpointInUse; try err := ep:Instantiate(ep, NIL) onexception |evt.ex.msg| do begin ep:MNotifyError(err); ep:MSetEndPointState(kState_Disconnected); return; end; ep:MSetEndPointState(kState_Connecting); ep:MMessage(kMessage_Connecting); if ep.fPort = kPort_B then call kEnableIRModuleFunc with (true); // funky voodoo magic err := ep:Connect(ep.fConnectAddress, NIL); // this blocks, but lets the MConnect thread continue execution ep:MConnectCompProc(err); // set up SELF to be the endpoint frame end, debug: "vMainApp", MNotify: func(message) begin GetRoot():Notify(kNotifyAlert, EnsureInternal(kAppName), EnsureInternal(message)); end, MSetButtons: func(connectText, listenText) begin SetValue(vConnect, 'text, connectText); if listenText then SetValue(vListen, 'text, listenText); else SetValue(vListen, 'text, connectText); end }; _view000 := /* child of vMainApp */ {text: "Messages & Received Data:", viewBounds: {left: 8, top: 16, right: 224, bottom: 32}, _proto: protoStaticText }; vMessage := /* child of vMainApp */ {text: "", viewBounds: {left: 9, top: 33, right: 223, bottom: 119}, viewJustify: 0, viewFormat: 337, viewFont: simpleFont18, _proto: protoStaticText, debug: "vMessage" }; // View vMessage is declared to vMainApp _view001 := /* child of vMainApp */ {text: "Message To Send:", viewBounds: {left: 8, top: 128, right: 168, bottom: 144}, _proto: protoStaticText }; vInputArea := /* child of vMainApp */ {viewFlags: 64001, viewFormat: 12625, viewlinespacing: 20, viewFont: 18434, viewBounds: {left: 9, top: 145, right: 167, bottom: 215}, text: "This is a test!", viewclass: 81, debug: "vInputArea" }; // View vInputArea is declared to vMainApp vConnect := /* child of vMainApp */ {text: "", buttonClickScript: func() begin if fEndPointState = kState_Disconnected then :MConnect(); else :MDisconnect(); end, viewBounds: {left: 122, top: 242, right: 222, bottom: 262}, _proto: protoTextButton, debug: "vConnect" }; // View vConnect is declared to vMainApp vListen := /* child of vMainApp */ { buttonClickScript: func() begin if fEndPointState = kState_Disconnected then :MListen(); else :MDisconnect(); end, text: "", viewBounds: {left: 122, top: 270, right: 222, bottom: 290}, _proto: protoTextButton, debug: "vListen" }; // View vListen is declared to vMainApp vSend := /* child of vMainApp */ {text: "Send", buttonClickScript: func() begin :MOutput(if vInputArea.text then vInputArea.text else ""); end, viewBounds: {left: 178, top: 146, right: 222, bottom: 166}, _proto: protoTextButton, debug: "vSend" }; // View vSend is declared to vMainApp _view002 := /* child of vMainApp */ {text: "Serial Port:", viewBounds: {left: 8, top: 240, right: 112, bottom: 256}, _proto: protoStaticText }; vPortCluster := /* child of vMainApp */ {viewBounds: {left: 24, top: 256, right: 112, bottom: 288}, clusterChanged: func() begin if fEndPointState <> kState_Disconnected and fEndPointState <> kState_Connected then begin :SetClusterValue(fEndPointPort); // can't switch ports just yet (currently in a transitional state) return; end; fEndPointPort := clusterValue; // we want to switch to this port fEndPoint:MDisconnect(); // won't hurt anything if we're not connected fEndPoint.fRetryCount := 1; // this automagically switches ports for us end, clusterValue: kPort_A, _proto: protoRadioCluster, debug: "vPortCluster" }; // View vPortCluster is declared to vMainApp vPortA := /* child of vPortCluster */ {buttonValue: kPort_A, viewBounds: {left: 0, top: 0, right: 80, bottom: 16}, text: "Main", _proto: protoRadioButton, debug: "vPortA" }; vPortB := /* child of vPortCluster */ {buttonValue: kPort_B, viewBounds: {left: 0, top: 16, right: 80, bottom: 32}, text: "Infra-Red", _proto: protoRadioButton, debug: "vPortB" }; // After Script for "vMainApp" thisView := vMainApp; thisView.title := kAppName; // ---- Beginning of non-used User Protos ---- // ---- File protoDisconnectSlip ---- // Before Script for "_userproto000" // Copyright © 1994-1995 Apple Computer, Inc. All rights reserved. _userproto000 := {viewBounds: {left: 0, top: 0, right: 108, bottom: 44}, viewJustify: 80, powerOffScript: func(what) begin NIL; end, viewSetupDoneScript: func() begin AddPowerOffHandler(SELF); end, viewQuitScript: func() begin RemovePowerOffHandler(SELF); end, _proto: protoFloater }; _view003 := /* child of _userproto000 */ {text: "Disconnecting...\nPlease Wait...", viewBounds: {left: 8, top: 8, right: 104, bottom: 40}, viewJustify: 2, _proto: protoStaticText }; // ---- End of non-used User Protos ---- // ---- Beginning of section for non used Layout files ---- // End of output