GM Gen 4 data logger

Disassembly, Reassembly, Tools and devleopment. Going deep with Hardware and Software.
Post Reply
hjtrbo
Posts: 184
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

GM Gen 4 data logger

Post by hjtrbo »

Half way through making a read by address data logger for e38, 67 and t43. Will most likely work on gen 5 stuff as well. A very kind member on this forum has been helping me out with some testing, he's on vacation the lucky bugger so I'm working the code part so he has something substantial to test with when he's back at his shop. We've got service service $2D and $22 working on a T43. I'm wanting to take it to the next level and get service $AA working. First off can I clarify the message creation flow path.

Code: Select all

 *      $2D --> OneShot --> $22
 *            |
 *             -> $2C --> Endless --> $AA
And lastly when creating the $2C objects, is there a limit of pids per dpid?

Image

And extra lastly, I'm trying to obey the spec with regards to the pci frames and negative response handling. Just wondering if those skilled in the art wouldn't mind taking a look? I'm a hack when it comes to code.

Code: Select all

        public static void SendCommand(PID.Pid_Obj pid_Obj, List<byte[]> sendCommand, List<byte[]> simReturnMessage = null)
        {
            int retryCnt_FlowCtrl    = 0;
            int negativeResponse     = 0;
            int dbg_RxMsgIndx        = 0;

            // Perform initialisation ops
            pid_Obj.Payload.Clear();
            pid_Obj.Status   = PID.J2534_Message.status.OK;
            pid_Obj.ErrorMsg = PID.J2534_Message.errorReason.None;
            GetMessageResults messages = null;
            int timeOut = pid_Obj.Timeout;

            try
            {
                // Number of messages to send
                for (int TxMsgIndx = 0; TxMsgIndx < sendCommand.Count; TxMsgIndx++)
                {
                    // Send out the message to the node
                    if (!Form1.SIM_MODE)
                    { 
                        Channel.SendMessage(sendCommand[TxMsgIndx]);
                    }

                    // Save the full length send command to the pid object for our reference
                    pid_Obj.TxBytes = sendCommand[TxMsgIndx];

                    // Logs the tx bytes to the status console
                    if (Logger.LogTxRxMsgsToStatusConsole) Logger.WriteLineAsync($"{Form1.indent} Tx {BitConverter.ToString(pid_Obj.TxBytes).Replace("-", " ").Remove(0, 12)}");

                    // Around we go
                    for (; ; )
                    {
                    AttemptWaitForResponse:
                        // Clear out the pci status and flow control registers before use
                        PciFrame.ResetPciBytes();

                        if (!Form1.SIM_MODE)
                        {
                            // Read response, block here till messages arrive or timeout expired
                            try
                            {
                                messages = Channel.GetMessages(1, timeOut);
                            }
                            catch (Exception e)
                            {
                                Logger.WriteLineAsync($"J2534.GetMessages() error. {e.Message}");
                            }

                            // Save full length message response for our reference
                            pid_Obj.RxBytes = messages.Messages[0].Data;
                        }
                        else
                        {
                            // Debug: Simulate message behaviour. Basically keep pumping them out until the buffer is empty
                            if (dbg_RxMsgIndx < simReturnMessage.Count)
                            {
                                pid_Obj.RxBytes = simReturnMessage[dbg_RxMsgIndx];
                            }
                            else
                            {
                                pid_Obj.Status   = PID.J2534_Message.status.Error;
                                pid_Obj.ErrorMsg = PID.J2534_Message.errorReason.BufferEmptyTimeout;
                                goto Exit;
                            }
                            dbg_RxMsgIndx++;
                        }

                        // Log to status console
                        if (Logger.LogTxRxMsgsToStatusConsole) Logger.WriteLineAsync($"{Form1.indent} Rx {BitConverter.ToString(pid_Obj.RxBytes).Replace("-", " ").Remove(0, 12)}");

                        // Split into can id and can data frame
                        (pid_Obj.CanID, pid_Obj.CanFrame) = ParseOutCanIdAndFrame(pid_Obj.RxBytes);

                        // Parses pci frame(s) information
                        PciFrame.ePciType pciType = PciFrame.GetPciFrames(pid_Obj.CanFrame);

                        switch (pciType)
                        {
                            case PciFrame.ePciType.Single:
                                pid_Obj.DataLen = PciFrame.DataLength;

                                // Payload. For pci type 'single' payload width is 7 bytes
                                pid_Obj.CanPayload = GetPayload(pid_Obj.CanFrame, 1, pid_Obj.DataLen);

                                // Add the payload to the response list
                                pid_Obj.Payload.Add(pid_Obj.CanPayload);

                                // Positive response
                                if (pid_Obj.CanFrame[1] == 0x6D) // Success, all done
                                {
                                    pid_Obj.Status = PID.J2534_Message.status.OK;
                                    goto Exit;
                                }

                                // Negative response
                                if (pid_Obj.CanFrame[1] == 0x7F) // Negative response
                                {
                                    bool error = ProcessNegativeResponse(pid_Obj, ref negativeResponse, pid_Obj.MaxRetries);

                                    if (error)
                                        goto Exit; // No good, exit with error
                                    else
                                        goto AttemptWaitForResponse; // Response pending, attempt again to wait for a message to arrive
                                }
                                goto Exit;

                            case PciFrame.ePciType.First:
                                pid_Obj.DataLen = PciFrame.DataLength;

                                // Calc number of messages we are to receive
                                pid_Obj.TrgtMsgCnt = NumberOfRxMsgs(pid_Obj.DataLen);

                                // This is the first message
                                pid_Obj.ActMsgCnt++;

                                // Payload. For pci type 'first' payload width is 6 bytes
                                pid_Obj.CanPayload = GetPayload(pid_Obj.CanFrame, 2, 6);

                                // Add the payload to the response list
                                pid_Obj.Payload.Add(pid_Obj.CanPayload);
                                break;

                            case PciFrame.ePciType.Consecutive:
                                pid_Obj.SeqNum = PciFrame.SequenceNum;

                                // The sequence numbers start at 0x21 through to 0x2F then it repeats. Anyway, I'm not
                                // looking at them, just keeping count instead 
                                pid_Obj.ActMsgCnt++;

                                // Payload. For pci type 'consecutive' payload width is 7 bytes
                                pid_Obj.CanPayload = GetPayload(pid_Obj.CanFrame, 1, 7);

                                // Add the payload to the response list
                                pid_Obj.Payload.Add(pid_Obj.CanPayload);
                                
                                // Last message?
                                if (pid_Obj.TrgtMsgCnt == pid_Obj.ActMsgCnt)
                                {
                                    pid_Obj.Status = PID.J2534_Message.status.OK;
                                    goto Exit;
                                }
                                break;

                            case PciFrame.ePciType.FlowControl:

                                PciFrame.eFlowCtrl flowCtrl = PciFrame.GetFlowCtrlCode(pid_Obj.CanFrame);

                                switch (flowCtrl)
                                {
                                    case PciFrame.eFlowCtrl.ClearToSend:
                                        goto SendNextMsg;

                                    case PciFrame.eFlowCtrl.Wait:
                                        if (retryCnt_FlowCtrl > pid_Obj.MaxRetries)
                                        {
                                            pid_Obj.Status   = PID.J2534_Message.status.Error;
                                            pid_Obj.ErrorMsg = PID.J2534_Message.errorReason.FlowControlWaitTimeout;
                                            goto Exit;
                                        }
                                        retryCnt_FlowCtrl++;
                                        Thread.Sleep(5);
                                        goto AttemptWaitForResponse;

                                    case PciFrame.eFlowCtrl.Overflow:
                                        pid_Obj.Status   = PID.J2534_Message.status.Error;
                                        pid_Obj.ErrorMsg = PID.J2534_Message.errorReason.FlowControlOverflow;
                                        goto Exit;
                                }
                                break;
                        }
                    }
                SendNextMsg:;
                }
            }
            catch (Exception ex)
            {
                Logger.WriteLineAsync($"SendCommand Error source: {0} {ex.Source}");
                APIFactory.StaticDispose();
                pid_Obj.Status       = PID.J2534_Message.status.Error;
                pid_Obj.ErrorMsg     = PID.J2534_Message.errorReason.Exception;
                pid_Obj.ExceptionMsg = ex.Message;
            }

        Exit:
            // If error, print result 
            if (pid_Obj.Status == PID.J2534_Message.status.Error)
            {
                Logger.WriteLineAsync
                    (
                    $"{Form1.indent} Send command error: " +
                    $"{pid_Obj.ErrorMsg.ToString()} " +
                    $"{(pid_Obj.NegResp == PID.J2534_Message.negResponce.None ? String.Empty : pid_Obj.NegResp.ToString())}"
                    );

                if (pid_Obj.ErrorMsg == PID.J2534_Message.errorReason.Exception)
                {
                    Logger.WriteLineAsync($"{Form1.indent} Exception message: {pid_Obj.ExceptionMsg.ToString()}");
                }
            }
        }

        private static bool ProcessNegativeResponse(PID.Pid_Obj pid, ref int retryCount, int maxRetries)
        {
            // 7. Negative Response ($7F) Service Definition pg 69
            bool error = false;

            byte errorCode = pid.CanFrame[3];
            switch (errorCode)
            {
                case 0x11:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.ServiceNotSupported;
                    error = true;
                    break;

                case 0x12:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.SubFunctionNotSupported_InvalidFormat;
                    error = true;
                    break;

                case 0x22:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.ConditionsNotCorrectOrRequestSequenceError;
                    error = true;
                    break;

                case 0x31:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.RequestOutOfRange;
                    error = true;
                    break;
                
                case 0x35:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.InvalidKey;
                    error = true;
                    break; 
                
                case 0x36:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.ExceedNumberOfAttempts;
                    error = true;
                    break;
                
                case 0x37:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.RequiredTimeDelayNotExpired;
                    error = true;
                    break;
                
                case 0x78:
                    error = false;
                    retryCount++;
                    if (retryCount > maxRetries)
                    {
                        pid.Status   = PID.J2534_Message.status.Error;
                        pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                        pid.NegResp  = PID.J2534_Message.negResponce.RequestCorrectlyReceived_ResponsePending;
                        error = true;
                    }
                    break;
                
                case 0x81:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.SchedulerFull;
                    error = true;
                    break;
                
                case 0x83:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.VoltageOutOfRangeFault;
                    error = true;
                    break;
                
                case 0x85:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.GeneralProgrammingFailure;
                    error = true;
                    break;
                
                case 0x89:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.DeviceTypeError;
                    error = true;
                    break;
                
                case 0x99:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.ReadyForDownload_DTCStored;
                    error = true;
                    break;
                
                case 0xE3:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.DeviceControlLimitsExceeded;
                    error = true;
                    break;
                
                default:
                    pid.Status   = PID.J2534_Message.status.Error;
                    pid.ErrorMsg = PID.J2534_Message.errorReason.NegativeResponse;
                    pid.NegResp  = PID.J2534_Message.negResponce.UndocumentedResponseCode;
                    error = true;
                    break;
            }
            return error;
        }
