package com.wayne.www.waynelib.fdc;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.hjq.toast.ToastUtils;
import com.wayne.www.waynelib.common.Tuple;
import com.wayne.www.waynelib.fdc.message.ChangeFuelPriceProduct;
import com.wayne.www.waynelib.fdc.message.DeviceClass;
import com.wayne.www.waynelib.fdc.message.FdcMessage;
import com.wayne.www.waynelib.fdc.message.FdcMessageFPStateChange;
import com.wayne.www.waynelib.fdc.message.FdcMessageFdcStarted;
import com.wayne.www.waynelib.fdc.message.FdcMessageFuelModeChange;
import com.wayne.www.waynelib.fdc.message.FdcMessageFuelPointCurrentFuellingStatus;
import com.wayne.www.waynelib.fdc.message.FdcMessageFuelPriceChange;
import com.wayne.www.waynelib.fdc.message.FdcMessageFuelSaleTrx;
import com.wayne.www.waynelib.fdc.message.FdcMessageGenericTypelessMessage;
import com.wayne.www.waynelib.fdc.message.FdcMessageHeartbeat;
import com.wayne.www.waynelib.fdc.message.FuelMode;
import com.wayne.www.waynelib.fdc.message.MD5Crypter;
import com.wayne.www.waynelib.fdc.message.PosData;
import com.wayne.www.waynelib.fdc.message.Product;
import com.wayne.www.waynelib.fdc.message.ServiceRequest;
import com.wayne.www.waynelib.fdc.message.ServiceRequestAuthoriseFuelPoint;
import com.wayne.www.waynelib.fdc.message.ServiceRequestChangeFuelPrice;
import com.wayne.www.waynelib.fdc.message.ServiceRequestClearFuelSaleTrx;
import com.wayne.www.waynelib.fdc.message.ServiceRequestFreeFuelPoint;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGenericTypelessMessage;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGetAvailableFuelSaleTrxs;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGetCountrySettings;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGetDeviceState;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGetDspConfiguration;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGetFuelPointTotals;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGetFuelSalesTrxDetails;
import com.wayne.www.waynelib.fdc.message.ServiceRequestGetProductTable;
import com.wayne.www.waynelib.fdc.message.ServiceRequestHeartbeat;
import com.wayne.www.waynelib.fdc.message.ServiceRequestLockFuelSaleTrx;
import com.wayne.www.waynelib.fdc.message.ServiceRequestLogOffV07;
import com.wayne.www.waynelib.fdc.message.ServiceRequestLogOnV07;
import com.wayne.www.waynelib.fdc.message.ServiceRequestReserveFuelPoint;
import com.wayne.www.waynelib.fdc.message.ServiceRequestUnlockFuelSaleTrx;
import com.wayne.www.waynelib.fdc.message.ServiceResponse;
import com.wayne.www.waynelib.fdc.message.ServiceResponseAuthoriseFuelPoint;
import com.wayne.www.waynelib.fdc.message.ServiceResponseChangeFuelPrice;
import com.wayne.www.waynelib.fdc.message.ServiceResponseClearFuelSaleTrx;
import com.wayne.www.waynelib.fdc.message.ServiceResponseFreeFuelPoint;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGenericTypelessMessage;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGetAvailableFuelSaleTrxs;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGetCountrySettings;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGetDspConfiguration;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGetFPState;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGetFuelPointTotals;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGetFuelSalesTrxDetails;
import com.wayne.www.waynelib.fdc.message.ServiceResponseGetProductTable;
import com.wayne.www.waynelib.fdc.message.ServiceResponseLockFuelSaleTrx;
import com.wayne.www.waynelib.fdc.message.ServiceResponseLogOff;
import com.wayne.www.waynelib.fdc.message.ServiceResponseLogOn;
import com.wayne.www.waynelib.fdc.message.ServiceResponseReserveFuelPoint;
import com.wayne.www.waynelib.fdc.message.ServiceResponseUnlockFuelSaleTrx;
import com.wayne.www.waynelib.ui.BuildConfig;
import com.wayne.www.waynelib.util.HsLogTagFlag;
import com.wayne.www.waynelib.util.HsLogUtils;
import com.wayne.www.waynelib.util.Util;

import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UnknownFormatFlagsException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;

/**
 * Created by Think on 4/19/2016.
 */
public class FdcClient implements OnFdcServiceResponseReceivedListener {
    private static String LOG_TAG = "FdcClient";
    private Logger fileLogger = LoggerFactory.getLogger(FdcClient.class);
    //connectionString:'Host=192.168.1.5,Port=4710,ClientId=101,ClientName=WDFCTest,PortB=4710,PortC=4710'
    //private byte[] remoteServerIpBytes = new byte[]{(byte) 192, (byte) 168, (byte) 199, (byte) 241};
    // for default virtual device
    //private byte[] remoteServerIpBytes = new byte[]{(byte) 10, (byte) 0, (byte) 2, (byte) 2};
    private String remoteServerIpString;
    private int remoteServerPort = 4711;

    // should unique for each POS, just a string with length <=8.
    private String defaultApplicationSender = "QJ_AND_HS";

    // should unique for each POS, uncorrelated from POS id (like 100, 101, 102)
    private int defaultWorkstationId = 8888;

    // for channel A
    private Socket clientSocket;

    private InetAddress remoteServerIpAdrs;
    private FdcClientState state = FdcClientState.Connecting;

    // 16 hashed bytes in the header.
    final int UnderlyingMessageExtraHashBytesCount = 16;

    /*finally, I know the channel B and C were combined into A, what the fuck! */

    //heartbeat related=====================
    private Timer heartbeatTimer;
    private TimerTask heartbeatTimerTask;

    /*every this time, will send a heartbeat actively, from the default implementation of FDC server, it'll wait 30 seconds for a client's heartbeat,
    then actively disconnect if no heart beat received. */
    private static final int heartbeatSendInterval = 10000;

    /*If during this time, still no heartbeat received from other peer, will trigger a disconnection event.
        from the default implementation of FDC server, it'll send by 10 seconds for each.
        NOTE, valid Fdc response or Message will also reset the timeout counter.*/
    private static final int heartbeatReceivedTimeout = 30000;
    // every 1000ms to check, the less the more accuracy, must less than
    private static final int heartbeatTimeControlAccurate = 1000;
    private int heartbeat_ElapsedCycleFromLastSend = 0;
    private int heartbeat_ElapsedCycleFromLastReceive = 0;
    //heartbeat related end======================

    private boolean shouldStopListening = false;
    // channel A only for sending service request.
    //private InputStream inputStream_channel_A;
    private OutputStream outputStream_channel_A;

    private List<OnFdcClientStateChangedListener> onFdcClientStateChangedListeners = new ArrayList<>();
    private List<OnFdcServiceResponseReceivedListener> onFdcServiceResponseReceivedListeners = new ArrayList<>();
    // unsolicited event
    private List<OnFdcMessageReceivedListener> onFdcMessageReceivedListeners = new ArrayList<>();

    /* set read() timeout to 2000ms, give a chance to quite read earlier rather than wait forever.*/
//    private static final int internal_tcp_read_timedOut = 15000;
    private static final int internal_tcp_read_timedOut = 2000;
    private AtomicBoolean isStarted = new AtomicBoolean(false);


    private ReentrantLock outputStreamLock = new ReentrantLock();
    private int connectTingTimes;
    private OnFdcClientStateChangedListener stopChange;
    private Thread worker;
    private boolean isOneRestart = true;//超时支持重连一次

    public void setDefaultApplicationSender(String value) {
        this.defaultApplicationSender = value;
    }

    public void setDefaultWorkstationId(String value) {
        this.defaultApplicationSender = value;
    }

    public String getDefaultApplicationSender() {
        return this.defaultApplicationSender;
    }

