From fba1e6f6f43b35a520a5bd09f45796287b06293f Mon Sep 17 00:00:00 2001
From: Anton Tananaev <anton.tananaev@gmail.com>
Date: Tue, 31 Jul 2012 22:40:27 +0400
Subject: Fix GL200 decoder (fix #33)

---
 src/org/traccar/protocol/Gl200ProtocolDecoder.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/org/traccar')

diff --git a/src/org/traccar/protocol/Gl200ProtocolDecoder.java b/src/org/traccar/protocol/Gl200ProtocolDecoder.java
index 506b55790..63def5ddf 100644
--- a/src/org/traccar/protocol/Gl200ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gl200ProtocolDecoder.java
@@ -42,7 +42,7 @@ public class Gl200ProtocolDecoder extends GenericProtocolDecoder {
      */
     static private Pattern pattern = Pattern.compile(
             "\\+RESP:GT...," +
-            "\\d{6}," +                         // Protocol version
+            "[0-9a-fA-F]{6}," +                 // Protocol version
             "(\\d{15})," +                      // IMEI
             "[^,]*," +                          // Device name
             "(?:(?:\\d," +                      // Report ID / Geo mode
-- 
cgit v1.2.3


From 58b6dc11e08f43b1a0dc7dc96ad43241aff94f0c Mon Sep 17 00:00:00 2001
From: Anton Tananaev <anton.tananaev@gmail.com>
Date: Sat, 4 Aug 2012 11:36:43 +0400
Subject: Merge pull request

---
 pom.xml                                            |  22 +-
 src/org/traccar/protocol/ST210ProtocolDecoder.java | 640 +++++++++++++++++++++
 .../traccar/protocol/ST210ProtocolDecoderTest.java |  73 +++
 3 files changed, 729 insertions(+), 6 deletions(-)
 create mode 100644 src/org/traccar/protocol/ST210ProtocolDecoder.java
 create mode 100644 test/org/traccar/protocol/ST210ProtocolDecoderTest.java

(limited to 'src/org/traccar')

diff --git a/pom.xml b/pom.xml
index b6d4a5a6d..9e11580f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>org.traccar</groupId>
 	<artifactId>traccar</artifactId>
-	<version>0.0.1-SNAPSHOT</version>
+	<version>1.1.0-SNAPSHOT</version>
 
 	<dependencies>
 		<dependency>
@@ -21,15 +21,25 @@
 			<artifactId>netty</artifactId>
 			<version>3.5.2.Final</version>
 		</dependency>
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>javax.servlet-api</artifactId>
-			<version>3.0.1</version>
-		</dependency>
 		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
 			<version>4.10</version>
 		</dependency>
 	</dependencies>
+
+	<build>
+	 	<finalName>tracker-server</finalName>
+		<outputDirectory>target\classes</outputDirectory>
+		<sourceDirectory>src</sourceDirectory>
+		<testSourceDirectory>test</testSourceDirectory>
+		<resources>
+			<resource>
+				<directory>src</directory>
+				<excludes>
+					<exclude>**/*.java</exclude>
+				</excludes>
+			</resource>
+		</resources>
+	</build>
 </project>
\ No newline at end of file
diff --git a/src/org/traccar/protocol/ST210ProtocolDecoder.java b/src/org/traccar/protocol/ST210ProtocolDecoder.java
new file mode 100644
index 000000000..8fbea8da5
--- /dev/null
+++ b/src/org/traccar/protocol/ST210ProtocolDecoder.java
@@ -0,0 +1,640 @@
+package org.traccar.protocol;
+
+import java.util.Calendar;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.traccar.GenericProtocolDecoder;
+import org.traccar.helper.Log;
+import org.traccar.model.DataManager;
+import org.traccar.model.Position;
+
+public class ST210ProtocolDecoder extends GenericProtocolDecoder {
+
+	/**
+	 * Initialize
+	 */
+	public ST210ProtocolDecoder(DataManager dataManager, Integer resetDelay) {
+		super(dataManager, resetDelay);
+	}
+
+	private enum ST210FIELDS {
+		HDR_STATUS("SA200STT;", "Status Report"), HDR_EMERGENCY("SA200EMG;",
+				"Emergency Report"), HDR_EVENT("SA200EVT;", "Event Report"), HDR_ALERT(
+				"SA200ALT;", "Alert Report"), HDR_ALIVE("SA200ALV;",
+				"Alive Report"), DEV_ID("(\\d+);", "Device ID"), SW_VER(
+				"(\\d{3});", "Software Release Version"), DATE("(\\d+);",
+				"GPS date (yyyymmdd) Year + Month + Day"), TIME(
+				"(\\d{2}:\\d{2}:\\d{2});",
+				"GPS time (hh:mm:ss) Hour : Minute : Second"), CELL(
+				"(\\d{2}\\w\\d{2});",
+				"Location Code ID (3 digits hex) + Serving Cell BSIC(2 digits decimal)"), LAT(
+				"(-\\d{2}.\\d+);", "Latitude (+/-xx.xxxxxx)"), LON(
+				"(-\\d{3}.\\d+);", "Longitude (+/-xxx.xxxxxx)"), SPD(
+				"(\\d{3}.\\d{3});",
+				"Speed in km/h - This value returns to 0 when it is over than 200,000Km"), CRS(
+				"(\\d{3}.\\d{2});", "Course over ground in degree"), SATT(
+				"(\\d+);", "Number of satellites"), FIX("(\\d);",
+				"GPS is fixed(1)\n" + "GPS is not fixed(0)"), DIST("(\\d+);",
+				"Traveled ddistance in meter"), PWR_VOLT("(\\d{2}.\\d{2});",
+				"Voltage value of main power"), IO("(\\d+);",
+				"Current I/O status of inputs and outputs."), MODE("(\\d);",
+				"1 = Idle mode (Parking)\n" + "2 = Active Mode (Driving)"), MSG_NUM(
+				"(\\d{4})",
+				"Message number - After 9999 is reported, message number returns to 0000"), EMG_ID(
+				"(\\d);", "Emergency type"), EVT_ID("(\\d);", "Event type"), ALERT_ID(
+				"(\\d);", "Alert type");
+
+		private String pattern;
+
+		private String desc;
+
+		private ST210FIELDS(String pattern, String desc) {
+			this.pattern = pattern;
+			this.desc = desc;
+		}
+
+		public String getPattern() {
+			return pattern;
+		}
+
+		public String getDesc() {
+			return desc;
+		}
+
+		public void populatePosition(Position position, String groupValue,
+				DataManager dataManager) throws Exception {
+
+			switch (this) {
+
+			case DEV_ID:
+				position.setDeviceId(dataManager.getDeviceByImei(groupValue)
+						.getId());
+				break;
+
+			case LAT:
+				position.setLatitude(Double.valueOf(groupValue));
+				break;
+
+			case LON:
+				position.setLongitude(Double.valueOf(groupValue));
+				break;
+
+			case CRS:
+				position.setCourse(Double.valueOf(groupValue));
+				break;
+
+			case PWR_VOLT:
+				position.setPower(Double.valueOf(groupValue));
+				break;
+
+			case SPD:
+				position.setSpeed(Double.valueOf(groupValue));
+				break;
+
+			case DATE: {
+				// Date
+				Calendar time = Calendar.getInstance(TimeZone
+						.getTimeZone("UTC"));
+				time.clear();
+				time.set(Calendar.YEAR, Integer.valueOf(Integer
+						.valueOf(groupValue.substring(0, 4))));
+				time.set(Calendar.MONTH, Integer.valueOf(Integer
+						.valueOf(groupValue.substring(4, 6))));
+				time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(Integer
+						.valueOf(groupValue.substring(6, 8))));
+				position.setTime(time.getTime());
+				break;
+			}
+
+			case TIME: {
+
+				Calendar time = Calendar.getInstance(TimeZone
+						.getTimeZone("UTC"));
+				time.clear();
+				time.setTime(position.getTime());
+
+				time.set(Calendar.HOUR, Integer.valueOf(Integer
+						.valueOf(groupValue.substring(0, 2))));
+				time.set(Calendar.MINUTE, Integer.valueOf(Integer
+						.valueOf(groupValue.substring(3, 5))));
+				time.set(Calendar.SECOND, Integer.valueOf(Integer
+						.valueOf(groupValue.substring(6, 8))));
+
+				position.setTime(time.getTime());
+				break;
+			}
+
+			default:
+				break;
+			}
+
+		}
+	}
+
+	private enum FIELD_FIX_VALUE {
+		FIXED(1, "GPS is fixed"), NOT_FIXED(0, "GPS is not fixed");
+
+		private int value;
+
+		private String desc;
+
+		private FIELD_FIX_VALUE(int value, String desc) {
+			this.value = value;
+			this.desc = desc;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public String getDesc() {
+			return desc;
+		}
+
+		public FIELD_FIX_VALUE getValueOf(String indiceStr) {
+			int indice = Integer.valueOf(indiceStr);
+			return getValueOf(indice);
+		}
+
+		public FIELD_FIX_VALUE getValueOf(int indice) {
+			switch (indice) {
+			case 1:
+				return FIXED;
+			case 0:
+				return NOT_FIXED;
+			default:
+				throw new IllegalArgumentException("�ndice n�o definido");
+			}
+		}
+	}
+
+	private enum FIELD_MODE_VALUE {
+		PARKING(1, "Idle mode (Parking)"), DRIVING(2, "Active Mode (Driving)");
+
+		private int value;
+
+		private String desc;
+
+		private FIELD_MODE_VALUE(int value, String desc) {
+			this.value = value;
+			this.desc = desc;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public String getDesc() {
+			return desc;
+		}
+
+		public FIELD_MODE_VALUE getValueOf(String indiceStr) {
+			int indice = Integer.valueOf(indiceStr);
+			return getValueOf(indice);
+		}
+
+		public FIELD_MODE_VALUE getValueOf(int indice) {
+			switch (indice) {
+			case 1:
+				return PARKING;
+			case 2:
+				return DRIVING;
+			default:
+				throw new IllegalArgumentException("�ndice n�o definido");
+			}
+		}
+	}
+
+	private enum FIELD_EMG_ID_VALUE {
+		PANIC(1, "Emergency by panic button"), PARKING(2,
+				"Emergency by parking lock"), MAIN_POWER(3,
+				"Emergency by removing main power"), ANTI_THEFT(5,
+				"Emergency by anti-theft");
+
+		private int value;
+
+		private String desc;
+
+		private FIELD_EMG_ID_VALUE(int value, String desc) {
+			this.value = value;
+			this.desc = desc;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public String getDesc() {
+			return desc;
+		}
+
+		public FIELD_EMG_ID_VALUE getValueOf(String indiceStr) {
+			int indice = Integer.valueOf(indiceStr);
+			return getValueOf(indice);
+		}
+
+		public FIELD_EMG_ID_VALUE getValueOf(int indice) {
+			switch (indice) {
+			case 1:
+				return PANIC;
+			case 2:
+				return PARKING;
+			case 3:
+				return MAIN_POWER;
+			case 5:
+				return ANTI_THEFT;
+			default:
+				throw new IllegalArgumentException("�ndice n�o definido");
+			}
+		}
+	}
+
+	private enum FIELD_EVT_ID_VALUE {
+		INPUT1_GROUND(1, "Input1 goes to ground state"), INPUT1_OPEN(2,
+				"Input1 goes to open state"), INPUT2_GROUND(3,
+				"Input2 goes to ground state"), INPUT2_OPEN(4,
+				"Input2 goes to open state"), INPUT3_GROUND(5,
+				"Input3 goes to ground state"), INPUT3_OPEN(6,
+				"Input3 goes to open state");
+
+		private int value;
+
+		private String desc;
+
+		private FIELD_EVT_ID_VALUE(int value, String desc) {
+			this.value = value;
+			this.desc = desc;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public String getDesc() {
+			return desc;
+		}
+
+		public FIELD_EVT_ID_VALUE getValueOf(String indiceStr) {
+			int indice = Integer.valueOf(indiceStr);
+			return getValueOf(indice);
+		}
+
+		public FIELD_EVT_ID_VALUE getValueOf(int indice) {
+			switch (indice) {
+			case 1:
+				return INPUT1_GROUND;
+			case 2:
+				return INPUT1_OPEN;
+			case 3:
+				return INPUT2_GROUND;
+			case 4:
+				return INPUT2_OPEN;
+			case 5:
+				return INPUT3_GROUND;
+			case 6:
+				return INPUT3_OPEN;
+			default:
+				throw new IllegalArgumentException("�ndice n�o definido");
+			}
+		}
+	}
+
+	private enum FIELD_ALERT_ID_VALUE {
+		DRIVING_FASTER(1, "Start driving faster than SPEED_LIMIT"), OVER_SPPED(
+				2, "Ended over speed condition"), DISCON_GPS(3,
+				"Disconnected GPS antenna"), RECON_GPS(4,
+				"Reconnected GPS antenna after disconnected"), OUT_GEO_FENCE(5,
+				"The vehicle went out from the geo-fence that has following ID"), INTO_GEO_FENCE(
+				6,
+				"The vehicle entered into the geo-fence that has following ID"), SHORTED_GPS(
+				8, "Shorted GPS antenna"), DEEP_SLEEP_ON(9,
+				"Enter to deep sleep mode"), DEEP_SLEEP_OFF(10,
+				"Exite from deep sleep mode"), BKP_BATTERY(13,
+				"Backup battery error"), BATTERY_DOWN(14,
+				"Vehicle battery goes down to so low level"), SHOCKED(15,
+				"Shocked"), COLLISION(16, "Occurred some collision"), DEVIATE_ROUT(
+				18, "Deviate from predefined rout"), ENTER_ROUT(19,
+				"Enter into predefined rout");
+
+		private int value;
+
+		private String desc;
+
+		private FIELD_ALERT_ID_VALUE(int value, String desc) {
+			this.value = value;
+			this.desc = desc;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		public String getDesc() {
+			return desc;
+		}
+
+		public FIELD_ALERT_ID_VALUE getValueOf(String indiceStr) {
+			int indice = Integer.valueOf(indiceStr);
+			return getValueOf(indice);
+		}
+
+		public FIELD_ALERT_ID_VALUE getValueOf(int indice) {
+			switch (indice) {
+			case 1:
+				return DRIVING_FASTER;
+			case 2:
+				return OVER_SPPED;
+			case 3:
+				return DISCON_GPS;
+			case 4:
+				return RECON_GPS;
+			case 5:
+				return OUT_GEO_FENCE;
+			case 6:
+				return INTO_GEO_FENCE;
+			case 8:
+				return SHORTED_GPS;
+			case 9:
+				return DEEP_SLEEP_ON;
+			case 10:
+				return DEEP_SLEEP_OFF;
+			case 13:
+				return BKP_BATTERY;
+			case 14:
+				return BATTERY_DOWN;
+			case 15:
+				return SHOCKED;
+			case 16:
+				return COLLISION;
+			case 18:
+				return DEVIATE_ROUT;
+			case 19:
+				return ENTER_ROUT;
+			default:
+				throw new IllegalArgumentException("�ndice n�o definido");
+			}
+		}
+	}
+
+	private enum ST210REPORTS {
+
+		STATUS("SA200STT"), EMERGENCY("SA200EMG"), EVENT("SA200EVT"), ALERT(
+				"SA200ALT"), ALIVE("SA200ALV");
+
+		private String header;
+
+		private ST210REPORTS(String header) {
+			this.header = header;
+		}
+
+		public String getHeader() {
+			return this.header;
+		}
+
+		public List<ST210FIELDS> getProtocol() {
+
+			if (this.equals(STATUS)) {
+				return StatusProtocol;
+			}
+
+			if (this.equals(EMERGENCY)) {
+				return EmergencyProtocol;
+			}
+
+			if (this.equals(EVENT)) {
+				return EventProtocol;
+			}
+
+			if (this.equals(ALERT)) {
+				return AlertProtocol;
+			}
+
+			if (this.equals(ALIVE)) {
+				return AliveProtocol;
+			}
+
+			return null;
+		}
+
+		public Pattern getProtocolPattern() {
+
+			if (this.equals(STATUS)) {
+				return getPattern(StatusProtocol);
+			}
+
+			if (this.equals(EMERGENCY)) {
+				return getPattern(EmergencyProtocol);
+			}
+
+			if (this.equals(EVENT)) {
+				return getPattern(EventProtocol);
+			}
+
+			if (this.equals(ALERT)) {
+				return getPattern(AlertProtocol);
+			}
+
+			if (this.equals(ALIVE)) {
+				return getPattern(AliveProtocol);
+			}
+
+			return null;
+		}
+
+	}
+
+	private static ST210REPORTS getReportType(String msg) {
+
+		if (msg.startsWith(ST210REPORTS.STATUS.getHeader())) {
+			return ST210REPORTS.STATUS;
+		}
+
+		if (msg.startsWith(ST210REPORTS.EMERGENCY.getHeader())) {
+			return ST210REPORTS.EMERGENCY;
+		}
+
+		if (msg.startsWith(ST210REPORTS.EVENT.getHeader())) {
+			return ST210REPORTS.EVENT;
+		}
+
+		if (msg.startsWith(ST210REPORTS.ALERT.getHeader())) {
+			return ST210REPORTS.ALERT;
+		}
+
+		if (msg.startsWith(ST210REPORTS.ALIVE.getHeader())) {
+			return ST210REPORTS.ALIVE;
+		}
+
+		return null;
+	}
+
+	public static Pattern getPattern(List<ST210FIELDS> protocol) {
+
+		String patternStr = "";
+
+		for (ST210FIELDS field : protocol) {
+			patternStr += field.getPattern();
+		}
+
+		return Pattern.compile(patternStr);
+
+	}
+
+	@SuppressWarnings("serial")
+	static private List<ST210FIELDS> StatusProtocol = new LinkedList<ST210FIELDS>() {
+
+		{
+			add(ST210FIELDS.HDR_STATUS);
+			add(ST210FIELDS.DEV_ID);
+			add(ST210FIELDS.SW_VER);
+			add(ST210FIELDS.DATE);
+			add(ST210FIELDS.TIME);
+			add(ST210FIELDS.CELL);
+			add(ST210FIELDS.LAT);
+			add(ST210FIELDS.LON);
+			add(ST210FIELDS.SPD);
+			add(ST210FIELDS.CRS);
+			add(ST210FIELDS.SATT);
+			add(ST210FIELDS.FIX);
+			add(ST210FIELDS.DIST);
+			add(ST210FIELDS.PWR_VOLT);
+			add(ST210FIELDS.IO);
+			add(ST210FIELDS.MODE);
+			add(ST210FIELDS.MSG_NUM);
+		}
+
+	};
+
+	@SuppressWarnings("serial")
+	static private List<ST210FIELDS> EmergencyProtocol = new LinkedList<ST210FIELDS>() {
+
+		{
+			add(ST210FIELDS.HDR_EMERGENCY);
+			add(ST210FIELDS.DEV_ID);
+			add(ST210FIELDS.SW_VER);
+			add(ST210FIELDS.DATE);
+			add(ST210FIELDS.TIME);
+			add(ST210FIELDS.CELL);
+			add(ST210FIELDS.LAT);
+			add(ST210FIELDS.LON);
+			add(ST210FIELDS.SPD);
+			add(ST210FIELDS.CRS);
+			add(ST210FIELDS.SATT);
+			add(ST210FIELDS.FIX);
+			add(ST210FIELDS.DIST);
+			add(ST210FIELDS.PWR_VOLT);
+			add(ST210FIELDS.IO);
+			add(ST210FIELDS.EMG_ID);
+		}
+
+	};
+
+	@SuppressWarnings("serial")
+	static private List<ST210FIELDS> EventProtocol = new LinkedList<ST210FIELDS>() {
+
+		{
+			add(ST210FIELDS.HDR_EVENT);
+			add(ST210FIELDS.DEV_ID);
+			add(ST210FIELDS.SW_VER);
+			add(ST210FIELDS.DATE);
+			add(ST210FIELDS.TIME);
+			add(ST210FIELDS.CELL);
+			add(ST210FIELDS.LAT);
+			add(ST210FIELDS.LON);
+			add(ST210FIELDS.SPD);
+			add(ST210FIELDS.CRS);
+			add(ST210FIELDS.SATT);
+			add(ST210FIELDS.FIX);
+			add(ST210FIELDS.DIST);
+			add(ST210FIELDS.PWR_VOLT);
+			add(ST210FIELDS.IO);
+			add(ST210FIELDS.EVT_ID);
+		}
+
+	};
+
+	@SuppressWarnings("serial")
+	static private List<ST210FIELDS> AlertProtocol = new LinkedList<ST210FIELDS>() {
+
+		{
+			add(ST210FIELDS.HDR_ALERT);
+			add(ST210FIELDS.DEV_ID);
+			add(ST210FIELDS.SW_VER);
+			add(ST210FIELDS.DATE);
+			add(ST210FIELDS.TIME);
+			add(ST210FIELDS.CELL);
+			add(ST210FIELDS.LAT);
+			add(ST210FIELDS.LON);
+			add(ST210FIELDS.SPD);
+			add(ST210FIELDS.CRS);
+			add(ST210FIELDS.SATT);
+			add(ST210FIELDS.FIX);
+			add(ST210FIELDS.DIST);
+			add(ST210FIELDS.PWR_VOLT);
+			add(ST210FIELDS.IO);
+			add(ST210FIELDS.ALERT_ID);
+		}
+
+	};
+
+	@SuppressWarnings("serial")
+	static private List<ST210FIELDS> AliveProtocol = new LinkedList<ST210FIELDS>() {
+
+		{
+			add(ST210FIELDS.HDR_ALIVE);
+			add(ST210FIELDS.DEV_ID);
+		}
+
+	};
+
+	@Override
+	public Object decode(ChannelHandlerContext ctx, Channel channel, Object msg)
+			throws Exception {
+		if (channel != null) {
+			channel.write("OI AMIGO do decoder");
+		}
+		String sentence = (String) msg;
+		return decodeMsg(sentence);
+	}
+
+	public Position decodeMsg(String msg) throws Exception {
+
+		ST210REPORTS report = getReportType(msg);
+
+		List<ST210FIELDS> protocol = report.getProtocol();
+
+		Pattern protocolPattern = report.getProtocolPattern();
+
+		Log.info("Protocol Pattern: " + protocolPattern.toString());
+		Log.info("Msg: " + msg);
+
+		// Parse message
+		Matcher parser = protocolPattern.matcher(msg);
+		if (!parser.matches()) {
+			return null;
+		}
+
+		// Create new position
+		Position position = new Position();
+
+		position.setAltitude(0D);
+		position.setExtendedInfo("");
+		position.setValid(true);
+
+		Integer index = 0;
+		for (ST210FIELDS field : protocol) {
+
+			String groupValue = parser.group(index++);
+
+			field.populatePosition(position, groupValue, getDataManager());
+		}
+
+		return position;
+	}
+}
diff --git a/test/org/traccar/protocol/ST210ProtocolDecoderTest.java b/test/org/traccar/protocol/ST210ProtocolDecoderTest.java
new file mode 100644
index 000000000..a2ffe5bac
--- /dev/null
+++ b/test/org/traccar/protocol/ST210ProtocolDecoderTest.java
@@ -0,0 +1,73 @@
+package org.traccar.protocol;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.traccar.Server;
+import org.traccar.helper.Log;
+import org.traccar.server.SocketCliente;
+
+public class ST210ProtocolDecoderTest {
+
+	@BeforeClass
+	public static void UpServer() {
+		final Server service = new Server();
+		String[] args = new String[1];
+		args[0] = "setup\\windows\\windows.cfg";
+		try {
+			service.init(args);
+
+			Log.info("starting server...");
+			service.start();
+
+			// Shutdown server properly
+			Runtime.getRuntime().addShutdownHook(new Thread() {
+				@Override
+				public void run() {
+					Log.info("shutting down server...");
+					service.stop();
+				}
+			});
+
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+	}
+
+	@Test
+	public void testClienteMsg() throws Exception {
+
+		SocketCliente cliente = new SocketCliente();
+		cliente.SendMSG(
+				"localhost",
+				5010,
+				"SA200STT;317652;042;20120718;15:37:12;16d41;-15.618755;-056.083241;000.024;000.00;8;1;41548;12.17;100000;2;1979");
+
+	}
+
+/*	@Test
+	public void testDecode() throws Exception {
+
+		ST210ProtocolDecoder decoder = new ST210ProtocolDecoder(
+				new TestDataManager(), 0);
+
+		assertNotNull(decoder
+				.decode(null,
+						null,
+						"SA200STT;317652;042;20120718;15:37:12;16d41;-15.618755;-056.083241;000.024;000.00;8;1;41548;12.17;100000;2;1979"));
+		assertNotNull(decoder
+				.decode(null,
+						null,
+						"SA200STT;317652;042;20120721;19:04:30;16d41;-15.618743;-056.083221;000.001;000.00;12;1;41557;12.21;000000;1;3125"));
+		assertNotNull(decoder
+				.decode(null,
+						null,
+						"SA200STT;317652;042;20120722;00:24:23;16d41;-15.618767;-056.083214;000.011;000.00;11;1;41557;12.21;000000;1;3205"));
+	}*/
+}
-- 
cgit v1.2.3


From d19a6c8cc2f5f2852e046eb20d5f00dfdf3baeff Mon Sep 17 00:00:00 2001
From: Anton Tananaev <anton.tananaev@gmail.com>
Date: Sat, 4 Aug 2012 13:00:45 +0400
Subject: Style corrections

---
 pom.xml                                            |   84 +-
 src/org/traccar/protocol/ST210ProtocolDecoder.java | 1242 ++++++++++----------
 .../traccar/protocol/ST210ProtocolDecoderTest.java |   96 +-
 3 files changed, 711 insertions(+), 711 deletions(-)

(limited to 'src/org/traccar')

diff --git a/pom.xml b/pom.xml
index 9e11580f3..5b93f062f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,45 +1,45 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<groupId>org.traccar</groupId>
-	<artifactId>traccar</artifactId>
-	<version>1.1.0-SNAPSHOT</version>
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.traccar</groupId>
+    <artifactId>traccar</artifactId>
+    <version>1.1.0-SNAPSHOT</version>
 
-	<dependencies>
-		<dependency>
-			<groupId>com.h2database</groupId>
-			<artifactId>h2</artifactId>
-			<version>1.3.167</version>
-		</dependency>
-		<dependency>
-			<groupId>org.eclipse.jetty.aggregate</groupId>
-			<artifactId>jetty-all</artifactId>
-			<version>8.1.4.v20120524</version>
-		</dependency>
-		<dependency>
-			<groupId>io.netty</groupId>
-			<artifactId>netty</artifactId>
-			<version>3.5.2.Final</version>
-		</dependency>
-		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-			<version>4.10</version>
-		</dependency>
-	</dependencies>
+    <dependencies>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <version>1.3.167</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty.aggregate</groupId>
+            <artifactId>jetty-all</artifactId>
+            <version>8.1.4.v20120524</version>
+        </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty</artifactId>
+            <version>3.5.2.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.10</version>
+        </dependency>
+    </dependencies>
 
-	<build>
-	 	<finalName>tracker-server</finalName>
-		<outputDirectory>target\classes</outputDirectory>
-		<sourceDirectory>src</sourceDirectory>
-		<testSourceDirectory>test</testSourceDirectory>
-		<resources>
-			<resource>
-				<directory>src</directory>
-				<excludes>
-					<exclude>**/*.java</exclude>
-				</excludes>
-			</resource>
-		</resources>
-	</build>
-</project>
\ No newline at end of file
+    <build>
+        <finalName>tracker-server</finalName>
+        <outputDirectory>target\classes</outputDirectory>
+        <sourceDirectory>src</sourceDirectory>
+        <testSourceDirectory>test</testSourceDirectory>
+        <resources>
+            <resource>
+                <directory>src</directory>
+                <excludes>
+                    <exclude>**/*.java</exclude>
+                </excludes>
+            </resource>
+        </resources>
+    </build>
+</project>
diff --git a/src/org/traccar/protocol/ST210ProtocolDecoder.java b/src/org/traccar/protocol/ST210ProtocolDecoder.java
index 8fbea8da5..230dd5505 100644
--- a/src/org/traccar/protocol/ST210ProtocolDecoder.java
+++ b/src/org/traccar/protocol/ST210ProtocolDecoder.java
@@ -16,625 +16,625 @@ import org.traccar.model.Position;
 
 public class ST210ProtocolDecoder extends GenericProtocolDecoder {
 
-	/**
-	 * Initialize
-	 */
-	public ST210ProtocolDecoder(DataManager dataManager, Integer resetDelay) {
-		super(dataManager, resetDelay);
-	}
-
-	private enum ST210FIELDS {
-		HDR_STATUS("SA200STT;", "Status Report"), HDR_EMERGENCY("SA200EMG;",
-				"Emergency Report"), HDR_EVENT("SA200EVT;", "Event Report"), HDR_ALERT(
-				"SA200ALT;", "Alert Report"), HDR_ALIVE("SA200ALV;",
-				"Alive Report"), DEV_ID("(\\d+);", "Device ID"), SW_VER(
-				"(\\d{3});", "Software Release Version"), DATE("(\\d+);",
-				"GPS date (yyyymmdd) Year + Month + Day"), TIME(
-				"(\\d{2}:\\d{2}:\\d{2});",
-				"GPS time (hh:mm:ss) Hour : Minute : Second"), CELL(
-				"(\\d{2}\\w\\d{2});",
-				"Location Code ID (3 digits hex) + Serving Cell BSIC(2 digits decimal)"), LAT(
-				"(-\\d{2}.\\d+);", "Latitude (+/-xx.xxxxxx)"), LON(
-				"(-\\d{3}.\\d+);", "Longitude (+/-xxx.xxxxxx)"), SPD(
-				"(\\d{3}.\\d{3});",
-				"Speed in km/h - This value returns to 0 when it is over than 200,000Km"), CRS(
-				"(\\d{3}.\\d{2});", "Course over ground in degree"), SATT(
-				"(\\d+);", "Number of satellites"), FIX("(\\d);",
-				"GPS is fixed(1)\n" + "GPS is not fixed(0)"), DIST("(\\d+);",
-				"Traveled ddistance in meter"), PWR_VOLT("(\\d{2}.\\d{2});",
-				"Voltage value of main power"), IO("(\\d+);",
-				"Current I/O status of inputs and outputs."), MODE("(\\d);",
-				"1 = Idle mode (Parking)\n" + "2 = Active Mode (Driving)"), MSG_NUM(
-				"(\\d{4})",
-				"Message number - After 9999 is reported, message number returns to 0000"), EMG_ID(
-				"(\\d);", "Emergency type"), EVT_ID("(\\d);", "Event type"), ALERT_ID(
-				"(\\d);", "Alert type");
-
-		private String pattern;
-
-		private String desc;
-
-		private ST210FIELDS(String pattern, String desc) {
-			this.pattern = pattern;
-			this.desc = desc;
-		}
-
-		public String getPattern() {
-			return pattern;
-		}
-
-		public String getDesc() {
-			return desc;
-		}
-
-		public void populatePosition(Position position, String groupValue,
-				DataManager dataManager) throws Exception {
-
-			switch (this) {
-
-			case DEV_ID:
-				position.setDeviceId(dataManager.getDeviceByImei(groupValue)
-						.getId());
-				break;
-
-			case LAT:
-				position.setLatitude(Double.valueOf(groupValue));
-				break;
-
-			case LON:
-				position.setLongitude(Double.valueOf(groupValue));
-				break;
-
-			case CRS:
-				position.setCourse(Double.valueOf(groupValue));
-				break;
-
-			case PWR_VOLT:
-				position.setPower(Double.valueOf(groupValue));
-				break;
-
-			case SPD:
-				position.setSpeed(Double.valueOf(groupValue));
-				break;
-
-			case DATE: {
-				// Date
-				Calendar time = Calendar.getInstance(TimeZone
-						.getTimeZone("UTC"));
-				time.clear();
-				time.set(Calendar.YEAR, Integer.valueOf(Integer
-						.valueOf(groupValue.substring(0, 4))));
-				time.set(Calendar.MONTH, Integer.valueOf(Integer
-						.valueOf(groupValue.substring(4, 6))));
-				time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(Integer
-						.valueOf(groupValue.substring(6, 8))));
-				position.setTime(time.getTime());
-				break;
-			}
-
-			case TIME: {
-
-				Calendar time = Calendar.getInstance(TimeZone
-						.getTimeZone("UTC"));
-				time.clear();
-				time.setTime(position.getTime());
-
-				time.set(Calendar.HOUR, Integer.valueOf(Integer
-						.valueOf(groupValue.substring(0, 2))));
-				time.set(Calendar.MINUTE, Integer.valueOf(Integer
-						.valueOf(groupValue.substring(3, 5))));
-				time.set(Calendar.SECOND, Integer.valueOf(Integer
-						.valueOf(groupValue.substring(6, 8))));
-
-				position.setTime(time.getTime());
-				break;
-			}
-
-			default:
-				break;
-			}
-
-		}
-	}
-
-	private enum FIELD_FIX_VALUE {
-		FIXED(1, "GPS is fixed"), NOT_FIXED(0, "GPS is not fixed");
-
-		private int value;
-
-		private String desc;
-
-		private FIELD_FIX_VALUE(int value, String desc) {
-			this.value = value;
-			this.desc = desc;
-		}
-
-		public int getValue() {
-			return value;
-		}
-
-		public String getDesc() {
-			return desc;
-		}
-
-		public FIELD_FIX_VALUE getValueOf(String indiceStr) {
-			int indice = Integer.valueOf(indiceStr);
-			return getValueOf(indice);
-		}
-
-		public FIELD_FIX_VALUE getValueOf(int indice) {
-			switch (indice) {
-			case 1:
-				return FIXED;
-			case 0:
-				return NOT_FIXED;
-			default:
-				throw new IllegalArgumentException("�ndice n�o definido");
-			}
-		}
-	}
-
-	private enum FIELD_MODE_VALUE {
-		PARKING(1, "Idle mode (Parking)"), DRIVING(2, "Active Mode (Driving)");
-
-		private int value;
-
-		private String desc;
-
-		private FIELD_MODE_VALUE(int value, String desc) {
-			this.value = value;
-			this.desc = desc;
-		}
-
-		public int getValue() {
-			return value;
-		}
-
-		public String getDesc() {
-			return desc;
-		}
-
-		public FIELD_MODE_VALUE getValueOf(String indiceStr) {
-			int indice = Integer.valueOf(indiceStr);
-			return getValueOf(indice);
-		}
-
-		public FIELD_MODE_VALUE getValueOf(int indice) {
-			switch (indice) {
-			case 1:
-				return PARKING;
-			case 2:
-				return DRIVING;
-			default:
-				throw new IllegalArgumentException("�ndice n�o definido");
-			}
-		}
-	}
-
-	private enum FIELD_EMG_ID_VALUE {
-		PANIC(1, "Emergency by panic button"), PARKING(2,
-				"Emergency by parking lock"), MAIN_POWER(3,
-				"Emergency by removing main power"), ANTI_THEFT(5,
-				"Emergency by anti-theft");
-
-		private int value;
-
-		private String desc;
-
-		private FIELD_EMG_ID_VALUE(int value, String desc) {
-			this.value = value;
-			this.desc = desc;
-		}
-
-		public int getValue() {
-			return value;
-		}
-
-		public String getDesc() {
-			return desc;
-		}
-
-		public FIELD_EMG_ID_VALUE getValueOf(String indiceStr) {
-			int indice = Integer.valueOf(indiceStr);
-			return getValueOf(indice);
-		}
-
-		public FIELD_EMG_ID_VALUE getValueOf(int indice) {
-			switch (indice) {
-			case 1:
-				return PANIC;
-			case 2:
-				return PARKING;
-			case 3:
-				return MAIN_POWER;
-			case 5:
-				return ANTI_THEFT;
-			default:
-				throw new IllegalArgumentException("�ndice n�o definido");
-			}
-		}
-	}
-
-	private enum FIELD_EVT_ID_VALUE {
-		INPUT1_GROUND(1, "Input1 goes to ground state"), INPUT1_OPEN(2,
-				"Input1 goes to open state"), INPUT2_GROUND(3,
-				"Input2 goes to ground state"), INPUT2_OPEN(4,
-				"Input2 goes to open state"), INPUT3_GROUND(5,
-				"Input3 goes to ground state"), INPUT3_OPEN(6,
-				"Input3 goes to open state");
-
-		private int value;
-
-		private String desc;
-
-		private FIELD_EVT_ID_VALUE(int value, String desc) {
-			this.value = value;
-			this.desc = desc;
-		}
-
-		public int getValue() {
-			return value;
-		}
-
-		public String getDesc() {
-			return desc;
-		}
-
-		public FIELD_EVT_ID_VALUE getValueOf(String indiceStr) {
-			int indice = Integer.valueOf(indiceStr);
-			return getValueOf(indice);
-		}
-
-		public FIELD_EVT_ID_VALUE getValueOf(int indice) {
-			switch (indice) {
-			case 1:
-				return INPUT1_GROUND;
-			case 2:
-				return INPUT1_OPEN;
-			case 3:
-				return INPUT2_GROUND;
-			case 4:
-				return INPUT2_OPEN;
-			case 5:
-				return INPUT3_GROUND;
-			case 6:
-				return INPUT3_OPEN;
-			default:
-				throw new IllegalArgumentException("�ndice n�o definido");
-			}
-		}
-	}
-
-	private enum FIELD_ALERT_ID_VALUE {
-		DRIVING_FASTER(1, "Start driving faster than SPEED_LIMIT"), OVER_SPPED(
-				2, "Ended over speed condition"), DISCON_GPS(3,
-				"Disconnected GPS antenna"), RECON_GPS(4,
-				"Reconnected GPS antenna after disconnected"), OUT_GEO_FENCE(5,
-				"The vehicle went out from the geo-fence that has following ID"), INTO_GEO_FENCE(
-				6,
-				"The vehicle entered into the geo-fence that has following ID"), SHORTED_GPS(
-				8, "Shorted GPS antenna"), DEEP_SLEEP_ON(9,
-				"Enter to deep sleep mode"), DEEP_SLEEP_OFF(10,
-				"Exite from deep sleep mode"), BKP_BATTERY(13,
-				"Backup battery error"), BATTERY_DOWN(14,
-				"Vehicle battery goes down to so low level"), SHOCKED(15,
-				"Shocked"), COLLISION(16, "Occurred some collision"), DEVIATE_ROUT(
-				18, "Deviate from predefined rout"), ENTER_ROUT(19,
-				"Enter into predefined rout");
-
-		private int value;
-
-		private String desc;
-
-		private FIELD_ALERT_ID_VALUE(int value, String desc) {
-			this.value = value;
-			this.desc = desc;
-		}
-
-		public int getValue() {
-			return value;
-		}
-
-		public String getDesc() {
-			return desc;
-		}
-
-		public FIELD_ALERT_ID_VALUE getValueOf(String indiceStr) {
-			int indice = Integer.valueOf(indiceStr);
-			return getValueOf(indice);
-		}
-
-		public FIELD_ALERT_ID_VALUE getValueOf(int indice) {
-			switch (indice) {
-			case 1:
-				return DRIVING_FASTER;
-			case 2:
-				return OVER_SPPED;
-			case 3:
-				return DISCON_GPS;
-			case 4:
-				return RECON_GPS;
-			case 5:
-				return OUT_GEO_FENCE;
-			case 6:
-				return INTO_GEO_FENCE;
-			case 8:
-				return SHORTED_GPS;
-			case 9:
-				return DEEP_SLEEP_ON;
-			case 10:
-				return DEEP_SLEEP_OFF;
-			case 13:
-				return BKP_BATTERY;
-			case 14:
-				return BATTERY_DOWN;
-			case 15:
-				return SHOCKED;
-			case 16:
-				return COLLISION;
-			case 18:
-				return DEVIATE_ROUT;
-			case 19:
-				return ENTER_ROUT;
-			default:
-				throw new IllegalArgumentException("�ndice n�o definido");
-			}
-		}
-	}
-
-	private enum ST210REPORTS {
-
-		STATUS("SA200STT"), EMERGENCY("SA200EMG"), EVENT("SA200EVT"), ALERT(
-				"SA200ALT"), ALIVE("SA200ALV");
-
-		private String header;
-
-		private ST210REPORTS(String header) {
-			this.header = header;
-		}
-
-		public String getHeader() {
-			return this.header;
-		}
-
-		public List<ST210FIELDS> getProtocol() {
-
-			if (this.equals(STATUS)) {
-				return StatusProtocol;
-			}
-
-			if (this.equals(EMERGENCY)) {
-				return EmergencyProtocol;
-			}
-
-			if (this.equals(EVENT)) {
-				return EventProtocol;
-			}
-
-			if (this.equals(ALERT)) {
-				return AlertProtocol;
-			}
-
-			if (this.equals(ALIVE)) {
-				return AliveProtocol;
-			}
-
-			return null;
-		}
-
-		public Pattern getProtocolPattern() {
-
-			if (this.equals(STATUS)) {
-				return getPattern(StatusProtocol);
-			}
-
-			if (this.equals(EMERGENCY)) {
-				return getPattern(EmergencyProtocol);
-			}
-
-			if (this.equals(EVENT)) {
-				return getPattern(EventProtocol);
-			}
-
-			if (this.equals(ALERT)) {
-				return getPattern(AlertProtocol);
-			}
-
-			if (this.equals(ALIVE)) {
-				return getPattern(AliveProtocol);
-			}
-
-			return null;
-		}
-
-	}
-
-	private static ST210REPORTS getReportType(String msg) {
-
-		if (msg.startsWith(ST210REPORTS.STATUS.getHeader())) {
-			return ST210REPORTS.STATUS;
-		}
-
-		if (msg.startsWith(ST210REPORTS.EMERGENCY.getHeader())) {
-			return ST210REPORTS.EMERGENCY;
-		}
-
-		if (msg.startsWith(ST210REPORTS.EVENT.getHeader())) {
-			return ST210REPORTS.EVENT;
-		}
-
-		if (msg.startsWith(ST210REPORTS.ALERT.getHeader())) {
-			return ST210REPORTS.ALERT;
-		}
-
-		if (msg.startsWith(ST210REPORTS.ALIVE.getHeader())) {
-			return ST210REPORTS.ALIVE;
-		}
-
-		return null;
-	}
-
-	public static Pattern getPattern(List<ST210FIELDS> protocol) {
-
-		String patternStr = "";
-
-		for (ST210FIELDS field : protocol) {
-			patternStr += field.getPattern();
-		}
-
-		return Pattern.compile(patternStr);
-
-	}
-
-	@SuppressWarnings("serial")
-	static private List<ST210FIELDS> StatusProtocol = new LinkedList<ST210FIELDS>() {
-
-		{
-			add(ST210FIELDS.HDR_STATUS);
-			add(ST210FIELDS.DEV_ID);
-			add(ST210FIELDS.SW_VER);
-			add(ST210FIELDS.DATE);
-			add(ST210FIELDS.TIME);
-			add(ST210FIELDS.CELL);
-			add(ST210FIELDS.LAT);
-			add(ST210FIELDS.LON);
-			add(ST210FIELDS.SPD);
-			add(ST210FIELDS.CRS);
-			add(ST210FIELDS.SATT);
-			add(ST210FIELDS.FIX);
-			add(ST210FIELDS.DIST);
-			add(ST210FIELDS.PWR_VOLT);
-			add(ST210FIELDS.IO);
-			add(ST210FIELDS.MODE);
-			add(ST210FIELDS.MSG_NUM);
-		}
-
-	};
-
-	@SuppressWarnings("serial")
-	static private List<ST210FIELDS> EmergencyProtocol = new LinkedList<ST210FIELDS>() {
-
-		{
-			add(ST210FIELDS.HDR_EMERGENCY);
-			add(ST210FIELDS.DEV_ID);
-			add(ST210FIELDS.SW_VER);
-			add(ST210FIELDS.DATE);
-			add(ST210FIELDS.TIME);
-			add(ST210FIELDS.CELL);
-			add(ST210FIELDS.LAT);
-			add(ST210FIELDS.LON);
-			add(ST210FIELDS.SPD);
-			add(ST210FIELDS.CRS);
-			add(ST210FIELDS.SATT);
-			add(ST210FIELDS.FIX);
-			add(ST210FIELDS.DIST);
-			add(ST210FIELDS.PWR_VOLT);
-			add(ST210FIELDS.IO);
-			add(ST210FIELDS.EMG_ID);
-		}
-
-	};
-
-	@SuppressWarnings("serial")
-	static private List<ST210FIELDS> EventProtocol = new LinkedList<ST210FIELDS>() {
-
-		{
-			add(ST210FIELDS.HDR_EVENT);
-			add(ST210FIELDS.DEV_ID);
-			add(ST210FIELDS.SW_VER);
-			add(ST210FIELDS.DATE);
-			add(ST210FIELDS.TIME);
-			add(ST210FIELDS.CELL);
-			add(ST210FIELDS.LAT);
-			add(ST210FIELDS.LON);
-			add(ST210FIELDS.SPD);
-			add(ST210FIELDS.CRS);
-			add(ST210FIELDS.SATT);
-			add(ST210FIELDS.FIX);
-			add(ST210FIELDS.DIST);
-			add(ST210FIELDS.PWR_VOLT);
-			add(ST210FIELDS.IO);
-			add(ST210FIELDS.EVT_ID);
-		}
-
-	};
-
-	@SuppressWarnings("serial")
-	static private List<ST210FIELDS> AlertProtocol = new LinkedList<ST210FIELDS>() {
-
-		{
-			add(ST210FIELDS.HDR_ALERT);
-			add(ST210FIELDS.DEV_ID);
-			add(ST210FIELDS.SW_VER);
-			add(ST210FIELDS.DATE);
-			add(ST210FIELDS.TIME);
-			add(ST210FIELDS.CELL);
-			add(ST210FIELDS.LAT);
-			add(ST210FIELDS.LON);
-			add(ST210FIELDS.SPD);
-			add(ST210FIELDS.CRS);
-			add(ST210FIELDS.SATT);
-			add(ST210FIELDS.FIX);
-			add(ST210FIELDS.DIST);
-			add(ST210FIELDS.PWR_VOLT);
-			add(ST210FIELDS.IO);
-			add(ST210FIELDS.ALERT_ID);
-		}
-
-	};
-
-	@SuppressWarnings("serial")
-	static private List<ST210FIELDS> AliveProtocol = new LinkedList<ST210FIELDS>() {
-
-		{
-			add(ST210FIELDS.HDR_ALIVE);
-			add(ST210FIELDS.DEV_ID);
-		}
-
-	};
-
-	@Override
-	public Object decode(ChannelHandlerContext ctx, Channel channel, Object msg)
-			throws Exception {
-		if (channel != null) {
-			channel.write("OI AMIGO do decoder");
-		}
-		String sentence = (String) msg;
-		return decodeMsg(sentence);
-	}
-
-	public Position decodeMsg(String msg) throws Exception {
-
-		ST210REPORTS report = getReportType(msg);
-
-		List<ST210FIELDS> protocol = report.getProtocol();
-
-		Pattern protocolPattern = report.getProtocolPattern();
-
-		Log.info("Protocol Pattern: " + protocolPattern.toString());
-		Log.info("Msg: " + msg);
-
-		// Parse message
-		Matcher parser = protocolPattern.matcher(msg);
-		if (!parser.matches()) {
-			return null;
-		}
-
-		// Create new position
-		Position position = new Position();
-
-		position.setAltitude(0D);
-		position.setExtendedInfo("");
-		position.setValid(true);
-
-		Integer index = 0;
-		for (ST210FIELDS field : protocol) {
-
-			String groupValue = parser.group(index++);
-
-			field.populatePosition(position, groupValue, getDataManager());
-		}
-
-		return position;
-	}
+    /**
+     * Initialize
+     */
+    public ST210ProtocolDecoder(DataManager dataManager, Integer resetDelay) {
+        super(dataManager, resetDelay);
+    }
+
+    private enum ST210FIELDS {
+        HDR_STATUS("SA200STT;", "Status Report"), HDR_EMERGENCY("SA200EMG;",
+                "Emergency Report"), HDR_EVENT("SA200EVT;", "Event Report"), HDR_ALERT(
+                "SA200ALT;", "Alert Report"), HDR_ALIVE("SA200ALV;",
+                "Alive Report"), DEV_ID("(\\d+);", "Device ID"), SW_VER(
+                "(\\d{3});", "Software Release Version"), DATE("(\\d+);",
+                "GPS date (yyyymmdd) Year + Month + Day"), TIME(
+                "(\\d{2}:\\d{2}:\\d{2});",
+                "GPS time (hh:mm:ss) Hour : Minute : Second"), CELL(
+                "(\\d{2}\\w\\d{2});",
+                "Location Code ID (3 digits hex) + Serving Cell BSIC(2 digits decimal)"), LAT(
+                "(-\\d{2}.\\d+);", "Latitude (+/-xx.xxxxxx)"), LON(
+                "(-\\d{3}.\\d+);", "Longitude (+/-xxx.xxxxxx)"), SPD(
+                "(\\d{3}.\\d{3});",
+                "Speed in km/h - This value returns to 0 when it is over than 200,000Km"), CRS(
+                "(\\d{3}.\\d{2});", "Course over ground in degree"), SATT(
+                "(\\d+);", "Number of satellites"), FIX("(\\d);",
+                "GPS is fixed(1)\n" + "GPS is not fixed(0)"), DIST("(\\d+);",
+                "Traveled ddistance in meter"), PWR_VOLT("(\\d{2}.\\d{2});",
+                "Voltage value of main power"), IO("(\\d+);",
+                "Current I/O status of inputs and outputs."), MODE("(\\d);",
+                "1 = Idle mode (Parking)\n" + "2 = Active Mode (Driving)"), MSG_NUM(
+                "(\\d{4})",
+                "Message number - After 9999 is reported, message number returns to 0000"), EMG_ID(
+                "(\\d);", "Emergency type"), EVT_ID("(\\d);", "Event type"), ALERT_ID(
+                "(\\d);", "Alert type");
+
+        private String pattern;
+
+        private String desc;
+
+        private ST210FIELDS(String pattern, String desc) {
+            this.pattern = pattern;
+            this.desc = desc;
+        }
+
+        public String getPattern() {
+            return pattern;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public void populatePosition(Position position, String groupValue,
+                DataManager dataManager) throws Exception {
+
+            switch (this) {
+
+            case DEV_ID:
+                position.setDeviceId(dataManager.getDeviceByImei(groupValue)
+                        .getId());
+                break;
+
+            case LAT:
+                position.setLatitude(Double.valueOf(groupValue));
+                break;
+
+            case LON:
+                position.setLongitude(Double.valueOf(groupValue));
+                break;
+
+            case CRS:
+                position.setCourse(Double.valueOf(groupValue));
+                break;
+
+            case PWR_VOLT:
+                position.setPower(Double.valueOf(groupValue));
+                break;
+
+            case SPD:
+                position.setSpeed(Double.valueOf(groupValue));
+                break;
+
+            case DATE: {
+                // Date
+                Calendar time = Calendar.getInstance(TimeZone
+                        .getTimeZone("UTC"));
+                time.clear();
+                time.set(Calendar.YEAR, Integer.valueOf(Integer
+                        .valueOf(groupValue.substring(0, 4))));
+                time.set(Calendar.MONTH, Integer.valueOf(Integer
+                        .valueOf(groupValue.substring(4, 6))));
+                time.set(Calendar.DAY_OF_MONTH, Integer.valueOf(Integer
+                        .valueOf(groupValue.substring(6, 8))));
+                position.setTime(time.getTime());
+                break;
+            }
+
+            case TIME: {
+
+                Calendar time = Calendar.getInstance(TimeZone
+                        .getTimeZone("UTC"));
+                time.clear();
+                time.setTime(position.getTime());
+
+                time.set(Calendar.HOUR, Integer.valueOf(Integer
+                        .valueOf(groupValue.substring(0, 2))));
+                time.set(Calendar.MINUTE, Integer.valueOf(Integer
+                        .valueOf(groupValue.substring(3, 5))));
+                time.set(Calendar.SECOND, Integer.valueOf(Integer
+                        .valueOf(groupValue.substring(6, 8))));
+
+                position.setTime(time.getTime());
+                break;
+            }
+
+            default:
+                break;
+            }
+
+        }
+    }
+
+    private enum FIELD_FIX_VALUE {
+        FIXED(1, "GPS is fixed"), NOT_FIXED(0, "GPS is not fixed");
+
+        private int value;
+
+        private String desc;
+
+        private FIELD_FIX_VALUE(int value, String desc) {
+            this.value = value;
+            this.desc = desc;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public FIELD_FIX_VALUE getValueOf(String indiceStr) {
+            int indice = Integer.valueOf(indiceStr);
+            return getValueOf(indice);
+        }
+
+        public FIELD_FIX_VALUE getValueOf(int indice) {
+            switch (indice) {
+            case 1:
+                return FIXED;
+            case 0:
+                return NOT_FIXED;
+            default:
+                throw new IllegalArgumentException("�ndice n�o definido");
+            }
+        }
+    }
+
+    private enum FIELD_MODE_VALUE {
+        PARKING(1, "Idle mode (Parking)"), DRIVING(2, "Active Mode (Driving)");
+
+        private int value;
+
+        private String desc;
+
+        private FIELD_MODE_VALUE(int value, String desc) {
+            this.value = value;
+            this.desc = desc;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public FIELD_MODE_VALUE getValueOf(String indiceStr) {
+            int indice = Integer.valueOf(indiceStr);
+            return getValueOf(indice);
+        }
+
+        public FIELD_MODE_VALUE getValueOf(int indice) {
+            switch (indice) {
+            case 1:
+                return PARKING;
+            case 2:
+                return DRIVING;
+            default:
+                throw new IllegalArgumentException("�ndice n�o definido");
+            }
+        }
+    }
+
+    private enum FIELD_EMG_ID_VALUE {
+        PANIC(1, "Emergency by panic button"), PARKING(2,
+                "Emergency by parking lock"), MAIN_POWER(3,
+                "Emergency by removing main power"), ANTI_THEFT(5,
+                "Emergency by anti-theft");
+
+        private int value;
+
+        private String desc;
+
+        private FIELD_EMG_ID_VALUE(int value, String desc) {
+            this.value = value;
+            this.desc = desc;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public FIELD_EMG_ID_VALUE getValueOf(String indiceStr) {
+            int indice = Integer.valueOf(indiceStr);
+            return getValueOf(indice);
+        }
+
+        public FIELD_EMG_ID_VALUE getValueOf(int indice) {
+            switch (indice) {
+            case 1:
+                return PANIC;
+            case 2:
+                return PARKING;
+            case 3:
+                return MAIN_POWER;
+            case 5:
+                return ANTI_THEFT;
+            default:
+                throw new IllegalArgumentException("�ndice n�o definido");
+            }
+        }
+    }
+
+    private enum FIELD_EVT_ID_VALUE {
+        INPUT1_GROUND(1, "Input1 goes to ground state"), INPUT1_OPEN(2,
+                "Input1 goes to open state"), INPUT2_GROUND(3,
+                "Input2 goes to ground state"), INPUT2_OPEN(4,
+                "Input2 goes to open state"), INPUT3_GROUND(5,
+                "Input3 goes to ground state"), INPUT3_OPEN(6,
+                "Input3 goes to open state");
+
+        private int value;
+
+        private String desc;
+
+        private FIELD_EVT_ID_VALUE(int value, String desc) {
+            this.value = value;
+            this.desc = desc;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public FIELD_EVT_ID_VALUE getValueOf(String indiceStr) {
+            int indice = Integer.valueOf(indiceStr);
+            return getValueOf(indice);
+        }
+
+        public FIELD_EVT_ID_VALUE getValueOf(int indice) {
+            switch (indice) {
+            case 1:
+                return INPUT1_GROUND;
+            case 2:
+                return INPUT1_OPEN;
+            case 3:
+                return INPUT2_GROUND;
+            case 4:
+                return INPUT2_OPEN;
+            case 5:
+                return INPUT3_GROUND;
+            case 6:
+                return INPUT3_OPEN;
+            default:
+                throw new IllegalArgumentException("�ndice n�o definido");
+            }
+        }
+    }
+
+    private enum FIELD_ALERT_ID_VALUE {
+        DRIVING_FASTER(1, "Start driving faster than SPEED_LIMIT"), OVER_SPPED(
+                2, "Ended over speed condition"), DISCON_GPS(3,
+                "Disconnected GPS antenna"), RECON_GPS(4,
+                "Reconnected GPS antenna after disconnected"), OUT_GEO_FENCE(5,
+                "The vehicle went out from the geo-fence that has following ID"), INTO_GEO_FENCE(
+                6,
+                "The vehicle entered into the geo-fence that has following ID"), SHORTED_GPS(
+                8, "Shorted GPS antenna"), DEEP_SLEEP_ON(9,
+                "Enter to deep sleep mode"), DEEP_SLEEP_OFF(10,
+                "Exite from deep sleep mode"), BKP_BATTERY(13,
+                "Backup battery error"), BATTERY_DOWN(14,
+                "Vehicle battery goes down to so low level"), SHOCKED(15,
+                "Shocked"), COLLISION(16, "Occurred some collision"), DEVIATE_ROUT(
+                18, "Deviate from predefined rout"), ENTER_ROUT(19,
+                "Enter into predefined rout");
+
+        private int value;
+
+        private String desc;
+
+        private FIELD_ALERT_ID_VALUE(int value, String desc) {
+            this.value = value;
+            this.desc = desc;
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        public String getDesc() {
+            return desc;
+        }
+
+        public FIELD_ALERT_ID_VALUE getValueOf(String indiceStr) {
+            int indice = Integer.valueOf(indiceStr);
+            return getValueOf(indice);
+        }
+
+        public FIELD_ALERT_ID_VALUE getValueOf(int indice) {
+            switch (indice) {
+            case 1:
+                return DRIVING_FASTER;
+            case 2:
+                return OVER_SPPED;
+            case 3:
+                return DISCON_GPS;
+            case 4:
+                return RECON_GPS;
+            case 5:
+                return OUT_GEO_FENCE;
+            case 6:
+                return INTO_GEO_FENCE;
+            case 8:
+                return SHORTED_GPS;
+            case 9:
+                return DEEP_SLEEP_ON;
+            case 10:
+                return DEEP_SLEEP_OFF;
+            case 13:
+                return BKP_BATTERY;
+            case 14:
+                return BATTERY_DOWN;
+            case 15:
+                return SHOCKED;
+            case 16:
+                return COLLISION;
+            case 18:
+                return DEVIATE_ROUT;
+            case 19:
+                return ENTER_ROUT;
+            default:
+                throw new IllegalArgumentException("�ndice n�o definido");
+            }
+        }
+    }
+
+    private enum ST210REPORTS {
+
+        STATUS("SA200STT"), EMERGENCY("SA200EMG"), EVENT("SA200EVT"), ALERT(
+                "SA200ALT"), ALIVE("SA200ALV");
+
+        private String header;
+
+        private ST210REPORTS(String header) {
+            this.header = header;
+        }
+
+        public String getHeader() {
+            return this.header;
+        }
+
+        public List<ST210FIELDS> getProtocol() {
+
+            if (this.equals(STATUS)) {
+                return StatusProtocol;
+            }
+
+            if (this.equals(EMERGENCY)) {
+                return EmergencyProtocol;
+            }
+
+            if (this.equals(EVENT)) {
+                return EventProtocol;
+            }
+
+            if (this.equals(ALERT)) {
+                return AlertProtocol;
+            }
+
+            if (this.equals(ALIVE)) {
+                return AliveProtocol;
+            }
+
+            return null;
+        }
+
+        public Pattern getProtocolPattern() {
+
+            if (this.equals(STATUS)) {
+                return getPattern(StatusProtocol);
+            }
+
+            if (this.equals(EMERGENCY)) {
+                return getPattern(EmergencyProtocol);
+            }
+
+            if (this.equals(EVENT)) {
+                return getPattern(EventProtocol);
+            }
+
+            if (this.equals(ALERT)) {
+                return getPattern(AlertProtocol);
+            }
+
+            if (this.equals(ALIVE)) {
+                return getPattern(AliveProtocol);
+            }
+
+            return null;
+        }
+
+    }
+
+    private static ST210REPORTS getReportType(String msg) {
+
+        if (msg.startsWith(ST210REPORTS.STATUS.getHeader())) {
+            return ST210REPORTS.STATUS;
+        }
+
+        if (msg.startsWith(ST210REPORTS.EMERGENCY.getHeader())) {
+            return ST210REPORTS.EMERGENCY;
+        }
+
+        if (msg.startsWith(ST210REPORTS.EVENT.getHeader())) {
+            return ST210REPORTS.EVENT;
+        }
+
+        if (msg.startsWith(ST210REPORTS.ALERT.getHeader())) {
+            return ST210REPORTS.ALERT;
+        }
+
+        if (msg.startsWith(ST210REPORTS.ALIVE.getHeader())) {
+            return ST210REPORTS.ALIVE;
+        }
+
+        return null;
+    }
+
+    public static Pattern getPattern(List<ST210FIELDS> protocol) {
+
+        String patternStr = "";
+
+        for (ST210FIELDS field : protocol) {
+            patternStr += field.getPattern();
+        }
+
+        return Pattern.compile(patternStr);
+
+    }
+
+    @SuppressWarnings("serial")
+    static private List<ST210FIELDS> StatusProtocol = new LinkedList<ST210FIELDS>() {
+
+        {
+            add(ST210FIELDS.HDR_STATUS);
+            add(ST210FIELDS.DEV_ID);
+            add(ST210FIELDS.SW_VER);
+            add(ST210FIELDS.DATE);
+            add(ST210FIELDS.TIME);
+            add(ST210FIELDS.CELL);
+            add(ST210FIELDS.LAT);
+            add(ST210FIELDS.LON);
+            add(ST210FIELDS.SPD);
+            add(ST210FIELDS.CRS);
+            add(ST210FIELDS.SATT);
+            add(ST210FIELDS.FIX);
+            add(ST210FIELDS.DIST);
+            add(ST210FIELDS.PWR_VOLT);
+            add(ST210FIELDS.IO);
+            add(ST210FIELDS.MODE);
+            add(ST210FIELDS.MSG_NUM);
+        }
+
+    };
+
+    @SuppressWarnings("serial")
+    static private List<ST210FIELDS> EmergencyProtocol = new LinkedList<ST210FIELDS>() {
+
+        {
+            add(ST210FIELDS.HDR_EMERGENCY);
+            add(ST210FIELDS.DEV_ID);
+            add(ST210FIELDS.SW_VER);
+            add(ST210FIELDS.DATE);
+            add(ST210FIELDS.TIME);
+            add(ST210FIELDS.CELL);
+            add(ST210FIELDS.LAT);
+            add(ST210FIELDS.LON);
+            add(ST210FIELDS.SPD);
+            add(ST210FIELDS.CRS);
+            add(ST210FIELDS.SATT);
+            add(ST210FIELDS.FIX);
+            add(ST210FIELDS.DIST);
+            add(ST210FIELDS.PWR_VOLT);
+            add(ST210FIELDS.IO);
+            add(ST210FIELDS.EMG_ID);
+        }
+
+    };
+
+    @SuppressWarnings("serial")
+    static private List<ST210FIELDS> EventProtocol = new LinkedList<ST210FIELDS>() {
+
+        {
+            add(ST210FIELDS.HDR_EVENT);
+            add(ST210FIELDS.DEV_ID);
+            add(ST210FIELDS.SW_VER);
+            add(ST210FIELDS.DATE);
+            add(ST210FIELDS.TIME);
+            add(ST210FIELDS.CELL);
+            add(ST210FIELDS.LAT);
+            add(ST210FIELDS.LON);
+            add(ST210FIELDS.SPD);
+            add(ST210FIELDS.CRS);
+            add(ST210FIELDS.SATT);
+            add(ST210FIELDS.FIX);
+            add(ST210FIELDS.DIST);
+            add(ST210FIELDS.PWR_VOLT);
+            add(ST210FIELDS.IO);
+            add(ST210FIELDS.EVT_ID);
+        }
+
+    };
+
+    @SuppressWarnings("serial")
+    static private List<ST210FIELDS> AlertProtocol = new LinkedList<ST210FIELDS>() {
+
+        {
+            add(ST210FIELDS.HDR_ALERT);
+            add(ST210FIELDS.DEV_ID);
+            add(ST210FIELDS.SW_VER);
+            add(ST210FIELDS.DATE);
+            add(ST210FIELDS.TIME);
+            add(ST210FIELDS.CELL);
+            add(ST210FIELDS.LAT);
+            add(ST210FIELDS.LON);
+            add(ST210FIELDS.SPD);
+            add(ST210FIELDS.CRS);
+            add(ST210FIELDS.SATT);
+            add(ST210FIELDS.FIX);
+            add(ST210FIELDS.DIST);
+            add(ST210FIELDS.PWR_VOLT);
+            add(ST210FIELDS.IO);
+            add(ST210FIELDS.ALERT_ID);
+        }
+
+    };
+
+    @SuppressWarnings("serial")
+    static private List<ST210FIELDS> AliveProtocol = new LinkedList<ST210FIELDS>() {
+
+        {
+            add(ST210FIELDS.HDR_ALIVE);
+            add(ST210FIELDS.DEV_ID);
+        }
+
+    };
+
+    @Override
+    public Object decode(ChannelHandlerContext ctx, Channel channel, Object msg)
+            throws Exception {
+        if (channel != null) {
+            channel.write("OI AMIGO do decoder");
+        }
+        String sentence = (String) msg;
+        return decodeMsg(sentence);
+    }
+
+    public Position decodeMsg(String msg) throws Exception {
+
+        ST210REPORTS report = getReportType(msg);
+
+        List<ST210FIELDS> protocol = report.getProtocol();
+
+        Pattern protocolPattern = report.getProtocolPattern();
+
+        Log.info("Protocol Pattern: " + protocolPattern.toString());
+        Log.info("Msg: " + msg);
+
+        // Parse message
+        Matcher parser = protocolPattern.matcher(msg);
+        if (!parser.matches()) {
+            return null;
+        }
+
+        // Create new position
+        Position position = new Position();
+
+        position.setAltitude(0D);
+        position.setExtendedInfo("");
+        position.setValid(true);
+
+        Integer index = 0;
+        for (ST210FIELDS field : protocol) {
+
+            String groupValue = parser.group(index++);
+
+            field.populatePosition(position, groupValue, getDataManager());
+        }
+
+        return position;
+    }
 }
diff --git a/test/org/traccar/protocol/ST210ProtocolDecoderTest.java b/test/org/traccar/protocol/ST210ProtocolDecoderTest.java
index a2ffe5bac..cf5b0b8e6 100644
--- a/test/org/traccar/protocol/ST210ProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/ST210ProtocolDecoderTest.java
@@ -10,64 +10,64 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 import org.traccar.Server;
 import org.traccar.helper.Log;
-import org.traccar.server.SocketCliente;
+//import org.traccar.server.SocketCliente;
 
 public class ST210ProtocolDecoderTest {
 
-	@BeforeClass
-	public static void UpServer() {
-		final Server service = new Server();
-		String[] args = new String[1];
-		args[0] = "setup\\windows\\windows.cfg";
-		try {
-			service.init(args);
+    /*@BeforeClass
+    public static void UpServer() {
+        final Server service = new Server();
+        String[] args = new String[1];
+        args[0] = "setup\\windows\\windows.cfg";
+        try {
+            service.init(args);
 
-			Log.info("starting server...");
-			service.start();
+            Log.info("starting server...");
+            service.start();
 
-			// Shutdown server properly
-			Runtime.getRuntime().addShutdownHook(new Thread() {
-				@Override
-				public void run() {
-					Log.info("shutting down server...");
-					service.stop();
-				}
-			});
+            // Shutdown server properly
+            Runtime.getRuntime().addShutdownHook(new Thread() {
+                @Override
+                public void run() {
+                    Log.info("shutting down server...");
+                    service.stop();
+                }
+            });
 
-		} catch (Exception e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-	}
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
 
-	@Test
-	public void testClienteMsg() throws Exception {
+    @Test
+    public void testClienteMsg() throws Exception {
 
-		SocketCliente cliente = new SocketCliente();
-		cliente.SendMSG(
-				"localhost",
-				5010,
-				"SA200STT;317652;042;20120718;15:37:12;16d41;-15.618755;-056.083241;000.024;000.00;8;1;41548;12.17;100000;2;1979");
+        SocketCliente cliente = new SocketCliente();
+        cliente.SendMSG(
+                "localhost",
+                5010,
+                "SA200STT;317652;042;20120718;15:37:12;16d41;-15.618755;-056.083241;000.024;000.00;8;1;41548;12.17;100000;2;1979");
 
-	}
+    }*/
 
-/*	@Test
-	public void testDecode() throws Exception {
+/*    @Test
+    public void testDecode() throws Exception {
 
-		ST210ProtocolDecoder decoder = new ST210ProtocolDecoder(
-				new TestDataManager(), 0);
+        ST210ProtocolDecoder decoder = new ST210ProtocolDecoder(
+                new TestDataManager(), 0);
 
-		assertNotNull(decoder
-				.decode(null,
-						null,
-						"SA200STT;317652;042;20120718;15:37:12;16d41;-15.618755;-056.083241;000.024;000.00;8;1;41548;12.17;100000;2;1979"));
-		assertNotNull(decoder
-				.decode(null,
-						null,
-						"SA200STT;317652;042;20120721;19:04:30;16d41;-15.618743;-056.083221;000.001;000.00;12;1;41557;12.21;000000;1;3125"));
-		assertNotNull(decoder
-				.decode(null,
-						null,
-						"SA200STT;317652;042;20120722;00:24:23;16d41;-15.618767;-056.083214;000.011;000.00;11;1;41557;12.21;000000;1;3205"));
-	}*/
+        assertNotNull(decoder
+                .decode(null,
+                        null,
+                        "SA200STT;317652;042;20120718;15:37:12;16d41;-15.618755;-056.083241;000.024;000.00;8;1;41548;12.17;100000;2;1979"));
+        assertNotNull(decoder
+                .decode(null,
+                        null,
+                        "SA200STT;317652;042;20120721;19:04:30;16d41;-15.618743;-056.083221;000.001;000.00;12;1;41557;12.21;000000;1;3125"));
+        assertNotNull(decoder
+                .decode(null,
+                        null,
+                        "SA200STT;317652;042;20120722;00:24:23;16d41;-15.618767;-056.083214;000.011;000.00;11;1;41557;12.21;000000;1;3205"));
+    }*/
 }
-- 
cgit v1.2.3


From 9784127324bee141e8f3740a156a6fd24bd0273b Mon Sep 17 00:00:00 2001
From: Anton Tananaev <anton.tananaev@gmail.com>
Date: Sat, 4 Aug 2012 17:11:59 +0400
Subject: Change pattern (fix #34)

---
 src/org/traccar/protocol/Gps103ProtocolDecoder.java      | 2 +-
 test/org/traccar/protocol/Gps103ProtocolDecoderTest.java | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

(limited to 'src/org/traccar')

diff --git a/src/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/org/traccar/protocol/Gps103ProtocolDecoder.java
index 714a80e49..88a83ef80 100644
--- a/src/org/traccar/protocol/Gps103ProtocolDecoder.java
+++ b/src/org/traccar/protocol/Gps103ProtocolDecoder.java
@@ -45,7 +45,7 @@ public class Gps103ProtocolDecoder extends GenericProtocolDecoder {
             "([\\d]+)," +                       // IMEI
             "[^,]+," +
             "(\\d{2})(\\d{2})(\\d{2})[\\d]+," + // Date
-            "[+]?[\\d]*," +
+            "[^,]*," +
             "[FL]," +                           // F - full / L - low
             "([\\d]{2})([\\d]{2})([\\d]{2}).([\\d]{3})," + // Time (HHMMSS.SSS)
             "([AV])," +                         // Validity
diff --git a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java
index c09b34bc7..7a90e4fbd 100644
--- a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java
@@ -23,6 +23,9 @@ public class Gps103ProtocolDecoderTest {
 
         assertNotNull(decoder.decode(null, null,
                 "imei:359587010124900,tracker,0809231929,13554900601,F,112909.397,A,2234.4669,N,11354.3287,E,0.11,321.53,"));
+        
+        assertNotNull(decoder.decode(null, null,
+                "imei:353451049926460,tracker,1208042043,123456 99008026,F,124336.000,A,3509.8668,N,03322.7636,E,0.00,,"));
 
     }
 
-- 
cgit v1.2.3


From 9d31234ca1882f58c6046e8f6897a39a77b1e6a7 Mon Sep 17 00:00:00 2001
From: Anton Tananaev <anton.tananaev@gmail.com>
Date: Sat, 4 Aug 2012 17:54:50 +0400
Subject: Modify directory structure

---
 pom.xml                             |   2 +-
 src/Main.java                       |  24 -
 src/favicon.ico                     | Bin 1150 -> 0 bytes
 src/index.html                      | 977 ------------------------------------
 src/org/traccar/Main.java           |  40 ++
 src/org/traccar/http/WebServer.java |   4 +-
 src/web/favicon.ico                 | Bin 0 -> 1150 bytes
 src/web/index.html                  | 977 ++++++++++++++++++++++++++++++++++++
 8 files changed, 1020 insertions(+), 1004 deletions(-)
 delete mode 100644 src/Main.java
 delete mode 100644 src/favicon.ico
 delete mode 100644 src/index.html
 create mode 100644 src/org/traccar/Main.java
 create mode 100644 src/web/favicon.ico
 create mode 100644 src/web/index.html

(limited to 'src/org/traccar')

diff --git a/pom.xml b/pom.xml
index 7b4a16420..c25d05498 100644
--- a/pom.xml
+++ b/pom.xml
@@ -101,7 +101,7 @@
                 <configuration>
                     <archive>
                         <manifest>
-                            <mainClass>Main</mainClass>
+                            <mainClass>org.traccar.Main</mainClass>
                             <addClasspath>true</addClasspath>
                             <classpathPrefix>lib/</classpathPrefix>
                         </manifest>
diff --git a/src/Main.java b/src/Main.java
deleted file mode 100644
index 8b6f2002e..000000000
--- a/src/Main.java
+++ /dev/null
@@ -1,24 +0,0 @@
-import org.traccar.Server;
-import org.traccar.helper.Log;
-
-public class Main {
-
-    public static void main(String[] args) throws Exception {
-
-        final Server service = new Server();
-        service.init(args);
-
-        Log.info("starting server...");
-        service.start();
-
-        // Shutdown server properly
-        Runtime.getRuntime().addShutdownHook(new Thread() {
-            @Override
-            public void run() {
-                Log.info("shutting down server...");
-                service.stop();
-            }
-        });
-
-    }
-}
diff --git a/src/favicon.ico b/src/favicon.ico
deleted file mode 100644
index 6fd696c25..000000000
Binary files a/src/favicon.ico and /dev/null differ
diff --git a/src/index.html b/src/index.html
deleted file mode 100644
index cde73f47b..000000000
--- a/src/index.html
+++ /dev/null
@@ -1,977 +0,0 @@
-<!doctype html>
-<html>
-<head>
-<title>Traccar Manager</title>
-<link rel="stylesheet" type="text/css" href="http://cdn.sencha.io/ext-4.1.0-gpl/resources/css/ext-all.css" />
-<script type="text/javascript" src="http://cdn.sencha.io/ext-4.1.0-gpl/ext-all.js"></script>
-
-<!-- check for new version: https://raw.github.com/VinylFox/ExtJS.ux.GMapPanel/master/src/GMapPanel3.js -->
-
-<script type="text/javascript">
-Ext.ns('Ext.ux');
-/**
- * @class Ext.ux.GMapPanel
- * @extends Ext.Panel
- * @author Shea Frederick
- */
-Ext.define('Ext.ux.GMapPanel', {
-
-    extend: 'Ext.panel.Panel',
-
-    alias: 'widget.gmappanel',
-
-    requires: ['Ext.window.MessageBox'],
-    /**
-     * @cfg {Boolean} border
-     * Defaults to <tt>false</tt>.  See {@link Ext.Panel}.<code>{@link Ext.Panel#border border}</code>.
-     */
-    border: false,
-
-    /**
-     * @cfg {Array} respErrors
-     * An array of msg/code pairs.
-     */
-    respErrors: [{
-            code: 'UNKNOWN_ERROR',
-            msg: 'A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.'
-        },{
-            code: 'ERROR',
-            msg: 'There was a problem contacting the Google servers.'
-        },{
-            code: 'ZERO_RESULTS',
-            msg: 'The request did not encounter any errors but returns zero results.'
-        },{
-            code: 'INVALID_REQUEST',
-            msg: 'This request was invalid.'
-        },{
-            code: 'REQUEST_DENIED',
-            msg: 'The webpage is not allowed to use the geocoder for some reason.'
-        },{
-            code: 'OVER_QUERY_LIMIT',
-            msg: 'The webpage has gone over the requests limit in too short a period of time.'
-    }],
-    /**
-     * @cfg {Array} locationTypes
-     * An array of msg/code/level pairs.
-     */
-    locationTypes: [{
-            level: 4,
-            code: 'ROOFTOP',
-            msg: 'The returned result is a precise geocode for which we have location information accurate down to street address precision.'
-        },{
-            level: 3,
-            code: 'RANGE_INTERPOLATED',
-            msg: 'The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.'
-        },{
-            level: 2,
-            code: 'GEOMETRIC_CENTER',
-            msg: 'The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).'
-        },{
-            level: 1,
-            code: 'APPROXIMATE',
-            msg: 'The returned result is approximate.'
-    }],
-    /**
-     * @cfg {String} respErrorTitle
-     * Defaults to <tt>'Error'</tt>.
-     */
-    respErrorTitle : 'Error',
-    /**
-     * @cfg {String} geoErrorMsgUnable
-     * Defaults to <tt>'Unable to Locate the Address you provided'</tt>.
-     */
-    geoErrorMsgUnable : 'Unable to Locate the Address you provided',
-    /**
-     * @cfg {String} geoErrorTitle
-     * Defaults to <tt>'Address Location Error'</tt>.
-     */
-    geoErrorTitle : 'Address Location Error',
-    /**
-     * @cfg {String} geoErrorMsgAccuracy
-     * Defaults to <tt>'The address provided has a low accuracy.<br><br>{0} Accuracy.'</tt>.
-     * <div class="mdetail-params"><ul>
-     * <li><b><code>ROOFTOP</code></b> : <div class="sub-desc"><p>
-     * The returned result is a precise geocode for which we have location information accurate down to street address precision.
-     * </p></div></li>
-     * <li><b><code>RANGE_INTERPOLATED</code></b> : <div class="sub-desc"><p>
-     * The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.
-     * </p></div></li>
-     * <li><b><code>GEOMETRIC_CENTER</code></b> : <div class="sub-desc"><p>
-     * The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).
-     * </p></div></li>
-     * <li><b><code>APPROXIMATE</code></b> : <div class="sub-desc"><p>
-     * The returned result is approximate.
-     * </p></div></li>
-     * </ul></div>
-     */
-    geoErrorMsgAccuracy : 'The address provided has a low accuracy.<br><br>"{0}" Accuracy.<br><br>{1}',
-    /**
-     * @cfg {String} gmapType
-     * The type of map to display, generic options available are: 'map', 'panorama'.
-     * Defaults to <tt>'map'</tt>.
-     * More specific maps can be used by specifying the google map type:
-     * <div class="mdetail-params"><ul>
-     * <li><b><code>G_NORMAL_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays the default road map view
-     * </p></div></li>
-     * <li><b><code>G_SATELLITE_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays Google Earth satellite images
-     * </p></div></li>
-     * <li><b><code>G_HYBRID_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays a mixture of normal and satellite views
-     * </p></div></li>
-     * <li><b><code>G_DEFAULT_MAP_TYPES</code></b> : <div class="sub-desc"><p>
-     * Contains an array of the above three types, useful for iterative processing.
-     * </p></div></li>
-     * <li><b><code>G_PHYSICAL_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays a physical map based on terrain information.
-     * </p></div></li>
-     * <li><b><code>G_MOON_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays a shaded terrain map of the surface of the Moon, color-coded by altitude.
-     * </p></div></li>
-     * <li><b><code>G_MOON_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays photographic imagery taken from orbit around the moon.
-     * </p></div></li>
-     * <li><b><code>G_MARS_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays a shaded terrain map of the surface of Mars, color-coded by altitude.
-     * </p></div></li>
-     * <li><b><code>G_MARS_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays photographs taken from orbit around Mars.
-     * </p></div></li>
-     * <li><b><code>G_MARS_INFRARED_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays a shaded infrared map of the surface of Mars, where warmer areas appear brighter and colder areas appear darker.
-     * </p></div></li>
-     * <li><b><code>G_SKY_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
-     * Displays a mosaic of the sky, as seen from Earth, covering the full celestial sphere.
-     * </p></div></li>
-     * </ul></div>
-     * Sample usage:
-     * <pre><code>
-     * gmapType: G_MOON_VISIBLE_MAP
-     * </code></pre>
-     */
-    gmapType : 'map',
-    /**
-     * @cfg {Object} setCenter
-     * The initial center location of the map. The map needs to be centered before it can be used.
-     * A marker is not required to be specified.
-     * More markers can be added to the map using the <code>{@link #markers}</code> array.
-     * For example:
-     * <pre><code>
-setCenter: {
-    geoCodeAddr: '4 Yawkey Way, Boston, MA, 02215-3409, USA',
-    marker: {title: 'Fenway Park'}
-},
-
-// or just specify lat/long
-setCenter: {
-    lat: 42.345573,
-    lng: -71.098326
-}
-     * </code></pre>
-     */
-    /**
-     * @cfg {Number} zoomLevel
-     * The zoom level to initialize the map at, generally between 1 (whole planet) and 40 (street).
-     * Also used as the zoom level for panoramas, zero specifies no zoom at all.
-     * Defaults to <tt>3</tt>.
-     */
-    zoomLevel: 3,
-    /**
-     * @cfg {Number} yaw
-     * The Yaw, or rotational direction of the users perspective in degrees. Only applies to panoramas.
-     * Defaults to <tt>180</tt>.
-     */
-    yaw: 180,
-    /**
-     * @cfg {Number} pitch
-     * The pitch, or vertical direction of the users perspective in degrees.
-     * Defaults to <tt>0</tt> (straight ahead). Valid values are between +90 (straight up) and -90 (straight down).
-     */
-    pitch: 0,
-    /**
-     * @cfg {Boolean} displayGeoErrors
-     * True to display geocoding errors to the end user via a message box.
-     * Defaults to <tt>false</tt>.
-     */
-    displayGeoErrors: false,
-    /**
-     * @cfg {Boolean} minGeoAccuracy
-     * The level to display an accuracy error below. Defaults to <tt>ROOFTOP</tt>. For additional information
-     * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy">here</a>.
-     */
-    minGeoAccuracy: 'ROOFTOP',
-    /**
-     * @cfg {Array} mapConfOpts
-     * Array of strings representing configuration methods to call, a full list can be found
-     * <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2">here</a>.
-     * For example:
-     * <pre><code>
-     * mapConfOpts: ['enableScrollWheelZoom','enableDoubleClickZoom','enableDragging'],
-     * </code></pre>
-     */
-    /**
-     * @cfg {Array} mapControls
-     * Array of strings representing map controls to initialize, a full list can be found
-     * <a href="http://code.google.com/apis/maps/documentation/reference.html#GControlImpl">here</a>.
-     * For example:
-     * <pre><code>
-     * mapControls: ['GSmallMapControl','GMapTypeControl','NonExistantControl']
-     * </code></pre>
-     */
-    /**
-     * @cfg {Array} markers
-     * Markers may be added to the map. Instead of specifying <code>lat</code>/<code>lng</code>,
-     * geocoding can be specified via a <code>geoCodeAddr</code> string.
-     * For example:
-     * <pre><code>
-markers: [{
-    //lat: 42.339641,
-    //lng: -71.094224,
-    // instead of lat/lng:
-    geoCodeAddr: '465 Huntington Avenue, Boston, MA, 02215-5597, USA',
-    marker: {title: 'Boston Museum of Fine Arts'},
-    listeners: {
-        click: function(e){
-            Ext.Msg.alert('Its fine', 'and its art.');
-        }
-    }
-},{
-    lat: 42.339419,
-    lng: -71.09077,
-    marker: {title: 'Northeastern University'}
-}]
-     * </code></pre>
-     */
-    // private
-    mapDefined: false,
-    // private
-    mapDefinedGMap: false,
-    initComponent : function(){
-
-        this.addEvents(
-            /**
-             * @event mapready
-             * Fires when the map is ready for interaction
-             * @param {GMapPanel} this
-             * @param {GMap} map
-             */
-            'mapready',
-            /**
-             * @event apiready
-             * Fires when the Google Maps API is loaded
-             */
-            'apiready'
-        );
-
-        Ext.applyIf(this,{
-          markers: [],
-          cache: {
-              marker: [],
-              polyline: [],
-              infowindow: []
-          }
-        });
-
-        Ext.ux.GMapPanel.superclass.initComponent.call(this);
-
-        if (window.google && window.google.maps){
-          this.on('afterrender', this.apiReady, this);
-        }else{
-          window.gmapapiready = Ext.Function.bind(this.apiReady,this);
-          this.buildScriptTag('http://maps.google.com/maps/api/js?sensor=false&callback=gmapapiready');
-        }
-
-    },
-    apiReady : function(){
-
-        if (this.rendered){
-
-          Ext.defer(function(){
-              if (this.gmapType === 'map'){
-                  this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
-                  this.mapDefined = true;
-                  this.mapDefinedGMap = true;
-              }
-
-              if (this.gmapType === 'panorama'){
-                  this.gmap = new GStreetviewPanorama(this.getEl().dom);
-                  this.mapDefined = true;
-              }
-
-              if (!this.mapDefined && this.gmapType){
-                 this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
-                 this.gmap.setMapTypeId(this.gmapType);
-                 this.mapDefined = true;
-                 this.mapDefinedGMap = true;
-              }
-
-              google.maps.event.addListenerOnce(this.getMap(), 'tilesloaded', Ext.Function.bind(this.onMapReady, this));
-              google.maps.event.addListener(this.getMap(), 'dragend', Ext.Function.bind(this.dragEnd, this));
-
-
-              if (typeof this.setCenter === 'object') {
-                  if (typeof this.setCenter.geoCodeAddr === 'string'){
-                      this.geoCodeLookup(this.setCenter.geoCodeAddr, this.setCenter.marker, false, true, this.setCenter.listeners);
-                  }else{
-                      if (this.gmapType === 'map'){
-                          var point = new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng);
-                          this.getMap().setCenter(point, this.zoomLevel);
-                          this.lastCenter = point;
-                      }
-                      if (typeof this.setCenter.marker === 'object' && typeof point === 'object') {
-                          this.addMarker(point, this.setCenter.marker, this.setCenter.marker.clear);
-                      }
-                  }
-                  if (this.gmapType === 'panorama'){
-                      this.getMap().setLocationAndPOV(new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoomLevel});
-                  }
-              }
-        }, 200,this); // Ext.defer
-
-        }else{
-          this.on('afterrender', this.apiReady, this);
-        }
-    },
-    // private
-    afterRender : function(){
-
-        var wh = this.ownerCt.getSize();
-        Ext.applyIf(this, wh);
-
-        Ext.ux.GMapPanel.superclass.afterRender.call(this);
-
-    },
-    // private
-    buildScriptTag: function(filename, callback) {
-        var script  = document.createElement('script'),
-        head        = document.getElementsByTagName("head")[0];
-        script.type = "text/javascript";
-        script.src  = filename;
-
-        return head.appendChild(script);
-    },
-    // private
-    onMapReady : function(){
-
-        this.addMapControls();
-        this.addOptions();
-
-        this.addMarkers(this.markers);
-        this.addMapListeners();
-
-        this.fireEvent('mapready', this, this.getMap());
-        return this;
-    },
-    // private
-    addMapListeners : function () {
-      	if (this.maplisteners){
-      		Ext.iterate(this.maplisteners, function(key,val){
-      			google.maps.event.addListener(this.getMap(), key, Ext.Function.bind(val,this));
-      		},this);
-      	}
-    },
-    // private
-    onResize : function(w, h){
-
-        Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);
-
-        // check for the existance of the google map in case the onResize fires too early
-        if (typeof this.getMap() == 'object') {
-            google.maps.event.trigger(this.getMap(), 'resize');
-            if (this.lastCenter){
-              this.getMap().setCenter(this.lastCenter, this.zoomLevel);
-            }
-        }
-
-    },
-    // private
-    setSize : function(width, height, animate){
-
-        Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);
-
-        // check for the existance of the google map in case setSize is called too early
-        if (Ext.isObject(this.getMap())) {
-            google.maps.event.trigger(this.getMap(), 'resize');
-            if (this.lastCenter){
-              this.getMap().setCenter(this.lastCenter, this.zoomLevel);
-            }
-        }
-
-    },
-    // private
-    dragEnd: function(){
-      this.lastCenter = this.getMap().getCenter();
-    },
-    /**
-     * Returns the current google map which can be used to call Google Maps API specific handlers.
-     * @return {GMap} this
-     */
-    getMap : function(){
-
-        return this.gmap;
-
-    },
-    /**
-     * Returns the maps center as a GLatLng object
-     * @return {GLatLng} this
-     */
-    getCenter : function(){
-
-        return this.getMap().getCenter();
-
-    },
-    /**
-     * Returns the maps center as a simple object
-     * @return {Object} this has lat and lng properties only
-     */
-    getCenterLatLng : function(){
-
-        var ll = this.getCenter();
-        return {lat: ll.lat(), lng: ll.lng()};
-
-    },
-    /**
-     * Creates markers from the array that is passed in. Each marker must consist of at least
-     * <code>lat</code> and <code>lng</code> properties or a <code>geoCodeAddr</code>.
-     * @param {Array} markers an array of marker objects
-     */
-    addMarkers : function(markers) {
-        if (Ext.isArray(markers)){
-            for (var i = 0; i < markers.length; i++) {
-                if (markers[i]) {
-                    if (typeof markers[i].geoCodeAddr == 'string') {
-                        this.geoCodeLookup(markers[i].geoCodeAddr, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
-                    } else {
-                        var mkr_point = new google.maps.LatLng(markers[i].lat, markers[i].lng);
-                        this.addMarker(mkr_point, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
-                    }
-                }
-            }
-        }
-
-    },
-    /**
-     * Creates a single marker.
-     * @param {Object} point a GLatLng point
-     * @param {Object} marker a marker object consisting of at least lat and lng
-     * @param {Boolean} clear clear other markers before creating this marker
-     * @param {Boolean} center true to center the map on this marker
-     * @param {Object} listeners a listeners config
-     */
-    addMarker : function(point, marker, clear, center, listeners){
-
-        Ext.applyIf(marker,{});
-
-        if (clear === true){
-            this.clearMarkers();
-        }
-        if (center === true) {
-            this.getMap().setCenter(point, this.zoomLevel)
-            this.lastCenter = point;
-        }
-
-        var mark = new google.maps.Marker(Ext.apply(marker, {
-            position: point
-        }));
-
-        if (marker.infoWindow){
-            this.createInfoWindow(marker.infoWindow, point, mark);
-        }
-
-        this.cache.marker.push(mark);
-        mark.setMap(this.getMap());
-
-        if (typeof listeners === 'object'){
-            for (evt in listeners) {
-                google.maps.event.addListener(mark, evt, listeners[evt]);
-            }
-        }
-
-        return mark;
-
-    },
-    /**
-     * Creates a single polyline.
-     * @param {Array} points an array of polyline points
-     * @param {Object} linestyle an object defining the line style to use
-     */
-    addPolyline : function(points, linestyle){
-
-        var plinepnts = new google.maps.MVCArray, pline, linestyle = linestyle ? linestyle : {
-            strokeColor: '#FF0000',
-            strokeOpacity: 1.0,
-            strokeWeight: 2
-        };
-
-        Ext.each(points, function(point){
-            plinepnts.push(new google.maps.LatLng(point.lat, point.lng));
-        }, this);
-
-        var pline = new google.maps.Polyline(Ext.apply({
-          path: plinepnts
-        },linestyle));
-
-        this.cache.polyline.push(pline);
-
-        pline.setMap(this.getMap());
-
-    },
-    /**
-     * Creates an Info Window.
-     * @param {Object} inwin an Info Window configuration
-     * @param {GLatLng} point the point to show the Info Window at
-     * @param {GMarker} marker a marker to attach the Info Window to
-     */
-    createInfoWindow : function(inwin, point, marker){
-
-        var me = this, infoWindow = new google.maps.InfoWindow({
-            content: inwin.content,
-            position: point
-        });
-
-        if (marker) {
-            google.maps.event.addListener(marker, 'click', function(){
-                me.hideAllInfoWindows();
-                infoWindow.open(me.getMap());
-            });
-        }
-
-        this.cache.infowindow.push(infoWindow);
-
-        return infoWindow;
-
-    },
-    // private
-    hideAllInfoWindows : function(){
-        for (var i = 0; i < this.cache.infowindow.length; i++) {
-            this.cache.infowindow[i].close();
-        }
-    },
-    // private
-    clearMarkers : function(){
-
-        this.hideAllInfoWindows();
-        this.hideMarkers();
-
-    },
-    // private
-    hideMarkers : function(){
-        Ext.each(this.cache.marker, function(mrk){
-            mrk.setMap(null);
-        });
-    },
-    // private
-    showMarkers : function(){
-        Ext.each(this.cache.marker, function(mrk){
-            mrk.setMap(this.getMap());
-        },this);
-    },
-    // private
-    addMapControls : function(){
-
-        if (this.gmapType === 'map') {
-            if (Ext.isArray(this.mapControls)) {
-                for(i=0;i<this.mapControls.length;i++){
-                    //this.addMapControl(this.mapControls[i]);
-                }
-            }else if(typeof this.mapControls === 'string'){
-                //this.addMapControl(this.mapControls);
-            }else if(typeof this.mapControls === 'object'){
-                //this.getMap().add_control(this.mapControls);
-            }
-        }
-
-    },
-    /**
-     * Adds a GMap control to the map.
-     * @param {String} mc a string representation of the control to be instantiated.
-     */
-    addMapControl : function(mc){
-
-        var mcf = window[mc];
-        if (typeof mcf === 'function') {
-            //this.getMap().addControl(new mcf());
-        }
-
-    },
-    // private
-    addOptions : function(){
-
-        if (Ext.isArray(this.mapConfOpts)) {
-            var mc;
-            for(i=0;i<this.mapConfOpts.length;i++){
-                //this.addOption(this.mapConfOpts[i]);
-            }
-        }else if(typeof this.mapConfOpts === 'string'){
-            //this.addOption(this.mapConfOpts);
-        }
-
-    },
-    /**
-     * Adds a GMap option to the map.
-     * @param {String} mo a string representation of the option to be instantiated.
-     */
-    addOption : function(mo){
-
-        var mof = this.getMap()[mo];
-        if (typeof mof === 'function') {
-            this.getMap()[mo]();
-        }
-
-    },
-    /**
-     * Looks up and address and optionally add a marker, center the map to this location, or
-     * clear other markers. Sample usage:
-     * <pre><code>
-buttons: [
-    {
-        text: 'Fenway Park',
-        handler: function(){
-            var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
-            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
-        }
-    },{
-        text: 'Zoom Fenway Park',
-        handler: function(){
-            Ext.getCmp('my_map').zoomLevel = 19;
-            var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
-            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
-        }
-    },{
-        text: 'Low Accuracy',
-        handler: function(){
-            Ext.getCmp('my_map').geoCodeLookup('Paris, France', undefined, false, true, undefined);
-        }
-    },{
-
-        text: 'Bogus Address',
-        handler: function(){
-            var addr = 'Some Fake, Address, For, Errors';
-            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
-        }
-    }
-]
-     * </code></pre>
-     * @param {String} addr the address to lookup.
-     * @param {Object} marker the marker to add (optional).
-     * @param {Boolean} clear clear other markers before creating this marker
-     * @param {Boolean} center true to set this point as the center of the map.
-     * @param {Object} listeners a listeners config
-     */
-    geoCodeLookup : function(addr, marker, clear, center, listeners) {
-
-        if (!this.geocoder) {
-            this.geocoder = new google.maps.Geocoder();
-        }
-        this.geocoder.geocode({
-    			address: addr
-    		}, Ext.Function.bind(this.addAddressToMap, this, [addr, marker, clear, center, listeners], true));
-
-    },
-  	// private
-  	centerOnClientLocation : function(){
-  		this.getClientLocation(function(loc){
-  			var point = new google.maps.LatLng(loc.latitude,loc.longitude);
-        this.getMap().setCenter(point, this.zoomLevel);
-        this.lastCenter = point;
-  		});
-  	},
-  	// private
-  	getClientLocation : function(fn, errorFn){
-  		if (!errorFn) {
-          errorFn = Ext.emptyFn;
-      }
-  		if (!this.clientGeo) {
-  			this.clientGeo = google.gears.factory.create('beta.geolocation');
-  		}
-  		geo.getCurrentPosition(Ext.Function.bind(fn, this), errorFn);
-  	},
-    // private
-    addAddressToMap : function(response, status, addr, marker, clear, center, listeners){
-        if (!response || status !== 'OK') {
-            this.respErrorMsg(status);
-        }else{
-            var place = response[0].geometry.location,
-			          accuracy = this.getLocationTypeInfo(response[0].geometry.location_type,'level'),
-			          reqAccuracy = this.getLocationTypeInfo(this.minGeoAccuracy,'level');
-            if (accuracy === 0) {
-                this.geoErrorMsg(this.geoErrorTitle, this.geoErrorMsgUnable);
-            }else{
-                if (accuracy < reqAccuracy) {
-                    this.geoErrorMsg(this.geoErrorTitle, Ext.String.format(this.geoErrorMsgAccuracy, response[0].geometry.location_type, this.getLocationTypeInfo(response[0].geometry.location_type,'msg')));
-                }else{
-                    point = new google.maps.LatLng(place.lat(),place.lng());
-                    if (center){
-                        this.getMap().setCenter(point, this.zoomLevel);
-                        this.lastCenter = point;
-                    }
-                    if (typeof marker === 'object') {
-                        if (!marker.title){
-                            marker.title = response.formatted_address;
-                        }
-                        var mkr = this.addMarker(point, marker, clear, false, listeners);
-                        if (marker.callback){
-                          marker.callback.call(this, mkr, point);
-                        }
-                    }
-                }
-            }
-        }
-
-    },
-    // private
-    geoErrorMsg : function(title,msg){
-        if (this.displayGeoErrors) {
-            Ext.MessageBox.alert(title,msg);
-        }
-    },
-    // private
-    respErrorMsg : function(code){
-        Ext.each(this.respErrors, function(obj){
-            if (code == obj.code){
-                Ext.MessageBox.alert(this.respErrorTitle, obj.msg);
-            }
-        }, this);
-    },
-    // private
-    getLocationTypeInfo: function(location_type,property){
-      var val = 0;
-      Ext.each(this.locationTypes, function(itm){
-        if (itm.code === location_type){
-          val = itm[property];
-        }
-      });
-      return val;
-    }
-});
-</script>
-
-<script type="text/javascript">
-Ext.onReady(function() {
-
-    Ext.define('Device', {
-        extend: 'Ext.data.Model',
-        fields: [
-            {name: 'id', type: 'int'},
-            {name: 'imei',type: 'string'}
-        ]
-    });
-
-    Ext.define('Position', {
-        extend: 'Ext.data.Model',
-        fields: [
-            {name: 'device_id', type: 'int'},
-            {name: 'time', type: 'date'},
-            {name: 'valid', type: 'boolean'},
-            {name: 'latitude', type: 'float'},
-            {name: 'longitude', type: 'float'},
-            {name: 'speed', type: 'float'},
-            {name: 'course', type: 'float'},
-            {name: 'power', type: 'float'}
-        ]
-    });
-
-    var devicesUrl = 'devices.json';
-    var positionsUrl = 'positions.json';
-
-    var devices = Ext.create('Ext.data.Store', {
-        id: 'devices',
-        model: 'Device',
-        fields: ['id', 'imei'],
-        autoSync: true,
-        proxy: {
-            type: 'ajax',
-            api: {
-                create: devicesUrl + '?action=create',
-                read: devicesUrl,
-                update: devicesUrl + '?action=update',
-                destroy: devicesUrl + '?action=destroy'
-            },
-            reader: {
-                type: 'json',
-                root: 'results'
-            }
-        }
-    });
-
-    var positions = Ext.create('Ext.data.Store', {
-        id: 'positions',
-        model: 'Position',
-        fields: [
-            'device_id',
-            'time',
-            'valid',
-            'latitude',
-            'longitude',
-            'speed',
-            'course',
-            'power'
-        ],
-        proxy: {
-            type: 'ajax',
-            url: positionsUrl,
-            reader: {
-                type: 'json',
-                root: 'results'
-            }
-        }
-    });
-
-    var map = Ext.create('Ext.ux.GMapPanel', {
-        id: 'gmap',
-        setCenter: {lat: 0, lng: 0}
-    });
-
-    var devicesPanel = Ext.create('Ext.grid.Panel', {
-        title: 'Devices',
-        region: 'west',
-        split: true,
-        width: 300,
-        margins: {top: 5, bottom: 0, right: 0, left: 5},
-
-        sortableColumns: false,
-        enableColumnHide: false,
-
-        store: devices,
-        tbar: [
-            {
-                id: 'device_update',
-                text: 'Update',
-                handler : function() {
-                    devices.load();
-                }
-            },
-            {
-                id: 'device_add',
-                text: 'Add',
-                handler : function() {
-                    Ext.Msg.prompt('Add', 'Device IMEI:', function(btn, text) {
-                        if (btn == 'ok') {
-                            devices.add({imei: text});
-                        }
-                    });
-                }
-            },
-            {
-                id: 'device_remove',
-                text: 'Remove',
-                disabled: true,
-                handler : function() {
-                    Ext.Msg.confirm('Remove', 'Are you sure to remove item?', function(btn) {
-                        if (btn == 'yes') {
-                            devices.remove(devicesPanel.getSelectionModel().getLastSelected());
-                        }
-                    });
-                }
-            },
-            {
-                id: 'device_edit',
-                text: 'Edit',
-                disabled: true,
-                handler : function() {
-                    Ext.Msg.prompt('Edit', 'Device IMEI:', function(btn, text) {
-                        if (btn == 'ok') {
-                            devicesPanel.getSelectionModel().getLastSelected().set('imei', text);
-                        }
-                    }, this, false, devicesPanel.getSelectionModel().getLastSelected().get('imei'));
-                }
-            }
-        ],
-        columns: [
-            {header: 'Id',  dataIndex: 'id'},
-            {header: 'IMEI', dataIndex: 'imei', flex: 1}
-        ],
-        listeners: {
-            selectionchange: function(sender, selected, eOpts) {
-                if (selected.length != 0) {
-                    Ext.getCmp('device_remove').enable();
-                    Ext.getCmp('device_edit').enable();
-
-                    positions.getProxy().url = positionsUrl + '?deviceId=' +
-                        devicesPanel.getSelectionModel().getLastSelected().get('id');
-                    positions.load();
-                    Ext.getCmp('position_update').enable();
-                } else {
-                    Ext.getCmp('position_update').disable();
-                    positions.getProxy().url = positionsUrl;
-                    positions.load();
-
-                    Ext.getCmp('device_edit').disable();
-                    Ext.getCmp('device_remove').disable();
-                }
-            }
-        }
-    });
-
-    var positionsPanel = Ext.create('Ext.grid.Panel', {
-        title: 'Positions',
-        region: 'south',
-        split: true,
-        height: 300,
-        margins: {top: 0, bottom: 5, right: 5, left: 5},
-
-        sortableColumns: false,
-        enableColumnHide: false,
-
-        store: positions,
-        tbar: [
-            {
-                id: 'position_update',
-                text: 'Update',
-                disabled: true,
-                handler : function() {
-                    positions.load();
-                }
-            }
-        ],
-        columns: [
-            {header: 'Device Id', dataIndex: 'device_id'},
-            {
-                header: 'Time',
-                dataIndex: 'time',
-                flex: 1,
-                renderer: Ext.util.Format.dateRenderer('Y-m-d H:i:s')
-            },
-            {header: 'Valid', dataIndex: 'valid'},
-            {header: 'Latitude', dataIndex: 'latitude'},
-            {header: 'Longitude', dataIndex: 'longitude'},
-            {header: 'Speed', dataIndex: 'speed'},
-            {header: 'Course', dataIndex: 'course'},
-            {header: 'Power', dataIndex: 'power'}
-        ],
-        listeners: {
-            selectionchange: function(sender, selected, eOpts) {
-                if (selected.length != 0) {
-                    var lat = positionsPanel.getSelectionModel().getLastSelected().get('latitude');
-                    var lng = positionsPanel.getSelectionModel().getLastSelected().get('longitude');
-                    var point = new google.maps.LatLng(lat, lng);
-                    map.addMarker(point, {lat: lat, lng: lng}, true, true);
-                } else {
-                    map.clearMarkers(); // private?
-                }
-            }
-        }
-    });
-
-    var mapPanel = Ext.create('Ext.panel.Panel', {
-        title: 'Map',
-        region: 'center',
-        margins: {top: 5, bottom: 0, right: 5, left: 0},
-
-        layout: 'fit',
-        items: [map]
-    });
-
-    Ext.create('Ext.container.Viewport', {
-        renderTo: Ext.getBody(),
-        layout: 'border',
-        items: [devicesPanel, positionsPanel, mapPanel]
-    });
-
-    devices.load();
-});
-</script>
-</head>
-<body></body>
-</html>
diff --git a/src/org/traccar/Main.java b/src/org/traccar/Main.java
new file mode 100644
index 000000000..36228ee58
--- /dev/null
+++ b/src/org/traccar/Main.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar;
+
+import org.traccar.helper.Log;
+
+public class Main {
+
+    public static void main(String[] args) throws Exception {
+
+        final Server service = new Server();
+        service.init(args);
+
+        Log.info("starting server...");
+        service.start();
+
+        // Shutdown server properly
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                Log.info("shutting down server...");
+                service.stop();
+            }
+        });
+
+    }
+}
diff --git a/src/org/traccar/http/WebServer.java b/src/org/traccar/http/WebServer.java
index cf7b21623..6c79e6421 100644
--- a/src/org/traccar/http/WebServer.java
+++ b/src/org/traccar/http/WebServer.java
@@ -55,7 +55,7 @@ public class WebServer {
 
             response.setContentType("text/html");
 
-            InputStream in = this.getClass().getClassLoader().getResourceAsStream("index.html");
+            InputStream in = this.getClass().getClassLoader().getResourceAsStream("web/index.html");
             OutputStream out = response.getOutputStream();
 
             byte[] buffer = new byte[BUFFER_SIZE];
@@ -71,7 +71,7 @@ public class WebServer {
 
             response.setContentType("image/x-icon");
 
-            InputStream in = this.getClass().getClassLoader().getResourceAsStream("favicon.ico");
+            InputStream in = this.getClass().getClassLoader().getResourceAsStream("web/favicon.ico");
             OutputStream out = response.getOutputStream();
 
             byte[] buffer = new byte[BUFFER_SIZE];
diff --git a/src/web/favicon.ico b/src/web/favicon.ico
new file mode 100644
index 000000000..6fd696c25
Binary files /dev/null and b/src/web/favicon.ico differ
diff --git a/src/web/index.html b/src/web/index.html
new file mode 100644
index 000000000..cde73f47b
--- /dev/null
+++ b/src/web/index.html
@@ -0,0 +1,977 @@
+<!doctype html>
+<html>
+<head>
+<title>Traccar Manager</title>
+<link rel="stylesheet" type="text/css" href="http://cdn.sencha.io/ext-4.1.0-gpl/resources/css/ext-all.css" />
+<script type="text/javascript" src="http://cdn.sencha.io/ext-4.1.0-gpl/ext-all.js"></script>
+
+<!-- check for new version: https://raw.github.com/VinylFox/ExtJS.ux.GMapPanel/master/src/GMapPanel3.js -->
+
+<script type="text/javascript">
+Ext.ns('Ext.ux');
+/**
+ * @class Ext.ux.GMapPanel
+ * @extends Ext.Panel
+ * @author Shea Frederick
+ */
+Ext.define('Ext.ux.GMapPanel', {
+
+    extend: 'Ext.panel.Panel',
+
+    alias: 'widget.gmappanel',
+
+    requires: ['Ext.window.MessageBox'],
+    /**
+     * @cfg {Boolean} border
+     * Defaults to <tt>false</tt>.  See {@link Ext.Panel}.<code>{@link Ext.Panel#border border}</code>.
+     */
+    border: false,
+
+    /**
+     * @cfg {Array} respErrors
+     * An array of msg/code pairs.
+     */
+    respErrors: [{
+            code: 'UNKNOWN_ERROR',
+            msg: 'A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.'
+        },{
+            code: 'ERROR',
+            msg: 'There was a problem contacting the Google servers.'
+        },{
+            code: 'ZERO_RESULTS',
+            msg: 'The request did not encounter any errors but returns zero results.'
+        },{
+            code: 'INVALID_REQUEST',
+            msg: 'This request was invalid.'
+        },{
+            code: 'REQUEST_DENIED',
+            msg: 'The webpage is not allowed to use the geocoder for some reason.'
+        },{
+            code: 'OVER_QUERY_LIMIT',
+            msg: 'The webpage has gone over the requests limit in too short a period of time.'
+    }],
+    /**
+     * @cfg {Array} locationTypes
+     * An array of msg/code/level pairs.
+     */
+    locationTypes: [{
+            level: 4,
+            code: 'ROOFTOP',
+            msg: 'The returned result is a precise geocode for which we have location information accurate down to street address precision.'
+        },{
+            level: 3,
+            code: 'RANGE_INTERPOLATED',
+            msg: 'The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.'
+        },{
+            level: 2,
+            code: 'GEOMETRIC_CENTER',
+            msg: 'The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).'
+        },{
+            level: 1,
+            code: 'APPROXIMATE',
+            msg: 'The returned result is approximate.'
+    }],
+    /**
+     * @cfg {String} respErrorTitle
+     * Defaults to <tt>'Error'</tt>.
+     */
+    respErrorTitle : 'Error',
+    /**
+     * @cfg {String} geoErrorMsgUnable
+     * Defaults to <tt>'Unable to Locate the Address you provided'</tt>.
+     */
+    geoErrorMsgUnable : 'Unable to Locate the Address you provided',
+    /**
+     * @cfg {String} geoErrorTitle
+     * Defaults to <tt>'Address Location Error'</tt>.
+     */
+    geoErrorTitle : 'Address Location Error',
+    /**
+     * @cfg {String} geoErrorMsgAccuracy
+     * Defaults to <tt>'The address provided has a low accuracy.<br><br>{0} Accuracy.'</tt>.
+     * <div class="mdetail-params"><ul>
+     * <li><b><code>ROOFTOP</code></b> : <div class="sub-desc"><p>
+     * The returned result is a precise geocode for which we have location information accurate down to street address precision.
+     * </p></div></li>
+     * <li><b><code>RANGE_INTERPOLATED</code></b> : <div class="sub-desc"><p>
+     * The returned result reflects an approximation (usually on a road) interpolated between two precise points (such as intersections). Interpolated results are generally returned when rooftop geocodes are unavailable for a street address.
+     * </p></div></li>
+     * <li><b><code>GEOMETRIC_CENTER</code></b> : <div class="sub-desc"><p>
+     * The returned result is the geometric center of a result such as a polyline (for example, a street) or polygon (region).
+     * </p></div></li>
+     * <li><b><code>APPROXIMATE</code></b> : <div class="sub-desc"><p>
+     * The returned result is approximate.
+     * </p></div></li>
+     * </ul></div>
+     */
+    geoErrorMsgAccuracy : 'The address provided has a low accuracy.<br><br>"{0}" Accuracy.<br><br>{1}',
+    /**
+     * @cfg {String} gmapType
+     * The type of map to display, generic options available are: 'map', 'panorama'.
+     * Defaults to <tt>'map'</tt>.
+     * More specific maps can be used by specifying the google map type:
+     * <div class="mdetail-params"><ul>
+     * <li><b><code>G_NORMAL_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays the default road map view
+     * </p></div></li>
+     * <li><b><code>G_SATELLITE_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays Google Earth satellite images
+     * </p></div></li>
+     * <li><b><code>G_HYBRID_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays a mixture of normal and satellite views
+     * </p></div></li>
+     * <li><b><code>G_DEFAULT_MAP_TYPES</code></b> : <div class="sub-desc"><p>
+     * Contains an array of the above three types, useful for iterative processing.
+     * </p></div></li>
+     * <li><b><code>G_PHYSICAL_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays a physical map based on terrain information.
+     * </p></div></li>
+     * <li><b><code>G_MOON_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays a shaded terrain map of the surface of the Moon, color-coded by altitude.
+     * </p></div></li>
+     * <li><b><code>G_MOON_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays photographic imagery taken from orbit around the moon.
+     * </p></div></li>
+     * <li><b><code>G_MARS_ELEVATION_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays a shaded terrain map of the surface of Mars, color-coded by altitude.
+     * </p></div></li>
+     * <li><b><code>G_MARS_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays photographs taken from orbit around Mars.
+     * </p></div></li>
+     * <li><b><code>G_MARS_INFRARED_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays a shaded infrared map of the surface of Mars, where warmer areas appear brighter and colder areas appear darker.
+     * </p></div></li>
+     * <li><b><code>G_SKY_VISIBLE_MAP</code></b> : <div class="sub-desc"><p>
+     * Displays a mosaic of the sky, as seen from Earth, covering the full celestial sphere.
+     * </p></div></li>
+     * </ul></div>
+     * Sample usage:
+     * <pre><code>
+     * gmapType: G_MOON_VISIBLE_MAP
+     * </code></pre>
+     */
+    gmapType : 'map',
+    /**
+     * @cfg {Object} setCenter
+     * The initial center location of the map. The map needs to be centered before it can be used.
+     * A marker is not required to be specified.
+     * More markers can be added to the map using the <code>{@link #markers}</code> array.
+     * For example:
+     * <pre><code>
+setCenter: {
+    geoCodeAddr: '4 Yawkey Way, Boston, MA, 02215-3409, USA',
+    marker: {title: 'Fenway Park'}
+},
+
+// or just specify lat/long
+setCenter: {
+    lat: 42.345573,
+    lng: -71.098326
+}
+     * </code></pre>
+     */
+    /**
+     * @cfg {Number} zoomLevel
+     * The zoom level to initialize the map at, generally between 1 (whole planet) and 40 (street).
+     * Also used as the zoom level for panoramas, zero specifies no zoom at all.
+     * Defaults to <tt>3</tt>.
+     */
+    zoomLevel: 3,
+    /**
+     * @cfg {Number} yaw
+     * The Yaw, or rotational direction of the users perspective in degrees. Only applies to panoramas.
+     * Defaults to <tt>180</tt>.
+     */
+    yaw: 180,
+    /**
+     * @cfg {Number} pitch
+     * The pitch, or vertical direction of the users perspective in degrees.
+     * Defaults to <tt>0</tt> (straight ahead). Valid values are between +90 (straight up) and -90 (straight down).
+     */
+    pitch: 0,
+    /**
+     * @cfg {Boolean} displayGeoErrors
+     * True to display geocoding errors to the end user via a message box.
+     * Defaults to <tt>false</tt>.
+     */
+    displayGeoErrors: false,
+    /**
+     * @cfg {Boolean} minGeoAccuracy
+     * The level to display an accuracy error below. Defaults to <tt>ROOFTOP</tt>. For additional information
+     * see <a href="http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy">here</a>.
+     */
+    minGeoAccuracy: 'ROOFTOP',
+    /**
+     * @cfg {Array} mapConfOpts
+     * Array of strings representing configuration methods to call, a full list can be found
+     * <a href="http://code.google.com/apis/maps/documentation/reference.html#GMap2">here</a>.
+     * For example:
+     * <pre><code>
+     * mapConfOpts: ['enableScrollWheelZoom','enableDoubleClickZoom','enableDragging'],
+     * </code></pre>
+     */
+    /**
+     * @cfg {Array} mapControls
+     * Array of strings representing map controls to initialize, a full list can be found
+     * <a href="http://code.google.com/apis/maps/documentation/reference.html#GControlImpl">here</a>.
+     * For example:
+     * <pre><code>
+     * mapControls: ['GSmallMapControl','GMapTypeControl','NonExistantControl']
+     * </code></pre>
+     */
+    /**
+     * @cfg {Array} markers
+     * Markers may be added to the map. Instead of specifying <code>lat</code>/<code>lng</code>,
+     * geocoding can be specified via a <code>geoCodeAddr</code> string.
+     * For example:
+     * <pre><code>
+markers: [{
+    //lat: 42.339641,
+    //lng: -71.094224,
+    // instead of lat/lng:
+    geoCodeAddr: '465 Huntington Avenue, Boston, MA, 02215-5597, USA',
+    marker: {title: 'Boston Museum of Fine Arts'},
+    listeners: {
+        click: function(e){
+            Ext.Msg.alert('Its fine', 'and its art.');
+        }
+    }
+},{
+    lat: 42.339419,
+    lng: -71.09077,
+    marker: {title: 'Northeastern University'}
+}]
+     * </code></pre>
+     */
+    // private
+    mapDefined: false,
+    // private
+    mapDefinedGMap: false,
+    initComponent : function(){
+
+        this.addEvents(
+            /**
+             * @event mapready
+             * Fires when the map is ready for interaction
+             * @param {GMapPanel} this
+             * @param {GMap} map
+             */
+            'mapready',
+            /**
+             * @event apiready
+             * Fires when the Google Maps API is loaded
+             */
+            'apiready'
+        );
+
+        Ext.applyIf(this,{
+          markers: [],
+          cache: {
+              marker: [],
+              polyline: [],
+              infowindow: []
+          }
+        });
+
+        Ext.ux.GMapPanel.superclass.initComponent.call(this);
+
+        if (window.google && window.google.maps){
+          this.on('afterrender', this.apiReady, this);
+        }else{
+          window.gmapapiready = Ext.Function.bind(this.apiReady,this);
+          this.buildScriptTag('http://maps.google.com/maps/api/js?sensor=false&callback=gmapapiready');
+        }
+
+    },
+    apiReady : function(){
+
+        if (this.rendered){
+
+          Ext.defer(function(){
+              if (this.gmapType === 'map'){
+                  this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
+                  this.mapDefined = true;
+                  this.mapDefinedGMap = true;
+              }
+
+              if (this.gmapType === 'panorama'){
+                  this.gmap = new GStreetviewPanorama(this.getEl().dom);
+                  this.mapDefined = true;
+              }
+
+              if (!this.mapDefined && this.gmapType){
+                 this.gmap = new google.maps.Map(this.getEl().dom, {zoom:this.zoomLevel,mapTypeId: google.maps.MapTypeId.ROADMAP});
+                 this.gmap.setMapTypeId(this.gmapType);
+                 this.mapDefined = true;
+                 this.mapDefinedGMap = true;
+              }
+
+              google.maps.event.addListenerOnce(this.getMap(), 'tilesloaded', Ext.Function.bind(this.onMapReady, this));
+              google.maps.event.addListener(this.getMap(), 'dragend', Ext.Function.bind(this.dragEnd, this));
+
+
+              if (typeof this.setCenter === 'object') {
+                  if (typeof this.setCenter.geoCodeAddr === 'string'){
+                      this.geoCodeLookup(this.setCenter.geoCodeAddr, this.setCenter.marker, false, true, this.setCenter.listeners);
+                  }else{
+                      if (this.gmapType === 'map'){
+                          var point = new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng);
+                          this.getMap().setCenter(point, this.zoomLevel);
+                          this.lastCenter = point;
+                      }
+                      if (typeof this.setCenter.marker === 'object' && typeof point === 'object') {
+                          this.addMarker(point, this.setCenter.marker, this.setCenter.marker.clear);
+                      }
+                  }
+                  if (this.gmapType === 'panorama'){
+                      this.getMap().setLocationAndPOV(new google.maps.LatLng(this.setCenter.lat,this.setCenter.lng), {yaw: this.yaw, pitch: this.pitch, zoom: this.zoomLevel});
+                  }
+              }
+        }, 200,this); // Ext.defer
+
+        }else{
+          this.on('afterrender', this.apiReady, this);
+        }
+    },
+    // private
+    afterRender : function(){
+
+        var wh = this.ownerCt.getSize();
+        Ext.applyIf(this, wh);
+
+        Ext.ux.GMapPanel.superclass.afterRender.call(this);
+
+    },
+    // private
+    buildScriptTag: function(filename, callback) {
+        var script  = document.createElement('script'),
+        head        = document.getElementsByTagName("head")[0];
+        script.type = "text/javascript";
+        script.src  = filename;
+
+        return head.appendChild(script);
+    },
+    // private
+    onMapReady : function(){
+
+        this.addMapControls();
+        this.addOptions();
+
+        this.addMarkers(this.markers);
+        this.addMapListeners();
+
+        this.fireEvent('mapready', this, this.getMap());
+        return this;
+    },
+    // private
+    addMapListeners : function () {
+      	if (this.maplisteners){
+      		Ext.iterate(this.maplisteners, function(key,val){
+      			google.maps.event.addListener(this.getMap(), key, Ext.Function.bind(val,this));
+      		},this);
+      	}
+    },
+    // private
+    onResize : function(w, h){
+
+        Ext.ux.GMapPanel.superclass.onResize.call(this, w, h);
+
+        // check for the existance of the google map in case the onResize fires too early
+        if (typeof this.getMap() == 'object') {
+            google.maps.event.trigger(this.getMap(), 'resize');
+            if (this.lastCenter){
+              this.getMap().setCenter(this.lastCenter, this.zoomLevel);
+            }
+        }
+
+    },
+    // private
+    setSize : function(width, height, animate){
+
+        Ext.ux.GMapPanel.superclass.setSize.call(this, width, height, animate);
+
+        // check for the existance of the google map in case setSize is called too early
+        if (Ext.isObject(this.getMap())) {
+            google.maps.event.trigger(this.getMap(), 'resize');
+            if (this.lastCenter){
+              this.getMap().setCenter(this.lastCenter, this.zoomLevel);
+            }
+        }
+
+    },
+    // private
+    dragEnd: function(){
+      this.lastCenter = this.getMap().getCenter();
+    },
+    /**
+     * Returns the current google map which can be used to call Google Maps API specific handlers.
+     * @return {GMap} this
+     */
+    getMap : function(){
+
+        return this.gmap;
+
+    },
+    /**
+     * Returns the maps center as a GLatLng object
+     * @return {GLatLng} this
+     */
+    getCenter : function(){
+
+        return this.getMap().getCenter();
+
+    },
+    /**
+     * Returns the maps center as a simple object
+     * @return {Object} this has lat and lng properties only
+     */
+    getCenterLatLng : function(){
+
+        var ll = this.getCenter();
+        return {lat: ll.lat(), lng: ll.lng()};
+
+    },
+    /**
+     * Creates markers from the array that is passed in. Each marker must consist of at least
+     * <code>lat</code> and <code>lng</code> properties or a <code>geoCodeAddr</code>.
+     * @param {Array} markers an array of marker objects
+     */
+    addMarkers : function(markers) {
+        if (Ext.isArray(markers)){
+            for (var i = 0; i < markers.length; i++) {
+                if (markers[i]) {
+                    if (typeof markers[i].geoCodeAddr == 'string') {
+                        this.geoCodeLookup(markers[i].geoCodeAddr, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
+                    } else {
+                        var mkr_point = new google.maps.LatLng(markers[i].lat, markers[i].lng);
+                        this.addMarker(mkr_point, markers[i].marker, false, markers[i].setCenter, markers[i].listeners);
+                    }
+                }
+            }
+        }
+
+    },
+    /**
+     * Creates a single marker.
+     * @param {Object} point a GLatLng point
+     * @param {Object} marker a marker object consisting of at least lat and lng
+     * @param {Boolean} clear clear other markers before creating this marker
+     * @param {Boolean} center true to center the map on this marker
+     * @param {Object} listeners a listeners config
+     */
+    addMarker : function(point, marker, clear, center, listeners){
+
+        Ext.applyIf(marker,{});
+
+        if (clear === true){
+            this.clearMarkers();
+        }
+        if (center === true) {
+            this.getMap().setCenter(point, this.zoomLevel)
+            this.lastCenter = point;
+        }
+
+        var mark = new google.maps.Marker(Ext.apply(marker, {
+            position: point
+        }));
+
+        if (marker.infoWindow){
+            this.createInfoWindow(marker.infoWindow, point, mark);
+        }
+
+        this.cache.marker.push(mark);
+        mark.setMap(this.getMap());
+
+        if (typeof listeners === 'object'){
+            for (evt in listeners) {
+                google.maps.event.addListener(mark, evt, listeners[evt]);
+            }
+        }
+
+        return mark;
+
+    },
+    /**
+     * Creates a single polyline.
+     * @param {Array} points an array of polyline points
+     * @param {Object} linestyle an object defining the line style to use
+     */
+    addPolyline : function(points, linestyle){
+
+        var plinepnts = new google.maps.MVCArray, pline, linestyle = linestyle ? linestyle : {
+            strokeColor: '#FF0000',
+            strokeOpacity: 1.0,
+            strokeWeight: 2
+        };
+
+        Ext.each(points, function(point){
+            plinepnts.push(new google.maps.LatLng(point.lat, point.lng));
+        }, this);
+
+        var pline = new google.maps.Polyline(Ext.apply({
+          path: plinepnts
+        },linestyle));
+
+        this.cache.polyline.push(pline);
+
+        pline.setMap(this.getMap());
+
+    },
+    /**
+     * Creates an Info Window.
+     * @param {Object} inwin an Info Window configuration
+     * @param {GLatLng} point the point to show the Info Window at
+     * @param {GMarker} marker a marker to attach the Info Window to
+     */
+    createInfoWindow : function(inwin, point, marker){
+
+        var me = this, infoWindow = new google.maps.InfoWindow({
+            content: inwin.content,
+            position: point
+        });
+
+        if (marker) {
+            google.maps.event.addListener(marker, 'click', function(){
+                me.hideAllInfoWindows();
+                infoWindow.open(me.getMap());
+            });
+        }
+
+        this.cache.infowindow.push(infoWindow);
+
+        return infoWindow;
+
+    },
+    // private
+    hideAllInfoWindows : function(){
+        for (var i = 0; i < this.cache.infowindow.length; i++) {
+            this.cache.infowindow[i].close();
+        }
+    },
+    // private
+    clearMarkers : function(){
+
+        this.hideAllInfoWindows();
+        this.hideMarkers();
+
+    },
+    // private
+    hideMarkers : function(){
+        Ext.each(this.cache.marker, function(mrk){
+            mrk.setMap(null);
+        });
+    },
+    // private
+    showMarkers : function(){
+        Ext.each(this.cache.marker, function(mrk){
+            mrk.setMap(this.getMap());
+        },this);
+    },
+    // private
+    addMapControls : function(){
+
+        if (this.gmapType === 'map') {
+            if (Ext.isArray(this.mapControls)) {
+                for(i=0;i<this.mapControls.length;i++){
+                    //this.addMapControl(this.mapControls[i]);
+                }
+            }else if(typeof this.mapControls === 'string'){
+                //this.addMapControl(this.mapControls);
+            }else if(typeof this.mapControls === 'object'){
+                //this.getMap().add_control(this.mapControls);
+            }
+        }
+
+    },
+    /**
+     * Adds a GMap control to the map.
+     * @param {String} mc a string representation of the control to be instantiated.
+     */
+    addMapControl : function(mc){
+
+        var mcf = window[mc];
+        if (typeof mcf === 'function') {
+            //this.getMap().addControl(new mcf());
+        }
+
+    },
+    // private
+    addOptions : function(){
+
+        if (Ext.isArray(this.mapConfOpts)) {
+            var mc;
+            for(i=0;i<this.mapConfOpts.length;i++){
+                //this.addOption(this.mapConfOpts[i]);
+            }
+        }else if(typeof this.mapConfOpts === 'string'){
+            //this.addOption(this.mapConfOpts);
+        }
+
+    },
+    /**
+     * Adds a GMap option to the map.
+     * @param {String} mo a string representation of the option to be instantiated.
+     */
+    addOption : function(mo){
+
+        var mof = this.getMap()[mo];
+        if (typeof mof === 'function') {
+            this.getMap()[mo]();
+        }
+
+    },
+    /**
+     * Looks up and address and optionally add a marker, center the map to this location, or
+     * clear other markers. Sample usage:
+     * <pre><code>
+buttons: [
+    {
+        text: 'Fenway Park',
+        handler: function(){
+            var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
+            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
+        }
+    },{
+        text: 'Zoom Fenway Park',
+        handler: function(){
+            Ext.getCmp('my_map').zoomLevel = 19;
+            var addr = '4 Yawkey Way, Boston, MA, 02215-3409, USA';
+            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
+        }
+    },{
+        text: 'Low Accuracy',
+        handler: function(){
+            Ext.getCmp('my_map').geoCodeLookup('Paris, France', undefined, false, true, undefined);
+        }
+    },{
+
+        text: 'Bogus Address',
+        handler: function(){
+            var addr = 'Some Fake, Address, For, Errors';
+            Ext.getCmp('my_map').geoCodeLookup(addr, undefined, false, true, undefined);
+        }
+    }
+]
+     * </code></pre>
+     * @param {String} addr the address to lookup.
+     * @param {Object} marker the marker to add (optional).
+     * @param {Boolean} clear clear other markers before creating this marker
+     * @param {Boolean} center true to set this point as the center of the map.
+     * @param {Object} listeners a listeners config
+     */
+    geoCodeLookup : function(addr, marker, clear, center, listeners) {
+
+        if (!this.geocoder) {
+            this.geocoder = new google.maps.Geocoder();
+        }
+        this.geocoder.geocode({
+    			address: addr
+    		}, Ext.Function.bind(this.addAddressToMap, this, [addr, marker, clear, center, listeners], true));
+
+    },
+  	// private
+  	centerOnClientLocation : function(){
+  		this.getClientLocation(function(loc){
+  			var point = new google.maps.LatLng(loc.latitude,loc.longitude);
+        this.getMap().setCenter(point, this.zoomLevel);
+        this.lastCenter = point;
+  		});
+  	},
+  	// private
+  	getClientLocation : function(fn, errorFn){
+  		if (!errorFn) {
+          errorFn = Ext.emptyFn;
+      }
+  		if (!this.clientGeo) {
+  			this.clientGeo = google.gears.factory.create('beta.geolocation');
+  		}
+  		geo.getCurrentPosition(Ext.Function.bind(fn, this), errorFn);
+  	},
+    // private
+    addAddressToMap : function(response, status, addr, marker, clear, center, listeners){
+        if (!response || status !== 'OK') {
+            this.respErrorMsg(status);
+        }else{
+            var place = response[0].geometry.location,
+			          accuracy = this.getLocationTypeInfo(response[0].geometry.location_type,'level'),
+			          reqAccuracy = this.getLocationTypeInfo(this.minGeoAccuracy,'level');
+            if (accuracy === 0) {
+                this.geoErrorMsg(this.geoErrorTitle, this.geoErrorMsgUnable);
+            }else{
+                if (accuracy < reqAccuracy) {
+                    this.geoErrorMsg(this.geoErrorTitle, Ext.String.format(this.geoErrorMsgAccuracy, response[0].geometry.location_type, this.getLocationTypeInfo(response[0].geometry.location_type,'msg')));
+                }else{
+                    point = new google.maps.LatLng(place.lat(),place.lng());
+                    if (center){
+                        this.getMap().setCenter(point, this.zoomLevel);
+                        this.lastCenter = point;
+                    }
+                    if (typeof marker === 'object') {
+                        if (!marker.title){
+                            marker.title = response.formatted_address;
+                        }
+                        var mkr = this.addMarker(point, marker, clear, false, listeners);
+                        if (marker.callback){
+                          marker.callback.call(this, mkr, point);
+                        }
+                    }
+                }
+            }
+        }
+
+    },
+    // private
+    geoErrorMsg : function(title,msg){
+        if (this.displayGeoErrors) {
+            Ext.MessageBox.alert(title,msg);
+        }
+    },
+    // private
+    respErrorMsg : function(code){
+        Ext.each(this.respErrors, function(obj){
+            if (code == obj.code){
+                Ext.MessageBox.alert(this.respErrorTitle, obj.msg);
+            }
+        }, this);
+    },
+    // private
+    getLocationTypeInfo: function(location_type,property){
+      var val = 0;
+      Ext.each(this.locationTypes, function(itm){
+        if (itm.code === location_type){
+          val = itm[property];
+        }
+      });
+      return val;
+    }
+});
+</script>
+
+<script type="text/javascript">
+Ext.onReady(function() {
+
+    Ext.define('Device', {
+        extend: 'Ext.data.Model',
+        fields: [
+            {name: 'id', type: 'int'},
+            {name: 'imei',type: 'string'}
+        ]
+    });
+
+    Ext.define('Position', {
+        extend: 'Ext.data.Model',
+        fields: [
+            {name: 'device_id', type: 'int'},
+            {name: 'time', type: 'date'},
+            {name: 'valid', type: 'boolean'},
+            {name: 'latitude', type: 'float'},
+            {name: 'longitude', type: 'float'},
+            {name: 'speed', type: 'float'},
+            {name: 'course', type: 'float'},
+            {name: 'power', type: 'float'}
+        ]
+    });
+
+    var devicesUrl = 'devices.json';
+    var positionsUrl = 'positions.json';
+
+    var devices = Ext.create('Ext.data.Store', {
+        id: 'devices',
+        model: 'Device',
+        fields: ['id', 'imei'],
+        autoSync: true,
+        proxy: {
+            type: 'ajax',
+            api: {
+                create: devicesUrl + '?action=create',
+                read: devicesUrl,
+                update: devicesUrl + '?action=update',
+                destroy: devicesUrl + '?action=destroy'
+            },
+            reader: {
+                type: 'json',
+                root: 'results'
+            }
+        }
+    });
+
+    var positions = Ext.create('Ext.data.Store', {
+        id: 'positions',
+        model: 'Position',
+        fields: [
+            'device_id',
+            'time',
+            'valid',
+            'latitude',
+            'longitude',
+            'speed',
+            'course',
+            'power'
+        ],
+        proxy: {
+            type: 'ajax',
+            url: positionsUrl,
+            reader: {
+                type: 'json',
+                root: 'results'
+            }
+        }
+    });
+
+    var map = Ext.create('Ext.ux.GMapPanel', {
+        id: 'gmap',
+        setCenter: {lat: 0, lng: 0}
+    });
+
+    var devicesPanel = Ext.create('Ext.grid.Panel', {
+        title: 'Devices',
+        region: 'west',
+        split: true,
+        width: 300,
+        margins: {top: 5, bottom: 0, right: 0, left: 5},
+
+        sortableColumns: false,
+        enableColumnHide: false,
+
+        store: devices,
+        tbar: [
+            {
+                id: 'device_update',
+                text: 'Update',
+                handler : function() {
+                    devices.load();
+                }
+            },
+            {
+                id: 'device_add',
+                text: 'Add',
+                handler : function() {
+                    Ext.Msg.prompt('Add', 'Device IMEI:', function(btn, text) {
+                        if (btn == 'ok') {
+                            devices.add({imei: text});
+                        }
+                    });
+                }
+            },
+            {
+                id: 'device_remove',
+                text: 'Remove',
+                disabled: true,
+                handler : function() {
+                    Ext.Msg.confirm('Remove', 'Are you sure to remove item?', function(btn) {
+                        if (btn == 'yes') {
+                            devices.remove(devicesPanel.getSelectionModel().getLastSelected());
+                        }
+                    });
+                }
+            },
+            {
+                id: 'device_edit',
+                text: 'Edit',
+                disabled: true,
+                handler : function() {
+                    Ext.Msg.prompt('Edit', 'Device IMEI:', function(btn, text) {
+                        if (btn == 'ok') {
+                            devicesPanel.getSelectionModel().getLastSelected().set('imei', text);
+                        }
+                    }, this, false, devicesPanel.getSelectionModel().getLastSelected().get('imei'));
+                }
+            }
+        ],
+        columns: [
+            {header: 'Id',  dataIndex: 'id'},
+            {header: 'IMEI', dataIndex: 'imei', flex: 1}
+        ],
+        listeners: {
+            selectionchange: function(sender, selected, eOpts) {
+                if (selected.length != 0) {
+                    Ext.getCmp('device_remove').enable();
+                    Ext.getCmp('device_edit').enable();
+
+                    positions.getProxy().url = positionsUrl + '?deviceId=' +
+                        devicesPanel.getSelectionModel().getLastSelected().get('id');
+                    positions.load();
+                    Ext.getCmp('position_update').enable();
+                } else {
+                    Ext.getCmp('position_update').disable();
+                    positions.getProxy().url = positionsUrl;
+                    positions.load();
+
+                    Ext.getCmp('device_edit').disable();
+                    Ext.getCmp('device_remove').disable();
+                }
+            }
+        }
+    });
+
+    var positionsPanel = Ext.create('Ext.grid.Panel', {
+        title: 'Positions',
+        region: 'south',
+        split: true,
+        height: 300,
+        margins: {top: 0, bottom: 5, right: 5, left: 5},
+
+        sortableColumns: false,
+        enableColumnHide: false,
+
+        store: positions,
+        tbar: [
+            {
+                id: 'position_update',
+                text: 'Update',
+                disabled: true,
+                handler : function() {
+                    positions.load();
+                }
+            }
+        ],
+        columns: [
+            {header: 'Device Id', dataIndex: 'device_id'},
+            {
+                header: 'Time',
+                dataIndex: 'time',
+                flex: 1,
+                renderer: Ext.util.Format.dateRenderer('Y-m-d H:i:s')
+            },
+            {header: 'Valid', dataIndex: 'valid'},
+            {header: 'Latitude', dataIndex: 'latitude'},
+            {header: 'Longitude', dataIndex: 'longitude'},
+            {header: 'Speed', dataIndex: 'speed'},
+            {header: 'Course', dataIndex: 'course'},
+            {header: 'Power', dataIndex: 'power'}
+        ],
+        listeners: {
+            selectionchange: function(sender, selected, eOpts) {
+                if (selected.length != 0) {
+                    var lat = positionsPanel.getSelectionModel().getLastSelected().get('latitude');
+                    var lng = positionsPanel.getSelectionModel().getLastSelected().get('longitude');
+                    var point = new google.maps.LatLng(lat, lng);
+                    map.addMarker(point, {lat: lat, lng: lng}, true, true);
+                } else {
+                    map.clearMarkers(); // private?
+                }
+            }
+        }
+    });
+
+    var mapPanel = Ext.create('Ext.panel.Panel', {
+        title: 'Map',
+        region: 'center',
+        margins: {top: 5, bottom: 0, right: 5, left: 0},
+
+        layout: 'fit',
+        items: [map]
+    });
+
+    Ext.create('Ext.container.Viewport', {
+        renderTo: Ext.getBody(),
+        layout: 'border',
+        items: [devicesPanel, positionsPanel, mapPanel]
+    });
+
+    devices.load();
+});
+</script>
+</head>
+<body></body>
+</html>
-- 
cgit v1.2.3