hjtrbo
Posts: 184
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: GM Gen 4 data logger

Post by hjtrbo »

SubFunctionNotSupported - InvalidFormat
If the total number of data bytes which would be reported in the UUDT message exceeds
seven data bytes.
Is that it? 7 bytes per dpid?
User avatar
Tazzi
Posts: 3496
Joined: Thu May 17, 2012 8:53 pm
cars: VE SS Ute
Location: WA
Contact:

Re: GM Gen 4 data logger

Post by Tazzi »

hjtrbo wrote: Mon Aug 26, 2024 4:58 pm
SubFunctionNotSupported - InvalidFormat
If the total number of data bytes which would be reported in the UUDT message exceeds
seven data bytes.
Is that it? 7 bytes per dpid?
Correct,

So CAN frames can only be a max of 8 bytes each.

So when you do a DPID, and set it up for spamming messages, you end up with the following:

5E8 FE 11 22 33 44 55 66 77

Where FE is the DPID which has been loaded up with PIDs, and 11-77 is the data.
Your Local Aussie Reverse Engineer
Contact for Software/Hardware development and Reverse Engineering
Site:https://www.envyouscustoms.com
Mob:+61406 140 726
Image
hjtrbo
Posts: 184
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: GM Gen 4 data logger

Post by hjtrbo »