    public int getDefaultWorkstationId() {
        return this.defaultWorkstationId;
    }

//    @Override
//    public void onFdcMessageReceived(FdcClient sender, FdcMessage fdcMessage) {
//        if (fdcMessage != null && !fdcMessage.getMessageType().equals("FDCHeartBeat")) return;
//        // reset the disconnection heart timer.
//        //fileLogger.trace("<====Received FdcServerHeartBeat Message");
//        this.heartbeat_ElapsedCycleFromLastReceive = 0;
//    }

    @Override
    public void onServiceResponseReceived(FdcClient sender, ServiceResponse serviceResponse) {
        if (serviceResponse.getRequestType().equals("LogOn") && serviceResponse.getOverallResult().equalsIgnoreCase("Success")) {
            startHeartbeatTimer();
            //sendGetCountrySettingsRequestAsync();
        }
    }

    private static class SingleInstanceLoader {
        private static final FdcClient INSTANCE = new FdcClient();
    }

    public enum FdcClientState {
        // disconnected since timed out for receive other peer's heartbeat. need re-init the tcp to other peer.
        DisconnectedByHeartbeat,
        // connected to cashPos server.
        Connected,
        // disconnected just happened, and now is trying to re-connect to it.
        Connecting,
        //Fdc client fully stopped, and no connection will be made.
        Stopped,
        MyAddReConnect,//LzPeng增加的状态，如果网络异常会去重新连接View
    }

    private FdcClient() {
        //this.addOnFdcMessageReceivedListeners(this);
        this.addOnFdcServiceResponseReceivedListeners(this);
    }

