Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to send messages from MyGeotab to External device #5

Open
spehj opened this issue Jul 27, 2024 · 1 comment
Open

How to send messages from MyGeotab to External device #5

spehj opened this issue Jul 27, 2024 · 1 comment

Comments

@spehj
Copy link

spehj commented Jul 27, 2024

I'd like to send messages from MyGeotab Runner to the external Android device connected to the Geotab GO device with the USB cable.

I use this API call with the Runner:

api.call("Add", {
    "typeName": "TextMessage",
    "entity": {
        "device": {"id":"b2"}, // Replace with device ID that should receive the data
        "messageContent": {
            "contentType": "MimeContent",
            "channelNumber": 1,
            "mimeType": "text", // Can be changed to any free format text value
            "binaryDataPacketDelay": "00:00:03.0000000", // Applies a configurable delay of up to 5 seconds in between each sequenced message of a multimessage MIME payload
            "data": "SGVsbG8gV29ybGQ=" // Replace with your data encoded in base64
        },
    "isDirectionToVehicle": true,
    "messageSize": 235 // If unspecified defaults to 235. Max of 1000.
    },
}, function(result) {
    console.log("Done: ", result);
}, function(e) {
    console.error("Failed:", e);
});

I'm using your example code, where I only changed the Device ID to a number between 4200 and 4299 based on your documentation.

My ThirdParty.java code:

public class ThirdParty {
private static final int DEVICE_ID = 4208; // Used in methods sendHandshakeConfirmation and BuildHandshakeMessage

// Some other code ...

 // State machine to handle the third party protocol - See case SEND_CONFIRMATION
    private class StateMachine implements Runnable {
        private State eState = State.SEND_SYNC;
        private AtomicBoolean fRunning = new AtomicBoolean(true);

        public void run() {
            Log.i(TAG, "Third party SM started");

            while (fRunning.get()) {
                mLock.lock();        // The lock is needed for await and atomic access to flags/buffers

                try {
                    notifyStateChanged(eState);
                    switch (eState) {
                        case SEND_SYNC: {
                            byte[] abMessage = new byte[]{MESSAGE_SYNC};
                            mAccessoryControl.write(abMessage);
                            eState = State.WAIT_FOR_HANDSHAKE;
                            Log.d("THIRD_PARTY", "SEND_SYNC");
                            break;
                        }
                        case WAIT_FOR_HANDSHAKE: {
                            // Waits for the handshake message or resends sync every 1s
                            mEvent.await(1000, TimeUnit.MILLISECONDS);

                            if (mfHandshakeReceived) {
                                eState = State.SEND_CONFIRMATION;
                            } else {
                                eState = State.SEND_SYNC;
                            }
                            break;
                        }
                        case SEND_CONFIRMATION: {
                            sendHandshakeConfirmation(); // Added this (see below)
                            // Could also use this without flags:
                            // byte[] abMessage = BuildHandshakeMessage();
                            // mAccessoryControl.write(abMessage);
                            eState = State.PRE_IDLE;
                            break;
                        }
                        case PRE_IDLE: {
                            mfHandshakeReceived = false;
                            mfAckReceived = false;
                            mfMessageToSend = false;
                            eState = State.IDLE;
                            break;
                        }
                        case IDLE: {
                            // Sleep and wait for a handshake or a message to send
                            mEvent.await();

                            if (mfHandshakeReceived) {
                                eState = State.SEND_CONFIRMATION;
                            } else if (mfMessageToSend) {
                                mAccessoryControl.write(mabMessage);
                                eState = State.WAIT_FOR_ACK;
                            }
                            break;
                        }
                        case WAIT_FOR_ACK: {
                            // Wait for the ack or reset after 5s
                            mEvent.await(5000, TimeUnit.MILLISECONDS);

                            if (mfAckReceived) {
                                eState = State.PRE_IDLE;
                            } else {
                                eState = State.SEND_SYNC;
                            }
                            break;
                        }
                        default: {
                            eState = State.SEND_SYNC;
                            break;
                        }
                    }

                } catch (InterruptedException e) {
                    Log.w(TAG, "Exception during await", e);
                } finally {
                    mLock.unlock();
                }
            }
        }