Launching this pre alpha repo. If you're in a position to do some debugging on real hardware please help me out, I've just been simulating responses so its all been developed in a perfect world. My first crack at a proper c# app, no idea if the chosen implementations are right or wrong.
Current defaults are for the T43 but easily changed to ecm, bcm etc via the gui. The readme has some outstanding tasks, if you're bored create a branch and have a go. I've based this off the standard C# J2534 library and have extended it to implement the GM protocol information spec. Basically you can line up a whole bunch of byte[] commands in a list, then fire them off in 1 hit, sit back and wait for the responses. All the flow control, positive and negative responses, detecting end of messaging, sequencing bla bla are handled automatically with timeouts for backup. Each PID is its own object with all tx rx, scaling, messaging information contained within it. Supports logging scaled values to csv and Tx Rx byte strings to csv / txt. Currently only polling with service 22. I implemented a bin sort algo to optimise the dpid packing, its pretty sweet for how simple it is. Haven't done much more than that for service 2C

https://github.com/hjtrbo/GM-Read-Memor ... ata-Logger
hjtrbo
Posts: 184
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: GM Gen 4 data logger

Post by hjtrbo »

Question, the tester present message, does it matter if this message is broadcast to all nodes or is it preferred to address it directly to the node under test and subsequently process (or likely ignore) the $7E response?
User avatar
Tazzi
Posts: 3496
Joined: Thu May 17, 2012 8:53 pm
cars: VE SS Ute
Location: WA
Contact:

