public class MjComTalker {
 private static final int MAX_DELAY = 3000;
 
 private static final int STX = 0x82;
 private static final int STX_HOLE = 0x00;
 private static final int STX_HEAD = 0xAA;
 private static final int STX_TAIL = 0x55;
 
 private static final String NONAME = new String(new byte[]{0,0,0,0,0,0,0,0});
 
 private static final short EMPTY_SHORT_ARRAY[] = {};
 private static final byte EMPTY_BYTE_ARRAY[] = {};
 
 public static String TEST_IP = "192.168.27.234";
// public static String TEST_IP = "localhost";
 public static int TEST_PORT = 4001;
 /**
  * @param args
  */
 public static void main(String[] args) throws IOException {
  MjComTalker talker = new MjComTalker(TEST_IP, TEST_PORT);
  short[] stateList2 = null;
  System.out.println(new Date());
  long beg = System.nanoTime();
  if (talker.isAfterVersion(1, 6)) {
   for (int i = 0; i < 10; ++i) {
    Random rnd = new Random();
    short[] buf = new short[126];
    for(int idx=0; idx<buf.length; ++idx) 
     buf[idx] = (short)(rnd.nextInt(100)+5);
    stateList2 = talker.readDeviceAddress(1, buf);
    if (stateList2.length>0)
     System.out.println(rnd.nextInt());
    else
     System.out.println("failed read");
   }
  }
  long end = System.nanoTime();
  System.out.println("10 times read cost:");
  System.out.println((end-beg) / 1000000);
  System.out.println(new Date());
  
  System.out.println("continuous adress:");
  for (short i : talker.readDeviceAddress(1, 5, 100)) {
   System.out.println(i);
  }
  
  System.out.println("discrete single adress:");
  for (int i = 0; i < 60; ++i) {
   int v = talker.readDeviceAddressWord(1, i);
   System.out.println(v);
  }
 } /**MJCOM的CRC16算法实现
  * @param buf CRC16的数据源
  * @return CRC16的结果(只有16位有效)
  */
 public static int mjComCRC(byte buf[]) {
  int len = buf.length; 
  int crc,chr,i,j;
  crc = 0xffff;
  for(j=0; j<len; ++j){
      chr = buf[j] & 0xff;
      crc ^= chr;
      for(i=0;i<8;i++) {
          if ((crc & 01)!=0)
              crc = (crc>>1)^0xa001;
          else
              crc = crc>>1;
      }
      crc &= 0xffff;
  }
  return crc;
 }
 
 /**生成stuff串
  * @param data
  * @return
  */
 public static byte[] encodeStuff(byte []data) {
  ByteArrayOutputStream bytes = new ByteArrayOutputStream();
  for(byte b: data) {
   bytes.write(b);
   if (b == (byte)STX) {
    bytes.write((byte)STX_HOLE);
   }
  }
  return bytes.toByteArray();  
 }
 /**解码stuff串
  * @param data
  * @return
  */
 public static byte[] decodeStuff(byte []data) {
  ByteArrayOutputStream bytes = new ByteArrayOutputStream();
  byte prevb = (byte)STX_HOLE;
  for(byte b: data) {
   if (b == (byte)STX_HOLE && prevb == (byte)STX) {
    prevb = b;
    continue;
   }
   prevb = b;
   bytes.write(b);
  }
  return bytes.toByteArray();  
 } 
 
 
 /**生成应用层消息
  * @param dev 设备号
  * @param func 函数号
  * @param data byte数据
  * @return 应用层的消息
  */
 public static byte[] encodeAppMsg(int dev, int func, byte []data) {
  ByteArrayOutputStream bytes = new ByteArrayOutputStream();
  bytes.write(dev);
  bytes.write(func);
  int wordlen = data.length / 2;
  bytes.write(wordlen >> 8);
  bytes.write(wordlen);
  bytes.write(data, 0, data.length);
  return bytes.toByteArray();  
 }
 /**解读应用层消息
  * @param data 应用层的消息 
  * @return 
  */
 public static MjComAppMessage decodeAppMsg(byte []appMsg) {
  MjComAppMessage appMessage = new MjComAppMessage();
  if (appMsg.length < 4) {
   return null;
  }
  appMessage.setDev(appMsg[0] & 0xff);
  appMessage.setFunc(appMsg[1] & 0xff);
  int wordLen = ArraysUtils.getWordInBytes(appMsg, 2);
  //TODO: when the mjcom 700 device fix the bug , uncomment this code 
//  if (appMsg.length < wordLen*2 + 4) {
//   return null;
//  }
  appMessage.setWordLen( wordLen );
  appMessage.setData(Arrays.copyOfRange(appMsg,
    4,
    Math.min(4 + wordLen * 2, appMsg.length)));
  return appMessage;
 } 
 