        // Stop the thread
        public void close() {
            Log.i(TAG, "Shutting down third party SM");

            mLock.lock();
            try {
                fRunning.set(false);
                mfHandshakeReceived = false;
                mfAckReceived = false;
                mfMessageToSend = false;
                mEvent.signal();
            } finally {
                mLock.unlock();
            }
        }
    }

// Some other code ...


    
    // Wrote this to send a confirmation with the flags as stated in the documentation
    public void sendHandshakeConfirmation() {
        byte[] handshakeMessage = BuildHandshakeConfirmationMessage();
        Log.d(TAG, "Sending Handshake Confirmation: " + Arrays.toString(handshakeMessage));
        mAccessoryControl.write(handshakeMessage);
    }

    private byte[] BuildHandshakeConfirmationMessage() {
        byte[] abMessage = new byte[10];

        // Start of Text
        abMessage[0] = 0x02;

        // Message Type
        abMessage[1] = MESSAGE_CONFIRMATION;

        // Message Body Length
        abMessage[2] = 4;

        // External Device ID (little-endian)
        abMessage[3] = (byte) (DEVICE_ID & 0xFF);
        abMessage[4] = (byte) ((DEVICE_ID >> 8) & 0xFF);

        // Flags
        byte flags = 0x06; // Handshake ACK = 1, Binary data wrapping = 1, Self-powered = 0
        abMessage[5] = flags;

        // Checksum (Fletcher's checksum)
        byte[] abChecksum = CalcChecksum(abMessage, 6);
        abMessage[6] = abChecksum[0];
        abMessage[7] = abChecksum[1];

        // End of Text
        abMessage[8] = 0x03;

        return abMessage;
    }

// Some other code ...

// I also tried this method without the flags that uses existing BuildMessage method
// Assemble the handshake message with the device ID - could be called in 
    private byte[] BuildHandshakeMessage() {
        byte[] abDeviceId = new byte[]{
                (byte) (DEVICE_ID & 0xFF),
                (byte) ((DEVICE_ID >> 8) & 0xFF),
                0x00, 0x00 // Placeholder for any additional data
        };
        return BuildMessage(MESSAGE_CONFIRMATION, abDeviceId);
    }


}

My procedure:

  1. I press Run in the Geotab Runner, the message displayed in the console is Done:b54, Done:b55 (number increasing for each call).
  2. I watch for any raw bytes coming to my Android device inside the RxMessage method in ThirdParty.java, but nothing gets printed.

Example:

public void RxMessage(byte[] abData) {
        Log.d(TAG, "RxMessage: abData.length:" + abData.length + ", abData:" + Arrays.toString(abData));
 // Other code ....       
}

How to make this work? Am I missing something?

@spehj
Copy link
Author

spehj commented Aug 1, 2024

Additional information with logs for 3 cases:

  1. Normal BuildMessage method (creates confirmation for HOS_ENHANCED_ID_WITH_ACK message) - device ID 4141 (0x2D, 0x10 in bytes)
  2. Confirmation sending device ID with flags as per documentation (binary data wrapping etc) - BuildHandshakeConfirmationMessageWithFlags method with device ID between 4200 and 4299 (4208 in this case)
  3. Confirmation sending device ID without flags - BuildHandshakeMessageWithoutFlags method with device ID between 4200 and 4299 (4208 in this case)

Modified ThirdParty.java

I modified the ThirdParty.java a bit to test all 3 cases.

I created two methods to test cases 2 and 3: BuildHandshakeConfirmationMessageWithFlags and BuildHandshakeMessageWithoutFlags. Full modified ThirdParty.java file content:

/*****************************************************************************
 *
 * Copyright (C) 2017, Geotab Inc.
 *
 ******************************************************************************
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************/
package com.geotab.AOA;

import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicBoolean;

import android.content.Context;
import android.os.Handler;
import android.util.Log;

import com.geotab.ioxproto.IoxMessaging;
import com.google.protobuf.InvalidProtocolBufferException;

public class ThirdParty {
    private static final String TAG = ThirdParty.class.getSimpleName();    // Used for error logging

    private static final HOSData mHOSdata = new HOSData();