Re: GM Gen 4 data logger

Post by Tazzi »

It’s best to use the standardized global tester present: 101 FE 01 3E.

Modules don’t respond to the tester present message when it’s global but they do recognize it.

If you send it directly to a module such as 7E0 01 3E, then the ecu will respond with 7E8 01 7E.

Depending how the app has been designed, it can get caught up with the DPID messages being broadcasted and make it hard to process through or miss messages.
Your Local Aussie Reverse Engineer
Contact for Software/Hardware development and Reverse Engineering
Site:https://www.envyouscustoms.com
Mob:+61406 140 726
Image
hjtrbo
Posts: 184
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: GM Gen 4 data logger

Post by hjtrbo »

Thanks Tazz.
User avatar
Tre-Cool
Posts: 359
Joined: Tue Oct 16, 2012 12:17 pm
cars: VY SS UTE, VX Drag Car
Location: Perth
Contact:

Re: GM Gen 4 data logger

Post by Tre-Cool »

when i get some free time i'll have a play.
hjtrbo
Posts: 184
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: GM Gen 4 data logger

Post by hjtrbo »

Cheers. Service AA is not finished yet. Service 22 is working .
hjtrbo
Posts: 184
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: GM Gen 4 data logger

Post by hjtrbo »

Just finished pushing an update. If you have a previous version I suggest archiving it and grabbing a fresh copy. I wasn't sending an 0x30 clear to send flow control when I received a 0x10 first frame message from the node. There's another bug in there which I think is coming from the way I'm frequently connecting / disconnecting. Any way changed the strategy to stay connected for the entire session or until the disconnect button is clicked. Hope that fixes it. Service AA is still a work in progress. Service 1A and 22 are working so at least there is something useable to log memory addresses of interest as a part of the reverse engineering process.
Post Reply