SerDemo.c

 
#include    <Types.h>
#include    <SysEqu.h>
#include    <Resources.h>
#include    <QuickDraw.h>
#include    <Fonts.h>
#include    <Events.h>
#include    <Menus.h>
#include    <Controls.h>
#include    <Dialogs.h>
#include    <Memory.h>
#include    <Files.h>
#include    <Devices.h>
#include    <Serial.h>
#include    <Timer.h>
#include    <OSUtils.h>
#include    <GestaltEqu.h>
#include    <Errors.h>
#include    <string.h>
 
 
 
#define     rMonitor        1000
#define     kSendButton     1
#define     kStopButton     2
#define     kQuitButton     3
#define     kMsgBox         4
#define     kSendSpinner    5
#define     kRcvSpinner     7
#define     kHoldFlag       9
#define     kBreakButton    10
 
#define     rSerDataRsrc    128
#define     rSpinnerIcon    1000
#define     rHeldIcon       128
#define     rNotHeldIcon    129
 
#define     rPortOpenALRT   256
#define     kReset          2
 
#define     kCtlEnable      0
#define     kCtlDisable     255
#define     kSerBufSize     16384
#define     kSerRdSize      64
#define     kSerConfig      baud9600 + noParity + data8 + stop10
#define     kBreakLength    666             /* serial break length in milliseconds */
 
enum        {drvrName = 0x12};              /* offset to driver name in 'DRVR' std. header */
enum        {dOpened = 5,
                dRAMBased = 6,
                drvrActive = 7};            /* Device Manager DCtlFlag bits */
                
enum        {kSerStatus = 8,
                kSerClrBrk = 11,            /* Serial Driver csCodes */
                kSerSetBrk = 12,
                kSerHShakeDTR = 14};
enum        {breakR0 = 128};                /* mask for break bit in SCC RR0 -- See TN #56 */
enum        {breakErr = 8};                 /* mask for break bit in cumErrs -- System 7.0 */
 
 
 
typedef struct {
    IOParam     fIOParam;
    long        appA5;
} ExtIOParam;
 
typedef struct {
    TMTask      fTMTask;
    long        appA5;
} ExtTMTask;
 
typedef struct {
    unsigned char   readR0;
    unsigned char   deltaBits;
    short           drvrPosting;
} SERDEventMessage;
 
 
 
Boolean     gHeldOff,
            gAllDone = false,
            gShouldSend = false,
            gReload = false,
            gKillBreak = false,
            gBreakReceived = false;
Ptr         gpSerBuf, *ghSerBuf,
            gpOutputData, *ghOutputData,
            gpBitBucket, *ghBitBucket;
long        gOutputDataSize;
IOParam     *gpSendPB, **ghSendPB;
short       gSysVersion,
            gOutRefNum, gInRefNum,
            gSendCount = 0,
            gRcvCount = 0;
char        *panicString = "Help! I'm stuffed! And here's a bunch of characters to prove it!\n\r";
ExtTMTask   gUnBreakTask;
 
 
 
short   Initialize (void);
void    CleanUp (void);
Boolean OpenSERD (void);
void    CloseSERD (void);
void    DoIOStuff (void);
OSErr   PrimeSend (void);
void    CheckSerStatus (void);
void    CheckSerData (long reqBytes);
pascal Boolean NullGrabber (DialogPtr, EventRecord *evt, short *itemHit);
pascal void SendCompRout (void);
pascal void FlagBreakTimeout (void);
OSErr   AssertDrvrOpen (Str255 name, short *refNum);
 
ParmBlkPtr GetParmBlkPtr (void) = 0x2008;       /* MOVE.L A0,D0 */
TMTaskPtr GetTMTaskPtr (void) = 0x2009;         /* MOVE.L A1,D0 */
 
#pragma parameter __D0 PBControlImmed(__A0)
pascal OSErr PBControlImmed(ParmBlkPtr paramBlock) = 0xA204;        /* _Control ,IMMED */
 