    private static final byte MESSAGE_HANDSHAKE = 1;
    private static final byte MESSAGE_ACK = 2;
    private static final byte MESSAGE_GO_DEVICE_DATA = 0x21;
    private static final byte PROTOBUF_DATA_FROM_GO = 0x26;
    private static final byte MESSAGE_CONFIRMATION = (byte) 0x81;
    private static final byte CUSTOM_IOX = (byte) 0x1D;
    private static final byte MESSAGE_STATUS_DATA = (byte) 0x80;
    private static final byte TP_FREE_FORMAT_DATA = (byte) 0x82;
    private static final byte TP_DEVICE_INFO_RECEIVED = (byte) 0x83;
    private static final byte TP_HOS_ACK = (byte) 0x84;
    static final byte PROTOBUF_DATA_TO_GO = (byte) 0x8c;
    private static final byte MESSAGE_SYNC = 0x55;
    private static final byte[] HOS_ENHANCED_ID_WITH_ACK = new byte[]{0x2D, 0x10, 0x00, 0x00}; //
    // private static final byte[] HOS_ENHANCED_ID_WITH_ACK = new byte[]{0x60, 0x10, 0x00, 0x00};
    // private static final int DEVICE_ID = 4141; // device ID that works
    private static final int DEVICE_ID = 4208; // this device ID does not work


    static final ThirdPartyMessage[] THIRD_PARTY_MESSAGE_DEFINEs = new ThirdPartyMessage[]
            {
                    new ThirdPartyMessage("-BYPASS-", (byte) 0, null),
                    new ThirdPartyMessage("STATUS: OUTSIDE TEMPERATURE", MESSAGE_STATUS_DATA, new byte[]{0x35, 0x00}),        // 53
                    new ThirdPartyMessage("STATUS: ENGINE WARNING LIGHT", MESSAGE_STATUS_DATA, new byte[]{0x24, 0x00}),        // 36
                    new ThirdPartyMessage("STATUS: PARK BRAKE", MESSAGE_STATUS_DATA, new byte[]{0x31, 0x00}),                // 49
                    new ThirdPartyMessage("FREE FORMAT", TP_FREE_FORMAT_DATA, null),
                    new ThirdPartyMessage("DEVICE INFO", TP_DEVICE_INFO_RECEIVED, null),
                    new ThirdPartyMessage("HOS ACK", TP_HOS_ACK, null),
                    new ThirdPartyMessage("PROTOBUF PUB/SUB", PROTOBUF_DATA_TO_GO, null),
            };

    private final Lock mLock = new ReentrantLock();
    private final Condition mEvent = mLock.newCondition();

    private byte[] mabMessage;
    private boolean mfAckReceived, mfHandshakeReceived, mfMessageToSend;

    private AccessoryControl mAccessoryControl;
    private StateMachine mStateMachine;
    private final Handler mHandler;
    private IOXListener mIOXListener;

    public enum State {
        SEND_SYNC, WAIT_FOR_HANDSHAKE, SEND_CONFIRMATION, PRE_IDLE, IDLE, WAIT_FOR_ACK
    }

    // Constructor
    public ThirdParty(AccessoryControl accessory, Context context, IOXListener ioxListener) {
        mfHandshakeReceived = false;
        mfAckReceived = false;
        mfMessageToSend = false;
        mAccessoryControl = accessory;
        mHandler = new Handler(context.getMainLooper());
        mIOXListener = ioxListener;
        mStateMachine = new StateMachine();
        new Thread(mStateMachine).start();        // Run as a separate thread
    }

    // State machine to handle the third party protocol
    private class StateMachine implements Runnable {
        private State eState = State.SEND_SYNC;
        private AtomicBoolean fRunning = new AtomicBoolean(true);