 /**生成网络通讯层消息
  * @param appMsg 应用层消息
  * @return 网络通讯层消息
  */
 public static byte[] encodeNetMsg(byte []appMsg) {
  ByteArrayOutputStream bytes_stuff = new ByteArrayOutputStream();
  ByteArrayOutputStream bytes = new ByteArrayOutputStream();
  
  int dataCRC = mjComCRC(Arrays.copyOfRange(appMsg, 1, appMsg.length));
  bytes_stuff.write(appMsg, 0, appMsg.length);
  
  bytes_stuff.write(dataCRC); //CRC填充时用小端结构
  bytes_stuff.write(dataCRC >> 8);
  
  byte[] stuffed = encodeStuff(bytes_stuff.toByteArray());
  bytes.write(STX);
  bytes.write(STX_HEAD);
  bytes.write(stuffed, 0, stuffed.length);
  bytes.write(STX);
  bytes.write(STX_TAIL);
  return bytes.toByteArray();  
 } 
 
 /**解码网络通讯层消息
  * @param appMsg 应用层消息
  * @return 网络通讯层消息
  */
 public static byte[] decodeNetMsg(byte []netMsg) {
  if (netMsg.length < 4) {
   return EMPTY_BYTE_ARRAY;
  }
  byte[] noSTX = Arrays.copyOfRange(netMsg, 2, netMsg.length - 2);
  byte[] unstaffed = decodeStuff(noSTX);
  if (unstaffed.length < 2) {
   return EMPTY_BYTE_ARRAY;
  }
  return Arrays.copyOfRange(unstaffed, 0, unstaffed.length - 2);  //no CRC
 }  
  private String deviceIpAddress = "";
 private int    deviceIpPort = 0;
 private boolean versionIsAsked = false;
 public MjComTalker() {
 } 
 
 public MjComTalker(String deviceIpAddress, int deviceIpPort) {
  this.deviceIpAddress = deviceIpAddress;
  this.deviceIpPort    = deviceIpPort;
 }
 
 public boolean isConnected(){
  return false;
 } private byte[] MjComTalk(byte[] appMsg) throws  IOException {
  Socket socket = null;
  try {
   socket = new ZhiYuanJrTalker(deviceIpAddress, deviceIpPort);
   byte[] netMsg = encodeNetMsg(appMsg);
   
   OutputStream out = socket.getOutputStream();
   InputStream   in = socket.getInputStream();
   out.write(netMsg); //发送消息
   out.flush();
   
   ByteArrayOutputStream bytes = new ByteArrayOutputStream(1000);
   
   socket.setSoTimeout(MAX_DELAY);
   //long startTime = System.currentTimeMillis(); 
   boolean reachEnd = false;
   
   byte [] buf = new byte[2048];
   int len = 0;
   byte lastc = 0; 
   while(!reachEnd  && (len = in.read(buf)) > 0) {
    if (len > 2 && buf[len-1] == (byte)STX_TAIL && buf[len-2] == (byte)STX) {
     reachEnd = true;
    }
    if (buf[0] == (byte)STX_TAIL && lastc == (byte)STX) {
     reachEnd = true;
    }
    bytes.write(buf, 0, len);
    lastc = buf[len-1];
    
   }
   
   return decodeNetMsg(bytes.toByteArray());
  }
  catch (Exception e) {
   return new byte[0];
  }
  finally{
   try{
    if (socket!=null)
     socket.close();
   }catch (Exception e) {
   }
  }
 }
 
 public int getVersion(int dev) {
  try {
   byte[] appMsg = encodeAppMsg(1, 43, new byte[]{0, 10});
   byte[] backMsg = MjComTalk(appMsg);
   return backMsg[25];
  }catch (Exception e) {
   return 0;
  }
 } 
 
 
 public boolean isAfterVersion(int dev, int version) {
  return getVersion(dev) >= version;
 }
 
 
 
 public short[] readDeviceAddress(int dev, short addrs[]) {
  try {
   if (true) {
    versionIsAsked = isAfterVersion(dev, 6);
   }
   short[] withHead = ArraysUtils.addShortHead(addrs);
   byte[] appMsg = encodeAppMsg(1, 72, ArraysUtils.padShortToByte(withHead));
   byte[] backMsg = MjComTalk(appMsg);
   MjComAppMessage appMessage = decodeAppMsg(backMsg);
   return ArraysUtils.padByteToShort(appMessage.getData());
  }catch (Exception e) {
   e.printStackTrace();
   return EMPTY_SHORT_ARRAY;
  }  
 }
}