    /* trigger send and receive heartbeat timer.*/
    public void startHeartbeatTimer() {
        if (clientSocket == null || !clientSocket.isConnected()) {
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "clientSocket no connect ");
            return;
        }
        FdcClient.this.sendHeartbeatRequestAsync();//连接成功后发送心跳
        fileLogger.info("Start heartbeat Service request timer");
        heartbeat_ElapsedCycleFromLastSend = 0;
        heartbeat_ElapsedCycleFromLastReceive = 0;
        if (this.heartbeatTimer == null)
            this.heartbeatTimer = new Timer();
        if (this.heartbeatTimerTask == null)
            this.heartbeatTimerTask = new TimerTask() {
                @Override
                public void run() {
                    if (heartbeat_ElapsedCycleFromLastSend * heartbeatTimeControlAccurate >= heartbeatSendInterval) {//每heartbeatSendInterval秒发送一次心跳
                        boolean succeed = FdcClient.this.sendHeartbeatRequestAsync();
                        if (!succeed) {//当前这次心跳发送失败了，但是不做什么操作
                            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Previous heartbeat request Send failed, do nothing here");
                            fileLogger.error("Previous heartbeat request Send failed, do nothing here");
                            //stopHeartbeatTimer();
                        }
                        heartbeat_ElapsedCycleFromLastSend = 0;
                    } else {
                        heartbeat_ElapsedCycleFromLastSend++;
                    }
                    if (heartbeat_ElapsedCycleFromLastReceive * heartbeatTimeControlAccurate >= heartbeatReceivedTimeout) {
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Remote server Heartbeat have not been received, and exceed defined max time, will trigger disconnection");
                        fileLogger.error("Remote server Heartbeat have not been received, and exceed defined max time, will trigger disconnection");
//                    stopHeartbeatTimer();
                        safeCloseSocketResource();
                        changeFdcClientStateAndNotifyAllListeners(FdcClientState.DisconnectedByHeartbeat);
                        heartbeat_ElapsedCycleFromLastReceive = 0;
                    } else {
                        heartbeat_ElapsedCycleFromLastReceive++;
                    }
                    if (BuildConfig.DEBUG)
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, heartbeat_ElapsedCycleFromLastSend + "/" + heartbeat_ElapsedCycleFromLastReceive);
                }
            };
        this.heartbeatTimer.schedule(heartbeatTimerTask, 0, this.heartbeatTimeControlAccurate);//每隔heartbeatTimeControlAccurate调用一次run方法
    }

    /**
     * @desc 停止倒计时心跳
     * @info Created by LzPeng on 2020/3/31 13:57
     */
    private void stopHeartbeatTimer() {
//        this.heartbeatTimer.cancel();
//        this.heartbeatTimer.purge();
        try {
            if (null != this.heartbeatTimerTask) {
                this.heartbeatTimerTask.cancel();
                this.heartbeatTimerTask = null;
                HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Stop heartbeat Service request timerTask");
            }
            if (null != this.heartbeatTimer) {
                this.heartbeatTimer.cancel();
                this.heartbeatTimer.purge();
                this.heartbeatTimer = null;
                HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Stop heartbeat Service request timer");
            }
        } catch (Exception e) {
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.getMessage());
        }


    }

    /*
     * get default singleton instance of PumpControl
     * */
    public static FdcClient getDefault() {
        return SingleInstanceLoader.INSTANCE;
    }

    private void safeCloseSocketResource() {
        fileLogger.error("Safe close all resources");
        stopHeartbeatTimer();
        if (this.outputStream_channel_A != null)
            try {
                this.outputStream_channel_A.close();
                this.outputStream_channel_A = null;
                HsLogUtils.o(HsLogTagFlag.FDC_TAG, "outputStream_channel_A is null");
            } catch (IOException e) {
                HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.getMessage());
            }
        if (this.clientSocket != null)
            try {
                this.clientSocket.close();
                this.clientSocket = null;
                HsLogUtils.o(HsLogTagFlag.FDC_TAG, "clientSocket is null");
            } catch (Exception e) {
                HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.getMessage());
            }
        if (null != worker) {
            worker = null;
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "worker is null");
        }
    }

    /*
     * Start the connection to remote FC (fdc server) with IP specified.
     * @param remoteServerIp, like: "10.0.2.2";
     * */
    public void start(String remoteServerIp, int remoteServerPort, String applicationSender, int workstationId, OnFdcClientStateChangedListener stopChange) {
        if (!this.isStarted.compareAndSet(false, true)) {
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Invalid call for start() since FdcClient still on running..");
//            restart();
            return;
//            fileLogger.error("Invalid call for start() since FdcClient still on running...");
//            throw new IllegalStateException("Stop the FdcClient first, then start()");
        }
        if (this.stopChange == null && null != stopChange)
            this.stopChange = stopChange;
        String ipPatternStr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}";
        // null indicates it's a restart() call.
        if (remoteServerIp != null) {
            if (!Pattern.matches(ipPatternStr, remoteServerIp)) {
                ToastUtils.show("remoteServerIp not a IP address: " + remoteServerIp);
//                throw new IllegalArgumentException("remoteServerIp not a IP address: " + remoteServerIp);
            }

            String[] stringBytes = remoteServerIp.split("\\.");
//            this.remoteServerIpBytes = new byte[]{
//                    Byte.parseByte(stringBytes[0]) & 0xFF,
//                    Byte.parseByte(stringBytes[1]),
//                    Byte.parseByte(stringBytes[2]),
//                    Byte.parseByte(stringBytes[3])};
            this.remoteServerIpString = remoteServerIp;
            this.remoteServerPort = remoteServerPort;
            this.defaultApplicationSender = applicationSender;
            HsLogUtils.o(defaultApplicationSender);
//            this.defaultWorkstationId = defaultApplicationSender;
            this.defaultWorkstationId = workstationId;
            try {
                this.remoteServerIpAdrs = InetAddress.getByName(remoteServerIp);
            } catch (UnknownHostException e) {
                e.printStackTrace();
                ToastUtils.show("remoteServerIp got UnknownHostException");
//                throw new IllegalArgumentException("remoteServerIp got UnknownHostException");
            }
        }
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, "start..");
        fileLogger.info("Start FdcClient (" + this.remoteServerIpAdrs.getHostAddress() + ":" + Integer.toString(remoteServerPort) + ", workstationId: " + defaultWorkstationId + ", applicationSender: " + defaultApplicationSender + ")");
        this.shouldStopListening = false;
        if (worker == null)
            worker = new Thread(() -> {
                // connecting socket to remote host.
                while (!shouldStopListening) {
                    HsLogUtils.o(HsLogTagFlag.FDC_TAG, "shouldStopListening is false");
                    try {
                        //==============init channel A
                        if (FdcClient.this.clientSocket == null) {
//                            synchronized (Socket.class) {
//                                if (FdcClient.this.clientSocket == null) {
                            FdcClient.this.clientSocket = new Socket();
                            HsLogUtils.o(HsLogTagFlag.FDC_TAG, FdcClient.this.clientSocket + "/");
//                                }
//                            }
                        }
                        changeFdcClientStateAndNotifyAllListeners(FdcClientState.Connecting);
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, FdcClient.this.clientSocket + "/");
                        fileLogger.info("Initializing Channel A socket to "
                                + FdcClient.this.remoteServerIpString + ", port " + FdcClient.this.remoteServerPort);
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, FdcClient.this.clientSocket + "/");
                        if (null != FdcClient.this.clientSocket) {
                            FdcClient.this.clientSocket.setSoTimeout(internal_tcp_read_timedOut);
                            HsLogUtils.o(HsLogTagFlag.FDC_TAG, FdcClient.this.clientSocket + "/");
                            HsLogUtils.o(HsLogTagFlag.FDC_TAG, FdcClient.this.remoteServerIpAdrs + ":" + FdcClient.this.remoteServerPort + "  ------Connecting-----" + internal_tcp_read_timedOut);
                            FdcClient.this.clientSocket.connect(new InetSocketAddress(FdcClient.this.remoteServerIpAdrs, FdcClient.this.remoteServerPort), internal_tcp_read_timedOut);
                            fileLogger.info("Initialized Channel A socket");
                            if (null != FdcClient.this.clientSocket.getOutputStream())
                                FdcClient.this.outputStream_channel_A = FdcClient.this.clientSocket.getOutputStream();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        if (e instanceof SocketTimeoutException) {//超时或者找不到路由地址直接停止并且return掉
                            HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.getMessage());
//                            if (isOneRestart) {
//                                isOneRestart = false;
//                                restartAsync(null);
//                            } else {
//                                safeCloseSocketResource();
//                                changeFdcClientStateAndNotifyAllListeners(FdcClientState.MyAddReConnect);
//                            }
//                            isStarted.set(false);
                            safeCloseSocketResource();
                            changeFdcClientStateAndNotifyAllListeners(FdcClientState.MyAddReConnect);
                            isStarted.set(false);
                            return;
                        }
                        if (e instanceof NoRouteToHostException) {
                            safeCloseSocketResource();
                            changeFdcClientStateAndNotifyAllListeners(FdcClientState.MyAddReConnect);
                            isStarted.set(false);
                            return;
                        }
                        if (e instanceof SocketException) {
                            safeCloseSocketResource();
                            changeFdcClientStateAndNotifyAllListeners(FdcClientState.MyAddReConnect);
                            isStarted.set(false);
                            return;
                        }
                        fileLogger.error("Got an IOException when connecting (via channel A) to Fdc Server, will retry later...");
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.getMessage());
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, e);
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.toString());
                        HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.getCause());
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e1) {
                        }
                        continue;
                    }

                    changeFdcClientStateAndNotifyAllListeners(FdcClientState.Connected);
                    isOneRestart = true;//可以支持重连一次
                    byte[] msgLenBytes = new byte[4];
                    while (!shouldStopListening && state != FdcClientState.DisconnectedByHeartbeat) {
                        try {
                            int previousTotalReadCount = 0;
                            do {
                                try {
                                    fileLogger.trace("TCP, Channel A, listening for Msg len...");
                                    int currentReadCount =
                                            FdcClient.this.clientSocket.getInputStream().read(msgLenBytes,
                                                    previousTotalReadCount,
                                                    msgLenBytes.length - previousTotalReadCount);
                                    // detect if need re-connect.
                                    if (currentReadCount < 0) {
                                        fileLogger.error("currentReadCount < 0 when listening for Msg len");
                                        //break;
                                        HsLogUtils.e("read negative value when reading MessageLenBytes");
//                                    throw new IOException("read negative value when reading MessageLenBytes");
                                    }

                                    previousTotalReadCount += currentReadCount;
                                } catch (SocketTimeoutException te) {
                                    if (!shouldStopListening && state != FdcClientState.DisconnectedByHeartbeat) {
                                        // it's OK to see timed out for this read().
                                        // nothing will break, let's continue to keep reading.
                                        continue;
                                    } else {
                                        HsLogUtils.e("cancel reading message (header) loop.");
                                        throw new InterruptedException("cancel reading message (header) loop.");
                                    }
                                }
                            } while (previousTotalReadCount < msgLenBytes.length);

                            int len = Util.byteArray4ToInt(msgLenBytes);
                            fileLogger.debug("TCP, Channel A, read 4 bytes MsgLen: " + len);
                            //999999 is an experience value
                            if (len <= 0 || len > 999999) {
                                fileLogger.error("TCP, Channel A, Ignore since len is " + len + ", unlikely a valid msg.");
                                if (len > 0) {
                                    byte[] strangeMsgBuffer = new byte[600];
                                    try {
                                        // let's try read more to see what exactly the strange message is
                                        FdcClient.this.clientSocket.getInputStream().read(
                                                Util.concat(msgLenBytes, strangeMsgBuffer),
                                                0, 600);
                                        // start parsing Xml data.
                                        String strangeMsg =
                                                new String(strangeMsgBuffer, "UTF-8");
                                        fileLogger.error("      invalid msg raw is: " + Util.bytesToHexString(strangeMsgBuffer)
                                                + "\r\n decoded as: \r\n" + strangeMsg);
                                    } catch (Exception te) {
                                        fileLogger.error("      failed to read invalid msg: " + te.getMessage());
                                    }
                                }

//                            throw new InterruptedException("Msg Len abnormal exception.");
                            }

                            //start reading msg body.
                            fileLogger.trace("TCP, Channel A, waiting read msg body (with " + internal_tcp_read_timedOut + "ms timeout)...");
                            // 16 hashed bytes.
                            //final int extraBytesCount = 16;
                            len += UnderlyingMessageExtraHashBytesCount;
                            byte[] msgBodyBytes = new byte[len];
                            previousTotalReadCount = 0;
                            do {
                                try {
                                    fileLogger.trace("        reading... (previousTotalReadCount: " + previousTotalReadCount + ")");
                                    int currentReadCount = FdcClient.this.clientSocket.getInputStream().read(msgBodyBytes,
                                            previousTotalReadCount, msgBodyBytes.length - previousTotalReadCount);
                                    // detect if need re-connect.
                                    if (currentReadCount < 0) {
                                        fileLogger.error("currentReadCount < 0 when listening for Msg body");
                                        //break;
                                        HsLogUtils.e("Channel A, read negative value when reading MessageBodyBytes");
//                                    throw new IOException("Channel A, read negative value when reading MessageBodyBytes");
                                    }

                                    previousTotalReadCount += currentReadCount;
                                } catch (SocketTimeoutException ste) {
                                    /* a timed out occurred in reading msgbody */
                                    safeCloseSocketResource();
                                    fileLogger.error("TCP, Channel A, waiting for read msg body timed out, will re-init the TCP connection");
                                    HsLogUtils.o(HsLogTagFlag.FDC_TAG, ste.getMessage());
//                                throw new TimeoutException("Channel A, read msg body timed out and exceed max retry times");
                                }
                            } while (previousTotalReadCount < msgBodyBytes.length);
                            try {
                                fileLogger.trace("Read " + msgBodyBytes.length + " bytes(with extra 16 bytes hash), Firing the msg to XmlMessageHandler");
                                FireToXmlMessageHandler(msgBodyBytes);
                            } catch (Exception noName) {
                                noName.printStackTrace();
                                HsLogUtils.o(HsLogTagFlag.FDC_TAG, noName.getMessage());
                                fileLogger.error("Ignore the exception");
                            }
                        } catch (IOException ee) {
                            ee.printStackTrace();
                            safeCloseSocketResource();
                            if (null != ee.getMessage() && ee.getMessage().contains("Connection timed out")) {
                                changeFdcClientStateAndNotifyAllListeners(FdcClientState.MyAddReConnect);
                                isStarted.set(false);
                            }
                            fileLogger.error("TCP, channel A read bytes error: " + ee.getMessage() + "\r\n will re-init the whole Socket");
                            break;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            fileLogger.error("TCP, channel A got InterruptedException, will re-init the whole Socket");
                            HsLogUtils.o(HsLogTagFlag.FDC_TAG, e.getMessage());
                            safeCloseSocketResource();
                            break;
                        } catch (Exception eee) {
                            eee.printStackTrace();
                            fileLogger.error("TCP, channel A got generic Exception: " + eee.getMessage() + "\r\n will re-init the whole Socket");
                            HsLogUtils.o(HsLogTagFlag.FDC_TAG, eee.getMessage());
                            safeCloseSocketResource();
                            break;
                        }
                    }
                }

                HsLogUtils.o(HsLogTagFlag.FDC_TAG, "isStarted set false");
                isStarted.set(false);
                fileLogger.warn("FDC client quit listening");
            }
            );
        worker.start();

    }

    private void FireToXmlMessageHandler(byte[] xmlMessageBytesWithHashHeader) {
        Thread t = new Thread(() -> {
            String xmlMessage = "";
            try {
                // start parsing Xml data.
                xmlMessage =
                        new String(
                                Arrays.copyOfRange(xmlMessageBytesWithHashHeader, UnderlyingMessageExtraHashBytesCount, xmlMessageBytesWithHashHeader.length),
                                "UTF-8");
                if (fileLogger.isTraceEnabled() || fileLogger.isDebugEnabled()) {
                    fileLogger.debug("Read Xml string msg data: ");
                    for (String line : xmlMessage.split("\r\n")) {
                        if (fileLogger.isTraceEnabled()) {
                            fileLogger.debug("    " + line);
                        } else if (fileLogger.isDebugEnabled()) {
                            if (xmlMessage.contains("MessageType=\"FPStateChange\"")) {
                                fileLogger.debug("  ...FPStateChange msg...");
                            } else if (xmlMessage.contains("MessageType=\"FDCHeartBeat\"")) {
                                fileLogger.debug("  ...Heartbeat msg...");
                            } else
                                fileLogger.debug("    " + line);
                        }
                    }
                }

                fileLogger.trace("      XmlMessageHandler is kicking off (Response listeners: "
                        + onFdcServiceResponseReceivedListeners.size() + ", Message listeners: " + onFdcMessageReceivedListeners.size()
                        + ")");
                if (xmlMessage.contains("<ServiceResponse ")) {
//                    HsLogUtils.o(HsLogTagFlag.FDC_TAG, "ServiceResponse");
                    heartbeat_ElapsedCycleFromLastReceive = 0;
                    HsLogUtils.o(HsLogTagFlag.FDC_TAG, "ServiceResponse");
                    ServiceResponse response = FdcClient.this.deserializeFdcServiceResponse(xmlMessage);
                    for (int i = 0; i < onFdcServiceResponseReceivedListeners.size(); i++)
                        if (onFdcServiceResponseReceivedListeners != null && onFdcServiceResponseReceivedListeners.size() > 0)
                            onFdcServiceResponseReceivedListeners.get(i)
                                    .onServiceResponseReceived(FdcClient.this, response);
                } else if (xmlMessage.contains("<FDCMessage ")) {
//                    HsLogUtils.o(HsLogTagFlag.FDC_TAG, "FDCMessage");
                    heartbeat_ElapsedCycleFromLastReceive = 0;
                    HsLogUtils.o(HsLogTagFlag.FDC_TAG, "FDCMessage");
                    FdcMessage fdcMessage = FdcClient.this.deserializeFdcMessage(xmlMessage);
                    if (null != onFdcMessageReceivedListeners && onFdcMessageReceivedListeners.size() > 0) {
                        for (int i = 0; i < onFdcMessageReceivedListeners.size(); i++) {
                            onFdcMessageReceivedListeners.get(i)
                                    .onFdcMessageReceived(FdcClient.this, fdcMessage);
                        }
                    }
                } else {
                    fileLogger.error("Unknown xml msg data read from remote peer:\r\n" + Util.bytesToHexString(xmlMessageBytesWithHashHeader));
                }
            } catch (Exception exx) {
                fileLogger.error("FireToXmlMessageHandler with xmlMsg:" + xmlMessage + " exceptioned: " + exx.getMessage() + "\r\n" + Log.getStackTraceString(exx));
            }
        });
        t.start();
    }

    /*stop and start with previous argument. */
    public void restart() {
        stop();
        start(null, -1, null, 0, null);
    }

    /**
     * @desc 异步重连，先stopc成功后start
     * @info Created by LzPeng on 2020/5/7 17:07
     */
    public void restartAsync() {
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, "restartAsync");
        stopAsync(new OnFdcClientStateChangedListener() {
            @Override
            public void onFdcClientStateChanged(FdcClient sender, FdcClientState newState) {
                start(null, -1, null, 0, stopChange);
            }
        });
    }


    /* may block caller since need wait background worker to quit, if it is called via UI thread, use stopAsync(...) instead */
    public void stop() {
        fileLogger.warn("Stopping the FdcClient");
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Stopping the FdcClient");
        this.shouldStopListening = true;
        safeCloseSocketResource();
        // wait the worker thread to fully quit TCP listening.
        while (isStarted.get()) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Stopping the FdcClient finish");
            isStarted.set(false);
        }
        fileLogger.warn("Stopped the FdcClient");
    }

    public void stopAsync(final OnFdcClientStateChangedListener callback) {
        fileLogger.warn("Stopping the FdcClient with Async");
        this.shouldStopListening = true;
        safeCloseSocketResource();
        Thread noName = new Thread(new Runnable() {
            @Override
            public void run() {
                // wait the worker thread to fully quit TCP listening.
                while (isStarted.get()) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Stopped the FdcClient with Async finish");
                fileLogger.warn("Stopped the FdcClient with Async");
                if (null != stopChange)
                    stopChange.onFdcClientStateChanged(FdcClient.this, FdcClientState.Stopped);
                if (callback != null)
                    callback.onFdcClientStateChanged(FdcClient.this, FdcClientState.Stopped);
            }
        });
        noName.start();
    }

    public void addOnFdcClientStateChangedListeners(OnFdcClientStateChangedListener listener) {
        this.onFdcClientStateChangedListeners.add(listener);
    }

    public boolean removeOnFdcClientStateChangedListeners(OnFdcClientStateChangedListener listener) {
        return this.onFdcClientStateChangedListeners.remove(listener);
    }

    public void addOnFdcServiceResponseReceivedListeners(OnFdcServiceResponseReceivedListener listener) {
        this.onFdcServiceResponseReceivedListeners.add(listener);
    }

    public boolean removeOnFdcServiceResponseReceivedListeners(OnFdcServiceResponseReceivedListener listener) {
        return this.onFdcServiceResponseReceivedListeners.remove(listener);
    }

    public void addOnFdcMessageReceivedListeners(OnFdcMessageReceivedListener listener) {
        this.onFdcMessageReceivedListeners.add(listener);
    }

    public boolean removeOnFdcMessageReceivedListeners(OnFdcMessageReceivedListener listener) {
        return this.onFdcMessageReceivedListeners.remove(listener);
    }

    public void changeFdcClientStateAndNotifyAllListeners(FdcClientState newState) {
        // only notify when state switched.
        if (this.state == newState) {
            if (this.state == FdcClientState.Connecting) {//连接中超过3次则退出主界面去重连
                connectTingTimes++;
                if (connectTingTimes == 1) return;//第一次可能是断开重连
                if (connectTingTimes >= 2) {//重连3次还不成功则回去连接界面
                    connectTingTimes = 0;
                    HsLogUtils.o(HsLogTagFlag.FDC_TAG, "???");
                    safeCloseSocketResource();
                    changeFdcClientStateAndNotifyAllListeners(FdcClientState.MyAddReConnect);
                }

            }
            return;
        }
        if (this.state == FdcClientState.Connected) {//连接成功标志置为0
            connectTingTimes = 0;
        }
        fileLogger.info("State changed from '" + this.state + "' to '" + newState
                + "' (listener count: " + this.onFdcClientStateChangedListeners.size() + ")");
        this.state = newState;
        for (int i = 0; i < this.onFdcClientStateChangedListeners.size(); i++) {
            this.onFdcClientStateChangedListeners.get(i).onFdcClientStateChanged(this, this.state);
        }
    }


    private MD5Crypter md5Crypter = new MD5Crypter();

    /* the bytes send on TCP is constructed with: [4 bytes RequestMsgLen] + [HashedBytes] + [RequestMsgBodyBytes],
     * while the [HashedBytes] is the hash value for [RequestMsgBodyBytes + KeyBytes], total 16 bytes.
     * */
    private byte[] serializeFdcServiceRequest(ServiceRequest request) throws Exception {
        Serializer serializer = new Persister();
        final List<Byte> result = new ArrayList<>();
        OutputStream os = new OutputStream() {
            @Override
            public void write(int oneByte) throws IOException {
//                byte[] noName = new byte[]{(byte) oneByte};
//                String onechar = new String(noName, StandardCharsets.UTF_8);
                result.add((byte) oneByte);
            }
        };
        serializer.write(request, os);
        byte[] _requestBodyBytes = new byte[result.size()];
        for (int i = 0; i < result.size(); i++) {
            _requestBodyBytes[i] = result.get(i);
        }

        byte[] xmlHeader = "<?xml version=\"1.0\"?>\n".getBytes(Charset.forName("UTF-8"));
        byte[] requestBodyBytes = Util.concat(xmlHeader, _requestBodyBytes);
        String reverseGenerateRequestBodyString = new String(requestBodyBytes, Charset.forName("UTF-8"));
        if (reverseGenerateRequestBodyString.contains("RequestType=\"POSHeartBeat\""))
            fileLogger.trace("====>Prepare sending POSHeartBeat ServiceRequest");
        else
            fileLogger.debug("====>Prepare sending ServiceRequest: \n" + reverseGenerateRequestBodyString);
        byte[] requestMsgLen = Util.intToByteArray4Reverse(requestBodyBytes.length);
        //Log.v(LOG_TAG, "requestMsgLen: " + Util.bytesToHexString(requestMsgLen));

        //Log.v(LOG_TAG, "md5Crypter.getPassphrase(): " + Util.bytesToHexString(md5Crypter.getPassphrase()));

        byte[] pendingForHashing = Util.concat(requestBodyBytes, md5Crypter.getPassphrase());
        //Log.v(LOG_TAG, "pendingForHashing: " + Util.bytesToHexString(pendingForHashing));

        byte[] hashed = md5Crypter.computeHash(pendingForHashing);
        //Log.v(LOG_TAG, "hashed: " + Util.bytesToHexString(hashed));

        byte[] finalBytes = Util.concat(Util.concat(requestMsgLen, hashed), requestBodyBytes);
        //Log.v(LOG_TAG, "ServiceRequest: " + request.getRequestType() + " fully converted into bytes: " + Util.bytesToHexString(finalBytes));
        return finalBytes;
    }

    private void safeWrite(byte[] data) throws IOException {
        this.outputStreamLock.lock();
        try {
            if (this.outputStream_channel_A != null)
                this.outputStream_channel_A.write(data);
        } finally {
            this.outputStreamLock.unlock();
        }
    }

    private ServiceResponse deserializeFdcServiceResponse(String xmlData) {
        Serializer serializer = new Persister();
        ServiceResponse result = null;
        try {
            if (xmlData.contains("RequestType=\"LogOn\"")) {
                result = serializer.read(ServiceResponseLogOn.class, xmlData);
            } else if (xmlData.contains("RequestType=\"LogOff\"")) {
                result = serializer.read(ServiceResponseLogOff.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetCountrySettings\"")) {
                result = serializer.read(ServiceResponseGetCountrySettings.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetProductTable\"")) {
                result = serializer.read(ServiceResponseGetProductTable.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetDSPConfiguration\"")) {
                result = serializer.read(ServiceResponseGetDspConfiguration.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetConfiguration\"")) {
                result = serializer.read(ServiceResponseGetDspConfiguration.class, xmlData);
            } else if (xmlData.contains("RequestType=\"ReserveFuelPoint\"")) {
                result = serializer.read(ServiceResponseReserveFuelPoint.class, xmlData);
            } else if (xmlData.contains("RequestType=\"FreeFuelPoint\"")) {
                result = serializer.read(ServiceResponseFreeFuelPoint.class, xmlData);
            } else if (xmlData.contains("RequestType=\"ClearFuelSaleTrx\"")) {
                result = serializer.read(ServiceResponseClearFuelSaleTrx.class, xmlData);
            } else if (xmlData.contains("RequestType=\"AuthoriseFuelPoint\"")) {
                result = serializer.read(ServiceResponseAuthoriseFuelPoint.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetAvailableFuelSaleTrxs\"")) {
                result = serializer.read(ServiceResponseGetAvailableFuelSaleTrxs.class, xmlData);
            } else if (xmlData.contains("RequestType=\"ClearFuelSaleTrx\"")) {
                result = serializer.read(ServiceResponseClearFuelSaleTrx.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetFuelSaleTrxDetails\"")) {
                result = serializer.read(ServiceResponseGetFuelSalesTrxDetails.class, xmlData);
            } else if (xmlData.contains("RequestType=\"LockFuelSaleTrx\"")) {
                result = serializer.read(ServiceResponseLockFuelSaleTrx.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetFPState\"")) {
                result = serializer.read(ServiceResponseGetFPState.class, xmlData);
            } else if (xmlData.contains("RequestType=\"UnlockFuelSaleTrx\"")) {
                result = serializer.read(ServiceResponseUnlockFuelSaleTrx.class, xmlData);
            } else if (xmlData.contains("RequestType=\"ChangeFuelPrice\"")) {
                result = serializer.read(ServiceResponseChangeFuelPrice.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GetTotals\"")) {
                result = serializer.read(ServiceResponseGetFuelPointTotals.class, xmlData);
            } else if (xmlData.contains("RequestType=\"GenericTypelessMessage\"")) {
                result = serializer.read(ServiceResponseGenericTypelessMessage.class, xmlData);
            }
        } catch (Exception ee) {
            fileLogger.error(LOG_TAG, "deserializeFdcServiceResponse got exception");
            ee.printStackTrace();
        }

        return result;
    }

    private FdcMessage deserializeFdcMessage(String xmlData) {
        Serializer serializer = new Persister();
        FdcMessage result = null;
        try {
            if (xmlData.contains("MessageType=\"FDCHeartBeat\"")) {
                result = serializer.read(FdcMessageHeartbeat.class, xmlData);
            } else if (xmlData.contains("MessageType=\"FDCStarted\"")) {
                result = serializer.read(FdcMessageFdcStarted.class, xmlData);
            } else if (xmlData.contains("MessageType=\"FPStateChange\"")) {
                result = serializer.read(FdcMessageFPStateChange.class, xmlData);
            } else if (xmlData.contains("MessageType=\"FuelSaleTrx\"")) {
                result = serializer.read(FdcMessageFuelSaleTrx.class, xmlData);
            } else if (xmlData.contains("MessageType=\"FuelModeChange\"")) {
                result = serializer.read(FdcMessageFuelModeChange.class, xmlData);
            } else if (xmlData.contains("MessageType=\"CurrentFuellingStatus\"")) {
                result = serializer.read(FdcMessageFuelPointCurrentFuellingStatus.class, xmlData);
            } else if (xmlData.contains("MessageType=\"FuelPriceChange\"")) {
                result = serializer.read(FdcMessageFuelPriceChange.class, xmlData);
            } else if (xmlData.contains("MessageType=\"GenericTypelessMessage\"")) {
                result = serializer.read(FdcMessageGenericTypelessMessage.class, xmlData);
            } else {
                throw new UnknownFormatFlagsException("Unknown type of FdcMessage");
            }
        } catch (Exception ee) {
            fileLogger.error("deserializeFdcMessage got exception");
            ee.printStackTrace();
        }

        return result;
    }

    /**
     * @desc 发送心跳
     * @info Created by LzPeng on 2020/3/31 14:01
     */
    private boolean sendHeartbeatRequestAsync() {
        ServiceRequestHeartbeat requestHeartbeat = new ServiceRequestHeartbeat();
        requestHeartbeat.setApplicationSender(this.defaultApplicationSender);
        requestHeartbeat.setWorkstationID(this.defaultWorkstationId);
        requestHeartbeat.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        List<PosData> posDatas = new ArrayList<>();
        posDatas.add(posData);
        requestHeartbeat.setPosData(posDatas);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestHeartbeat);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestHeartbeat);
            this.safeWrite(fullBytes);
            fileLogger.debug("sendHeartbeatRequestAsync successfully.");
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "sendHeartbeatRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "sendHeartbeatRequestAsync exceptioned: " + e.getMessage());
            fileLogger.error("sendHeartbeatRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /* make the service request async call support inline response call back */
    private void buildInlineServiceResponseCallback(final String serviceRequestId, final OnFdcServiceResponseReceivedListener callback, final int timeout) {
        if (callback == null || timeout == 0)
            return;
        fileLogger.debug("Queue request with id: " + serviceRequestId + " with timedout: " + timeout + "ms");
        final AtomicBoolean callbackCalled = new AtomicBoolean(false);
        OnFdcServiceResponseReceivedListener onetimeListener = null;
        // avoid final effect
        final Tuple<OnFdcServiceResponseReceivedListener, Integer> noName = new Tuple<>(onetimeListener, 1);

        // make it on UI thread.
        final Handler timeoutHandler = new Handler(Looper.getMainLooper());
        final Runnable timeoutHandlerRunable = () -> {
            if (false == callbackCalled.compareAndSet(false, true)) return;
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "Response timed out for request id: " + serviceRequestId + " (timeout set to: " + timeout + "ms)");
            fileLogger.error("Response timed out for request id: " + serviceRequestId + " (timeout set to: " + timeout + "ms)");
            FdcClient.this.removeOnFdcServiceResponseReceivedListeners(noName.first);
            // 2nd parameter null indicate timedout.
            callback.onServiceResponseReceived(FdcClient.this, null);
        };
        onetimeListener = (final FdcClient sender, final ServiceResponse serviceResponse) -> {
            if (Util.isRequestIdEqual(serviceResponse.getRequestID(), serviceRequestId)) {
                if (false == callbackCalled.compareAndSet(false, true)) return;
                timeoutHandler.removeCallbacks(timeoutHandlerRunable);
                FdcClient.this.removeOnFdcServiceResponseReceivedListeners(noName.first);
                // make it on UI thread.
                final Handler uiHandler = new Handler(Looper.getMainLooper());
                uiHandler.post(() -> {
                    callback.onServiceResponseReceived(sender, serviceResponse);
                });
            }
        };
        noName.first = onetimeListener;
        this.addOnFdcServiceResponseReceivedListeners(onetimeListener);
        timeoutHandler.postDelayed(timeoutHandlerRunable, timeout);
    }

    public boolean sendGetCountrySettingsRequestAsync() {
        ServiceRequestGetCountrySettings requestGetCountrySettings = new ServiceRequestGetCountrySettings();
        requestGetCountrySettings.setApplicationSender(this.defaultApplicationSender);
        requestGetCountrySettings.setWorkstationID(this.defaultWorkstationId);
        requestGetCountrySettings.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        requestGetCountrySettings.setSinglePosData(posData);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestGetCountrySettings);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGetCountrySettingsRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGetCountrySettingsRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    //interfaceversion 00.07
    public boolean sendLogonRequestAsync(int responsePort, int unsolicitedPort, String interfaceVersion, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestLogOnV07 requestLogOn = new ServiceRequestLogOnV07();
        requestLogOn.setApplicationSender(this.defaultApplicationSender);
        requestLogOn.setWorkstationID(this.defaultWorkstationId);
        requestLogOn.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosName("");
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        posData.setResponsePort(responsePort);
        posData.setUnsolicitedPort(unsolicitedPort);
        posData.setInterfaceVersion(interfaceVersion);
        requestLogOn.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestLogOn);
        this.buildInlineServiceResponseCallback(requestLogOn.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestLogOn);
            this.safeWrite(fullBytes);
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "sendLogonRequestAsync successfully.");
            fileLogger.info("sendLogonRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            HsLogUtils.o("");
            HsLogUtils.o(HsLogTagFlag.FDC_TAG, "sendLogonRequestAsync exceptioned:");
            fileLogger.error("sendLogonRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendChangeFuelPriceAsync(String productNo, String newPrice, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestChangeFuelPrice requestChangeFuelPrice = new ServiceRequestChangeFuelPrice();
        requestChangeFuelPrice.setApplicationSender(this.defaultApplicationSender);
        requestChangeFuelPrice.setWorkstationID(this.defaultWorkstationId);
        requestChangeFuelPrice.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        ChangeFuelPriceProduct priceChangeProduct = new ChangeFuelPriceProduct();
        priceChangeProduct.setProductNoInAttribute(productNo);
        FuelMode fuelMode = new FuelMode();
        fuelMode.setModeNo(1);
        fuelMode.setPriceNewForSendingToFdcServer(newPrice);
        //fuelMode.setEffectiveDateTime("2016");
        priceChangeProduct.setModeNo(fuelMode);

        posData.setFuelPrice(priceChangeProduct);
        requestChangeFuelPrice.setSinglePosData(posData);

        this.buildInlineServiceResponseCallback(requestChangeFuelPrice.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestChangeFuelPrice);
            this.safeWrite(fullBytes);
            fileLogger.info("sendChangeFuelPriceAsync successfully(productNo: " + productNo + ", newPrice: " + newPrice + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendChangeFuelPriceAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * send log off will trigger FDC server to close the tcp channel and there's no more heart beat response from server anymore.
     *
     * @param callback
     * @param timeout
     * @return
     */
    //interfaceversion 00.07
    public boolean sendLogOffRequestAsync(final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestLogOffV07 requestLogOff = new ServiceRequestLogOffV07();
        requestLogOff.setApplicationSender(this.defaultApplicationSender);
        requestLogOff.setWorkstationID(this.defaultWorkstationId);
        requestLogOff.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        requestLogOff.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestLogOff);
        this.buildInlineServiceResponseCallback(requestLogOff.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestLogOff);
            this.safeWrite(fullBytes);
            fileLogger.info("sendLogOffRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendLogOffRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param deviceId -1 indicates query all available devices state from fdc server.
     * @param callback
     * @param timeout
     * @return
     */
    public boolean sendGetDeviceStateAsync(int deviceId, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestGetDeviceState getDeviceStateRequest = new ServiceRequestGetDeviceState();
        getDeviceStateRequest.setApplicationSender(this.defaultApplicationSender);
        getDeviceStateRequest.setWorkstationID(this.defaultWorkstationId);
        getDeviceStateRequest.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId((deviceId == -1) ? "*" : Integer.toString(deviceId));
        posData.setDeviceClass(deviceClass);
        getDeviceStateRequest.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, getDeviceStateRequest);
        this.buildInlineServiceResponseCallback(getDeviceStateRequest.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(getDeviceStateRequest);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGetDeviceStateAsync successfully(deviceId: " + deviceId + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGetDeviceStateAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param deviceId -1 means query all available pumps
     * @param callback
     * @param timeout
     * @return
     */
    public boolean sendGetAvailableFuelSaleTrxs(int deviceId, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestGetAvailableFuelSaleTrxs getAvailableFuelSaleTrxs = new ServiceRequestGetAvailableFuelSaleTrxs();
        getAvailableFuelSaleTrxs.setApplicationSender(this.defaultApplicationSender);
        getAvailableFuelSaleTrxs.setWorkstationID(this.defaultWorkstationId);
        getAvailableFuelSaleTrxs.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId((deviceId == -1) ? "*" : Integer.toString(deviceId));
        posData.setDeviceClass(deviceClass);
        getAvailableFuelSaleTrxs.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, getAvailableFuelSaleTrxs);
        this.buildInlineServiceResponseCallback(getAvailableFuelSaleTrxs.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(getAvailableFuelSaleTrxs);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGetAvailableFuelSaleTrxs successfully(deviceId: " + deviceId + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGetAvailableFuelSaleTrxs exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendGetProductTableRequestAsync() {
        ServiceRequestGetProductTable requestGetProductTable = new ServiceRequestGetProductTable();
        requestGetProductTable.setApplicationSender(this.defaultApplicationSender);
        requestGetProductTable.setWorkstationID(this.defaultWorkstationId);
        requestGetProductTable.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        requestGetProductTable.setSinglePosData(posData);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestGetProductTable);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGetProductTableRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGetProductTableRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendGetDspConfigurationRequestAsync(final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestGetDspConfiguration requestGetDspConfiguration = new ServiceRequestGetDspConfiguration();
        requestGetDspConfiguration.setApplicationSender(this.defaultApplicationSender);
        requestGetDspConfiguration.setWorkstationID(this.defaultWorkstationId);
        requestGetDspConfiguration.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        requestGetDspConfiguration.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestGetDspConfiguration);
        this.buildInlineServiceResponseCallback(requestGetDspConfiguration.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestGetDspConfiguration);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGetDspConfigurationRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGetDspConfigurationRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param authorizingDeviceId the id of the device which will be authorized, the pump id.
     * @param productBarcodes     which products need to be authorised for fuelling.
     * @param maxTrxAmount
     * @param maxTrxVolume
     * @param callback
     * @param timeout
     * @return
     */
    public boolean sendAuthoriseFuelPointRequestAsync(int authorizingDeviceId, List<Integer> productBarcodes, float maxTrxAmount, float maxTrxVolume, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestAuthoriseFuelPoint requestAuthoriseFuelPoint = new ServiceRequestAuthoriseFuelPoint();
        requestAuthoriseFuelPoint.setApplicationSender(this.defaultApplicationSender);
        requestAuthoriseFuelPoint.setWorkstationID(this.defaultWorkstationId);
        requestAuthoriseFuelPoint.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId(Integer.toString(authorizingDeviceId));
        if (maxTrxAmount != 0) {
            deviceClass.setMaxTrxAmount(maxTrxAmount);
            deviceClass.setMaxTrxVolume(0f);
        } else {
            deviceClass.setMaxTrxVolume(maxTrxVolume);
            deviceClass.setMaxTrxAmount(0f);
        }

        deviceClass.setPayType("PC");

        ArrayList<Product> releaseProducts = new ArrayList<>();
        for (int i = 0; i < productBarcodes.size(); i++) {
            Product p = new Product();
            if (productBarcodes.get(i) == null) {
                fileLogger.error("sendAuthoriseFuelPointRequestAsync failed due to productBarcodes have null values");
                return false;
            } else if (productBarcodes.get(i) == 0) {
                fileLogger.error("sendAuthoriseFuelPointRequestAsync failed due to productBarcodes contains 0 value, which is not allowed for a product barcode, barcode must be range from 1 to 99999999");
                return false;
            }
            p.setProductNoInAttribute(productBarcodes.get(i));
            releaseProducts.add(p);
        }

//        Product product1 = new Product();
//        product1.setProductNoInAttribute(3);
//
//        releaseProducts.add(product1);
        deviceClass.setReleasedProducts(releaseProducts);

//        FuelMode fuelMode = new FuelMode();
//        fuelMode.setModeNo(1);
//
//        deviceClass.setFuelMode(fuelMode);

        deviceClass.setReleaseTokenElement(Integer.toString(authorizingDeviceId));
        // allow other fdc client can pay this trx.
        deviceClass.setLockFuelSaleTrx(false);

//        deviceClass.setReservingDeviceId(this.defaultWorkstationId);//没用到，暂时注释掉
        //Unknown = 0, FullService = 1, Postpaid = 2, Prepaid = 3, OptCardPaid = 4, OptCashPaid = 5, DetectedFromAccumulators = 6, TestFuelling = 7
        deviceClass.setFuellingType("2");
        posData.setDeviceClass(deviceClass);
        requestAuthoriseFuelPoint.setSinglePosData(posData);

        this.buildInlineServiceResponseCallback(requestAuthoriseFuelPoint.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestAuthoriseFuelPoint);
            this.safeWrite(fullBytes);
            fileLogger.info("sendAuthoriseFuelPointRequestAsync successfully(deviceId: " + authorizingDeviceId + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendAuthoriseFuelPointRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }


    public boolean sendReserveFuelPointRequestAsync(int deviceId, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        final ServiceRequestReserveFuelPoint requestReserveFuelPoint = new ServiceRequestReserveFuelPoint();
        requestReserveFuelPoint.setApplicationSender(this.defaultApplicationSender);
        requestReserveFuelPoint.setWorkstationID(this.defaultWorkstationId);
        requestReserveFuelPoint.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId(Integer.toString(deviceId));
        posData.setDeviceClass(deviceClass);
        requestReserveFuelPoint.setSinglePosData(posData);

        this.buildInlineServiceResponseCallback(requestReserveFuelPoint.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestReserveFuelPoint);
            this.safeWrite(fullBytes);
            fileLogger.info("sendReserveFuelPointRequestAsync successfully(deviceId: " + deviceId + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendReserveFuelPointRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendFreeFuelPointRequestAsync(int deviceId) {
        ServiceRequestFreeFuelPoint requestFreeFuelPoint = new ServiceRequestFreeFuelPoint();
        requestFreeFuelPoint.setApplicationSender(this.defaultApplicationSender);
        requestFreeFuelPoint.setWorkstationID(this.defaultWorkstationId);
        requestFreeFuelPoint.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId(Integer.toString(deviceId));
        posData.setDeviceClass(deviceClass);
        requestFreeFuelPoint.setSinglePosData(posData);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestFreeFuelPoint);
            this.safeWrite(fullBytes);
            fileLogger.info("sendFreeFuelPointRequestAsync successfully(deviceId: " + deviceId + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendFreeFuelPointRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendClearFuelSaleTrxRequestAsync(int deviceId, String transactionSeqNo, int releaseTokenAttribute, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestClearFuelSaleTrx requestClearFuelSaleTrx = new ServiceRequestClearFuelSaleTrx();
        requestClearFuelSaleTrx.setApplicationSender(this.defaultApplicationSender);
        requestClearFuelSaleTrx.setWorkstationID(this.defaultWorkstationId);
        requestClearFuelSaleTrx.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId(Integer.toString(deviceId));
        deviceClass.setTransactionSeqNo(transactionSeqNo);
        deviceClass.setReleaseTokenAttribute(Integer.toString(releaseTokenAttribute));
        posData.setDeviceClass(deviceClass);
        requestClearFuelSaleTrx.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestClearFuelSaleTrx);
        this.buildInlineServiceResponseCallback(requestClearFuelSaleTrx.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestClearFuelSaleTrx);
            this.safeWrite(fullBytes);
            fileLogger.info("sendClearFuelSaleTrxRequestAsync successfully(deviceId: " + deviceId + ", trxSeqNo: " + transactionSeqNo + ", releaseToken: " + releaseTokenAttribute + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendClearFuelSaleTrxRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /*get the detail info for a trx.
     * @param transactionSeqNo wildchar * is acceptable by standard doc, means query all trx detail on this device, include Payable and other state trx.   but failed on EMSG FCC, liteFCC is good.
     * */
    public boolean sendGetFuelSalesTrxDetailsRequestAsync(int deviceId, String transactionSeqNo, String releaseToken, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestGetFuelSalesTrxDetails requestGetFuelSalesTrxDetails = new ServiceRequestGetFuelSalesTrxDetails();
        requestGetFuelSalesTrxDetails.setApplicationSender(this.defaultApplicationSender);
        requestGetFuelSalesTrxDetails.setWorkstationID(this.defaultWorkstationId);
        requestGetFuelSalesTrxDetails.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId(Integer.toString(deviceId));
        deviceClass.setTransactionSeqNo(transactionSeqNo);
        if (releaseToken != null && releaseToken != "") {
            deviceClass.setReleaseTokenAttribute(releaseToken);
        } else {
            deviceClass.setReleaseTokenAttribute("12345");
        }
        posData.setDeviceClass(deviceClass);
        requestGetFuelSalesTrxDetails.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestGetFuelSalesTrxDetails);
        this.buildInlineServiceResponseCallback(requestGetFuelSalesTrxDetails.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestGetFuelSalesTrxDetails);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGetFuelSalesTrxDetailsRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGetFuelSalesTrxDetailsRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendLockFuelSaleTrxRequestAsync(int deviceId, String transactionSeqNo, int releaseTokenAttribute, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        final ServiceRequestLockFuelSaleTrx requestLockFuelSaleTrx = new ServiceRequestLockFuelSaleTrx();
        requestLockFuelSaleTrx.setApplicationSender(this.defaultApplicationSender);
        requestLockFuelSaleTrx.setWorkstationID(this.defaultWorkstationId);
        requestLockFuelSaleTrx.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId(Integer.toString(deviceId));
        deviceClass.setTransactionSeqNo(transactionSeqNo);
        deviceClass.setReleaseTokenAttribute(Integer.toString(releaseTokenAttribute));
        posData.setDeviceClass(deviceClass);
        requestLockFuelSaleTrx.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestLockFuelSaleTrx);
        this.buildInlineServiceResponseCallback(requestLockFuelSaleTrx.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestLockFuelSaleTrx);
            this.safeWrite(fullBytes);
            fileLogger.info("sendLockFuelSaleTrxRequestAsync successfully(deviceId: " + deviceId + ", trxSeqNo: " + transactionSeqNo + ", releaseToken: " + releaseTokenAttribute + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendLockFuelSaleTrxRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    public boolean sendUnLockFuelSaleTrxRequestAsync(int deviceId, String transactionSeqNo, int releaseTokenAttribute, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        final ServiceRequestUnlockFuelSaleTrx requestUnlockFuelSaleTrx = new ServiceRequestUnlockFuelSaleTrx();
        requestUnlockFuelSaleTrx.setApplicationSender(this.defaultApplicationSender);
        requestUnlockFuelSaleTrx.setWorkstationID(this.defaultWorkstationId);
        requestUnlockFuelSaleTrx.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId(Integer.toString(deviceId));
        deviceClass.setTransactionSeqNo(transactionSeqNo);
        deviceClass.setReleaseTokenAttribute(Integer.toString(releaseTokenAttribute));
        posData.setDeviceClass(deviceClass);
        requestUnlockFuelSaleTrx.setSinglePosData(posData);
        HsLogUtils.o(HsLogTagFlag.FDC_TAG, requestUnlockFuelSaleTrx);
        this.buildInlineServiceResponseCallback(requestUnlockFuelSaleTrx.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestUnlockFuelSaleTrx);
            this.safeWrite(fullBytes);
            fileLogger.info("sendUnLockFuelSaleTrxRequestAsync successfully(deviceId: " + deviceId + ", trxSeqNo: " + transactionSeqNo + ", releaseToken: " + releaseTokenAttribute + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendUnLockFuelSaleTrxRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param deviceId        -1 for query all pumps, all nozzels, no matter what value in 2nd parameter 'logicalNozzleId'
     * @param logicalNozzleId
     * @param callback
     * @param timeout
     * @return
     */
    public boolean sendGetFuelPointTotalsRequestAsync(int deviceId, int logicalNozzleId, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        final ServiceRequestGetFuelPointTotals requestGetFuelPointTotals = new ServiceRequestGetFuelPointTotals();
        requestGetFuelPointTotals.setApplicationSender(this.defaultApplicationSender);
        requestGetFuelPointTotals.setWorkstationID(this.defaultWorkstationId);
        requestGetFuelPointTotals.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId((deviceId == -1) ? "*" : Integer.toString(deviceId));
        deviceClass.setNozzleNo((logicalNozzleId <= 0) ? 1 : logicalNozzleId);
        posData.setDeviceClass(deviceClass);
        requestGetFuelPointTotals.setSinglePosData(posData);

        this.buildInlineServiceResponseCallback(requestGetFuelPointTotals.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestGetFuelPointTotals);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGetFuelPointTotalsRequestAsync successfully(deviceId: " + deviceId + ", logicalNozzleId: " + logicalNozzleId + ")");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGetFuelPointTotalsRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param deviceId -1 indicates the query is aginst all devices.
     * @param message
     * @return
     */
    public boolean sendGenericTypelessMessageRequestAsync(int deviceId, String message, final OnFdcServiceResponseReceivedListener callback, int timeout) {
        ServiceRequestGenericTypelessMessage requestGenericTypelessMessage = new ServiceRequestGenericTypelessMessage();
        requestGenericTypelessMessage.setApplicationSender(this.defaultApplicationSender);
        requestGenericTypelessMessage.setWorkstationID(this.defaultWorkstationId);
        requestGenericTypelessMessage.setRequestID(Util.pad(Integer.toString(IdDistributer.getAndConsumeId()), "left", 8, '0'));
        PosData posData = new PosData();
        posData.setPosTimeStamp(Util.getNowTimeWithXmlFormat());
        posData.setGenericTypelessMessageData(message);
        DeviceClass deviceClass = new DeviceClass();
        deviceClass.setDeviceId((deviceId == -1) ? "*" : Integer.toString(deviceId));
        posData.setDeviceClass(deviceClass);
        requestGenericTypelessMessage.setSinglePosData(posData);

        this.buildInlineServiceResponseCallback(requestGenericTypelessMessage.getRequestID(), callback, timeout);
        try {
            byte[] fullBytes = serializeFdcServiceRequest(requestGenericTypelessMessage);
            this.safeWrite(fullBytes);
            fileLogger.info("sendGenericTypelessMessageRequestAsync successfully.");
            return true;
        } catch (Exception e) {
            fileLogger.error("sendGenericTypelessMessageRequestAsync exceptioned: " + e.getMessage());
            e.printStackTrace();
            return false;
        }
    }
}