        public void run() {
            Log.i(TAG, "Third party SM started");

            while (fRunning.get()) {
                mLock.lock();        // The lock is needed for await and atomic access to flags/buffers

                try {
                    notifyStateChanged(eState);
                    switch (eState) {
                        case SEND_SYNC: {
                            byte[] abMessage = new byte[]{MESSAGE_SYNC};
                            mAccessoryControl.write(abMessage);
                            eState = State.WAIT_FOR_HANDSHAKE;
                            Log.d("THIRD_PARTY", "SEND_SYNC");
                            break;
                        }
                        case WAIT_FOR_HANDSHAKE: {
                            // Waits for the handshake message or resends sync every 1s
                            mEvent.await(1000, TimeUnit.MILLISECONDS);

                            if (mfHandshakeReceived) {
                                eState = State.SEND_CONFIRMATION;
                            } else {
                                eState = State.SEND_SYNC;
                            }
                            break;
                        }
                        case SEND_CONFIRMATION: {
                            /// Test with HOS_ENHANCED_ID_WITH_ACK
                            // byte[] abMessage = BuildMessage(MESSAGE_CONFIRMATION, HOS_ENHANCED_ID_WITH_ACK); // Uncomment this
                            /// Test with flags
                            // byte[] abMessage = BuildHandshakeConfirmationMessageWithFlags(); // Uncomment this
                            /// Test without flags
                             byte[] abMessage = BuildHandshakeMessageWithoutFlags(); // Uncomment this
                            Log.d(TAG, "SEND_CONFIRMATION: abMessage.length:" + abMessage.length + ", abMessage:" + Arrays.toString(abMessage));
                            mAccessoryControl.write(abMessage);
                            eState = State.PRE_IDLE;
                            break;
                        }
                        case PRE_IDLE: {
                            mfHandshakeReceived = false;
                            mfAckReceived = false;
                            mfMessageToSend = false;
                            eState = State.IDLE;
                            break;
                        }
                        case IDLE: {
                            // Sleep and wait for a handshake or a message to send
                            mEvent.await();

                            if (mfHandshakeReceived) {
                                eState = State.SEND_CONFIRMATION;
                            } else if (mfMessageToSend) {
                                mAccessoryControl.write(mabMessage);
                                eState = State.WAIT_FOR_ACK;
                            }
                            break;
                        }
                        case WAIT_FOR_ACK: {
                            // Wait for the ack or reset after 5s
                            mEvent.await(5000, TimeUnit.MILLISECONDS);

                            if (mfAckReceived) {
                                eState = State.PRE_IDLE;
                            } else {
                                eState = State.SEND_SYNC;
                            }
                            break;
                        }
                        default: {
                            eState = State.SEND_SYNC;
                            break;
                        }
                    }

                } catch (InterruptedException e) {
                    Log.w(TAG, "Exception during await", e);
                } finally {
                    mLock.unlock();
                }
            }
        }

        // Stop the thread
        public void close() {
            Log.i(TAG, "Shutting down third party SM");

            mLock.lock();
            try {
                fRunning.set(false);
                mfHandshakeReceived = false;
                mfAckReceived = false;
                mfMessageToSend = false;
                mEvent.signal();
            } finally {
                mLock.unlock();
            }
        }
    }

    // Signal the state machine to stop
    public void close() {
        if (mStateMachine != null)
            mStateMachine.close();
    }

    // Encapsulate a message to be sent
    public void TxMessage(byte bType, byte[] abData) {
        Log.d(TAG, "TxMessage:" + bType + ", abData.length:" + abData.length);
        mLock.lock();
        try {
            mabMessage = BuildMessage(bType, abData);
            Log.d(TAG, "TxMessage: mabMessage.length:" + mabMessage.length + ", mabMessage:" + Arrays.toString(mabMessage));
            mfMessageToSend = true;
            mEvent.signal();
        } finally {
            mLock.unlock();
        }
    }

