System Management code

Dependencies:   mbed CANBuffer Watchdog MODSERIAL mbed-rtos xbeeRelay IAP

Fork of SystemManagement by Martin Deng

Revision:
38:8efacce315ae
Parent:
36:0afc0fc8f86b
Child:
39:ddf38df9699e
--- a/outDiagnostics/outDiagnostics.cpp	Thu Jan 22 07:59:48 2015 +0000
+++ b/outDiagnostics/outDiagnostics.cpp	Sat Feb 07 08:54:51 2015 +0000
@@ -1,13 +1,18 @@
 #include "outDiagnostics.h"
+#include "outMacros.h"
 
-osThreadId serialID = 0;                         // RTOS thread ID of thread_serialOut
-const int max_charsPerLine = 80;                        // Max chars per line of printed information
+Timer serialLoop;
+Timer CANloop;
+int serialTime_ms = 0;
+int CANtime_ms = 0;
+const int max_charsPerLine = 80;                 // Max chars per line of printed information
 
 // Use the txEmpty interrupt from MODSERIAL to pace the thread so it always runs as fast as possible
-void empty(MODSERIAL_IRQ_INFO *q) {
+osThreadId serialID=0;
+void empty(MODSERIAL_IRQ_INFO *q)
+{
     osSignalSet(serialID, 1);
 }
-
 // Output a string to the MODSERIAL tx buffer, wait when buffer full
 void AddtoBuffer(char *str, bool newline=true)
 {
@@ -39,182 +44,347 @@
     AddtoBuffer(line);
 }
 
-
-// Macros for working with the strings
-#define ADD_SPRINTF_LINE    padCenter(max_charsPerLine, temp, ' ');   // Cetner the string, then add newlines, and add to chunk
-#define DIVIDER_LINE        padCenter(max_charsPerLine, "", 196);     // Generate a line full of divider char 196, add to chunk
-#define TITLE(string)       padCenter(max_charsPerLine, string, 196); // Generate a title line (centered, surrounded by char 196), add to chunk
-#define BLANK_LINE          padCenter(max_charsPerLine, "", ' ');     // Generate a line full of spaces (blank), add to chunk
-#define BOOL(VAR)           (VAR)?"ERR":"OK"
-
 // Generates the serial dashboard, uses MODSERIAL, self-paced (thread yields when buffer is full, resumes when empty)
 void outDiagnostics::thread_serialOut(void const *args)
 {
-    serialID = Thread::gettid();                     // Record thread ID so empty() can signal this thread
-    char temp[max_charsPerLine+5];                   // String buffer to sprintf into, max 1 line
+    serialID = Thread::gettid();                    // Record thread ID so empty() can signal this thread
+    char temp[max_charsPerLine+5];                  // String buffer to sprintf into, max 1 line
+    pc.attach(&empty, MODSERIAL::TxEmpty);          // Attach the tx empty interrupt which paces this thread
+    pc.printf("\033[2J");                           // Clear the screen to get rid of reset message
 
-    pc.attach(&empty, MODSERIAL::TxEmpty);           // Attach the tx empty interrupt which paces this thread
+    // Use these bools to track changes in display mode between iterations
+    bool lastModeExtended = param->extendedSerial;
+    bool inExtendedMode = param->extendedSerial;
 
-    tempData.parseGoodChar = ' ';
-    tempData.inputStr[0] = 0;
-    
-    Timer serialLoop;                                // Timer to track the serial loop time, since this thread paces itself
-    int serialTime_ms = 0;
+    // For determining where the data displayed is coming from
+    bool freeze = false;                            // Not a freeze frame
+    bool notLiveProfile = false;                    // Live data
+
+    const char barSpace[4] = { ' ', 179, ' ', 0 };  // Commonly used string with char 179
+    OperatingInfo* dashOp    = op;
+    Profile*       dashParam = param;
+
     serialLoop.reset();
-    serialLoop.start();                              // Start the counter for tracking serial loop time
-
-    //const char barSpace[4] = { ' ', 179, ' ', 0 };   // Commonly used string with char 179
+    serialLoop.start();                             // Start the counter for tracking serial loop time
 
     while(1) {
-
-        serialTime_ms = serialLoop.read_ms();        // Update loop timer, reset for next loop
+        serialTime_ms = serialLoop.read_ms();       // Update loop timer, reset for next loop
         serialLoop.reset();
 
+        // Update display mode, change detection used at end of while(1) loop
+        lastModeExtended = inExtendedMode;
+        inExtendedMode = param->extendedSerial;
+
+        // Determine whether to display freeze frame, change pointers accordingly
+        if (tempData.freeze != NULL) {
+            freeze = true;                          // Indicate showing freeze
+            dashOp = &(tempData.freeze->op.op);             // Point to freeze
+            dashParam = &(tempData.freeze->param.param);    // Point to freeze
+
+            // Determine whether to display a non-live profile, change pointers accordingly
+        } else if (tempData.viewProfile != NULL && tempData.viewProfileNum != -2) {
+            dashParam = tempData.viewProfile;       // Point to other profile
+            notLiveProfile = true;                  // Indicate notLive
+
+            // Show live data only
+        } else {
+            freeze = false;             // Not freeze
+            notLiveProfile = false;     // Live profile
+            dashOp = op;                // Point to live RAM data
+            dashParam = param;          // Point to live RAM data
+        }
+
         sprintf(temp, "\033[0;0H\033[0;0H");
-        AddtoBuffer(temp, false);                     // Move to 0,0, do not append newline
-        
+        AddtoBuffer(temp, false);                   // Move to 0,0, do not append newline
+
         DIVIDER_LINE
         TITLE(" Penn Electric Racing - REV0 System Management Controller Dashboard ")
         DIVIDER_LINE
-        
+
         int tempLength=0;
         tempLength += sprintf(temp, "Command Input:%c %s%c", tempData.parseGoodChar, tempData.inputStr, 176);  // Command input: print header, reply, input string, and cursor marker
         for (int i = 0; i < max_charsPerLine - tempLength - 1; i++) {      // Fill in the rest of the line with blanks
             tempLength += sprintf(temp+tempLength, " ");                    // Append spaces
         }
         AddtoBuffer(temp);        // Add this to the chunk
-        
-        TITLE(" GLV Battery ")
-        BLANK_LINE
-        sprintf(temp, "Current: %4.3fA          Capacity: %4.3fAh", data.glvCurrent, data.glvCapacity);
+
+        const char profile[NUM_STORED_PROFILES+2][8] = {"Freeze", "Default", "1", "2", "3"};
+        if (inExtendedMode) {
+
+            // Parameters Section
+            snprintf(temp, max_charsPerLine, " Configuration Parameters %s%s%s%s", freeze?"(Viewing Freeze of Last Fault) ":"", (notLiveProfile && !freeze)?" (Viewing Profile ":"", (notLiveProfile && !freeze)?profile[tempData.viewProfileNum+1]:"", (notLiveProfile && !freeze)?") ":"");
+            TITLE(temp)
+            snprintf(temp, max_charsPerLine, "GLVchar:    %5.2fA%sGLVdisch: %5.2fA%sGLVnomCap: %5.2fAh%sGLVtaps:     %3d", dashParam->chargeCurrent,barSpace, dashParam->dischargeCurrent,barSpace, dashParam->nominalCapacity,barSpace, dashParam->glvBat_taps);
+            ADD_SPRINTF_LINE
+            snprintf(temp, max_charsPerLine, "dcdcThres:  %5.2fA%sdcdcOver: %5.2fA%sdcdcStart: %5.2fs %sdcdcStop: %5.2fs", dashParam->dcdcThreshold,barSpace, dashParam->dcdcOverCurrent,barSpace, dashParam->dcdcStartDelay,barSpace, dashParam->dcdcStopDelay);
+            ADD_SPRINTF_LINE
+            snprintf(temp, max_charsPerLine, "dcdcTaps:     %3d %simdStart: %5.1fs%samsStart:  %5.1fs %sIntOverT: %5.1fC",  dashParam->dcdc_taps,barSpace, dashParam->imdStartDelay,barSpace, dashParam->amsStartDelay,barSpace, dashParam->internalOverTemp);
+            ADD_SPRINTF_LINE
+            snprintf(temp, max_charsPerLine, "CANtxSize:   %4d %sCANrxSize: %4d %sSerialBaud: %6d%sSerialTx:  %5d",  dashParam->CANtxSize,barSpace, dashParam->CANrxSize,barSpace, dashParam->SerialBaud,barSpace, dashParam->SerialTxSize);
+            ADD_SPRINTF_LINE
+            snprintf(temp, max_charsPerLine, "XbeeBaud:  %6d %sXbeeTxSize: %4d%sXbeeRxSize: %4d  %s CANack:   %5s",  dashParam->XbeeBaud, barSpace, dashParam->XbeeTxSize,barSpace, dashParam->XbeeRxSize,barSpace, dashParam->CANnoAck?"NOACK":"ACK");
+            ADD_SPRINTF_LINE
+
+            BLANK_LINE
+        }
+        snprintf(temp, max_charsPerLine, " Operating Info %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+
+        // Operating mode
+        const char modeStr[3][8] = {"FAULT", "OKAY", "INVALID"};
+        tempLength = 0;
+        tempLength += sprintf(temp+tempLength, "Sloop: %3dms CANloop: %3dms Time: %s", serialTime_ms, CANtime_ms, ctime(&dashOp->SysTime));
+        tempLength--;
+        char modeN = dashOp->mode;
+        if (modeN == FAULT) modeN=0;
+        else if (modeN == OKAY) modeN=1;
+        else modeN=2;
+        tempLength += sprintf(temp+tempLength, " Uptime: %5ds", dashOp->SysTime - dashOp->startTime);
         ADD_SPRINTF_LINE
-        sprintf(temp, "Amphours: %4.3fAh    SOC: %5.3f    Overcurrent: %s", data.glvAmphours, data.glvSOC, BOOL(data.glvOverCurrent));
+        tempLength = snprintf(temp, max_charsPerLine, "Profile: %7s%10s Mode: %7s%6s     Faults: ", profile[dashOp->profileIndex+1], dashOp->profileModded?", MODIFIED":"", modeStr[modeN], dashOp->faultCode?"+ERROR":"");
+
+        // Fault codes
+        const char topErrStr[12][20] = {"WATCHDOG", "BROWNOUT", "CAN_FAULT", "INT_OVER_TEMP", "IMD_LATCH", "AMS_LATCH", "IMD_FAULT", "DCDC_FAULT", "GLVBAT_FAULT", "FREEZE_FRAME"};
+        int numFaults = 0;
+        for (int i = 0; i < sizeof(dashOp->faultCode)*8; i++) {
+            if (dashOp->faultCode & (1<<i)) numFaults++;
+        }
+        if (numFaults == 0) tempLength+=sprintf(temp+tempLength, "No Faults");                          // Start new appending chain string, faults not present
+        else tempLength+=sprintf(temp+tempLength, "(%d 0x%04x)", numFaults, dashOp->faultCode);  // Start new appending chain string, faults present
         ADD_SPRINTF_LINE
+        tempLength = 0;
+        temp[0] = 0;
 
-        BLANK_LINE
-        TITLE(" DC-DC Converter ")
+        // Print max number of strings that will fit per line, then dump line and continue till finished all error flags
+        int num = 0;
+        for (int i = 0; i < 29; i++) {
+            if (dashOp->faultCode & (1<<i)) {        // Fault found
+                // Room for fault string?
+                if (max_charsPerLine-tempLength > strlen(topErrStr[i])+2) {
+                    // Yes, append
+                    tempLength += sprintf(temp+tempLength, "%s ", topErrStr[i]);
+                    num++;
+                } else {
+                    // No, Dump then start new line
+                    ADD_SPRINTF_LINE
+                    tempLength = 0;
+                    tempLength += sprintf(temp+tempLength, "%s ", topErrStr[i]);
+                    num++;
+                }
+            }
+            if (num >= numFaults || numFaults == 0) {
+                // Done printing all faults
+                tempLength = 0;
+                num = 0;
+                if (numFaults != 0) {
+                    ADD_SPRINTF_LINE
+                }
+                break;
+            }
+        }
         BLANK_LINE
-        char DCDC = data.dcdcStatus;
-        char dcdcModesStr[5][12] = {"INVALID","POWER-UP","POWER-DOWN","ON","OFF"};
-        int dcdcMode = 0;
-        if (DCDC & PowerUp)        dcdcMode = 1;
-        else if (DCDC & PowerDown) dcdcMode = 2;
-        else if (DCDC & SetOn)     dcdcMode = 3;
-        else if (!(DCDC & SetOn))  dcdcMode = 4;
-        sprintf(temp, "Active: %3s     Mode: %10s     AIRS: %6s     StatusByte: 0x%02x", (DCDC & ConvOn)?"YES":"NO", dcdcModesStr[dcdcMode], CANdata.airsClosed?"CLOSED":"OPEN", DCDC);
-        ADD_SPRINTF_LINE
-        sprintf(temp, "Current: %5.2fA     Overcurrent: %3s     SensorFault: %3s", data.dcdcCurrent, BOOL(DCDC & OverCurrent), BOOL(DCDC & SensorFault));
-        ADD_SPRINTF_LINE
-        sprintf(temp, "StartFault: %3s     StopFault:   %3s     CritErrors:  %3s", BOOL(DCDC & FailStart), BOOL(DCDC & FailStop), BOOL(data.dcdcError));
+        snprintf(temp, max_charsPerLine, "ShtdwnCrct: %6s %s AIRs: %6s %s Charger: %4s %s IntTemp: %5.1f", (dashOp->signals & SHUTDOWN_CLOSED)?"CLOSED":"OPEN", barSpace, (dashOp->signals & AIRS_CLOSED)?"CLOSED":"OPEN", barSpace, (dashOp->signals & CHARGER_DET)?"DET":"NDET", barSpace, dashOp->internalTemp);
         ADD_SPRINTF_LINE
 
         BLANK_LINE
-        TITLE(" PWM Channels ")
+        snprintf(temp, max_charsPerLine, " GLV Battery %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+        sprintf(temp, "Current: %4.3fA %s Cap: %4.3fAh %s Ah: %4.3fAh %s SOC: %5.3f", dashOp->glvBat.current, barSpace, dashOp->glvBat.capacity, barSpace, dashOp->glvBat.Ah, barSpace, dashOp->glvBat.SOC);
+        ADD_SPRINTF_LINE
+        // GLV Fault codes
+        const char glvBatErrStr[12][20] = {"OVER_CHARGE_I", "OVER_DISCHARGE_I", "CAP_INIT", "SOC_INIT" };
+        numFaults = 0;
+        for (int i = 0; i < sizeof(dashOp->glvBat.error)*8; i++) {
+            if (dashOp->glvBat.error & (1<<i)) numFaults++;
+        }
+        if (numFaults == 0) tempLength+=sprintf(temp+tempLength, "No Faults");                          // Start new appending chain string, faults not present
+        else tempLength+=sprintf(temp+tempLength, "(%d 0x%04x)", numFaults, dashOp->glvBat.error);  // Start new appending chain string, faults present
+        ADD_SPRINTF_LINE
+        tempLength = 0;
+        temp[0] = 0;
+
+        // Print max number of strings that will fit per line, then dump line and continue till finished all error flags
+        num = 0;
+        for (int i = 0; i < 29; i++) {
+            if (dashOp->glvBat.error & (1<<i)) {        // Fault found
+                // Room for fault string?
+                if (max_charsPerLine-tempLength > strlen(glvBatErrStr[i])+2) {
+                    // Yes, append
+                    tempLength += sprintf(temp+tempLength, "%s ", glvBatErrStr[i]);
+                    num++;
+                } else {
+                    // No, Dump then start new line
+                    ADD_SPRINTF_LINE
+                    tempLength = 0;
+                    tempLength += sprintf(temp+tempLength, "%s ", glvBatErrStr[i]);
+                    num++;
+                }
+            }
+            if (num >= numFaults || numFaults == 0) {
+                // Done printing all faults
+                tempLength = 0;
+                num = 0;
+                if (numFaults != 0) {
+                    ADD_SPRINTF_LINE
+                }
+                break;
+            }
+        }
+        
         BLANK_LINE
-        sprintf(temp, "Actual:     FAN1: %5.3f   FAN2: %5.3f   PUMP1: %5.3f   PUMP2: %5.3f", data.dcdcFan1Duty, data.dcdcFan2Duty, data.dcdcPump1Duty, data.dcdcPump2Duty);
+        snprintf(temp, max_charsPerLine, " DC-DC Converter %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+        char DCDC = dashOp->dcdc.status;
+        const char dcdcModesStr[5][12] = {"INVALID","POWER-UP","POWER-DOWN","SET-ON","SET-OFF"};
+        int dcdcMode = 0;
+        if (DCDC & POWER_UP)        dcdcMode = 1;
+        else if (DCDC & POWER_DOWN) dcdcMode = 2;
+        else if (DCDC & SET_ON)     dcdcMode = 3;
+        else if (!(DCDC & SET_ON))  dcdcMode = 4;
+        sprintf(temp, "Active:    %3s  %s Mode: %10s %s Status:     0x%02x", (DCDC & CONV_ON)?"YES":"NO", barSpace, dcdcModesStr[dcdcMode], barSpace, DCDC);
         ADD_SPRINTF_LINE
-        sprintf(temp, "Requestd:   FAN1: %5.3f   FAN2: %5.3f   PUMP1: %5.3f   PUMP2: %5.3f", CANdata.dcdcFan1Duty, CANdata.dcdcFan2Duty, CANdata.dcdcPump1Duty, CANdata.dcdcPump2Duty);
+        sprintf(temp, "Current: %5.2fA %s Overcurrent: %3s %s SensorFault: %3s", dashOp->dcdc.current,barSpace, (DCDC & OVER_CURRENT)?"ERR":"OK",barSpace, (DCDC & SENSOR_FAULT)?"ERR":"OK");
         ADD_SPRINTF_LINE
 
         BLANK_LINE
-        TITLE(" IMD ")
-        BLANK_LINE
-        const char IMDstr[7][12] = {"OFF","NORMAL","UNDERVOLT","SPEEDSTART","ERROR","GROUNDFLT","INVALID"};
-        sprintf(temp, "Status: %10s Resistance: %7.0fkohm CritError: %3s",IMDstr[data.imdStatus], data.imdResistance/1e3, BOOL(data.imdError));
+        snprintf(temp, max_charsPerLine, " DC-DC PWM Channels %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+        sprintf(temp, "Actual:      FAN1: %5.3f   FAN2: %5.3f %s PUMP1: %5.3f   PUMP2: %5.3f", dashOp->dcdc.actual.fan1, dashOp->dcdc.actual.fan2, barSpace, dashOp->dcdc.actual.pump1, dashOp->dcdc.actual.pump2);
+        ADD_SPRINTF_LINE
+        sprintf(temp, "Requested:   FAN1: %5.3f   FAN2: %5.3f %s PUMP1: %5.3f   PUMP2: %5.3f", dashOp->dcdc.request.fan1, dashOp->dcdc.request.fan2, barSpace, dashOp->dcdc.request.pump1, dashOp->dcdc.request.pump2);
         ADD_SPRINTF_LINE
 
         BLANK_LINE
-        TITLE(" Latch Circuit Monitors ")
-        BLANK_LINE
-        char AMSerr = data.AMSlatchError;
-        char IMDerr = data.IMDlatchError;
-        sprintf(temp, "AMS -    OK: %4s Latch: %3s   SoftFault: %3s   HardFault: %3s", (AMSerr & 1)?"LOW":"HIGH", BOOL(AMSerr & 2), BOOL(AMSerr & 4), BOOL(AMSerr & 8));
-        ADD_SPRINTF_LINE
-        sprintf(temp, "IMD -    OK: %4s Latch: %3s   SoftFault: %3s   HardFault: %3s", (IMDerr & 1)?"LOW":"HIGH", BOOL(IMDerr & 2), BOOL(IMDerr & 4), BOOL(IMDerr & 8));
+        snprintf(temp, max_charsPerLine, " IMD %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+        const char IMDstr[7][12] = {"OFF","NORMAL","UNDERVOLT","SPEEDSTART","ERROR","GROUNDFLT","INVALID"};
+        sprintf(temp, "Status: %10s   Resistance: %7.0fKohm   Error: %3s", IMDstr[dashOp->imd.status], dashOp->imd.resistance/1.0e3, dashOp->imd.error?"ERR":"OK");
         ADD_SPRINTF_LINE
 
         BLANK_LINE
-        TITLE(" Shutdown Switches ")
+        snprintf(temp, max_charsPerLine, " Latch Monitors %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+        char AMSerr = dashOp->latch.ams;
+        char IMDerr = dashOp->latch.imd;
+        sprintf(temp, "AMS -    OK: %4s Latch: %3s   SoftFault: %3s   HardFault: %3s", (AMSerr & OK_FAULT)?"LOW":"HIGH", (AMSerr & LATCHED_HARD)?"ERR":"OK", (AMSerr & LATCHED_SOFT)?"ERR":"OK", (AMSerr & HARD_FAULT)?"ERR":"OK");
+        ADD_SPRINTF_LINE
+        sprintf(temp, "IMD -    OK: %4s Latch: %3s   SoftFault: %3s   HardFault: %3s", (IMDerr & OK_FAULT)?"LOW":"HIGH", (IMDerr & LATCHED_HARD)?"ERR":"OK", (IMDerr & LATCHED_SOFT)?"ERR":"OK", (IMDerr & HARD_FAULT)?"ERR":"OK");
+        ADD_SPRINTF_LINE
+
         BLANK_LINE
-        char switches = data.switchState;
+        snprintf(temp, max_charsPerLine, " Shutdown Switches %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+        char switches = dashOp->switchState;
         const char switchNames[12][26] = {"FUSE","AMS LATCH","IMD LATCH","PCM RELAY","BRAKE PLAUSIBILITY RELAY","LEFT E-STOP","INERTIA SWITCH","BRAKE OVER-TRAVEL SWITCH","COCKPIT E-STOP","RIGHT E-STOP","HVD","TSMS"};
         if (switches == 0) sprintf(temp, "All switches are CLOSED.");
         else sprintf(temp, "%s is OPEN.", switchNames[switches-1]);
         ADD_SPRINTF_LINE
 
         BLANK_LINE
-        TITLE(" Telemetry ")
-        BLANK_LINE
-        sprintf(temp, "Channel 1 -    MessagesIn: %5d   MessagesOut: %5d", xbeeRelay.counterX1in, xbeeRelay.counterX1out);
+        snprintf(temp, max_charsPerLine, " Telemetry %s", freeze?"(Viewing Freeze of Last Fault) ":"");
+        TITLE(temp)
+        sprintf(temp, "Channel 1 -    MsgIn: %5d   MsgOut: %5d %5.2f", dashOp->xbee1.msgIn, dashOp->xbee1.msgOut, dashOp->xbee1.rateOut);
         ADD_SPRINTF_LINE
-        sprintf(temp, "Channel 2 -    MessagesIn: %5d   MessagesOut: %5d", xbeeRelay.counterX2in, xbeeRelay.counterX2out);
+        sprintf(temp, "Channel 2 -    MsgIn: %5d   MsgOut: %5d %5.2f", dashOp->xbee2.msgIn, dashOp->xbee2.msgOut, dashOp->xbee2.rateOut);
         ADD_SPRINTF_LINE
 
-        BLANK_LINE
-        TITLE(" Miscellaneous ")
-        BLANK_LINE
-        sprintf(temp, "Temp: %5.1fC %3s canFault: %3s Watchdog: %3s ErrorCode: 0x%02x SerialTime: %3dms", data.internalTemp, BOOL(data.internalOverTemp), BOOL(data.canFault), BOOL(data.watchdogReset), data.errorFrame, serialTime_ms);
-        ADD_SPRINTF_LINE
-
-        // Erase screen every 5sec to remove glitches
-        static int count=0;
-        if (count%50==0) {
-            pc.printf("\033[2J");        // Clear the screen
+        // Erase screen every few counts to remove glitches
+        static int count = 0;
+        if (count % 50 == 0 || lastModeExtended != inExtendedMode) {
+            sprintf(temp, "\033[2J");
+            AddtoBuffer(temp, false);     // Clear the screen
         }
         count++;
     }
 }
 
-// Macros to streamline can bus message formatting and sending
-#define SEND_CAN(LEN, ID)               msg.len=LEN; msg.id=ID; msg.type = CANData; msg.format = CANStandard; if (!can.txWrite(msg)) data.canFault = true; xbeeRelay.send(msg);
-#define FLOAT(DATA, ID)                 *((float*)((void*)&msg.data[0]))    = DATA;    SEND_CAN(sizeof(float), ID)
-#define UINT(DATA, ID)                  *((uint32_t*)((void*)&msg.data[0])) = DATA;    SEND_CAN(sizeof(uint32_t), ID)
-#define FLOAT_PAIR(DATA1, DATA2, ID)    *((float*)((void*)&msg.data[0]))    = DATA1;   *((float*)((void*)&msg.data[4]))    = DATA2;   SEND_CAN(2*sizeof(float), ID)
-#define UINT_PAIR(DATA1, DATA2, ID)     *((uint32_t*)((void*)&msg.data[0])) = DATA1;   *((uint32_t*)((void*)&msg.data[4])) = DATA2;   SEND_CAN(2*sizeof(uint32_t), ID)
-#define CHAR(DATA, ID)                  msg.data[0]                         = DATA;    SEND_CAN(sizeof(char), ID)
-
 void outDiagnostics::thread_canOut(void const *args)
 {
-    CANMessage msg;
+    bool lastCANnoAck = param->CANnoAck;        // Get initial noAck mode setting
+    bool thisCANnoAck = param->CANnoAck;
+
+    if (param->CANnoAck) {                      // Turn on noack mode
+        can.mode(NoAck);
+    } else {                                    // Setup normal mode
+        can.mode(Normal);
+    }
+    can.mode(FIFO);
+
+    CANloop.reset();
+    CANloop.start();
+
     while(1) {
 
-        // Sys Mgmt Error Frame
-        CHAR(data.errorFrame, SYS_ERROR_ID)
+        CANtime_ms = CANloop.read_ms();
+        CANloop.reset();
+
+        // Update last and this variables to check for changes in noAck mode
+        lastCANnoAck = thisCANnoAck;
+        thisCANnoAck = param->CANnoAck;
+        if (thisCANnoAck && !lastCANnoAck) {    // NoAck mode turn on
+            can.mode(NoAck);
+            can.mode(FIFO);
+            can.txFlush();                      // Must flush buffer when switching modes, or else buffer gets stuck
+        }
+        if (!thisCANnoAck && lastCANnoAck) {    // NoAck mode turn off
+            can.mode(Normal);
+            can.mode(FIFO);
+            can.txFlush();                      // Must flush buffer when switching modes, or else buffer gets stuck
+        }
+
+        // OPERATING DIAGNOSTICS
+        // Error Frame
+        CAN_SINGLE(faultCode,   SYS_ERROR_ID)
+
+        // Mode
+        CAN_SINGLE(mode,        SYS_MODE_ID)
+
+        // Flags
+        CAN_SINGLE(signals,     SYS_FLAGS_ID)
+
+        // Profile
+        char byte = (op->profileIndex != -1) ? op->profileIndex : 1<<6;   // Mark the second to last bit of the byte if using Freeze profile (data[0]=64 for freeze, data[0]=0 for default)
+        byte |= (op->profileModded) ? 1<<7 : 0;                           // Mark the last bit of the byte if the profile was modified (OR'd with profile id from above)
+        SEND_CAN_SINGLE(byte, SYS_PROFILE_ID);
+
+        // Time
+        CAN_PAIR(SysTime,       startTime,      SYS_TIME_ID)
 
         // Xbee1 Counter
-        UINT_PAIR(xbeeRelay.counterX1in, xbeeRelay.counterX1out, SYS_XBEE1_ID)
+        CAN_PAIR(xbee1.msgIn,   xbee1.msgOut,   SYS_XBEE1_MSG_ID)
 
         // Xbee2 Counter
-        UINT_PAIR(xbeeRelay.counterX2in, xbeeRelay.counterX2out, SYS_XBEE2_ID)
+        CAN_PAIR(xbee2.msgIn,   xbee2.msgOut,   SYS_XBEE2_MSG_ID)
 
         // Internal temperature
-        FLOAT(data.internalTemp, SYS_TEMP_ID)
+        CAN_SINGLE(internalTemp, SYS_TEMP_ID)
 
         // GLV Battery
-        FLOAT(data.glvCurrent, SYS_GLV_CURRENT_ID)
-        FLOAT(data.glvCapacity, SYS_GLV_CAPACITY_ID)
-        FLOAT(data.glvAmphours, SYS_GLV_AH_ID)
-        FLOAT(data.glvSOC, SYS_GLV_SOC_ID)
-        
+        CAN_SINGLE(glvBat.current,  SYS_GLV_CURRENT_ID)
+        CAN_SINGLE(glvBat.capacity, SYS_GLV_CAPACITY_ID)
+        CAN_SINGLE(glvBat.Ah,       SYS_GLV_AH_ID)
+        CAN_SINGLE(glvBat.SOC,      SYS_GLV_SOC_ID)
+        CAN_SINGLE(glvBat.error,    SYS_GLV_ERROR_ID)
+
         // DC-DC Converter
-        FLOAT(data.dcdcCurrent, SYS_DCDC_CURRENT_ID)
-        CHAR(data.dcdcStatus, SYS_DCDC_STATUS_ID)
+        CAN_SINGLE(dcdc.current,    SYS_DCDC_CURRENT_ID)
+        CAN_SINGLE(dcdc.status,     SYS_DCDC_STATUS_ID)
 
         // PWM Channels
-        FLOAT_PAIR(data.dcdcFan1Duty, data.dcdcFan2Duty, SYS_PWM_FAN_ID)
-        FLOAT_PAIR(data.dcdcPump1Duty, data.dcdcPump2Duty, SYS_PWM_PUMP_ID)
+        CAN_PAIR(dcdc.actual.fan1,  dcdc.actual.fan2,  SYS_PWM_FAN_ID)
+        CAN_PAIR(dcdc.actual.pump1, dcdc.actual.pump2, SYS_PWM_PUMP_ID)
 
         // IMD
-        FLOAT(data.imdStatus, SYS_IMD_STATUS_ID)
-        CHAR(data.imdResistance, SYS_IMD_RESIST_ID)
+        CAN_SINGLE(imd.status,      SYS_IMD_STATUS_ID)
+        CAN_SINGLE(imd.resistance,  SYS_IMD_RESIST_ID)
 
         // Latches
-        CHAR(data.IMDlatchError, SYS_IMD_LATCH_ID)
-        CHAR(data.AMSlatchError, SYS_AMS_LATCH_ID)
+        CAN_SINGLE(latch.imd,       SYS_IMD_LATCH_ID)
+        CAN_SINGLE(latch.ams,       SYS_AMS_LATCH_ID)
 
         // Shutdown Switches
-        CHAR(data.switchState, SYS_SWITCHES_ID)
-        
+        CAN_SINGLE(switchState,     SYS_SWITCHES_ID)
+
         osSignalSet((osThreadId)(tempData.wdtThreadId), 1<<4);      // Signal watchdog thread
         Thread::wait(CAN_LOOP*1000);
     }