#pragma parameter __D0 PBStatusImmed(__A0)
pascal OSErr PBStatusImmed(ParmBlkPtr paramBlock) = 0xA205;         /* _Status ,IMMED */
 
 
 
void main (void)
{
    InitGraf(&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    InitDialogs(NULL);
    InitCursor();
    
    if (Initialize() == noErr) {
        
        if (OpenSERD()) {   /* open the Serial Driver */
            DoIOStuff();
            CloseSERD();    /* close the Serial Driver */
        }
        CleanUp();
    }
    
}
 
 
 
Boolean OpenSERD (void)
{
    OSErr   openOutErr, openInErr;
    OSErr   setBufErr, setCfgErr, setHskErr;
    SerShk  hskFlags;
    long    finalTicks;
    Boolean takeOverPort = true;
    Boolean openAOut, openAIn;
    
    openAOut = AssertDrvrOpen("\p.AOut", &gOutRefNum) == noErr;
    openAIn = AssertDrvrOpen("\p.AIn", &gInRefNum) == noErr;
    if (openAOut || openAIn) {
        if (takeOverPort = CautionAlert(rPortOpenALRT, nil) == kReset) {
            if (openAIn) {
                KillIO(gInRefNum);
                CloseDriver(gInRefNum);
            }
            if (openAOut) {
                KillIO(gOutRefNum);
                CloseDriver(gOutRefNum);
            }
        }
    }
    
    if (takeOverPort) {
        openOutErr = OpenDriver("\p.AOut", &gOutRefNum);
        openInErr = OpenDriver("\p.AIn", &gInRefNum);
        
        if (openOutErr == noErr && openInErr == noErr) {
        
            /* There is a bug in the Macintosh IIfx IOP serial driver, in which      */
            /* SerGetBuf() will always return zero characters, even if characters    */
            /* have been received. The bug is exhibited when a remote serial device  */
            /* attempts to send data to the Macintosh IIfx when its serial driver is */
            /* not yet open and it holding off data with hardware handshaking. In    */
            /* such a case, data will flow in immediately when the serial driver     */
            /* opens--quickly filling up the default 64-byte buffer. If the buffer   */
            /* fills up before setting a larger buffer with SerSetBuf(), SerGetBuf() */
            /* "sticks" and stubbornly maintains that there is nothing coming in.    */
            /* At 9600 baud, it takes only a little more than four ticks to fill the */
            /* input buffer. This application demonstrates the bug by virtue of the  */
            /* following delay.                                                      */
            
            Delay(5, &finalTicks);
            
            /* It's always good to first set a non-default input buffer, if desired. */
            /* There is no output buffering, so specify only the input driver.       */
            
            setBufErr = SerSetBuf(gInRefNum, gpSerBuf, kSerBufSize);
            
            hskFlags.fXOn = false;
            hskFlags.fCTS = true;
            hskFlags.xOn = 0x11;
            hskFlags.xOff = 0x13;
            hskFlags.errs = 0;
            
            if (gSysVersion >= 0x0700) {
                hskFlags.evts = 0;              /* I can use new means of break detection. */
            }
            else {
                hskFlags.evts = breakEvent;     /* I need the driver to post break events. */
            }
            
            hskFlags.fInX = false;
            hskFlags.fDTR = true;
            
            /* SerHShake() does not support full DTR/CTS hardware handshaking. You   */
            /* accomplish the same thing and more with a Control call and csCode 14. */
            /* You only need to specify hskFlags once, to the output driver.         */
            
            setHskErr = Control(gOutRefNum, kSerHShakeDTR, (Ptr) &hskFlags);
 
            /* Now reset both input and output drivers with the same configuration.  */
            /* Only a single call to the output driver is necessary to do this.      */
            /* Differing concurrent input/output baud rates are not supported.       */
            
            setCfgErr = SerReset(gOutRefNum, kSerConfig);
            
        }
    }
    
    return takeOverPort;
}
 
 
 
void CloseSERD (void)
{
    OSErr   killErr, closeOutErr, closeInErr;
 
    killErr = KillIO(gInRefNum);
    closeInErr = CloseDriver(gInRefNum);
 
    killErr = KillIO(gOutRefNum);
    closeOutErr = CloseDriver(gOutRefNum);
 
}
 
 
 
void CheckSerStatus (void)
{
    OSErr       checkStatErr, panicErr;
    IOParam     altSendPB, *pAltSendPB = &altSendPB;
    CntrlParam  statPB;
    SerStaRec   serStat;
    
    statPB.ioCRefNum = gInRefNum;
    statPB.csCode = kSerStatus;
    checkStatErr = PBStatusImmed((ParmBlkPtr) &statPB);
    serStat =  *(SerStaRec *) &statPB.csParam;
    
    /* I check to see if the remote system has told me to stop sending. */
    
    gHeldOff = (Boolean) serStat.ctsHold;
    
    /* Check for errors. */
    
    if (serStat.cumErrs & swOverrunErr)
        DebugStr("\pSoftware Overrun Error");
    else if (serStat.cumErrs & parityErr)
        DebugStr("\pParity Error");
    else if (serStat.cumErrs & hwOverrunErr)
        DebugStr("\pHardware Overrun Error");
    else if (serStat.cumErrs & framingErr)
        DebugStr("\pFraming Error");
    
    /* If I have System 7.0 or better, I can check directly to see if   */
    /* I've received a break. Usually I don't check for a feature this  */
    /* way, but in this case I have no alternative.                     */
    
    if (gSysVersion >= 0x0700) {
        gBreakReceived = (serStat.cumErrs & breakErr) != 0;
    }
    
    /* All I do here is send a small "panic" packet of characters back  */
    /* to the remote system when it fills _my_ buffer. I don't actually */
    /* know the exact state of my buffer, but I can see if I've told    */
    /* the remote system to shut up, indicating that I'm mostly full.   */
    
    if (checkStatErr == noErr) {
        if (( (unsigned char) serStat.xOffSent & dtrNegated) != 0) {
            pAltSendPB->ioCompletion = NULL;
            pAltSendPB->ioRefNum = gOutRefNum;
            pAltSendPB->ioBuffer = panicString;
            pAltSendPB->ioReqCount = strlen(panicString);
            PBWriteAsync((ParmBlkPtr) pAltSendPB);
            
            /* The program may hang here if the user quits the    */
            /* remote application first--that could hold off our  */
            /* serial output, leaving a pending asynchronous I/O  */
            /* request and keeping us in an infinite loop.        */
            
            while (pAltSendPB->ioResult > 0) {}             /* I'll fix it later. */
            /* The reason I do this instead of just calling it synchronously */
            /* is so that if I do hang, I'll hang in my code for an obvious  */
            /* reason instead of hanging up in the Device Manager.           */
            panicErr = pAltSendPB->ioResult;
        }
    }
}
            
 
 
void CheckSerData (long reqBytes)
{
    OSErr   checkBufErr, serRdErr;
    long    charCount;
    
    // long finalTicks;
    // register long overrun;
    
    checkBufErr = SerGetBuf(gInRefNum, &charCount);
    if (checkBufErr == noErr) {
    
        /* The general strategy here is this: if number of available characters */
        /* meets a certain minimum threshold, then I read in everything in the  */
        /* buffer. If I get delayed, I'll catch up quickly.                     */
 
        if (charCount != 0 && charCount >= reqBytes) {
            /*
            overrun = charCount;
            Control(gOutRefNum, 18, nil);
            Delay(300, &finalTicks);
            Control(gOutRefNum, 17, nil);
            SerGetBuf(gInRefNum, &charCount);
            overrun = charCount - overrun;
            DebugStr("\pSender Halted");
            */
            serRdErr = FSRead(gInRefNum, &charCount, gpBitBucket);
            if (serRdErr == noErr) {
                gRcvCount++;            /* increment a counter for the input spinner */
            }
        }
    }
}
 
 
 
void DoIOStuff (void)
{
    DialogPtr       serMonitor;
    OSErr           primeErr;
    short           itemHit, itemType;
    ControlHandle   sendItem, stopItem, breakItem;
    Handle          spinner, flag, item;
    Rect            box;
    CntrlParam      breakPB;
    
    serMonitor = GetNewDialog(rMonitor, NULL, NULL);
    SetPort((GrafPtr) serMonitor);
            
    while (!gAllDone) {
        
            CheckSerStatus();
        
        /* Holding down the mouse button prevents checking the input buffer     */
        /* and forces the input buffer to fill up. This allows experimentation. */
            
            if (!Button()) {
                CheckSerData(kSerRdSize);
            }
    
            if (gShouldSend && gReload) {
                gSendCount++;               /* increment a counter for the output spinner */
                gReload = !gReload;
            }
            
        /* The break timer simply sets a global flag which I use to indicate when  */
        /* to clear a break condition. Again, I use an immediate Control call, but */
        /* primarily for consistency, and also to show off.                        */
            
            if (gKillBreak) {
                breakPB.ioCRefNum = gOutRefNum;
                breakPB.csCode = kSerClrBrk;
                PBControlImmed((ParmBlkPtr) &breakPB);      /* SerClrBrk(), but IMMED */
                gKillBreak = !gKillBreak;
                GetDItem(serMonitor, kBreakButton, &itemType, &(Handle) breakItem, &box);
                HiliteControl(breakItem, kCtlEnable);
            }
        
        /* If another area of the program detects a break, I flag the occurrence here. */
        
            if (gBreakReceived) {
                SysBeep(1);
                SysBeep(1);
                gBreakReceived = !gBreakReceived;
            }
        
        /* Here I update all the buttons and icons. I probably spend too much time    */
        /* doing this when it isn't necessary, but that's not the point.              */
        
        /* I animate the beach balls by changing the resource IDs of the ICONs in the */
        /* DITL and invalidating their enclosing rectangles. I also hilight controls  */
        /* appropriately and display annunciators when necessary.                     */
            
            GetDItem(serMonitor, kSendButton, &itemType, &(Handle) sendItem, &box);
            GetDItem(serMonitor, kStopButton, &itemType, &(Handle) stopItem, &box);
            if (gShouldSend) {
                HiliteControl(sendItem, kCtlDisable);
                HiliteControl(stopItem, kCtlEnable);
            }
            else {
                HiliteControl(sendItem, kCtlEnable);
                HiliteControl(stopItem, kCtlDisable);
            }
            
            spinner = Get1Resource('ICON', rSpinnerIcon + gSendCount % 8);
            GetDItem(serMonitor, kSendSpinner, &itemType, &item, &box);
            SetDItem(serMonitor, kSendSpinner, iconItem, spinner, &box);
            InvalRect(&box);
            
            spinner = Get1Resource('ICON', rSpinnerIcon + gRcvCount % 8);
            GetDItem(serMonitor, kRcvSpinner, &itemType, &item, &box);
            SetDItem(serMonitor, kRcvSpinner, iconItem, spinner, &box);
            InvalRect(&box);
            
            if (gShouldSend && gHeldOff) {
                flag = Get1Resource('ICON', rHeldIcon);
            }
            else {
                flag = Get1Resource('ICON', rNotHeldIcon);
            }
            GetDItem(serMonitor, kHoldFlag, &itemType, &item, &box);
            SetDItem(serMonitor, kHoldFlag, iconItem, flag, &box);
            InvalRect(&box);
                
        /* In lieu of an event loop, I just use a modal dialog with a relatively  */
        /* simple (but unusual) filterProc. This is not a good example of how to  */
        /* write an app. Modal dialogs are evil and to be avoided if possible.    */
        /* Nonetheless, the filterProc is an interesting example unto itself....  */
    
            ModalDialog(NullGrabber, &itemHit);
            switch (itemHit) {
                
                case kStopButton:
                    if (gShouldSend) {
                        gShouldSend = !gShouldSend;
                    }
                    break;
                
                case kSendButton:
                    if (!gShouldSend) {
                        gShouldSend = !gShouldSend;
                        primeErr = PrimeSend();
                    }
                    break;
                
                case kBreakButton:
                
                /* In another possible Mac IIfx IOP bug, SerSetBrk() called synchronously  */
                /* appears to hang the machine if an async write is pending. Since that is */
                /* often the case (at least in this application) I work around the problem */
                /* by making the Control call immediate--this prevents the hang, but also  */
                /* raises another interesting issue about when break is actually asserted. */
                
                    breakPB.ioCRefNum = gOutRefNum;
                    breakPB.csCode = kSerSetBrk;
                    PBControlImmed((ParmBlkPtr) &breakPB);      /* SerSetBrk(), but IMMED */
                    
                /* With break asserted, I prime a Time Manager task to flag the end of    */
                /* the break, and disable the Break button so that it cannot be selected  */
                /* again until break is negated.                                          */
                
                    PrimeTime((QElemPtr) &gUnBreakTask, kBreakLength);
                    GetDItem(serMonitor, kBreakButton, &itemType, &(Handle) breakItem, &box);
                    HiliteControl(breakItem, kCtlDisable);
                    break;
                
                case kQuitButton:
                    gAllDone = true;
                    break;
                
            }
    }
    
    DisposDialog(serMonitor);
    
}
 
 
 
pascal Boolean NullGrabber (DialogPtr, EventRecord *evt, short *itemHit)
{
    EventRecord driverEvent;
    
    /* Without this filterProc, none of the animation works: */
    
    /* In order to keep things rolling along even when there are no events    */
    /* such as mouse clicks or keystrokes, I have to return true in response  */
    /* to null events. This is unusual, but otherwise ModalDialog() "handles" */
    /* null events by eating them and waiting for something better. This is   */
    /* bad if I need to turn the spinner or clear a break condition.          */
    
    /* Also, I check for driver events here in order to detect breaks. */
    
    if (GetNextEvent(driverMask, &driverEvent)) {
        if ((*(SERDEventMessage *) &driverEvent.message).drvrPosting == gInRefNum) {
            gBreakReceived = ((*(SERDEventMessage *) &driverEvent.message).readR0 & breakR0) != 0;
        }
    }
    
    if (evt->what == nullEvent) {
        *itemHit = 0;
        return true;
    }
    
    else {
        return false;
    }
    
}
 
 
 
pascal void SendCompRout (void)
{
    long    oldA5;
    
    oldA5 = SetA5(((ExtIOParam *) GetParmBlkPtr())->appA5);     /* retrieve A5 */
    
    if (gShouldSend && !gAllDone) {
        gReload = true;
        gpSendPB->ioCompletion = (ProcPtr) SendCompRout;
        PBWriteAsync((ParmBlkPtr) gpSendPB);            /* this is the self-sustaining part */      
    }
    
    SetA5(oldA5);
    
}
 
 
 
OSErr PrimeSend (void)
{
    gpSendPB->ioCompletion = (ProcPtr) SendCompRout;
    gpSendPB->ioRefNum = gOutRefNum;
    gpSendPB->ioBuffer = gpOutputData;
    gpSendPB->ioReqCount = gOutputDataSize;
    ((ExtIOParam *) gpSendPB)->appA5 = SetCurrentA5();  /* completion routine needs A5 */
    gReload = true;
    
    return PBWriteAsync((ParmBlkPtr) gpSendPB);         /* asynchronous self-sustaining sends */
 
}
 
 
 
pascal void FlagBreakTimeout (void)
{
    long    oldA5;
    
    oldA5 = SetA5(((ExtTMTask *) GetTMTaskPtr())->appA5);       /* retrieve A5 */
    
    gKillBreak = true;
    
    SetA5(oldA5);
    
}
 
 
 
OSErr AssertDrvrOpen (Str255 name, short *refNum)
{
    DCtlHandle  *pUTEntry;
    Ptr         pDrvr;
    OSErr       result = notOpenErr;        /* assume not open */
    short       unitNo;
    char        *aDrvrName;
    
    /* The point here is to determine whether a driver is open, given its name.  */
    /* This allows one to check a driver to see if it's open without hard coding */
    /* its reference number. (Normally, the way to get the refNum is to open     */
    /* the driver--but that defeats the whole purpose!)                          */
    /* This is an extension of the code discussed in Tech Note #71.              */
    
    *refNum = 0;
    pUTEntry = *(DCtlHandle **) UTableBase;
    for (unitNo = 0; unitNo < *(short *) UnitNtryCnt; unitNo++, pUTEntry++) {
        if (*pUTEntry != nil && **pUTEntry != nil) {
            if (((***pUTEntry).dCtlFlags & 1 << dRAMBased) != 0)
                pDrvr = *(Handle) (***pUTEntry).dCtlDriver;
            else
                pDrvr = (***pUTEntry).dCtlDriver;
            
            if (pDrvr != nil) {
                aDrvrName = pDrvr + drvrName;
                if (memcmp(aDrvrName, name, 1 + name[0]) == 0) {
                    /* We found the one we're after. */
                    *refNum = ~unitNo;
                    if (((***pUTEntry).dCtlFlags & 1 << dOpened) != 0)
                        result = noErr;
                    break;
                }
            }
        }
    }   
    
    return result;
}
 
 
 
short Initialize (void)
{
    long    result;
    OSErr   gestErr;
    
    gestErr = Gestalt(gestaltSystemVersion, &result);
    gSysVersion = result;
    
    ghSendPB = (IOParam **) NewHandle( sizeof(ExtIOParam));
    if (ghSendPB != NULL) {
        MoveHHi((Handle) ghSendPB);
        HLock((Handle) ghSendPB);
        gpSendPB = *ghSendPB;
    }
    else {
        return MemError();
    }
 
    ghSerBuf = NewHandle(kSerBufSize);
    if (ghSerBuf != NULL) {
        MoveHHi((Handle) ghSerBuf);
        HLock((Handle) ghSerBuf);
        gpSerBuf = *ghSerBuf;
    }
    else {
        DisposHandle((Handle) ghSendPB);
        return MemError();
    }
    
    ghBitBucket = NewHandle(kSerBufSize);
    if (ghBitBucket != NULL) {
        MoveHHi(ghBitBucket);
        HLock(ghBitBucket);
        gpBitBucket = *ghBitBucket;
    }
    else {
        DisposHandle((Handle) ghSendPB);
        DisposHandle((Handle) ghSerBuf);
        return MemError();
    }
    
    ghOutputData = Get1Resource('sDAT', rSerDataRsrc);
    if (ghOutputData != NULL) {
        MoveHHi(ghOutputData);
        HLock(ghOutputData);
        gpOutputData = *ghOutputData;
        gOutputDataSize = SizeResource(ghOutputData);
    }
    else {
        DisposHandle((Handle) ghSendPB);
        DisposHandle((Handle) ghSerBuf);
        DisposHandle(ghBitBucket);
        return ResError();
    }
    
    /* I use a Time Manager task to time breaks. This is where I install it. */
    
    gUnBreakTask.fTMTask.tmAddr = FlagBreakTimeout;
    gUnBreakTask.appA5 = SetCurrentA5();
    InsTime((QElemPtr) &gUnBreakTask);
    
    return noErr;
}
 
 
 
void CleanUp (void)
{
    /* I remove the Time Manager task before exiting. */
    
    RmvTime((QElemPtr) &gUnBreakTask);
 
    DisposHandle((Handle) ghSendPB);
    DisposHandle(ghSerBuf);
    DisposHandle(ghBitBucket);
}