    // Checks if a received message matches the expected third party format
    public void RxMessage(byte[] abData) {
        Log.d(TAG, "RxMessage: abData.length:" + abData.length + ", abData:" + Arrays.toString(abData));

        // Check length
        if (abData == null || abData.length < 6) {
            Log.e(TAG, "RxMessage: Bad Data length!");
            return;
        }

        // Check structure
        byte bSTX = abData[0];
        byte bLength = abData[2];
        byte bETX = abData[abData.length - 1];

        if (bSTX != 0x02 || bETX != 0x03) {
            Log.e(TAG, "RxMessage: Bad Data format!");
            return;
        }


        // Check checksum
        byte[] abChecksum = new byte[]{abData[abData.length - 3], abData[abData.length - 2]};
        byte[] abCalcChecksum = CalcChecksum(abData, bLength + 3);

        if (!Arrays.equals(abChecksum, abCalcChecksum)) {
            Log.e(TAG, "RxMessage: Bad Data Checksum!");
            return;
        }

        byte bType = abData[1];

        switch (bType) {
            case MESSAGE_HANDSHAKE:
                mLock.lock();
                try {
                    mfHandshakeReceived = true;
                    mEvent.signal();
                } finally {
                    mLock.unlock();
                }
                break;

            case MESSAGE_ACK:
                mLock.lock();
                try {
                    mfAckReceived = true;
                    mEvent.signal();
                } finally {
                    mLock.unlock();
                }
                break;

            case MESSAGE_GO_DEVICE_DATA:
                ExtractHOSData(abData);

                byte[] abAck = new byte[]{};
                mabMessage = BuildMessage(TP_HOS_ACK, abAck);
                mAccessoryControl.write(mabMessage);
                break;
            case PROTOBUF_DATA_FROM_GO:
                try {
                    byte[] mDate = new byte[abData.length - 6];
                    System.arraycopy(abData, 3, mDate, 0, mDate.length);
                    IoxMessaging.IoxFromGo ioxFromGoMsg = IoxMessaging.IoxFromGo.parseFrom(mDate);
                    Log.d(TAG, "RxMessage: PROTOBUF_DATA_FROM_GO MsgCase:"
                            + ioxFromGoMsg.getMsgCase());
                    if (mIOXListener != null && mHandler != null) {
                        mHandler.post(() -> {
                            mIOXListener.onIOXReceived(ioxFromGoMsg);
                        });
                    }
                } catch (InvalidProtocolBufferException e) {
                    Log.e(TAG, "RxMessage: Failed to decode the protobuf data\n"
                            + e.getMessage());
                }
                break;

            // Case some other message type
            default:
                Log.d(TAG, "RxMessage: Unknown message type: " + bType);
                break;
        }
    }

    // Assemble a third party message
    private byte[] BuildMessage(byte bType, byte[] abData) {
        byte[] abMessage = new byte[abData.length + 6];

        abMessage[0] = 0x02;
        abMessage[1] = bType;
        abMessage[2] = (byte) abData.length;

        System.arraycopy(abData, 0, abMessage, 3, abData.length);

        int iLengthUpToChecksum = abData.length + 3;
        byte[] abCalcChecksum = CalcChecksum(abMessage, iLengthUpToChecksum);
        System.arraycopy(abCalcChecksum, 0, abMessage, iLengthUpToChecksum, 2);

        abMessage[abMessage.length - 1] = 0x03;

        return abMessage;
    }

    private byte[] BuildHandshakeConfirmationMessageWithFlags() {
        byte[] abMessage = new byte[10];

        // Start of Text
        abMessage[0] = 0x02;

        // Message Type
        abMessage[1] = MESSAGE_CONFIRMATION;

        // Message Body Length
        abMessage[2] = 4;

        // External Device ID (little-endian)
        abMessage[3] = (byte) (DEVICE_ID & 0xFF);
        abMessage[4] = (byte) ((DEVICE_ID >> 8) & 0xFF);

        // Flags
        byte flags = 0x06; // Handshake ACK = 1, Binary data wrapping = 1, Self-powered = 0
        abMessage[5] = flags;

        // Checksum (Fletcher's checksum)
        byte[] abChecksum = CalcChecksum(abMessage, 6);
        abMessage[6] = abChecksum[0];
        abMessage[7] = abChecksum[1];

        // End of Text
        abMessage[8] = 0x03;

        return abMessage;
    }

    // Assemble the handshake message with device ID and without flags
    private byte[] BuildHandshakeMessageWithoutFlags(){
        byte[] abDeviceId = new byte[]{
                (byte) ((DEVICE_ID >> 8) & 0xFF),
                (byte) (DEVICE_ID & 0xFF),
                0x00, 0x00
        };
        return BuildMessage(MESSAGE_CONFIRMATION, abDeviceId);
    }

