// License: GPL. For details, see LICENSE file. /** * Communicate with a GlobalSat DG-100 GPS mouse and data logger. * @author Stefan Kaintoch, Raphael Mack * license: GPLv3 or any later version */ package org.openstreetmap.josm.plugins.globalsat; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.kaintoch.gps.globalsat.dg100.ByteHelper; import org.kaintoch.gps.globalsat.dg100.Dg100Config; import org.kaintoch.gps.globalsat.dg100.FileInfoRec; import org.kaintoch.gps.globalsat.dg100.GpsRec; import org.kaintoch.gps.globalsat.dg100.Response; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.gpx.GpxData; import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack; import org.openstreetmap.josm.data.gpx.WayPoint; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import gnu.io.CommPortIdentifier; import gnu.io.PortInUseException; import gnu.io.SerialPort; import gnu.io.UnsupportedCommOperationException; /** * @author ramack * */ public class GlobalsatDg100 { public class ConnectionException extends Exception { ConnectionException(Exception cause) { super(cause); } ConnectionException(String msg) { super(msg); } } public static final int TIMEOUT = 2000; public static final int TRACK_TYPE = 1; /** delete file: A0 A2 00 02 BC 01 00 BD B0 B3 */ /*private static byte dg100CmdSwitch2Nmea[] = { (byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x18, (byte) 0x81, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x25, (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0xB0, (byte) 0xB3 };*/ /** delete file: A0 A2 00 02 BC 01 00 BD B0 B3 */ /*private static byte dg100CmdEnterGMouse[] = { (byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x02, (byte) 0xBC , (byte) 0x01, (byte) 0x00, (byte) 0xBD, (byte) 0xB0, (byte) 0xB3 };*/ /** delete file: A0 A2 00 03 BA FF FF 02 B8 B0 B3 */ private static byte[] dg100CmdDelFile = {(byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x03, (byte) 0xBA, (byte) 0xFF, (byte) 0xFF, (byte) 0x02, (byte) 0xB8, (byte) 0xB0, (byte) 0xB3 }; /** get file info: A0 A2 00 03 BB 00 00 00 BB B0 B3 */ private static byte[] dg100CmdGetFileInfo = {(byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x03, (byte) 0xBB, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xBB, (byte) 0xB0, (byte) 0xB3 }; /** get gps recs: A0 A2 00 03 B5 00 02 00 B7 B0 B3 */ private static byte[] dg100CmdGetGpsRecs = {(byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x03, (byte) 0xB5, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0xB7, (byte) 0xB0, (byte) 0xB3 }; /** read config: A0 A2 00 01 B7 00 B7 B0 B3 */ private static byte[] dg100CmdGetConfig = {(byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x01, (byte) 0xB7, (byte) 0x00, (byte) 0xB7, (byte) 0xB0, (byte) 0xB3 }; /** set config: A0 A2 00 2A B8 jj kk ll ll ll ll mm nn nn nn nn aa aa aa aa bb bb bb bb cc cc cc cc 00 00 gg hh ii dd dd dd dd ee ee ee ee ff ff ff ff 01 xx xx B0 B3 */ private static byte[] dg100CmdSetConfig = {(byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x2A, (byte) 0xB8, (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0xE8, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0xE8, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0xE8, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0xB0, (byte) 0xB3 }; /** read config: A0 A2 00 01 BF 00 BF B0 B3 */ /*private static byte dg100CmdGetId[] = { (byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x01, (byte) 0xBF , (byte) 0x00, (byte) 0xBF, (byte) 0xB0, (byte) 0xB3 };*/ /** read config: A0 A2 00 01 BF 00 BF B0 B3 */ /*private static byte dg100CmdSetId[] = { (byte) 0xA0, (byte) 0xA2, (byte) 0x00, (byte) 0x09, (byte) 0xC0 , (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 , (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 , (byte) 0x00, (byte) 0xC0, (byte) 0xB0, (byte) 0xB3};*/ private byte[] response = new byte[65536]; private CommPortIdentifier portIdentifier; private SerialPort port = null; private boolean canceled = false; public GlobalsatDg100(CommPortIdentifier portId) { this.portIdentifier = portId; } public void cancel() { canceled = true; disconnect(); } /** * Export DG-100's complete data to a GPX file. * @param port DG-100 is connected to port. */ public GpxData readData(ProgressMonitor progressMonitor) throws ConnectionException { progressMonitor.beginTask(null); try { GpxData result = null; canceled = false; if (port == null) { connect(); } List<FileInfoRec> fileInfoList = readFileInfoList(); List<GpsRec> gpsRecList = readGpsRecList(fileInfoList); progressMonitor.setTicksCount(gpsRecList.size()); if (gpsRecList.size() > 0) { GpsRec last = null; result = new GpxData(); Collection<WayPoint> seg = new ArrayList<>(100); for (GpsRec r:gpsRecList) { if (canceled) { return result; } WayPoint p = wayPointFrom(r); if (r.equals(last)) { result.waypoints.add(p); } else { seg.add(p); } last = r; progressMonitor.worked(1); } result.tracks.add(new ImmutableGpxTrack(Collections.singleton(seg), Collections.<String, Object>emptyMap())); } return result; } finally { progressMonitor.finishTask(); } } private WayPoint wayPointFrom(GpsRec r) { LatLon l = new LatLon(r.getLatitude(), r.getLongitude()); WayPoint result = new WayPoint(l); result.attr.put("time", "" + r.getStringZuluTime()); result.attr.put("speed", "" + r.getSpeed()); if (r.getDg100TypeOfCurRec() > 1) result.attr.put("ele", "" + r.getAltitude()); return result; } public void deleteData() throws ConnectionException { if (port == null) { connect(); } try { sendCmdDelFiles(); } catch (Exception e) { throw new ConnectionException(e); } } public void disconnect() { if (port != null) { port.close(); port = null; } } private void connect() throws ConnectionException { try { port = (SerialPort) portIdentifier.open("DG100", TIMEOUT); port.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_2, SerialPort.PARITY_NONE); port.notifyOnOutputEmpty(false); } catch (PortInUseException e) { throw new ConnectionException(e); } catch (UnsupportedCommOperationException e) { throw new ConnectionException(e); } } private List<FileInfoRec> readFileInfoList() throws ConnectionException { int nextIdx = 0; List<FileInfoRec> result = new ArrayList<>(64); try { do { Response<FileInfoRec> response = sendCmdGetFileInfo(nextIdx); nextIdx = response.getNextIdx(); result.addAll(response.getRecs()); } while (nextIdx > 0); return result; } catch (Exception e) { throw new ConnectionException(e); } } public List<GpsRec> readGpsRecList(List<FileInfoRec> fileInfoList) throws ConnectionException { List<GpsRec> result = new ArrayList<>(200); try { for (FileInfoRec fileInfoRec:fileInfoList) { Response<GpsRec> response = sendCmdGetGpsRecs(fileInfoRec.getIdx()); result.addAll(response.getRecs()); } return result; } catch (Exception e) { throw new ConnectionException(e); } } private Response<?> sendCmdDelFiles() throws IOException, UnsupportedCommOperationException { System.out.println("deleting data..."); int len = sendCmd(dg100CmdDelFile, response, -1); return Response.parseResponse(response, len); } @SuppressWarnings("unchecked") private Response<FileInfoRec> sendCmdGetFileInfo(int idx) throws IOException, UnsupportedCommOperationException { byte[] src = dg100CmdGetFileInfo; ByteBuffer buf = ByteBuffer.wrap(src); buf.position(5); buf.putShort((short) idx); // index of first file info rec to be read updateCheckSum(buf); int len = sendCmd(src, response, -1); return (Response<FileInfoRec>) Response.parseResponse(response, len); } private Response<?> sendCmdGetConfig() throws IOException, UnsupportedCommOperationException { byte[] src = dg100CmdGetConfig; int len = sendCmd(src, response, -1); return Response.parseResponse(response, len); } public Dg100Config getConfig() throws ConnectionException { try { if (port == null) { connect(); } return sendCmdGetConfig().getConfig(); } catch (Exception e) { throw new ConnectionException(e); } } private void sendCmdSetConfig(Dg100Config config) throws IOException, UnsupportedCommOperationException { byte[] src = dg100CmdSetConfig; ByteBuffer buf = ByteBuffer.wrap(src); if (config != null) { config.write(buf); } updateCheckSum(buf); int len = sendCmd(src, response, -1); Response.parseResponse(response, len); } public void setConfig(Dg100Config conf) throws ConnectionException { try { sendCmdSetConfig(conf); } catch (Exception e) { throw new ConnectionException(e); } } public boolean isCanceled() { return canceled; } @SuppressWarnings("unchecked") private Response<GpsRec> sendCmdGetGpsRecs(int idx) throws IOException, UnsupportedCommOperationException { byte[] src = dg100CmdGetGpsRecs; ByteBuffer buf = ByteBuffer.wrap(src); buf.position(5); buf.putShort((short) idx); // index of first chunk of gps recs to be read updateCheckSum(buf); int len = sendCmd(src, response, 2074); return (Response<GpsRec>) Response.parseResponse(response, len); } private void updateCheckSum(ByteBuffer buf) { buf.position(2); short len = buf.getShort(); int sum = 0; for (int ii = 0; ii < len; ++ii) { sum += ByteHelper.byte2IntUnsigned(buf.get()); } sum = sum & 0x7FFF; buf.putShort((short) sum); } private int sendCmd(byte[] cmdAndArgs, byte[] response, int bytesToRead) throws IOException, UnsupportedCommOperationException { int cntBytTot = 0; OutputStream outputStream = null; outputStream = port.getOutputStream(); outputStream.write(cmdAndArgs); outputStream.flush(); cntBytTot = readResponse(response, bytesToRead); return cntBytTot; } private int readResponse(byte[] response, int bytesToRead) throws IOException, UnsupportedCommOperationException { byte[] readBuffer = new byte[200]; int responsePos = 0; boolean headerReceived = false; port.enableReceiveTimeout(TIMEOUT); int cntBytTot = 0; InputStream inputStream = port.getInputStream(); if (inputStream != null) { ByteBuffer buf = ByteBuffer.wrap(response); port.enableReceiveThreshold(2); // read at least 2 byte boolean a0A2Received = false; while (!a0A2Received) { response[0] = response[1]; int rcvd = inputStream.read(response, 1, 1); if (rcvd == 0) { return 0; } a0A2Received = (response[0] == (byte) 0xa0 && response[1] == (byte) 0xa2); } // we already have read 2 bytes responsePos = 2; cntBytTot = 2; // read interesting data port.enableReceiveThreshold(2); // read at least 2 bytes while (bytesToRead < 0 || (bytesToRead >= 0 && cntBytTot < bytesToRead)) { int cntBytAct = 0; cntBytAct = inputStream.read(readBuffer); cntBytTot += cntBytAct; // copy readBuffer to response responsePos = ByteHelper.copyByteArr2ByteArr(readBuffer, 0, cntBytAct, response, responsePos); if (!headerReceived && cntBytTot >= 4) { // do we have enough bytes to extract bytesToRead if (bytesToRead == -1) { // get bytesToRead from response buf.position(2); // here starts length of payload bytesToRead = buf.getShort() + 4; } port.enableReceiveThreshold(1); // read at least 1 byte headerReceived = true; } if (cntBytAct == 0) { break; } } return ((cntBytTot > bytesToRead) ? bytesToRead : cntBytTot); } return -1; } } // class GlobalsatDg100