    // Calculate the Fletcher's checksum over the given bytes
    private byte[] CalcChecksum(byte[] abData, int iLength) {
        byte[] abChecksum = new byte[]{0x00, 0x00};

        for (int i = 0; i < iLength; i++) {
            abChecksum[0] += abData[i];
            abChecksum[1] += abChecksum[0];
        }

        return abChecksum;
    }

    public void ExtractHOSData(byte[] abData) {
        synchronized (mHOSdata) {
            Log.d("NewTest", "ExtractHOSData: " + abData.length);
            ByteBuffer abConvert;

            byte[] abDateTime = new byte[4];
            System.arraycopy(abData, 3, abDateTime, 0, abDateTime.length);
            abConvert = ByteBuffer.wrap(abDateTime).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            int iDateTime = abConvert.getInt();
            Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            c.clear();
            c.set(2002, Calendar.JANUARY, 1);        // (Units given in seconds since Jan 1, 2002)
            c.add(Calendar.SECOND, iDateTime);
            SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US);
            mHOSdata.sDateTime = dataFormat.format(c.getTime());

            byte[] abLatitude = new byte[4];
            System.arraycopy(abData, 7, abLatitude, 0, abLatitude.length);
            abConvert = ByteBuffer.wrap(abLatitude).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            int iLatitude = abConvert.getInt();
            mHOSdata.Latitude = (float) iLatitude / 10000000;    // (Units given in 10^-7)

            byte[] abLogitude = new byte[4];
            System.arraycopy(abData, 11, abLogitude, 0, abLogitude.length);
            abConvert = ByteBuffer.wrap(abLogitude).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            int iLogitude = abConvert.getInt();
            mHOSdata.Longitude = (float) iLogitude / 10000000;    // (Units given in 10^-7)

            mHOSdata.iRoadSpeed = abData[15];

            byte[] abPRM = new byte[2];
            System.arraycopy(abData, 16, abPRM, 0, abPRM.length);
            abConvert = ByteBuffer.wrap(abPRM).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            mHOSdata.iRPM = abConvert.getShort();
            mHOSdata.iRPM /= 4;            // Convert to RPM (Units given in 0.25)

            byte[] abOdometer = new byte[4];
            System.arraycopy(abData, 18, abOdometer, 0, abOdometer.length);
            abConvert = ByteBuffer.wrap(abOdometer).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            mHOSdata.iOdometer = abConvert.getInt();    // (Units given in 0.1/km)

            byte bStatus = abData[22];
            mHOSdata.sStatus = "";

            if ((bStatus & (1 << 0)) != 0)
                mHOSdata.sStatus += "GPS Latched | ";
            else
                mHOSdata.sStatus += "GPS Invalid | ";

            if ((bStatus & (1 << 1)) != 0)
                mHOSdata.sStatus += "IGN on | ";
            else
                mHOSdata.sStatus += "IGN off | ";

            if ((bStatus & (1 << 2)) != 0)
                mHOSdata.sStatus += "Engine Data | ";
            else
                mHOSdata.sStatus += "No Engine Data | ";

            if ((bStatus & (1 << 3)) != 0)
                mHOSdata.sStatus += "Date/Time Valid | ";
            else
                mHOSdata.sStatus += "Date/Time Invalid | ";

            if ((bStatus & (1 << 4)) != 0)
                mHOSdata.sStatus += "Speed From Engine | ";
            else
                mHOSdata.sStatus += "Speed From GPS | ";

            if ((bStatus & (1 << 5)) != 0)
                mHOSdata.sStatus += "Distance From Engine | ";
            else
                mHOSdata.sStatus += "Distance From GPS | ";

            byte[] abTripOdometer = new byte[4];
            System.arraycopy(abData, 23, abTripOdometer, 0, abTripOdometer.length);
            abConvert = ByteBuffer.wrap(abTripOdometer).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            mHOSdata.iTripOdometer = abConvert.getInt();    // (Units given in 0.1/km)

            byte[] abEngineHours = new byte[4];
            System.arraycopy(abData, 27, abEngineHours, 0, abEngineHours.length);
            abConvert = ByteBuffer.wrap(abEngineHours).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            mHOSdata.iEngineHours = abConvert.getInt();        // Already in units of 0.1h

            byte[] abTripDuration = new byte[4];
            System.arraycopy(abData, 31, abTripDuration, 0, abTripDuration.length);
            abConvert = ByteBuffer.wrap(abTripDuration).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            mHOSdata.iTripDuration = abConvert.getInt();        // Units of seconds

            byte[] abVehicleId = new byte[4];
            System.arraycopy(abData, 35, abVehicleId, 0, abVehicleId.length);
            abConvert = ByteBuffer.wrap(abVehicleId).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            mHOSdata.iVehicleId = abConvert.getInt();

            byte[] abDriverId = new byte[4];
            System.arraycopy(abData, 39, abDriverId, 0, abDriverId.length);
            abConvert = ByteBuffer.wrap(abDriverId).order(java.nio.ByteOrder.LITTLE_ENDIAN);
            mHOSdata.iDriverId = abConvert.getInt();
        }

        updateHOSTextFromThread();
    }

    public synchronized HOSData getHOSData() {
        return mHOSdata;
    }

    // Update text on the UI thread from another thread
    private void updateHOSTextFromThread() {
        HOSData dataHOS = getHOSData();
        if (mHandler != null && mIOXListener != null) {
            mHandler.post(() -> mIOXListener.onUpdateHOSText(dataHOS));
        }
    }

    private void showStatusMsg(final String msg) {
        Log.i(TAG, msg);
        if (mHandler != null && mIOXListener != null) {
            mHandler.post(() -> mIOXListener.onStatusUpdate(msg));
        }
    }

    private void notifyStateChanged(ThirdParty.State state) {
        Log.i(TAG, "notifyStateChanged " + state.name());
        if (mHandler != null && mIOXListener != null) {
            mHandler.post(() -> mIOXListener.onIOXStateChanged(state));
        }
    }
}

Logs

Logs for case 1:

2024-08-01 08:45:37.240 12918-12963 ThirdParty              com.geotab.AOA                       I  Third party SM started
2024-08-01 08:45:37.241 12918-12963 ThirdParty              com.geotab.AOA                       I  notifyStateChanged SEND_SYNC
2024-08-01 08:45:37.245 12918-12963 ThirdParty              com.geotab.AOA                       I  notifyStateChanged WAIT_FOR_HANDSHAKE
2024-08-01 08:45:37.246 12918-12964 ThirdParty              com.geotab.AOA                       D  RxMessage: abData.length:58, abData:[2, 33, 52, 12, 51, 122, 42, 0, -110, 59, 27, 0, 93, -97, 8, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, 71, 65, 85, 54, 66, 69, 85, 69, 80, 50, 90, 50, 118, -40, 3]
2024-08-01 08:45:37.353 12918-12964 ThirdParty              com.geotab.AOA                       D  RxMessage: abData.length:6, abData:[2, 1, 0, 3, 8, 3]
2024-08-01 08:45:37.355 12918-12963 ThirdParty              com.geotab.AOA                       I  notifyStateChanged SEND_CONFIRMATION
2024-08-01 08:45:37.362 12918-12963 ThirdParty              com.geotab.AOA                       D  SEND_CONFIRMATION: abMessage.length:10, abMessage:[2, -127, 4, 45, 16, 0, 0, -60, 12, 3]
2024-08-01 08:45:37.363 12918-12963 ThirdParty              com.geotab.AOA                       I  notifyStateChanged PRE_IDLE
2024-08-01 08:45:37.366 12918-12963 ThirdParty              com.geotab.AOA                       I  notifyStateChanged IDLE
2024-08-01 08:45:39.377 12918-12964 ThirdParty              com.geotab.AOA                       D  RxMessage: abData.length:58, abData:[2, 33, 52, 19, 51, 122, 42, 0, -110, 59, 27, 0, 93, -97, 8, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, 71, 65, 85, 54, 66, 69, 85, 69, 80, 50, 90, 50, -124, -20, 3]
2024-08-01 08:45:41.376 12918-12964 ThirdParty              com.geotab.AOA                       D  RxMessage: abData.length:58, abData:[2, 33, 52, 21, 51, 122, 42, 0, -110, 59, 27, 0, 93, -97, 8, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, 71, 65, 85, 54, 66, 69, 85, 69, 80, 50, 90, 50, -120, -124, 3]

HOS data is being receved as expected.

  1. Logs for case 2:
2024-08-01 08:47:40.879 13622-13672 ThirdParty              com.geotab.AOA                       I  Third party SM started
2024-08-01 08:47:40.879 13622-13672 ThirdParty              com.geotab.AOA                       I  notifyStateChanged SEND_SYNC
2024-08-01 08:47:40.882 13622-13672 ThirdParty              com.geotab.AOA                       I  notifyStateChanged WAIT_FOR_HANDSHAKE
2024-08-01 08:47:40.883 13622-13673 ThirdParty              com.geotab.AOA                       D  RxMessage: abData.length:6, abData:[2, 1, 0, 3, 8, 3]
2024-08-01 08:47:40.884 13622-13672 ThirdParty              com.geotab.AOA                       I  notifyStateChanged SEND_CONFIRMATION
2024-08-01 08:47:40.885 13622-13672 ThirdParty              com.geotab.AOA                       D  SEND_CONFIRMATION: abMessage.length:10, abMessage:[2, -127, 4, 112, 16, 6, 13, 23, 3, 0]
2024-08-01 08:47:40.886 13622-13672 ThirdParty              com.geotab.AOA                       I  notifyStateChanged PRE_IDLE
2024-08-01 08:47:40.886 13622-13672 ThirdParty              com.geotab.AOA                       I  notifyStateChanged IDLE
2024-08-01 08:48:23.408 13622-13673 ThirdParty              com.geotab.AOA                       I  Shutting down third party SM

No data is being received from the device after the confirmation sent from the Android device.

I tried sending the Runner's data, but nothing was logged.

  1. Logs for case 3:
2024-08-01 08:51:33.701 15060-15107 ThirdParty              com.geotab.AOA                       I  Third party SM started
2024-08-01 08:51:33.701 15060-15107 ThirdParty              com.geotab.AOA                       I  notifyStateChanged SEND_SYNC
2024-08-01 08:51:33.702 15060-15107 ThirdParty              com.geotab.AOA                       I  notifyStateChanged WAIT_FOR_HANDSHAKE
2024-08-01 08:51:33.704 15060-15108 ThirdParty              com.geotab.AOA                       D  RxMessage: abData.length:6, abData:[2, 1, 0, 3, 8, 3]
2024-08-01 08:51:33.706 15060-15107 ThirdParty              com.geotab.AOA                       I  notifyStateChanged SEND_CONFIRMATION
2024-08-01 08:51:33.707 15060-15107 ThirdParty              com.geotab.AOA                       D  SEND_CONFIRMATION: abMessage.length:10, abMessage:[2, -127, 4, 16, 112, 0, 0, 7, -72, 3]
2024-08-01 08:51:33.708 15060-15107 ThirdParty              com.geotab.AOA                       I  notifyStateChanged PRE_IDLE
2024-08-01 08:51:33.709 15060-15107 ThirdParty              com.geotab.AOA                       I  notifyStateChanged IDLE
2024-08-01 08:57:32.009 15060-15060 ThirdParty              com.geotab.AOA                       I  Shutting down third party SM

Same as case 2 - no data is being received from the device after the confirmation sent from the Android device.

I tried sending the Runner's data, but nothing was logged.

For cases 2 and 3 I used this API call to get data from the Runner to external Android device:

api.call("Add", {
    "typeName": "TextMessage",
    "entity": {
        "device": {"id":"b1"}, // Replace with device ID that should receive the data
        "messageContent": {
            "contentType": "MimeContent",
            "channelNumber": 5,
            "mimeType": "text", // Can be changed to any free format text value
            "binaryDataPacketDelay": "00:00:03.0000000", // Applies a configurable delay of up to 5 seconds in between each sequenced message of a multimessage MIME payload
            "data": "SGVsbG8gV29ybGQ=" // Replace with your data encoded in base64
        },
    "isDirectionToVehicle": true,
    "messageSize": 235 // If unspecified defaults to 235. Max of 1000.
    },
}, function(result) {
    console.log("Done: ", result);
}, function(e) {
    console.error("Failed:", e);
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant