diff options
166 files changed, 3177 insertions, 944 deletions
diff --git a/database/db.changelog-3.0.xml b/database/changelog-3.3.xml index 453f6f00b..1893a0200 100644 --- a/database/db.changelog-3.0.xml +++ b/database/changelog-3.3.xml @@ -5,28 +5,28 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - <changeSet author="author" id="changelog-3.0"> + <changeSet author="author" id="changelog-3.3"> <preConditions onFail="MARK_RAN"> <not> - <tableExists tableName="traccar" /> + <tableExists tableName="server" /> </not> </preConditions> - <createTable tableName="user"> + <createTable tableName="users"> <column name="id" type="INT" autoIncrement="true"> <constraints primaryKey="true" /> </column> - <column name="name" type="VARCHAR(1024)"> + <column name="name" type="VARCHAR(128)"> <constraints nullable="false" /> </column> - <column name="email" type="VARCHAR(256)"> + <column name="email" type="VARCHAR(128)"> <constraints nullable="false" /> </column> - <column name="hashedPassword" type="VARCHAR(1024)"> + <column name="hashedpassword" type="VARCHAR(128)"> <constraints nullable="false" /> </column> - <column name="salt" type="VARCHAR(1024)" defaultValue=""> + <column name="salt" type="VARCHAR(128)"> <constraints nullable="false" /> </column> <column name="readonly" type="BOOLEAN" defaultValueBoolean="false"> @@ -35,16 +35,16 @@ <column name="admin" type="BOOLEAN" defaultValueBoolean="false"> <constraints nullable="false" /> </column> - <column name="map" type="VARCHAR(1024)" defaultValue="osm"> + <column name="map" type="VARCHAR(128)" defaultValue="osm"> <constraints nullable="false" /> </column> - <column name="language" type="VARCHAR(1024)" defaultValue="en"> + <column name="language" type="VARCHAR(128)" defaultValue="en"> <constraints nullable="false" /> </column> - <column name="distanceUnit" type="VARCHAR(1024)" defaultValue="km"> + <column name="distanceunit" type="VARCHAR(128)" defaultValue="km"> <constraints nullable="false" /> </column> - <column name="speedUnit" type="VARCHAR(1024)" defaultValue="kmh"> + <column name="speedunit" type="VARCHAR(128)" defaultValue="kmh"> <constraints nullable="false" /> </column> <column name="latitude" type="DOUBLE" defaultValueNumeric="0"> @@ -58,63 +58,56 @@ </column> </createTable> - <addUniqueConstraint tableName="user" columnNames="email" constraintName="uk_user_email" /> + <addUniqueConstraint tableName="users" columnNames="email" constraintName="uk_user_email" /> - <createTable tableName="device"> + <createTable tableName="devices"> <column name="id" type="INT" autoIncrement="true"> <constraints primaryKey="true" /> </column> - <column name="name" type="VARCHAR(1024)"> + <column name="name" type="VARCHAR(128)"> <constraints nullable="false" /> </column> - <column name="uniqueId" type="VARCHAR(256)"> + <column name="uniqueid" type="VARCHAR(128)"> <constraints nullable="false" /> </column> - <column name="status" type="VARCHAR(1024)" /> - <column name="lastUpdate" type="TIMESTAMP" /> - <column name="positionId" type="INT" /> - <column name="dataId" type="INT" /> + <column name="status" type="VARCHAR(128)" /> + <column name="lastupdate" type="TIMESTAMP" /> + <column name="positionid" type="INT" /> </createTable> - <addUniqueConstraint tableName="device" columnNames="uniqueId" constraintName="uk_device_uniqueId" /> + <addUniqueConstraint tableName="devices" columnNames="uniqueid" constraintName="uk_device_uniqueid" /> <createTable tableName="user_device"> - <column name="userId" type="INT"> + <column name="userid" type="INT"> <constraints nullable="false" /> </column> - <column name="deviceId" type="INT"> - <constraints nullable="false" /> - </column> - <column name="read" type="BOOLEAN" defaultValueBoolean="true"> - <constraints nullable="false" /> - </column> - <column name="write" type="BOOLEAN" defaultValueBoolean="true"> + <column name="deviceid" type="INT"> <constraints nullable="false" /> </column> </createTable> - <addForeignKeyConstraint baseTableName="user_device" baseColumnNames="userId" constraintName="fk_user_device_userId" referencedTableName="user" referencedColumnNames="id" onDelete="CASCADE" /> - <addForeignKeyConstraint baseTableName="user_device" baseColumnNames="deviceId" constraintName="fk_user_device_deviceId" referencedTableName="device" referencedColumnNames="id" onDelete="CASCADE" /> + <addForeignKeyConstraint baseTableName="user_device" baseColumnNames="userid" constraintName="fk_user_device_userid" referencedTableName="users" referencedColumnNames="id" onDelete="CASCADE" /> + <addForeignKeyConstraint baseTableName="user_device" baseColumnNames="deviceid" constraintName="fk_user_device_deviceid" referencedTableName="devices" referencedColumnNames="id" onDelete="CASCADE" /> <createIndex tableName="user_device" indexName="user_device_user_id"> - <column name="userId" /> + <column name="userid" /> </createIndex> - <createTable tableName="position"> + <createTable tableName="positions"> <column name="id" type="INT" autoIncrement="true"> <constraints primaryKey="true" /> </column> - <column name="protocol" type="VARCHAR(1024)" /> - <column name="deviceId" type="int"> + <column name="protocol" type="VARCHAR(128)" /> + <column name="deviceid" type="int"> <constraints nullable="false" /> </column> - <column name="serverTime" type="TIMESTAMP"> + <column name="servertime" type="TIMESTAMP"> <constraints nullable="false" /> </column> - <column name="deviceTime" type="TIMESTAMP"> + <column name="devicetime" type="TIMESTAMP"> <constraints nullable="false" /> </column> - <column name="fixTime" type="TIMESTAMP"> + <column name="fixtime" type="TIMESTAMP"> <constraints nullable="false" /> </column> <column name="valid" type="BOOLEAN"> @@ -126,65 +119,52 @@ <column name="longitude" type="DOUBLE"> <constraints nullable="false" /> </column> - <column name="altitude" type="DOUBLE"> + <column name="altitude" type="FLOAT"> <constraints nullable="false" /> </column> - <column name="speed" type="DOUBLE"> + <column name="speed" type="FLOAT"> <constraints nullable="false" /> </column> - <column name="course" type="DOUBLE"> + <column name="course" type="FLOAT"> <constraints nullable="false" /> </column> - <column name="address" type="VARCHAR(1024)" /> - <column name="other" type="VARCHAR(8192)"> + <column name="address" type="VARCHAR(512)" /> + <column name="attributes" type="VARCHAR(4096)"> <constraints nullable="false" /> </column> </createTable> - <addForeignKeyConstraint baseTableName="position" baseColumnNames="deviceId" constraintName="fk_position_deviceId" referencedTableName="device" referencedColumnNames="id" onDelete="CASCADE" /> + <addForeignKeyConstraint baseTableName="positions" baseColumnNames="deviceid" constraintName="fk_position_deviceid" referencedTableName="devices" referencedColumnNames="id" onDelete="CASCADE" /> - <createTable tableName="data"> - <column name="id" type="INT" autoIncrement="true"> - <constraints primaryKey="true" /> - </column> - <column name="protocol" type="VARCHAR(1024)" /> - <column name="deviceId" type="int"> - <constraints nullable="false" /> - </column> - <column name="serverTime" type="TIMESTAMP"> - <constraints nullable="false" /> - </column> - <column name="deviceTime" type="TIMESTAMP"> - <constraints nullable="false" /> - </column> - <column name="other" type="VARCHAR(8192)"> - <constraints nullable="false" /> - </column> - </createTable> - - <addForeignKeyConstraint baseTableName="data" baseColumnNames="deviceId" constraintName="fk_data_deviceId" referencedTableName="device" referencedColumnNames="id" onDelete="CASCADE" /> + <createIndex tableName="positions" indexName="position_deviceid_fixtime"> + <column name="deviceid" /> + <column name="fixtime" /> + </createIndex> <createTable tableName="server"> <column name="id" type="INT" autoIncrement="true"> <constraints primaryKey="true" /> </column> - <column name="registration" type="BOOLEAN"> + <column name="registration" type="BOOLEAN" defaultValueBoolean="true"> <constraints nullable="false" /> </column> - <column name="latitude" type="DOUBLE"> + <column name="latitude" type="DOUBLE" defaultValueNumeric="0"> <constraints nullable="false" /> </column> - <column name="longitude" type="DOUBLE"> + <column name="longitude" type="DOUBLE" defaultValueNumeric="0"> <constraints nullable="false" /> </column> - <column name="zoom" type="INT"> + <column name="zoom" type="INT" defaultValueNumeric="0"> <constraints nullable="false" /> </column> - </createTable> - - <createTable tableName="traccar"> - <column name="id" type="INT" autoIncrement="true"> - <constraints primaryKey="true" /> + <column name="map" type="VARCHAR(128)" /> + <column name="language" type="VARCHAR(128)" /> + <column name="distanceunit" type="VARCHAR(128)" /> + <column name="speedunit" type="VARCHAR(128)" /> + <column name="bingkey" type="VARCHAR(128)" /> + <column name="mapurl" type="VARCHAR(128)" /> + <column name="readonly" type="BOOLEAN" defaultValueBoolean="false"> + <constraints nullable="false" /> </column> </createTable> @@ -195,10 +175,10 @@ <column name="zoom" valueNumeric="0" /> </insert> - <insert tableName="user"> + <insert tableName="users"> <column name="name" value="admin" /> <column name="email" value="admin" /> - <column name="hashedPassword" value="D33DCA55ABD4CC5BC76F2BC0B4E603FE2C6F61F4C1EF2D47" /> + <column name="hashedpassword" value="D33DCA55ABD4CC5BC76F2BC0B4E603FE2C6F61F4C1EF2D47" /> <column name="salt" value="000000000000000000000000000000000000000000000000" /> <column name="admin" valueBoolean="true" /> </insert> diff --git a/database/changelog-3.5.xml b/database/changelog-3.5.xml new file mode 100644 index 000000000..f1af6ef10 --- /dev/null +++ b/database/changelog-3.5.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<databaseChangeLog + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + + <changeSet author="author" id="changelog-3.5"> + + <createTable tableName="groups"> + <column name="id" type="INT" autoIncrement="true"> + <constraints primaryKey="true" /> + </column> + <column name="name" type="VARCHAR(128)"> + <constraints nullable="false" /> + </column> + <column name="groupid" type="INT" /> + </createTable> + + <createTable tableName="user_group"> + <column name="userid" type="INT"> + <constraints nullable="false" /> + </column> + <column name="groupid" type="INT"> + <constraints nullable="false" /> + </column> + </createTable> + + <addForeignKeyConstraint baseTableName="user_group" baseColumnNames="userid" constraintName="fk_user_group_userid" referencedTableName="users" referencedColumnNames="id" onDelete="CASCADE" /> + <addForeignKeyConstraint baseTableName="user_group" baseColumnNames="groupid" constraintName="fk_user_group_groupid" referencedTableName="groups" referencedColumnNames="id" onDelete="CASCADE" /> + + <addColumn tableName="devices"> + <column name="groupid" type="INT" /> + </addColumn> + + </changeSet> +</databaseChangeLog> diff --git a/database/changelog-master.xml b/database/changelog-master.xml new file mode 100644 index 000000000..269a0da1a --- /dev/null +++ b/database/changelog-master.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<databaseChangeLog + xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog + http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> + + <include file="changelog-3.3.xml" relativeToChangelogFile="true" /> + <include file="changelog-3.5.xml" relativeToChangelogFile="true" /> +</databaseChangeLog> diff --git a/database/db.changelog-3.1.xml b/database/db.changelog-3.1.xml deleted file mode 100644 index 740da4e2f..000000000 --- a/database/db.changelog-3.1.xml +++ /dev/null @@ -1,131 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<databaseChangeLog - xmlns="http://www.liquibase.org/xml/ns/dbchangelog" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - - <changeSet author="author" id="changelog-3.1"> - - <preConditions onFail="MARK_RAN"> - <not> - <columnExists tableName="traccar" columnName="version" /> - </not> - </preConditions> - - <dropNotNullConstraint tableName="user" columnName="name" columnDataType="VARCHAR(1024)" /> - <modifyDataType tableName="user" columnName="name" newDataType="VARCHAR(128)" /> - <addNotNullConstraint tableName="user" columnName="name" columnDataType="VARCHAR(128)" /> - <dropUniqueConstraint tableName="user" uniqueColumns="email" constraintName="uk_user_email" /> - <dropNotNullConstraint tableName="user" columnName="email" columnDataType="VARCHAR(256)" /> - <modifyDataType tableName="user" columnName="email" newDataType="VARCHAR(128)" /> - <addNotNullConstraint tableName="user" columnName="email" columnDataType="VARCHAR(128)" /> - <addUniqueConstraint tableName="user" columnNames="email" constraintName="uk_user_email" /> - <dropNotNullConstraint tableName="user" columnName="hashedPassword" columnDataType="VARCHAR(1024)" /> - <modifyDataType tableName="user" columnName="hashedPassword" newDataType="VARCHAR(128)" /> - <addNotNullConstraint tableName="user" columnName="hashedPassword" columnDataType="VARCHAR(128)" /> - <dropDefaultValue tableName="user" columnName="salt" /> - <dropNotNullConstraint tableName="user" columnName="salt" columnDataType="VARCHAR(1024)" /> - <modifyDataType tableName="user" columnName="salt" newDataType="VARCHAR(128)" /> - <addNotNullConstraint tableName="user" columnName="salt" columnDataType="VARCHAR(128)" /> - <addDefaultValue tableName="user" columnName="salt" defaultValue="" /> - <dropDefaultValue tableName="user" columnName="map" /> - <dropNotNullConstraint tableName="user" columnName="map" columnDataType="VARCHAR(128)" /> - <modifyDataType tableName="user" columnName="map" newDataType="VARCHAR(128)" /> - <dropDefaultValue tableName="user" columnName="language" /> - <dropNotNullConstraint tableName="user" columnName="language" columnDataType="VARCHAR(128)" /> - <modifyDataType tableName="user" columnName="language" newDataType="VARCHAR(128)" /> - <dropDefaultValue tableName="user" columnName="distanceUnit" /> - <dropNotNullConstraint tableName="user" columnName="distanceUnit" columnDataType="VARCHAR(128)" /> - <modifyDataType tableName="user" columnName="distanceUnit" newDataType="VARCHAR(128)" /> - <dropDefaultValue tableName="user" columnName="speedUnit" /> - <dropNotNullConstraint tableName="user" columnName="speedUnit" columnDataType="VARCHAR(128)" /> - <modifyDataType tableName="user" columnName="speedUnit" newDataType="VARCHAR(128)" /> - <dropDefaultValue tableName="user" columnName="latitude" /> - <dropNotNullConstraint tableName="user" columnName="latitude" columnDataType="DOUBLE" /> - <modifyDataType tableName="user" columnName="latitude" newDataType="FLOAT" /> - <addNotNullConstraint tableName="user" columnName="latitude" columnDataType="FLOAT" /> - <addDefaultValue tableName="user" columnName="latitude" defaultValueNumeric="0" /> - <dropDefaultValue tableName="user" columnName="longitude" /> - <dropNotNullConstraint tableName="user" columnName="longitude" columnDataType="DOUBLE" /> - <modifyDataType tableName="user" columnName="longitude" newDataType="FLOAT" /> - <addNotNullConstraint tableName="user" columnName="longitude" columnDataType="FLOAT" /> - <addDefaultValue tableName="user" columnName="longitude" defaultValueNumeric="0" /> - - <dropNotNullConstraint tableName="device" columnName="name" columnDataType="VARCHAR(1024)" /> - <modifyDataType tableName="device" columnName="name" newDataType="VARCHAR(128)" /> - <addNotNullConstraint tableName="device" columnName="name" columnDataType="VARCHAR(128)" /> - <dropUniqueConstraint tableName="device" uniqueColumns="uniqueId" constraintName="uk_device_uniqueId" /> - <dropNotNullConstraint tableName="device" columnName="uniqueId" columnDataType="VARCHAR(256)" /> - <modifyDataType tableName="device" columnName="uniqueId" newDataType="VARCHAR(128)" /> - <addNotNullConstraint tableName="device" columnName="uniqueId" columnDataType="VARCHAR(128)" /> - <addUniqueConstraint tableName="device" columnNames="uniqueId" constraintName="uk_device_uniqueId" /> - <modifyDataType tableName="device" columnName="status" newDataType="VARCHAR(128)" /> - - <modifyDataType tableName="position" columnName="protocol" newDataType="VARCHAR(128)" /> - <dropNotNullConstraint tableName="position" columnName="latitude" columnDataType="DOUBLE" /> - <modifyDataType tableName="position" columnName="latitude" newDataType="FLOAT" /> - <addNotNullConstraint tableName="position" columnName="latitude" columnDataType="FLOAT" /> - <dropNotNullConstraint tableName="position" columnName="longitude" columnDataType="DOUBLE" /> - <modifyDataType tableName="position" columnName="longitude" newDataType="FLOAT" /> - <addNotNullConstraint tableName="position" columnName="longitude" columnDataType="FLOAT" /> - <dropNotNullConstraint tableName="position" columnName="altitude" columnDataType="DOUBLE" /> - <modifyDataType tableName="position" columnName="altitude" newDataType="FLOAT" /> - <addNotNullConstraint tableName="position" columnName="altitude" columnDataType="FLOAT" /> - <dropNotNullConstraint tableName="position" columnName="speed" columnDataType="DOUBLE" /> - <modifyDataType tableName="position" columnName="speed" newDataType="FLOAT" /> - <addNotNullConstraint tableName="position" columnName="speed" columnDataType="FLOAT" /> - <dropNotNullConstraint tableName="position" columnName="course" columnDataType="DOUBLE" /> - <modifyDataType tableName="position" columnName="course" newDataType="FLOAT" /> - <addNotNullConstraint tableName="position" columnName="course" columnDataType="FLOAT" /> - <modifyDataType tableName="position" columnName="address" newDataType="VARCHAR(512)" /> - <dropNotNullConstraint tableName="position" columnName="other" columnDataType="VARCHAR(8192)" /> - <modifyDataType tableName="position" columnName="other" newDataType="VARCHAR(4096)" /> - <addNotNullConstraint tableName="position" columnName="other" columnDataType="VARCHAR(4096)" /> - - <createIndex tableName="position" indexName="position_deviceId_fixTime"> - <column name="deviceId" /> - <column name="fixTime" /> - </createIndex> - - <modifyDataType tableName="data" columnName="protocol" newDataType="VARCHAR(128)" /> - <dropNotNullConstraint tableName="data" columnName="other" columnDataType="VARCHAR(8192)" /> - <modifyDataType tableName="data" columnName="other" newDataType="VARCHAR(4096)" /> - <addNotNullConstraint tableName="data" columnName="other" columnDataType="VARCHAR(4096)" /> - - <addColumn tableName="server"> - <column name="map" type="VARCHAR(128)" /> - </addColumn> - <addColumn tableName="server"> - <column name="language" type="VARCHAR(128)" /> - </addColumn> - <addColumn tableName="server"> - <column name="distanceUnit" type="VARCHAR(128)" /> - </addColumn> - <addColumn tableName="server"> - <column name="speedUnit" type="VARCHAR(128)" /> - </addColumn> - <dropNotNullConstraint tableName="server" columnName="latitude" columnDataType="DOUBLE" /> - <modifyDataType tableName="server" columnName="latitude" newDataType="FLOAT" /> - <addNotNullConstraint tableName="server" columnName="latitude" columnDataType="FLOAT" /> - <addDefaultValue tableName="server" columnName="latitude" defaultValueNumeric="0" /> - <dropNotNullConstraint tableName="server" columnName="longitude" columnDataType="DOUBLE" /> - <modifyDataType tableName="server" columnName="longitude" newDataType="FLOAT" /> - <addNotNullConstraint tableName="server" columnName="longitude" columnDataType="FLOAT" /> - <addDefaultValue tableName="server" columnName="longitude" defaultValueNumeric="0" /> - <addDefaultValue tableName="server" columnName="zoom" defaultValueNumeric="0" /> - - <dropTable tableName="traccar" /> - - <createTable tableName="traccar"> - <column name="version" type="INT" defaultValueNumeric="0"> - <constraints nullable="false" /> - </column> - </createTable> - - <insert tableName="traccar"> - <column name="version" valueNumeric="301" /> - </insert> - - </changeSet> -</databaseChangeLog> diff --git a/database/db.changelog-3.2.xml b/database/db.changelog-3.2.xml deleted file mode 100644 index fdeabfb68..000000000 --- a/database/db.changelog-3.2.xml +++ /dev/null @@ -1,63 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<databaseChangeLog - xmlns="http://www.liquibase.org/xml/ns/dbchangelog" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - - <changeSet author="author" id="changelog-3.2"> - - <preConditions onFail="MARK_RAN"> - <not> - <columnExists tableName="position" columnName="attributes" /> - </not> - </preConditions> - - <dropDefaultValue tableName="user" columnName="latitude" /> - <dropNotNullConstraint tableName="user" columnName="latitude" columnDataType="FLOAT" /> - <modifyDataType tableName="user" columnName="latitude" newDataType="DOUBLE" /> - <addNotNullConstraint tableName="user" columnName="latitude" columnDataType="DOUBLE" /> - <addDefaultValue tableName="user" columnName="latitude" defaultValueNumeric="0" /> - <dropDefaultValue tableName="user" columnName="longitude" /> - <dropNotNullConstraint tableName="user" columnName="longitude" columnDataType="FLOAT" /> - <modifyDataType tableName="user" columnName="longitude" newDataType="DOUBLE" /> - <addNotNullConstraint tableName="user" columnName="longitude" columnDataType="DOUBLE" /> - <addDefaultValue tableName="user" columnName="longitude" defaultValueNumeric="0" /> - - <dropColumn tableName="device" columnName="dataId" /> - - <dropNotNullConstraint tableName="position" columnName="latitude" columnDataType="FLOAT" /> - <modifyDataType tableName="position" columnName="latitude" newDataType="DOUBLE" /> - <addNotNullConstraint tableName="position" columnName="latitude" columnDataType="DOUBLE" /> - <dropNotNullConstraint tableName="position" columnName="longitude" columnDataType="FLOAT" /> - <modifyDataType tableName="position" columnName="longitude" newDataType="DOUBLE" /> - <addNotNullConstraint tableName="position" columnName="longitude" columnDataType="DOUBLE" /> - <dropNotNullConstraint tableName="position" columnName="other" columnDataType="VARCHAR(4096)" /> - <renameColumn tableName="position" oldColumnName="other" newColumnName="attributes" columnDataType="VARCHAR(4096)" /> - <addNotNullConstraint tableName="position" columnName="attributes" columnDataType="VARCHAR(4096)" /> - - <dropTable tableName="data" /> - - <addColumn tableName="server"> - <column name="bingKey" type="VARCHAR(128)" /> - </addColumn> - <addColumn tableName="server"> - <column name="mapUrl" type="VARCHAR(128)" /> - </addColumn> - <dropDefaultValue tableName="server" columnName="latitude" /> - <dropNotNullConstraint tableName="server" columnName="latitude" columnDataType="FLOAT" /> - <modifyDataType tableName="server" columnName="latitude" newDataType="DOUBLE" /> - <addNotNullConstraint tableName="server" columnName="latitude" columnDataType="DOUBLE" /> - <addDefaultValue tableName="server" columnName="latitude" defaultValueNumeric="0" /> - <dropDefaultValue tableName="server" columnName="longitude" /> - <dropNotNullConstraint tableName="server" columnName="longitude" columnDataType="FLOAT" /> - <modifyDataType tableName="server" columnName="longitude" newDataType="DOUBLE" /> - <addNotNullConstraint tableName="server" columnName="longitude" columnDataType="DOUBLE" /> - <addDefaultValue tableName="server" columnName="longitude" defaultValueNumeric="0" /> - - <update tableName="traccar"> - <column name="version" valueNumeric="302" /> - </update> - - </changeSet> -</databaseChangeLog> diff --git a/database/db.changelog-3.3.xml b/database/db.changelog-3.3.xml deleted file mode 100644 index 22fcd6f5c..000000000 --- a/database/db.changelog-3.3.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<databaseChangeLog - xmlns="http://www.liquibase.org/xml/ns/dbchangelog" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - - <changeSet author="author" id="changelog-3.3"> - - <dropDefaultValue tableName="user" columnName="salt" /> - - <addDefaultValue tableName="server" columnName="registration" defaultValueBoolean="true" /> - - <addColumn tableName="server"> - <column name="readonly" type="BOOLEAN" defaultValueBoolean="false"> - <constraints nullable="false" /> - </column> - </addColumn> - - <dropTable tableName="traccar" /> - - <renameTable oldTableName="user" newTableName="users" /> - <renameTable oldTableName="device" newTableName="devices" /> - <renameTable oldTableName="position" newTableName="positions" /> - - <dropDefaultValue tableName="user_device" columnName="read" /> - <dropColumn tableName="user_device" columnName="read" /> - <dropDefaultValue tableName="user_device" columnName="write" /> - <dropColumn tableName="user_device" columnName="write" /> - - </changeSet> -</databaseChangeLog> diff --git a/database/db.changelog-master.xml b/database/db.changelog-master.xml deleted file mode 100644 index 06fc7a695..000000000 --- a/database/db.changelog-master.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<databaseChangeLog - xmlns="http://www.liquibase.org/xml/ns/dbchangelog" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - - <include file="db.changelog-normalize.xml" relativeToChangelogFile="true" /> - <include file="db.changelog-3.0.xml" relativeToChangelogFile="true" /> - <include file="db.changelog-3.1.xml" relativeToChangelogFile="true" /> - <include file="db.changelog-3.2.xml" relativeToChangelogFile="true" /> - <include file="db.changelog-3.3.xml" relativeToChangelogFile="true" /> -</databaseChangeLog> diff --git a/database/db.changelog-normalize.xml b/database/db.changelog-normalize.xml deleted file mode 100644 index 7621846ca..000000000 --- a/database/db.changelog-normalize.xml +++ /dev/null @@ -1,114 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<databaseChangeLog - xmlns="http://www.liquibase.org/xml/ns/dbchangelog" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog - http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> - - <changeSet author="author" id="changelog-normalize-user"> - - <preConditions onFail="MARK_RAN"> - <tableExists tableName="traccar" /> - <not> - <columnExists tableName="user" columnName="id"/> - </not> - </preConditions> - - <renameTable oldTableName="quotedUser" newTableName="user" /> - - <modifySql> - <replace replace="quotedUser" with=""user"" /> - </modifySql> - - </changeSet> - - <changeSet author="author" id="changelog-normalize-user-device"> - - <preConditions onFail="MARK_RAN"> - <tableExists tableName="traccar" /> - <not> - <columnExists tableName="user_device" columnName="read"/> - </not> - </preConditions> - - <renameColumn tableName="user_device" oldColumnName="quotedRead" newColumnName="read" columnDataType="BOOLEAN" /> - <renameColumn tableName="user_device" oldColumnName="quotedWrite" newColumnName="write" columnDataType="BOOLEAN" /> - - <modifySql> - <replace replace="quotedRead" with=""read"" /> - <replace replace="quotedWrite" with=""write"" /> - </modifySql> - - </changeSet> - - <changeSet author="author" id="changelog-normalize-fk-h2"> - - <preConditions onFail="MARK_RAN"> - <tableExists tableName="traccar" /> - <dbms type="h2" /> - </preConditions> - - <sql>CREATE ALIAS IF NOT EXISTS EXECUTE AS $$ void executeSql(Connection conn, String sql) throws SQLException { try { conn.createStatement().executeUpdate(sql); } catch (Exception e) {} } $$;</sql> - - <sql>CALL EXECUTE('ALTER TABLE POSITION DROP CONSTRAINT ' || (SELECT DISTINCT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINTS WHERE CONSTRAINT_TYPE = 'REFERENTIAL' AND TABLE_NAME = 'POSITION' AND COLUMN_LIST = 'DEVICEID'));</sql> - <sql>CALL EXECUTE('ALTER TABLE DEVICE DROP CONSTRAINT ' || (SELECT DISTINCT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINTS WHERE CONSTRAINT_TYPE = 'REFERENTIAL' AND TABLE_NAME = 'DEVICE' AND COLUMN_LIST = 'POSITIONID'));</sql> - <sql>CALL EXECUTE('ALTER TABLE DEVICE DROP CONSTRAINT ' || (SELECT DISTINCT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINTS WHERE CONSTRAINT_TYPE = 'REFERENTIAL' AND TABLE_NAME = 'DEVICE' AND COLUMN_LIST = 'DATAID'));</sql> - <sql>CALL EXECUTE('ALTER TABLE USER_DEVICE DROP CONSTRAINT ' || (SELECT DISTINCT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINTS WHERE CONSTRAINT_TYPE = 'REFERENTIAL' AND TABLE_NAME = 'USER_DEVICE' AND COLUMN_LIST = 'USERID'));</sql> - <sql>CALL EXECUTE('ALTER TABLE USER_DEVICE DROP CONSTRAINT ' || (SELECT DISTINCT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINTS WHERE CONSTRAINT_TYPE = 'REFERENTIAL' AND TABLE_NAME = 'USER_DEVICE' AND COLUMN_LIST = 'DEVICEID'));</sql> - <sql>CALL EXECUTE('ALTER TABLE DATA DROP CONSTRAINT ' || (SELECT DISTINCT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINTS WHERE CONSTRAINT_TYPE = 'REFERENTIAL' AND TABLE_NAME = 'DATA' AND COLUMN_LIST = 'DEVICEID'));</sql> - - </changeSet> - - <changeSet author="author" id="changelog-normalize-fk-other"> - - <preConditions onFail="MARK_RAN"> - <tableExists tableName="traccar" /> - <not> - <dbms type="h2" /> - </not> - </preConditions> - - <dropAllForeignKeyConstraints baseTableName="position" /> - <dropAllForeignKeyConstraints baseTableName="device" /> - <dropAllForeignKeyConstraints baseTableName="user_device" /> - - </changeSet> - - <changeSet author="author" id="changelog-normalize-fk"> - - <preConditions onFail="MARK_RAN"> - <tableExists tableName="traccar" /> - </preConditions> - - <addForeignKeyConstraint baseTableName="user_device" baseColumnNames="userId" constraintName="fk_user_device_userId" referencedTableName="user" referencedColumnNames="id" onDelete="CASCADE" /> - <addForeignKeyConstraint baseTableName="user_device" baseColumnNames="deviceId" constraintName="fk_user_device_deviceId" referencedTableName="device" referencedColumnNames="id" onDelete="CASCADE" /> - - <addForeignKeyConstraint baseTableName="position" baseColumnNames="deviceId" constraintName="fk_position_deviceId" referencedTableName="device" referencedColumnNames="id" onDelete="CASCADE" /> - - </changeSet> - - <changeSet author="author" id="changelog-normalize-fk-data-other"> - - <preConditions onFail="MARK_RAN"> - <tableExists tableName="traccar" /> - <tableExists tableName="data" /> - <not> - <dbms type="h2" /> - </not> - </preConditions> - - <dropAllForeignKeyConstraints baseTableName="data" /> - - </changeSet> - - <changeSet author="author" id="changelog-normalize-fk-data"> - - <preConditions onFail="MARK_RAN"> - <tableExists tableName="traccar" /> - <tableExists tableName="data" /> - </preConditions> - - <addForeignKeyConstraint baseTableName="data" baseColumnNames="deviceId" constraintName="fk_data_deviceId" referencedTableName="device" referencedColumnNames="id" onDelete="CASCADE" /> - - </changeSet> -</databaseChangeLog> @@ -14,6 +14,7 @@ <entry key='web.path'>web</entry> <entry key='web.debug'>true</entry> <entry key='web.console'>true</entry> + <entry key='web.timeout'>30000</entry> <entry key='geocoder.enable'>true</entry> <entry key='geocoder.type'>nominatim</entry> @@ -48,7 +49,7 @@ <entry key='database.user'>sa</entry> <entry key='database.password'></entry> <entry key='database.xml'>false</entry> - <entry key='database.changelog'>./database/db.changelog-master.xml</entry> + <entry key='database.changelog'>./database/changelog-master.xml</entry> <entry key='database.selectServers'> SELECT * FROM server; @@ -112,24 +113,24 @@ DELETE FROM users WHERE id = :id; </entry> - <entry key='database.getPermissionsAll'> + <entry key='database.selectDevicePermissions'> SELECT userId, deviceId FROM user_device; </entry> - <entry key='database.selectDevicesAll'> - SELECT * FROM devices; + <entry key='database.selectGroupPermissions'> + SELECT userId, groupId FROM user_group; </entry> - <entry key='database.selectDevices'> - SELECT * FROM devices d INNER JOIN user_device ud ON d.id = ud.deviceId WHERE ud.userId = :userId; + <entry key='database.selectDevicesAll'> + SELECT * FROM devices; </entry> <entry key='database.insertDevice'> - INSERT INTO devices (name, uniqueId) VALUES (:name, :uniqueId); + INSERT INTO devices (name, uniqueId, groupId) VALUES (:name, :uniqueId, :groupId); </entry> <entry key='database.updateDevice'> - UPDATE devices SET name = :name, uniqueId = :uniqueId WHERE id = :id; + UPDATE devices SET name = :name, uniqueId = :uniqueId, groupId = :groupId WHERE id = :id; </entry> <entry key='database.updateDeviceStatus'> @@ -148,6 +149,30 @@ DELETE FROM user_device WHERE userId = :userId AND deviceId = :deviceId; </entry> + <entry key='database.selectGroupsAll'> + SELECT * FROM groups; + </entry> + + <entry key='database.insertGroup'> + INSERT INTO groups (name) VALUES (:name); + </entry> + + <entry key='database.updateGroup'> + UPDATE groups SET name = :name, groupId = :groupId WHERE id = :id; + </entry> + + <entry key='database.deleteGroup'> + DELETE FROM groups WHERE id = :id; + </entry> + + <entry key='database.linkGroup'> + INSERT INTO user_group (userId, groupId) VALUES (:userId, :groupId); + </entry> + + <entry key='database.unlinkGroup'> + DELETE FROM user_group WHERE userId = :userId AND groupId = :groupId; + </entry> + <entry key='database.selectPositions'> SELECT * FROM positions WHERE deviceId = :deviceId AND fixTime BETWEEN :from AND :to ORDER BY fixTime; </entry> @@ -235,7 +260,7 @@ <entry key='eelink.port'>5064</entry> <entry key='box.port'>5065</entry> <entry key='freedom.port'>5066</entry> - <entry key='telik.port'>5067</entry> + <entry key='telic.port'>5067</entry> <entry key='trackbox.port'>5068</entry> <entry key='visiontek.port'>5069</entry> <entry key='orion.port'>5070</entry> @@ -247,6 +272,7 @@ <entry key='xt013.port'>5076</entry> <entry key='autofon.port'>5077</entry> <entry key='gosafe.port'>5078</entry> + <entry key='tt8850.port'>5079</entry> <entry key='bce.port'>5080</entry> <entry key='xirgo.port'>5081</entry> <entry key='calamp.port'>5082</entry> @@ -270,5 +296,7 @@ <entry key='arnavi.port'>5100</entry> <entry key='nvs.port'>5101</entry> <entry key='kenji.port'>5102</entry> + <entry key='astra.port'>5103</entry> + <entry key='homtecs.port'>5104</entry> </properties> @@ -4,15 +4,15 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.traccar</groupId> <artifactId>traccar</artifactId> - <version>3.3-SNAPSHOT</version> + <version>3.4-SNAPSHOT</version> <name>traccar</name> <url>https://www.traccar.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <jetty.version>9.2.14.v20151106</jetty.version> <!-- Jetty 9.3+ requires Java 8 --> - <jersey.version>2.22.1</jersey.version> + <jetty.version>9.2.15.v20160210</jetty.version> <!-- Jetty 9.3+ requires Java 8 --> + <jersey.version>2.22.2</jersey.version> </properties> <dependencies> @@ -30,12 +30,12 @@ <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> - <version>2.9.1</version> + <version>2.9.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> - <version>1.4.190</version> + <version>1.4.191</version> </dependency> <dependency> <groupId>mysql</groupId> @@ -45,7 +45,7 @@ <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> - <version>9.4-1206-jdbc41</version> + <version>9.4.1208.jre7</version> </dependency> <dependency> <groupId>com.mchange</groupId> @@ -60,12 +60,12 @@ <dependency> <groupId>com.ning</groupId> <!-- org.asynchttpclient starting from version 2.0 --> <artifactId>async-http-client</artifactId> - <version>1.9.31</version> + <version>1.9.33</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> - <version>1.7.13</version> + <version>1.7.18</version> </dependency> <dependency> <groupId>org.glassfish</groupId> @@ -164,7 +164,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.3</version> + <version>3.5.1</version> <configuration> <source>1.7</source> <target>1.7</target> diff --git a/setup/package.sh b/setup/package.sh index 8014836bb..fa5fbd277 100755 --- a/setup/package.sh +++ b/setup/package.sh @@ -15,7 +15,6 @@ fi VERSION=$1 check_requirement () { - eval $1 &>/dev/null if ! eval $1 &>/dev/null then echo $2 diff --git a/setup/unix/setup.sh b/setup/unix/setup.sh index 902139a1a..478966ea0 100755 --- a/setup/unix/setup.sh +++ b/setup/unix/setup.sh @@ -2,13 +2,18 @@ UNIX_PATH="/opt/traccar" -if [ $(java -version 2>&1 | grep -i version | sed 's/.*version "\(.*\)\.\(.*\)\..*"/\1\2/; 1q') -lt 17 ] +if which java &>/dev/null then - echo "Please install Java version 7 or higher" + if [ $(java -version 2>&1 | grep -i version | sed 's/.*version "\(.*\)\.\(.*\)\..*"/\1\2/; 1q') -lt 17 ] + then + echo "Java 7 or higher required" + else + mkdir -p $UNIX_PATH + cp -rf * $UNIX_PATH + chmod -R go+rX $UNIX_PATH + $UNIX_PATH/bin/traccar install + rm $UNIX_PATH/setup.sh + fi else - mkdir -p $UNIX_PATH - cp -rf * $UNIX_PATH - chmod -R go+rX $UNIX_PATH - $UNIX_PATH/bin/traccar install - rm $UNIX_PATH/setup.sh + echo "Java runtime is required" fi diff --git a/setup/unix/traccar.xml b/setup/unix/traccar.xml index 5eb64e7b0..c43fd25fd 100644 --- a/setup/unix/traccar.xml +++ b/setup/unix/traccar.xml @@ -24,7 +24,7 @@ <entry key='database.user'>sa</entry> <entry key='database.password'></entry> - <entry key='database.changelog'>/opt/traccar/data/db.changelog-master.xml</entry> + <entry key='database.changelog'>/opt/traccar/data/changelog-master.xml</entry> <entry key='database.selectServers'> SELECT * FROM server; @@ -210,7 +210,7 @@ <entry key='eelink.port'>5064</entry> <entry key='box.port'>5065</entry> <entry key='freedom.port'>5066</entry> - <entry key='telik.port'>5067</entry> + <entry key='telic.port'>5067</entry> <entry key='trackbox.port'>5068</entry> <entry key='visiontek.port'>5069</entry> <entry key='orion.port'>5070</entry> @@ -222,7 +222,7 @@ <entry key='xt013.port'>5076</entry> <entry key='autofon.port'>5077</entry> <entry key='gosafe.port'>5078</entry> - <entry key='autofon45.port'>5079</entry> + <entry key='tt8850.port'>5079</entry> <entry key='bce.port'>5080</entry> <entry key='xirgo.port'>5081</entry> <entry key='calamp.port'>5082</entry> @@ -244,5 +244,9 @@ <entry key='thinkrace.port'>5098</entry> <entry key='pathaway.port'>5099</entry> <entry key='arnavi.port'>5100</entry> + <entry key='nvs.port'>5101</entry> + <entry key='kenji.port'>5102</entry> + <entry key='astra.port'>5103</entry> + <entry key='homtecs.port'>5104</entry> </properties> diff --git a/setup/windows/traccar.iss b/setup/windows/traccar.iss index 298c27200..891cc47c3 100644 --- a/setup/windows/traccar.iss +++ b/setup/windows/traccar.iss @@ -83,6 +83,6 @@ begin StringChangeEx(S, '[WEB]', ExpandConstant('{app}\web'), true);
StringChangeEx(S, '[LOG]', ExpandConstant('{app}\logs\tracker-server.log'), true);
StringChangeEx(S, '[DATABASE]', ExpandConstant('{app}\data\database'), true);
- StringChangeEx(S, '[CHANGELOG]', ExpandConstant('{app}\data\db.changelog-master.xml'), true);
+ StringChangeEx(S, '[CHANGELOG]', ExpandConstant('{app}\data\changelog-master.xml'), true);
SaveStringToFile(ExpandConstant(CurrentFileName), S, false);
end;
diff --git a/setup/windows/traccar.xml b/setup/windows/traccar.xml index c1e675254..40b6fe20e 100644 --- a/setup/windows/traccar.xml +++ b/setup/windows/traccar.xml @@ -210,7 +210,7 @@ <entry key='eelink.port'>5064</entry>
<entry key='box.port'>5065</entry>
<entry key='freedom.port'>5066</entry>
- <entry key='telik.port'>5067</entry>
+ <entry key='telic.port'>5067</entry>
<entry key='trackbox.port'>5068</entry>
<entry key='visiontek.port'>5069</entry>
<entry key='orion.port'>5070</entry>
@@ -222,7 +222,7 @@ <entry key='xt013.port'>5076</entry>
<entry key='autofon.port'>5077</entry>
<entry key='gosafe.port'>5078</entry>
- <entry key='autofon45.port'>5079</entry>
+ <entry key='tt8850.port'>5079</entry>
<entry key='bce.port'>5080</entry>
<entry key='xirgo.port'>5081</entry>
<entry key='calamp.port'>5082</entry>
@@ -244,5 +244,9 @@ <entry key='thinkrace.port'>5098</entry>
<entry key='pathaway.port'>5099</entry>
<entry key='arnavi.port'>5100</entry>
+ <entry key='nvs.port'>5101</entry>
+ <entry key='kenji.port'>5102</entry>
+ <entry key='astra.port'>5103</entry>
+ <entry key='homtecs.port'>5104</entry>
</properties>
diff --git a/src/org/traccar/DistanceHandler.java b/src/org/traccar/DistanceHandler.java index 1586d116b..c737457f6 100644 --- a/src/org/traccar/DistanceHandler.java +++ b/src/org/traccar/DistanceHandler.java @@ -1,5 +1,6 @@ /* * Copyright 2015 Amila Silva + * Copyright 2016 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. @@ -40,11 +41,13 @@ public class DistanceHandler extends BaseDataHandler { distance = ((Number) last.getAttributes().get(Event.KEY_DISTANCE)).doubleValue(); } - distance += DistanceCalculator.distance( - position.getLatitude(), position.getLongitude(), - last.getLatitude(), last.getLongitude()); + if (position.getValid()) { + distance += DistanceCalculator.distance( + position.getLatitude(), position.getLongitude(), + last.getLatitude(), last.getLongitude()); - distance = BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_EVEN).doubleValue(); + distance = BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_EVEN).doubleValue(); + } } position.set(Event.KEY_DISTANCE, distance); diff --git a/src/org/traccar/MainEventHandler.java b/src/org/traccar/MainEventHandler.java index f0ef36e5b..f99765de4 100644 --- a/src/org/traccar/MainEventHandler.java +++ b/src/org/traccar/MainEventHandler.java @@ -48,7 +48,10 @@ public class MainEventHandler extends IdleStateAwareChannelHandler { s.append("course: ").append(String.format("%.1f", position.getCourse())); Log.info(s.toString()); - Context.getConnectionManager().updatePosition(position); + Position lastPosition = Context.getConnectionManager().getLastPosition(position.getDeviceId()); + if (lastPosition == null || position.getFixTime().compareTo(lastPosition.getFixTime()) > 0) { + Context.getConnectionManager().updatePosition(position); + } } } diff --git a/src/org/traccar/api/AsyncSocket.java b/src/org/traccar/api/AsyncSocket.java index 7de182d7e..c289367ea 100644 --- a/src/org/traccar/api/AsyncSocket.java +++ b/src/org/traccar/api/AsyncSocket.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -36,7 +36,7 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U private Collection<Long> devices; public AsyncSocket(long userId) { - devices = Context.getPermissionsManager().allowedDevices(userId); + devices = Context.getPermissionsManager().getDevicePermissions(userId); } @Override @@ -66,7 +66,7 @@ public class AsyncSocket extends WebSocketAdapter implements ConnectionManager.U } private void sendData(String key, Collection<?> data) { - if (!data.isEmpty()) { + if (!data.isEmpty() && isConnected()) { JsonObjectBuilder json = Json.createObjectBuilder(); json.add(key, JsonConverter.arrayToJson(data)); getRemote().sendString(json.build().toString(), null); diff --git a/src/org/traccar/api/AsyncSocketServlet.java b/src/org/traccar/api/AsyncSocketServlet.java index fbfe248e5..ef6cef732 100644 --- a/src/org/traccar/api/AsyncSocketServlet.java +++ b/src/org/traccar/api/AsyncSocketServlet.java @@ -20,6 +20,7 @@ import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.traccar.Context; import org.traccar.api.resource.SessionResource; public class AsyncSocketServlet extends WebSocketServlet { @@ -28,12 +29,16 @@ public class AsyncSocketServlet extends WebSocketServlet { @Override public void configure(WebSocketServletFactory factory) { - factory.getPolicy().setIdleTimeout(ASYNC_TIMEOUT); + factory.getPolicy().setIdleTimeout(Context.getConfig().getLong("web.timeout", ASYNC_TIMEOUT)); factory.setCreator(new WebSocketCreator() { @Override public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { - long userId = (Long) req.getSession().getAttribute(SessionResource.USER_ID_KEY); - return new AsyncSocket(userId); + if (req.getSession() != null) { + long userId = (Long) req.getSession().getAttribute(SessionResource.USER_ID_KEY); + return new AsyncSocket(userId); + } else { + return null; + } } }); } diff --git a/src/org/traccar/api/CorsResponseFilter.java b/src/org/traccar/api/CorsResponseFilter.java index 001f6ab4c..178d08812 100644 --- a/src/org/traccar/api/CorsResponseFilter.java +++ b/src/org/traccar/api/CorsResponseFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -19,8 +19,6 @@ import org.jboss.netty.handler.codec.http.HttpHeaders; import org.traccar.Context; import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.container.ContainerResponseFilter; @@ -37,7 +35,7 @@ public class CorsResponseFilter implements ContainerResponseFilter { public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_VALUE = "true"; public static final String ACCESS_CONTROL_ALLOW_METHODS_KEY = "Access-Control-Allow-Methods"; - public static final String ACCESS_CONTROL_ALLOW_METHODS_VALUE = "GET, POST, PUT, DELETE"; + public static final String ACCESS_CONTROL_ALLOW_METHODS_VALUE = "GET, POST, PUT, DELETE, OPTIONS"; @Override public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException { @@ -54,11 +52,11 @@ public class CorsResponseFilter implements ContainerResponseFilter { if (!response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN_KEY)) { String origin = request.getHeaderString(HttpHeaders.Names.ORIGIN); String allowed = Context.getConfig().getString("web.origin"); - if (allowed == null) { + + if (allowed == null || origin == null) { response.getHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN_KEY, ACCESS_CONTROL_ALLOW_ORIGIN_VALUE); - } else if (allowed.contains(origin)) { - String originSafe = URLEncoder.encode(origin, StandardCharsets.UTF_8.name()); - response.getHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN_KEY, originSafe); + } else if (allowed.equals(ACCESS_CONTROL_ALLOW_ORIGIN_VALUE) || allowed.contains(origin)) { + response.getHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN_KEY, origin); } } } diff --git a/src/org/traccar/api/SecurityRequestFilter.java b/src/org/traccar/api/SecurityRequestFilter.java index 20186b0cb..4c6137ede 100644 --- a/src/org/traccar/api/SecurityRequestFilter.java +++ b/src/org/traccar/api/SecurityRequestFilter.java @@ -55,6 +55,11 @@ public class SecurityRequestFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) { + + if (requestContext.getMethod().equals("OPTIONS")) { + throw new WebApplicationException(Response.status(Response.Status.OK).build()); + } + SecurityContext securityContext = null; String authHeader = requestContext.getHeaderString(AUTHORIZATION_HEADER); diff --git a/src/org/traccar/api/resource/PermissionResource.java b/src/org/traccar/api/resource/DevicePermissionResource.java index 50deb77c2..f77375cba 100644 --- a/src/org/traccar/api/resource/PermissionResource.java +++ b/src/org/traccar/api/resource/DevicePermissionResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -17,7 +17,7 @@ package org.traccar.api.resource; import org.traccar.Context; import org.traccar.api.BaseResource; -import org.traccar.model.Permission; +import org.traccar.model.DevicePermission; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -28,13 +28,13 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.sql.SQLException; -@Path("permissions") +@Path("permissions/devices") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) -public class PermissionResource extends BaseResource { +public class DevicePermissionResource extends BaseResource { @POST - public Response add(Permission entity) throws SQLException { + public Response add(DevicePermission entity) throws SQLException { Context.getPermissionsManager().checkAdmin(getUserId()); Context.getDataManager().linkDevice(entity.getUserId(), entity.getDeviceId()); Context.getPermissionsManager().refresh(); @@ -42,7 +42,7 @@ public class PermissionResource extends BaseResource { } @DELETE - public Response remove(Permission entity) throws SQLException { + public Response remove(DevicePermission entity) throws SQLException { Context.getPermissionsManager().checkAdmin(getUserId()); Context.getDataManager().unlinkDevice(entity.getUserId(), entity.getDeviceId()); Context.getPermissionsManager().refresh(); diff --git a/src/org/traccar/api/resource/GroupPermissionResource.java b/src/org/traccar/api/resource/GroupPermissionResource.java new file mode 100644 index 000000000..b8ec4ae3c --- /dev/null +++ b/src/org/traccar/api/resource/GroupPermissionResource.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 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.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.model.GroupPermission; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.sql.SQLException; + +@Path("permissions/groups") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class GroupPermissionResource extends BaseResource { + + @POST + public Response add(GroupPermission entity) throws SQLException { + Context.getPermissionsManager().checkAdmin(getUserId()); + Context.getDataManager().linkGroup(entity.getUserId(), entity.getGroupId()); + Context.getPermissionsManager().refresh(); + return Response.ok(entity).build(); + } + + @DELETE + public Response remove(GroupPermission entity) throws SQLException { + Context.getPermissionsManager().checkAdmin(getUserId()); + Context.getDataManager().unlinkGroup(entity.getUserId(), entity.getGroupId()); + Context.getPermissionsManager().refresh(); + return Response.noContent().build(); + } + +} diff --git a/src/org/traccar/api/resource/GroupResource.java b/src/org/traccar/api/resource/GroupResource.java new file mode 100644 index 000000000..49f839499 --- /dev/null +++ b/src/org/traccar/api/resource/GroupResource.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 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.api.resource; + +import org.traccar.Context; +import org.traccar.api.BaseResource; +import org.traccar.model.Device; +import org.traccar.model.Group; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.sql.SQLException; +import java.util.Collection; + +@Path("groups") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class GroupResource extends BaseResource { + + @GET + public Collection<Group> get( + @QueryParam("all") boolean all, @QueryParam("userId") long userId) throws SQLException { + if (all) { + Context.getPermissionsManager().checkAdmin(getUserId()); + return Context.getDataManager().getAllGroups(); + } else { + if (userId == 0) { + userId = getUserId(); + } + Context.getPermissionsManager().checkUser(getUserId(), userId); + return Context.getDataManager().getGroups(userId); + } + } + + @POST + public Response add(Group entity) throws SQLException { + Context.getPermissionsManager().checkReadonly(getUserId()); + Context.getDataManager().addGroup(entity); + Context.getDataManager().linkGroup(getUserId(), entity.getId()); + Context.getPermissionsManager().refresh(); + return Response.ok(entity).build(); + } + + @Path("{id}") + @PUT + public Response update(@PathParam("id") long id, Group entity) throws SQLException { + Context.getPermissionsManager().checkReadonly(getUserId()); + Context.getPermissionsManager().checkGroup(getUserId(), id); + Context.getDataManager().updateGroup(entity); + return Response.ok(entity).build(); + } + + @Path("{id}") + @DELETE + public Response remove(@PathParam("id") long id) throws SQLException { + Context.getPermissionsManager().checkReadonly(getUserId()); + Context.getPermissionsManager().checkGroup(getUserId(), id); + Context.getDataManager().removeGroup(id); + Context.getPermissionsManager().refresh(); + return Response.noContent().build(); + } + +} diff --git a/src/org/traccar/api/resource/PositionResource.java b/src/org/traccar/api/resource/PositionResource.java index ec6925b3a..9c174625c 100644 --- a/src/org/traccar/api/resource/PositionResource.java +++ b/src/org/traccar/api/resource/PositionResource.java @@ -38,9 +38,14 @@ public class PositionResource extends BaseResource { public Collection<Position> get( @QueryParam("deviceId") long deviceId, @QueryParam("from") String from, @QueryParam("to") String to) throws SQLException { - Context.getPermissionsManager().checkDevice(getUserId(), deviceId); - return Context.getDataManager().getPositions( - deviceId, JsonConverter.parseDate(from), JsonConverter.parseDate(to)); + if (deviceId == 0) { + return Context.getConnectionManager().getInitialState( + Context.getPermissionsManager().getDevicePermissions(getUserId())); + } else { + Context.getPermissionsManager().checkDevice(getUserId(), deviceId); + return Context.getDataManager().getPositions( + deviceId, JsonConverter.parseDate(from), JsonConverter.parseDate(to)); + } } } diff --git a/src/org/traccar/database/DataManager.java b/src/org/traccar/database/DataManager.java index 73984952e..d2da4e701 100644 --- a/src/org/traccar/database/DataManager.java +++ b/src/org/traccar/database/DataManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2012 - 2016 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. @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -36,10 +37,13 @@ import liquibase.exception.LiquibaseException; import liquibase.resource.FileSystemResourceAccessor; import liquibase.resource.ResourceAccessor; import org.traccar.Config; +import org.traccar.Context; import org.traccar.helper.Log; import org.traccar.model.Device; +import org.traccar.model.Group; +import org.traccar.model.GroupPermission; import org.traccar.model.MiscFormatter; -import org.traccar.model.Permission; +import org.traccar.model.DevicePermission; import org.traccar.model.Position; import org.traccar.model.Server; import org.traccar.model.User; @@ -53,10 +57,14 @@ public class DataManager implements IdentityManager { private DataSource dataSource; + private final long dataRefreshDelay; + private final Map<Long, Device> devicesById = new HashMap<>(); private final Map<String, Device> devicesByUniqueId = new HashMap<>(); private long devicesLastUpdate; - private final long devicesRefreshDelay; + + private final Map<Long, Group> groupsById = new HashMap<>(); + private long groupsLastUpdate; public DataManager(Config config) throws Exception { this.config = config; @@ -64,7 +72,7 @@ public class DataManager implements IdentityManager { initDatabase(); initDatabaseSchema(); - devicesRefreshDelay = config.getLong("database.refreshDelay", DEFAULT_REFRESH_DELAY) * 1000; + dataRefreshDelay = config.getLong("database.refreshDelay", DEFAULT_REFRESH_DELAY) * 1000; } public DataSource getDataSource() { @@ -112,7 +120,7 @@ public class DataManager implements IdentityManager { } private void updateDeviceCache(boolean force) throws SQLException { - if (System.currentTimeMillis() - devicesLastUpdate > devicesRefreshDelay || force) { + if (System.currentTimeMillis() - devicesLastUpdate > dataRefreshDelay || force) { devicesById.clear(); devicesByUniqueId.clear(); for (Device device : getAllDevices()) { @@ -140,6 +148,25 @@ public class DataManager implements IdentityManager { return devicesByUniqueId.get(uniqueId); } + private void updateGroupCache(boolean force) throws SQLException { + if (System.currentTimeMillis() - groupsLastUpdate > dataRefreshDelay || force) { + groupsById.clear(); + for (Group group : getAllGroups()) { + groupsById.put(group.getId(), group); + } + groupsLastUpdate = System.currentTimeMillis(); + } + } + + public Group getGroupById(long id) { + try { + updateGroupCache(!groupsById.containsKey(id)); + } catch (SQLException e) { + Log.warning(e); + } + return groupsById.get(id); + } + private String getQuery(String key) { String query = config.getString(key); if (query == null) { @@ -221,9 +248,14 @@ public class DataManager implements IdentityManager { .executeUpdate(); } - public Collection<Permission> getPermissions() throws SQLException { - return QueryBuilder.create(dataSource, getQuery("database.getPermissionsAll")) - .executeQuery(Permission.class); + public Collection<DevicePermission> getDevicePermissions() throws SQLException { + return QueryBuilder.create(dataSource, getQuery("database.selectDevicePermissions")) + .executeQuery(DevicePermission.class); + } + + public Collection<GroupPermission> getGroupPermissions() throws SQLException { + return QueryBuilder.create(dataSource, getQuery("database.selectGroupPermissions")) + .executeQuery(GroupPermission.class); } public Collection<Device> getAllDevices() throws SQLException { @@ -232,9 +264,11 @@ public class DataManager implements IdentityManager { } public Collection<Device> getDevices(long userId) throws SQLException { - return QueryBuilder.create(dataSource, getQuery("database.selectDevices")) - .setLong("userId", userId) - .executeQuery(Device.class); + Collection<Device> devices = new ArrayList<>(); + for (long id : Context.getPermissionsManager().getDevicePermissions(userId)) { + devices.add(getDeviceById(id)); + } + return devices; } public void addDevice(Device device) throws SQLException { @@ -286,6 +320,51 @@ public class DataManager implements IdentityManager { AsyncServlet.sessionRefreshUser(userId); } + public Collection<Group> getAllGroups() throws SQLException { + return QueryBuilder.create(dataSource, getQuery("database.selectGroupsAll")) + .executeQuery(Group.class); + } + + public Collection<Group> getGroups(long userId) throws SQLException { + Collection<Group> groups = new ArrayList<>(); + for (long id : Context.getPermissionsManager().getGroupPermissions(userId)) { + groups.add(getGroupById(id)); + } + return groups; + } + + public void addGroup(Group group) throws SQLException { + group.setId(QueryBuilder.create(dataSource, getQuery("database.insertGroup"), true) + .setObject(group) + .executeUpdate()); + } + + public void updateGroup(Group group) throws SQLException { + QueryBuilder.create(dataSource, getQuery("database.updateGroup")) + .setObject(group) + .executeUpdate(); + } + + public void removeGroup(long groupId) throws SQLException { + QueryBuilder.create(dataSource, getQuery("database.deleteGroup")) + .setLong("id", groupId) + .executeUpdate(); + } + + public void linkGroup(long userId, long groupId) throws SQLException { + QueryBuilder.create(dataSource, getQuery("database.linkGroup")) + .setLong("userId", userId) + .setLong("groupId", groupId) + .executeUpdate(); + } + + public void unlinkGroup(long userId, long groupId) throws SQLException { + QueryBuilder.create(dataSource, getQuery("database.unlinkGroup")) + .setLong("userId", userId) + .setLong("groupId", groupId) + .executeUpdate(); + } + public Collection<Position> getPositions(long deviceId, Date from, Date to) throws SQLException { return QueryBuilder.create(dataSource, getQuery("database.selectPositions")) .setLong("deviceId", deviceId) diff --git a/src/org/traccar/database/GroupTree.java b/src/org/traccar/database/GroupTree.java new file mode 100644 index 000000000..b383b1501 --- /dev/null +++ b/src/org/traccar/database/GroupTree.java @@ -0,0 +1,153 @@ +/* + * Copyright 2016 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.database; + +import org.traccar.model.Device; +import org.traccar.model.Group; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class GroupTree { + + private static class TreeNode { + + private Group group; + private Device device; + private Collection<TreeNode> children = new HashSet<>(); + + public TreeNode(Group group) { + this.group = group; + } + + public TreeNode(Device device) { + this.device = device; + } + + @Override + public int hashCode() { + if (group != null) { + return (int) group.getId(); + } else { + return (int) device.getId(); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TreeNode)) { + return false; + } + TreeNode other = (TreeNode) obj; + if (other == this) { + return true; + } + if (group != null) { + if (other.group != null) { + return group.getId() == other.group.getId(); + } + } else if (device != null) { + if (other.device != null) { + return device.getId() == other.device.getId(); + } + } + return false; + } + + public Group getGroup() { + return group; + } + + public Device getDevice() { + return device; + } + + public void setParent(TreeNode parent) { + if (parent != null) { + parent.children.add(this); + } + } + + public Collection<TreeNode> getChildren() { + return children; + } + + } + + private final Map<Long, TreeNode> groupMap = new HashMap<>(); + + public GroupTree(Collection<Group> groups, Collection<Device> devices) { + + for (Group group : groups) { + groupMap.put(group.getId(), new TreeNode(group)); + } + + for (TreeNode node : groupMap.values()) { + if (node.getGroup().getGroupId() != 0) { + node.setParent(groupMap.get(node.getGroup().getGroupId())); + } + } + + Map<Long, TreeNode> deviceMap = new HashMap<>(); + + for (Device device : devices) { + deviceMap.put(device.getId(), new TreeNode(device)); + } + + for (TreeNode node : deviceMap.values()) { + if (node.getDevice().getGroupId() != 0) { + node.setParent(groupMap.get(node.getDevice().getGroupId())); + } + } + + } + + public Collection<Group> getGroups(long groupId) { + Set<TreeNode> results = new HashSet<>(); + getNodes(results, groupMap.get(groupId)); + Collection<Group> groups = new ArrayList<>(); + for (TreeNode node : results) { + if (node.getGroup() != null) { + groups.add(node.getGroup()); + } + } + return groups; + } + + public Collection<Device> getDevices(long groupId) { + Set<TreeNode> results = new HashSet<>(); + getNodes(results, groupMap.get(groupId)); + Collection<Device> devices = new ArrayList<>(); + for (TreeNode node : results) { + if (node.getDevice() != null) { + devices.add(node.getDevice()); + } + } + return devices; + } + + private void getNodes(Set<TreeNode> results, TreeNode node) { + for (TreeNode child : node.getChildren()) { + results.add(child); + getNodes(results, child); + } + } + +} diff --git a/src/org/traccar/database/PermissionsManager.java b/src/org/traccar/database/PermissionsManager.java index c676dea23..dc37bbc84 100644 --- a/src/org/traccar/database/PermissionsManager.java +++ b/src/org/traccar/database/PermissionsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -16,13 +16,15 @@ package org.traccar.database; import java.sql.SQLException; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.traccar.helper.Log; -import org.traccar.model.Permission; +import org.traccar.model.Device; +import org.traccar.model.DevicePermission; +import org.traccar.model.Group; +import org.traccar.model.GroupPermission; import org.traccar.model.Server; import org.traccar.model.User; @@ -34,13 +36,21 @@ public class PermissionsManager { private final Map<Long, User> users = new HashMap<>(); - private final Map<Long, Set<Long>> permissions = new HashMap<>(); + private final Map<Long, Set<Long>> groupPermissions = new HashMap<>(); + private final Map<Long, Set<Long>> devicePermissions = new HashMap<>(); - private Set<Long> getPermissions(long userId) { - if (!permissions.containsKey(userId)) { - permissions.put(userId, new HashSet<Long>()); + public Set<Long> getGroupPermissions(long userId) { + if (!groupPermissions.containsKey(userId)) { + groupPermissions.put(userId, new HashSet<Long>()); } - return permissions.get(userId); + return groupPermissions.get(userId); + } + + public Set<Long> getDevicePermissions(long userId) { + if (!devicePermissions.containsKey(userId)) { + devicePermissions.put(userId, new HashSet<Long>()); + } + return devicePermissions.get(userId); } public PermissionsManager(DataManager dataManager) { @@ -50,15 +60,31 @@ public class PermissionsManager { public final void refresh() { users.clear(); - permissions.clear(); + groupPermissions.clear(); + devicePermissions.clear(); try { server = dataManager.getServer(); for (User user : dataManager.getUsers()) { users.put(user.getId(), user); } - for (Permission permission : dataManager.getPermissions()) { - getPermissions(permission.getUserId()).add(permission.getDeviceId()); + + GroupTree groupTree = new GroupTree(dataManager.getAllGroups(), dataManager.getAllDevices()); + for (GroupPermission permission : dataManager.getGroupPermissions()) { + Set<Long> userGroupPermissions = getGroupPermissions(permission.getUserId()); + Set<Long> userDevicePermissions = getDevicePermissions(permission.getUserId()); + userGroupPermissions.add(permission.getGroupId()); + for (Group group : groupTree.getGroups(permission.getGroupId())) { + userGroupPermissions.add(group.getId()); + } + for (Device device : groupTree.getDevices(permission.getGroupId())) { + userDevicePermissions.add(device.getId()); + } + } + + for (DevicePermission permission : dataManager.getDevicePermissions()) { + getDevicePermissions(permission.getUserId()).add(permission.getDeviceId()); } + } catch (SQLException error) { Log.warning(error); } @@ -80,12 +106,14 @@ public class PermissionsManager { } } - public Collection<Long> allowedDevices(long userId) { - return getPermissions(userId); + public void checkGroup(long userId, long groupId) throws SecurityException { + if (!getGroupPermissions(userId).contains(groupId)) { + throw new SecurityException("Group access denied"); + } } public void checkDevice(long userId, long deviceId) throws SecurityException { - if (!getPermissions(userId).contains(deviceId)) { + if (!getDevicePermissions(userId).contains(deviceId)) { throw new SecurityException("Device access denied"); } } diff --git a/src/org/traccar/helper/Checksum.java b/src/org/traccar/helper/Checksum.java index e6b6cd2c8..c156e53f7 100644 --- a/src/org/traccar/helper/Checksum.java +++ b/src/org/traccar/helper/Checksum.java @@ -158,13 +158,20 @@ public final class Checksum { public static String nmea(String msg) { int checksum = 0; - byte[] bytes = msg.getBytes(Charset.defaultCharset()); - for (int i = 1; i < msg.length(); i++) { - checksum ^= bytes[i]; + for (byte b : msg.getBytes(Charset.defaultCharset())) { + checksum ^= b; } return String.format("*%02x", checksum).toUpperCase(); } + public static String sum(String msg) { + byte checksum = 0; + for (byte b : msg.getBytes(Charset.defaultCharset())) { + checksum += b; + } + return String.format("%02X", checksum).toUpperCase(); + } + public static long luhn(long imei) { long checksum = 0; long remain = imei; diff --git a/src/org/traccar/helper/ObdDecoder.java b/src/org/traccar/helper/ObdDecoder.java index 35fa4dc07..c2d9b1841 100644 --- a/src/org/traccar/helper/ObdDecoder.java +++ b/src/org/traccar/helper/ObdDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -25,8 +25,9 @@ public final class ObdDecoder { private ObdDecoder() { } - private static final int MODE_CURRENT = 0x01; - private static final int MODE_FREEZE_FRAME = 0x02; + public static final int MODE_CURRENT = 0x01; + public static final int MODE_FREEZE_FRAME = 0x02; + public static final int MODE_CODES = 0x03; private static final int PID_ENGINE_LOAD = 0x04; private static final int PID_COOLANT_TEMPERATURE = 0x05; @@ -37,11 +38,13 @@ public final class ObdDecoder { private static final int PID_FUEL_LEVEL = 0x2F; private static final int PID_DISTANCE_CLEARED = 0x31; - public static Map.Entry<String, Object> decode(int mode, int pid, String value) { + public static Map.Entry<String, Object> decode(int mode, String value) { switch (mode) { case MODE_CURRENT: case MODE_FREEZE_FRAME: - return decodeData(pid, value); + return decodeData(Integer.parseInt(value.substring(0, 2), 16), value.substring(2)); + case MODE_CODES: + return decodeCodes(value); default: return null; } @@ -51,6 +54,30 @@ public final class ObdDecoder { return new AbstractMap.SimpleEntry<>(key, value); } + private static Map.Entry<String, Object> decodeCodes(String value) { + StringBuilder codes = new StringBuilder(); + for (int i = 0; i < value.length() / 4; i++) { + int numValue = Integer.parseInt(value.substring(i * 4, (i + 1) * 4), 16); + codes.append(','); + switch (numValue >> 14) { + case 1: + codes.append('C'); + break; + case 2: + codes.append('B'); + break; + case 3: + codes.append('U'); + break; + default: + codes.append('P'); + break; + } + codes.append(String.format("%04X", numValue & 0x3FFF)); + } + return createEntry("dtcs", codes.toString().replaceFirst(",", "")); + } + private static Map.Entry<String, Object> decodeData(int pid, String value) { int intValue = Integer.parseInt(value, 16); switch (pid) { @@ -69,7 +96,7 @@ public final class ObdDecoder { case PID_FUEL_LEVEL: return createEntry(Event.KEY_FUEL, intValue * 100 / 255); case PID_DISTANCE_CLEARED: - return createEntry(Event.KEY_FUEL, intValue); + return createEntry("cleared-distance", intValue); default: return null; } diff --git a/src/org/traccar/helper/Parser.java b/src/org/traccar/helper/Parser.java index a3f118df0..dcea1c8f7 100644 --- a/src/org/traccar/helper/Parser.java +++ b/src/org/traccar/helper/Parser.java @@ -37,12 +37,17 @@ public class Parser { return matcher.find(); } + public void skip(int number) { + position += number; + } + public boolean hasNext() { return hasNext(1); } public boolean hasNext(int number) { - if (matcher.group(position) != null) { + String value = matcher.group(position); + if (value != null && !value.isEmpty()) { return true; } else { position += number; diff --git a/src/org/traccar/model/Device.java b/src/org/traccar/model/Device.java index 7ca9fc843..934e753b1 100644 --- a/src/org/traccar/model/Device.java +++ b/src/org/traccar/model/Device.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2012 - 2016 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. @@ -91,4 +91,14 @@ public class Device { this.positionId = positionId; } + private long groupId; + + public long getGroupId() { + return groupId; + } + + public void setGroupId(long groupId) { + this.groupId = groupId; + } + } diff --git a/src/org/traccar/model/Permission.java b/src/org/traccar/model/DevicePermission.java index 393de2359..b3bc0cae0 100644 --- a/src/org/traccar/model/Permission.java +++ b/src/org/traccar/model/DevicePermission.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -15,7 +15,7 @@ */ package org.traccar.model; -public class Permission { +public class DevicePermission { private long userId; diff --git a/src/org/traccar/model/Event.java b/src/org/traccar/model/Event.java index a9f6f9204..221536530 100644 --- a/src/org/traccar/model/Event.java +++ b/src/org/traccar/model/Event.java @@ -29,6 +29,7 @@ public abstract class Event extends Extensible { public static final String KEY_ALARM = "alarm"; public static final String KEY_STATUS = "status"; public static final String KEY_ODOMETER = "odometer"; + public static final String KEY_HOURS = "hours"; public static final String KEY_INPUT = "input"; public static final String KEY_OUTPUT = "output"; public static final String KEY_POWER = "power"; diff --git a/src/org/traccar/model/Group.java b/src/org/traccar/model/Group.java new file mode 100644 index 000000000..00f2b2cfc --- /dev/null +++ b/src/org/traccar/model/Group.java @@ -0,0 +1,50 @@ +/* + * Copyright 2016 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.model; + +public class Group { + + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private long groupId; + + public long getGroupId() { + return groupId; + } + + public void setGroupId(long groupId) { + this.groupId = groupId; + } + +} diff --git a/src/org/traccar/model/GroupPermission.java b/src/org/traccar/model/GroupPermission.java new file mode 100644 index 000000000..9b0011575 --- /dev/null +++ b/src/org/traccar/model/GroupPermission.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 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.model; + +public class GroupPermission { + + private long userId; + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + private long groupId; + + public long getGroupId() { + return groupId; + } + + public void setGroupId(long groupId) { + this.groupId = groupId; + } + +} diff --git a/src/org/traccar/protocol/AplicomProtocolDecoder.java b/src/org/traccar/protocol/AplicomProtocolDecoder.java index 6ae6aa09f..a3d34cf3c 100644 --- a/src/org/traccar/protocol/AplicomProtocolDecoder.java +++ b/src/org/traccar/protocol/AplicomProtocolDecoder.java @@ -288,7 +288,7 @@ public class AplicomProtocolDecoder extends BaseProtocolDecoder { } if ((selector & 0x0200) != 0) { - buf.skipBytes(6); // button + position.set(Event.KEY_RFID, (((long) buf.readUnsignedShort()) << 32) + buf.readUnsignedInt()); } if ((selector & 0x0400) != 0) { diff --git a/src/org/traccar/protocol/AstraProtocol.java b/src/org/traccar/protocol/AstraProtocol.java new file mode 100644 index 000000000..d0b1d47a5 --- /dev/null +++ b/src/org/traccar/protocol/AstraProtocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 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.protocol; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder; +import org.traccar.BaseProtocol; +import org.traccar.TrackerServer; + +import java.util.List; + +public class AstraProtocol extends BaseProtocol { + + public AstraProtocol() { + super("astra"); + } + + @Override + public void initTrackerServers(List<TrackerServer> serverList) { + serverList.add(new TrackerServer(new ServerBootstrap(), this.getName()) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 1, 2, -3, 0)); + pipeline.addLast("objectDecoder", new AstraProtocolDecoder(AstraProtocol.this)); + } + }); + } + +} diff --git a/src/org/traccar/protocol/AstraProtocolDecoder.java b/src/org/traccar/protocol/AstraProtocolDecoder.java new file mode 100644 index 000000000..8cfec95fe --- /dev/null +++ b/src/org/traccar/protocol/AstraProtocolDecoder.java @@ -0,0 +1,119 @@ +/* + * Copyright 2016 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.protocol; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.helper.BitUtil; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.nio.charset.Charset; +import java.util.LinkedList; +import java.util.List; + +public class AstraProtocolDecoder extends BaseProtocolDecoder { + + public AstraProtocolDecoder(AstraProtocol protocol) { + super(protocol); + } + + public static final int MSG_HEARTBEAT = 0x1A; + public static final int MSG_DATA = 0x10; + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + ChannelBuffer buf = (ChannelBuffer) msg; + + if (channel != null) { + channel.write(ChannelBuffers.wrappedBuffer(new byte[] {0x06}), remoteAddress); + } + + buf.readUnsignedByte(); // protocol + buf.readUnsignedShort(); // length + + String imei = String.format("%08d", buf.readUnsignedInt()) + String.format("%07d", buf.readUnsignedMedium()); + if (!identify(imei, channel, remoteAddress)) { + return null; + } + + List<Position> positions = new LinkedList<>(); + + while (buf.readableBytes() > 2) { + + Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(getDeviceId()); + + buf.readUnsignedByte(); // index + + position.setLatitude(buf.readInt() * 0.000001); + position.setLongitude(buf.readInt() * 0.000001); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(1980, 1, 6).addMillis(buf.readUnsignedInt() * 1000L); + position.setTime(dateBuilder.getDate()); + + position.setSpeed(UnitsConverter.knotsFromKph(buf.readUnsignedByte() * 2)); + position.setCourse(buf.readUnsignedByte() * 2); + + int reason = buf.readUnsignedMedium(); + buf.readUnsignedShort(); // status + + position.set(Event.PREFIX_IO + 1, buf.readUnsignedByte()); + position.set(Event.PREFIX_ADC + 1, buf.readUnsignedByte()); + position.set(Event.KEY_BATTERY, buf.readUnsignedByte()); + position.set(Event.KEY_POWER, buf.readUnsignedByte()); + + buf.readUnsignedByte(); // max journey speed + buf.skipBytes(6); // accelerometer + buf.readUnsignedShort(); // journey distance + buf.readUnsignedShort(); // journey idle time + + position.setAltitude(buf.readUnsignedByte() * 20); + + int quality = buf.readUnsignedByte(); + position.set(Event.KEY_SATELLITES, quality & 0xf); + position.set(Event.KEY_GSM, quality >> 4); + + buf.readUnsignedByte(); // geofence events + + if (BitUtil.check(reason, 6) || BitUtil.check(reason, 7)) { + + position.set(Event.KEY_RFID, buf.readBytes(7).toString(Charset.defaultCharset())); + position.set(Event.KEY_ODOMETER, buf.readUnsignedMedium()); + + buf.readUnsignedShort(); // engine time + + } + + // extra data + + positions.add(position); + + } + + return positions; + } + +} diff --git a/src/org/traccar/protocol/BoxProtocol.java b/src/org/traccar/protocol/BoxProtocol.java index ae8207002..33cfca282 100644 --- a/src/org/traccar/protocol/BoxProtocol.java +++ b/src/org/traccar/protocol/BoxProtocol.java @@ -37,8 +37,8 @@ public class BoxProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '\r')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new BoxProtocolDecoder(BoxProtocol.this)); } }); diff --git a/src/org/traccar/protocol/CarscopProtocol.java b/src/org/traccar/protocol/CarscopProtocol.java index 1ac3e5e9f..4432fb87a 100644 --- a/src/org/traccar/protocol/CarscopProtocol.java +++ b/src/org/traccar/protocol/CarscopProtocol.java @@ -37,8 +37,8 @@ public class CarscopProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '^')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new CarscopProtocolDecoder(CarscopProtocol.this)); } }); diff --git a/src/org/traccar/protocol/CastelProtocolDecoder.java b/src/org/traccar/protocol/CastelProtocolDecoder.java index 3179cbd78..9f332dd04 100644 --- a/src/org/traccar/protocol/CastelProtocolDecoder.java +++ b/src/org/traccar/protocol/CastelProtocolDecoder.java @@ -109,6 +109,28 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder { } } + private void sendResponse( + Channel channel, SocketAddress remoteAddress, ChannelBuffer id, short type) { + + if (channel != null) { + int length = 2 + 2 + id.readableBytes() + 2 + 4 + 8 + 2 + 2; + + ChannelBuffer response = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, length); + response.writeByte('@'); response.writeByte('@'); + response.writeShort(length); + response.writeBytes(id); + response.writeShort(ChannelBuffers.swapShort(type)); + response.writeInt(0); + for (int i = 0; i < 8; i++) { + response.writeByte(0xff); + } + response.writeShort( + Checksum.crc16(Checksum.CRC16_X25, response.toByteBuffer(0, response.writerIndex()))); + response.writeByte(0x0D); response.writeByte(0x0A); + channel.write(response, remoteAddress); + } + } + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { @@ -134,23 +156,7 @@ public class CastelProtocolDecoder extends BaseProtocolDecoder { if (type == 0x2001) { - if (channel != null) { - int length = 2 + 2 + id.readableBytes() + 2 + 4 + 8 + 2 + 2; - - ChannelBuffer response = ChannelBuffers.directBuffer(ByteOrder.LITTLE_ENDIAN, length); - response.writeByte('@'); response.writeByte('@'); - response.writeShort(length); - response.writeBytes(id); - response.writeShort(ChannelBuffers.swapShort((short) 0x1001)); - response.writeInt(0); - for (int i = 0; i < 8; i++) { - response.writeByte(0xff); - } - response.writeShort( - Checksum.crc16(Checksum.CRC16_X25, response.toByteBuffer(0, response.writerIndex()))); - response.writeByte(0x0D); response.writeByte(0x0A); - channel.write(response, remoteAddress); - } + sendResponse(channel, remoteAddress, id, (short) 0x1001); buf.readUnsignedInt(); // index buf.readUnsignedInt(); // unix time diff --git a/src/org/traccar/protocol/CityeasyProtocol.java b/src/org/traccar/protocol/CityeasyProtocol.java index 1049e48eb..82302d2d7 100644 --- a/src/org/traccar/protocol/CityeasyProtocol.java +++ b/src/org/traccar/protocol/CityeasyProtocol.java @@ -41,8 +41,8 @@ public class CityeasyProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, -4, 0)); - pipeline.addLast("objectDecoder", new CityeasyProtocolDecoder(CityeasyProtocol.this)); pipeline.addLast("objectEncoder", new CityeasyProtocolEncoder()); + pipeline.addLast("objectDecoder", new CityeasyProtocolDecoder(CityeasyProtocol.this)); } }); } diff --git a/src/org/traccar/protocol/FlextrackProtocol.java b/src/org/traccar/protocol/FlextrackProtocol.java index 0d1b13d8a..3b1aaa3d8 100644 --- a/src/org/traccar/protocol/FlextrackProtocol.java +++ b/src/org/traccar/protocol/FlextrackProtocol.java @@ -36,8 +36,8 @@ public class FlextrackProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, "\r")); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new FlextrackProtocolDecoder(FlextrackProtocol.this)); } }); diff --git a/src/org/traccar/protocol/Gl100Protocol.java b/src/org/traccar/protocol/Gl100Protocol.java index cc2c826ab..f51834ea2 100644 --- a/src/org/traccar/protocol/Gl100Protocol.java +++ b/src/org/traccar/protocol/Gl100Protocol.java @@ -38,16 +38,16 @@ public class Gl100Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '\0')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Gl100ProtocolDecoder(Gl100Protocol.this)); } }); serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Gl100ProtocolDecoder(Gl100Protocol.this)); } }); diff --git a/src/org/traccar/protocol/Gl200Protocol.java b/src/org/traccar/protocol/Gl200Protocol.java index 3aa69567d..701652e12 100644 --- a/src/org/traccar/protocol/Gl200Protocol.java +++ b/src/org/traccar/protocol/Gl200Protocol.java @@ -44,19 +44,19 @@ public class Gl200Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, "$", "\0")); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); - pipeline.addLast("objectDecoder", new Gl200ProtocolDecoder(Gl200Protocol.this)); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectEncoder", new Gl200ProtocolEncoder()); + pipeline.addLast("objectDecoder", new Gl200ProtocolDecoder(Gl200Protocol.this)); } }); serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); - pipeline.addLast("objectDecoder", new Gl200ProtocolDecoder(Gl200Protocol.this)); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectEncoder", new Gl200ProtocolEncoder()); + pipeline.addLast("objectDecoder", new Gl200ProtocolDecoder(Gl200Protocol.this)); } }); } diff --git a/src/org/traccar/protocol/Gl200ProtocolDecoder.java b/src/org/traccar/protocol/Gl200ProtocolDecoder.java index e4bf43979..574f9a8c3 100644 --- a/src/org/traccar/protocol/Gl200ProtocolDecoder.java +++ b/src/org/traccar/protocol/Gl200ProtocolDecoder.java @@ -16,10 +16,13 @@ package org.traccar.protocol; import java.net.SocketAddress; +import java.util.LinkedList; import java.util.regex.Pattern; + import org.jboss.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.Context; +import org.traccar.helper.BitUtil; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -33,7 +36,7 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder { super(protocol); } - private static final Pattern PATTERN_HEARTBEAT = new PatternBuilder() + private static final Pattern PATTERN_HBD = new PatternBuilder() .text("+ACK:GTHBD,") .number("([0-9A-Z]{2}xxxx),") .any().text(",") @@ -68,115 +71,204 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder { .text("$").optional() .compile(); - private static final Pattern PATTERN = new PatternBuilder() + private static final Pattern PATTERN_LOCATION = new PatternBuilder() + .number("(?:d{1,2})?,") // gps accuracy + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3})?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6})?,") // longitude + .number("(-?d{1,2}.d{6})?,") // latitude + .number("(dddd)(dd)(dd)") // date + .number("(dd)(dd)(dd)").optional(2) // time + .text(",") .groupBegin() - .text("+").expression("(?:RESP|BUFF)").text(":") + .number("(0ddd)?,") // mcc + .number("(0ddd)?,") // mnc + .number("(xxxx)?,") // lac + .number("(xxxx)?,") // cell .or() - .binary("00?04,") - .number("xxxx,") - .expression("[01],") + .number("(d+)?,") // mcc + .number("(d+)?,") // mnc + .number("(d+)?,") // lac + .number("(d+)?,") // cell .groupEnd() - .expression("GT...,") - .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version - .expression("([^,]+),") // imei + .number("(?:d+|(d+.d))?,") // odometer + .compile(); - .groupBegin() - .expression("[0-9A-Z]{17},") // vin + private static final Pattern PATTERN_OBD = new PatternBuilder() + .text("+RESP:GTOBD,") + .number("[0-9A-Z]{2}xxxx,") // protocol version + .number("(d{15}),") // imei + .expression("(?:[0-9A-Z]{17})?,") // vin .expression("[^,]{0,20},") // device name .expression("[01],") // report type .number("x{1,8},") // report mask - .expression("[0-9A-Z]{17},") // vin + .expression("(?:[0-9A-Z]{17})?,") // vin .number("[01],") // obd connect - .number("d{1,5},") // obd voltage - .number("x{8},") // support pids - .number("(d{1,5}),") // engine rpm - .number("(d{1,3}),") // speed - .number("(-?d{1,3}),") // coolant temp + .number("(?:d{1,5})?,") // obd voltage + .number("(?:x{8})?,") // support pids + .number("(d{1,5})?,") // engine rpm + .number("(d{1,3})?,") // speed + .number("(-?d{1,3})?,") // coolant temp .number("(d+.?d*|Inf|NaN)?,") // fuel consumption - .number("(d{1,5}),") // dtcs cleared distance - .number("d{1,5},") - .expression("([01]),") // obd connect - .number("(d{1,3}),") // number of dtcs + .number("(d{1,5})?,") // dtcs cleared distance + .number("(?:d{1,5})?,") + .expression("([01])?,") // obd connect + .number("(d{1,3})?,") // number of dtcs .number("(x*),") // dtcs - .number("(d{1,3}),") // throttle - .number("d{1,3},") // engine load + .number("(d{1,3})?,") // throttle + .number("(?:d{1,3})?,") // engine load .number("(d{1,3})?,") // fuel level - .number("(d+)") // odometer - .or().any() - .groupEnd().text(",") - - .number("(d{1,2})?,") // gps accuracy - .number("(d{1,3}.d)?,") // speed - .number("(d{1,3})?,") // course - .number("(-?d{1,5}.d)?,") // altitude - .number("(-?d{1,3}.d{6})?,") // longitude - .number("(-?d{1,2}.d{6})?,") // latitude + .number("(d+),") // odometer + .expression(PATTERN_LOCATION.pattern()) + .number("(d{1,7}.d)?,") // odometer .number("(dddd)(dd)(dd)") // date .number("(dd)(dd)(dd)").optional(2) // time .text(",") - .number("(0ddd)?,") // mcc - .number("(0ddd)?,") // mnc - .number("(?:xxxx)?") - .number("(xxxx)").optional(2) // lac - .text(",") - .number("(xxxx)?,") // cell + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_FRI = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GTFRI,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei + .expression("[^,]*,") // device name + .number("(d+)?,") // power + .number("d{1,2},") // report type + .number("d{1,2},") // count + .expression("((?:") + .expression(PATTERN_LOCATION.pattern()) + .expression(")+)") .groupBegin() - .number("(d+.d)?,") // odometer + .number("(d{1,7}.d)?,").optional() // odometer .number("(d{1,3})?,") // battery - .groupEnd("?") + .or() + .number("(d{1,7}.d)?,") // odometer + .number("(d{5}:dd:dd)?,") // hour meter + .number("(x+)?,") // adc 1 + .number("(x+)?,") // adc 2 + .number("(d{1,3})?,") // battery + .number("(?:(xx)(xx)(xx))?,,,,") // device status + .groupEnd() + .number("(dddd)(dd)(dd)") // date + .number("(dd)(dd)(dd)").optional(2) // time + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF):GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}),") // imei + .expression("[^,]*,") // device name + .number("d*,") + .number("d{1,2},") // report type + .number("d{1,2},") // count + .expression(PATTERN_LOCATION.pattern()) .groupBegin() + .number("(d{1,7}.d)?,").optional() // odometer + .number("(d{1,3})?,") // battery + .or() + .number("(d{1,7}.d)?,") // odometer + .groupEnd() + .number("(dddd)(dd)(dd)") // date + .number("(dd)(dd)(dd)").optional(2) // time + .text(",") + .number("(xxxx)") // count number + .text("$").optional() + .compile(); + + private static final Pattern PATTERN_BASIC = new PatternBuilder() + .text("+").expression("(?:RESP|BUFF)").text(":") + .expression("GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .number("(d{15}|x{14}),") // imei .any() + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3})?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6}),") // longitude + .number("(-?d{1,2}.d{6}),") // latitude .number("(dddd)(dd)(dd)") // date .number("(dd)(dd)(dd)") // time - .or() + .text(",") .any() - .groupEnd() - .number(",(xxxx)") + .number("(xxxx)") // count number .text("$").optional() .compile(); - @Override - protected Object decode( - Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + private Object decodeHbd(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_HBD, sentence); + if (parser.matches() && channel != null) { + channel.write("+SACK:GTHBD," + parser.next() + "," + parser.next() + "$", remoteAddress); + } + return null; + } - String sentence = (String) msg; + private Object decodeInf(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_INF, sentence); + if (!parser.matches()) { + return null; + } - Parser parser = new Parser(PATTERN_HEARTBEAT, sentence); - if (parser.matches()) { - if (channel != null) { - channel.write("+SACK:GTHBD," + parser.next() + "," + parser.next() + "$", remoteAddress); - } + Position position = new Position(); + position.setProtocol(getProtocolName()); + + if (!identify(parser.next(), channel, remoteAddress)) { return null; } + position.setDeviceId(getDeviceId()); - parser = new Parser(PATTERN_INF, sentence); - if (parser.matches()) { + position.set(Event.KEY_STATUS, parser.next()); + position.set(Event.KEY_POWER, parser.next()); + position.set(Event.KEY_BATTERY, parser.next()); + position.set(Event.KEY_CHARGE, parser.next()); - Position position = new Position(); - position.setProtocol(getProtocolName()); + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); - if (!identify(parser.next(), channel, remoteAddress)) { - return null; - } - position.setDeviceId(getDeviceId()); + getLastLocation(position, dateBuilder.getDate()); + + position.set(Event.KEY_INDEX, parser.next()); - position.set(Event.KEY_STATUS, parser.next()); - position.set(Event.KEY_POWER, parser.next()); - position.set(Event.KEY_BATTERY, parser.next()); - position.set(Event.KEY_CHARGE, parser.next()); + return position; + } + + private void decodeLocation(Position position, Parser parser) { + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + position.setAltitude(parser.nextDouble()); + + if (parser.hasNext(8)) { + position.setValid(true); + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); DateBuilder dateBuilder = new DateBuilder() .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + } else { + getLastLocation(position, null); + } - getLastLocation(position, dateBuilder.getDate()); + if (parser.hasNext(4)) { + position.set(Event.KEY_MCC, parser.nextInt()); + position.set(Event.KEY_MNC, parser.nextInt()); + position.set(Event.KEY_LAC, parser.nextInt(16)); + position.set(Event.KEY_CID, parser.nextInt(16)); + } - position.set(Event.KEY_INDEX, parser.next()); + parser.skip(4); // alternative networks - return position; - } + position.set(Event.KEY_ODOMETER, parser.next()); + } - parser = new Parser(PATTERN, sentence); + private Object decodeObd(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_OBD, sentence); if (!parser.matches()) { return null; } @@ -189,12 +281,6 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder { } position.setDeviceId(getDeviceId()); - // RFID - if (sentence.startsWith("+RESP:GTIDA")) { - position.set(Event.KEY_RFID, sentence.split(",")[5]); - } - - // OBD position.set(Event.KEY_RPM, parser.next()); position.set(Event.KEY_OBD_SPEED, parser.next()); position.set(Event.PREFIX_TEMP + 1, parser.next()); @@ -207,32 +293,111 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder { position.set(Event.KEY_FUEL, parser.next()); position.set(Event.KEY_OBD_ODOMETER, parser.next()); - if (parser.hasNext(12)) { - position.setValid(parser.nextInt() < 20); - position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); - position.setCourse(parser.nextDouble()); - position.setAltitude(parser.nextDouble()); - position.setLongitude(parser.nextDouble()); - position.setLatitude(parser.nextDouble()); + decodeLocation(position, parser); + position.set(Event.KEY_ODOMETER, parser.next()); + + if (parser.hasNext(6)) { DateBuilder dateBuilder = new DateBuilder() .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); - position.setTime(dateBuilder.getDate()); - } else { - getLastLocation(position, null); + if (!position.getOutdated() && position.getFixTime().after(dateBuilder.getDate())) { + position.setTime(dateBuilder.getDate()); + } } - if (parser.hasNext(4)) { - position.set(Event.KEY_MCC, parser.nextInt()); - position.set(Event.KEY_MNC, parser.nextInt()); - position.set(Event.KEY_LAC, parser.nextInt(16)); - position.set(Event.KEY_CID, parser.nextInt(16)); + return position; + } + + private Object decodeFri(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_FRI, sentence); + if (!parser.matches()) { + return null; + } + + LinkedList<Position> positions = new LinkedList<>(); + + if (!identify(parser.next(), channel, remoteAddress)) { + return null; + } + + int power = parser.nextInt(); + + Parser itemParser = new Parser(PATTERN_LOCATION, parser.next()); + while (itemParser.find()) { + Position position = new Position(); + position.setProtocol(getProtocolName()); + position.setDeviceId(getDeviceId()); + + decodeLocation(position, itemParser); + + positions.add(position); + } + + Position position = positions.getLast(); + + decodeLocation(position, parser); + + // power value only on some devices + if (power > 10) { + position.set(Event.KEY_POWER, power); } position.set(Event.KEY_ODOMETER, parser.next()); position.set(Event.KEY_BATTERY, parser.next()); + position.set(Event.KEY_ODOMETER, parser.next()); + position.set(Event.KEY_HOURS, parser.next()); + position.set(Event.PREFIX_ADC + 1, parser.next()); + position.set(Event.PREFIX_ADC + 2, parser.next()); + position.set(Event.KEY_BATTERY, parser.next()); + + if (parser.hasNext(3)) { + int ignition = parser.nextInt(16); + if (BitUtil.check(ignition, 4)) { + position.set(Event.KEY_IGNITION, false); + } else if (BitUtil.check(ignition, 5)) { + position.set(Event.KEY_IGNITION, true); + } + position.set(Event.KEY_INPUT, parser.nextInt(16)); + position.set(Event.KEY_OUTPUT, parser.nextInt(16)); + } + + // workaround for wrong location time + if (parser.hasNext(6)) { + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + if (!position.getOutdated() && position.getFixTime().after(dateBuilder.getDate())) { + position.setTime(dateBuilder.getDate()); + } + } + + return positions; + } + + private Object decodeOther(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(); + position.setProtocol(getProtocolName()); + + if (!identify(parser.next(), channel, remoteAddress)) { + return null; + } + position.setDeviceId(getDeviceId()); + + decodeLocation(position, parser); + + position.set(Event.KEY_ODOMETER, parser.next()); + position.set(Event.KEY_BATTERY, parser.next()); + + position.set(Event.KEY_ODOMETER, parser.next()); + + // workaround for wrong location time if (parser.hasNext(6)) { DateBuilder dateBuilder = new DateBuilder() .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) @@ -249,4 +414,71 @@ public class Gl200ProtocolDecoder extends BaseProtocolDecoder { return position; } + private Object decodeBasic(Channel channel, SocketAddress remoteAddress, String sentence) { + Parser parser = new Parser(PATTERN_BASIC, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(); + position.setProtocol(getProtocolName()); + + if (!identify(parser.next(), channel, remoteAddress)) { + return null; + } + position.setDeviceId(getDeviceId()); + + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + position.setAltitude(parser.nextDouble()); + + position.setValid(true); + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + return position; + } + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + String sentence = (String) msg; + + int typeIndex = sentence.indexOf(":GT"); + if (typeIndex < 0) { + return null; + } + + Object result; + switch (sentence.substring(typeIndex + 3, typeIndex + 6)) { + case "HBD": + result = decodeHbd(channel, remoteAddress, sentence); + break; + case "INF": + result = decodeInf(channel, remoteAddress, sentence); + break; + case "OBD": + result = decodeObd(channel, remoteAddress, sentence); + break; + case "FRI": + result = decodeFri(channel, remoteAddress, sentence); + break; + default: + result = decodeOther(channel, remoteAddress, sentence); + break; + } + + if (result == null) { + result = decodeBasic(channel, remoteAddress, sentence); + } + + return result; + } + } diff --git a/src/org/traccar/protocol/GlobalSatProtocol.java b/src/org/traccar/protocol/GlobalSatProtocol.java index 10db8e313..592ba152e 100644 --- a/src/org/traccar/protocol/GlobalSatProtocol.java +++ b/src/org/traccar/protocol/GlobalSatProtocol.java @@ -37,8 +37,8 @@ public class GlobalSatProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '!')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new GlobalSatProtocolDecoder(GlobalSatProtocol.this)); } }); diff --git a/src/org/traccar/protocol/GoSafeProtocol.java b/src/org/traccar/protocol/GoSafeProtocol.java index af2c2858d..bc1ed14de 100644 --- a/src/org/traccar/protocol/GoSafeProtocol.java +++ b/src/org/traccar/protocol/GoSafeProtocol.java @@ -36,8 +36,8 @@ public class GoSafeProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '#')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new GoSafeProtocolDecoder(GoSafeProtocol.this)); } }); diff --git a/src/org/traccar/protocol/Gps103Protocol.java b/src/org/traccar/protocol/Gps103Protocol.java index 6ddc2dea1..d8ad0a8b7 100644 --- a/src/org/traccar/protocol/Gps103Protocol.java +++ b/src/org/traccar/protocol/Gps103Protocol.java @@ -47,19 +47,19 @@ public class Gps103Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, "\r\n", "\n", ";")); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); - pipeline.addLast("objectDecoder", new Gps103ProtocolDecoder(Gps103Protocol.this)); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectEncoder", new Gps103ProtocolEncoder()); + pipeline.addLast("objectDecoder", new Gps103ProtocolDecoder(Gps103Protocol.this)); } }); serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); - pipeline.addLast("objectDecoder", new Gps103ProtocolDecoder(Gps103Protocol.this)); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectEncoder", new Gps103ProtocolEncoder()); + pipeline.addLast("objectDecoder", new Gps103ProtocolDecoder(Gps103Protocol.this)); } }); } diff --git a/src/org/traccar/protocol/Gps103ProtocolDecoder.java b/src/org/traccar/protocol/Gps103ProtocolDecoder.java index a45e20ba2..e2aed6309 100644 --- a/src/org/traccar/protocol/Gps103ProtocolDecoder.java +++ b/src/org/traccar/protocol/Gps103ProtocolDecoder.java @@ -118,11 +118,16 @@ public class Gps103ProtocolDecoder extends BaseProtocolDecoder { } // Send response #2 - if (sentence.length() == 15 && Character.isDigit(sentence.charAt(0))) { + if (Character.isDigit(sentence.charAt(0))) { if (channel != null) { channel.write("ON", remoteAddress); } - return null; + int start = sentence.indexOf("imei:"); + if (start >= 0) { + sentence = sentence.substring(start); + } else { + return null; + } } Position position = new Position(); diff --git a/src/org/traccar/protocol/GpsGateProtocol.java b/src/org/traccar/protocol/GpsGateProtocol.java index 9f07550e4..c654810b1 100644 --- a/src/org/traccar/protocol/GpsGateProtocol.java +++ b/src/org/traccar/protocol/GpsGateProtocol.java @@ -37,8 +37,8 @@ public class GpsGateProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new GpsGateProtocolDecoder(GpsGateProtocol.this)); } }); diff --git a/src/org/traccar/protocol/GpsGateProtocolDecoder.java b/src/org/traccar/protocol/GpsGateProtocolDecoder.java index 421dc5304..b48fa6266 100644 --- a/src/org/traccar/protocol/GpsGateProtocolDecoder.java +++ b/src/org/traccar/protocol/GpsGateProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2013 - 2016 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. @@ -17,6 +17,7 @@ package org.traccar.protocol; import java.net.SocketAddress; import java.util.regex.Pattern; + import org.jboss.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; import org.traccar.helper.Checksum; @@ -31,7 +32,7 @@ public class GpsGateProtocolDecoder extends BaseProtocolDecoder { super(protocol); } - private static final Pattern PATTERN = new PatternBuilder() + private static final Pattern PATTERN_GPRMC = new PatternBuilder() .text("$GPRMC,") .number("(dd)(dd)(dd).?(d+)?,") // time .expression("([AV]),") // validity @@ -45,6 +46,24 @@ public class GpsGateProtocolDecoder extends BaseProtocolDecoder { .any() .compile(); + private static final Pattern PATTERN_FRCMD = new PatternBuilder() + .text("$FRCMD,") + .number("(d+),") // imei + .expression("[^,]*,") // command + .expression("[^,]*,") + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*),") // altitude + .number("(d+.?d*),") // speed + .number("(d+.?d*),") // course + .number("(dd)(dd)(dd),") // date (ddmmyy) + .number("(dd)(dd)(dd).?(d+)?,") // time + .expression("([01]),") // validity + .any() + .compile(); + private void send(Channel channel, String message) { if (channel != null) { channel.write(message + Checksum.nmea(message) + "\r\n"); @@ -85,10 +104,10 @@ public class GpsGateProtocolDecoder extends BaseProtocolDecoder { // Version check send(channel, "$FRVER,1,0,GpsGate Server 1.0"); - } else if (sentence.startsWith("$GPRMC,") && hasDeviceId()) { + } else if (sentence.startsWith("$GPRMC,")) { - Parser parser = new Parser(PATTERN, sentence); - if (!parser.matches()) { + Parser parser = new Parser(PATTERN_GPRMC, sentence); + if (!parser.matches() || !hasDeviceId()) { return null; } @@ -109,6 +128,37 @@ public class GpsGateProtocolDecoder extends BaseProtocolDecoder { position.setTime(dateBuilder.getDate()); return position; + + } else if (sentence.startsWith("$FRCMD,")) { + + Parser parser = new Parser(PATTERN_FRCMD, sentence); + if (!parser.matches()) { + return null; + } + + Position position = new Position(); + position.setProtocol(getProtocolName()); + + if (!identify(parser.next(), channel, remoteAddress)) { + return null; + } + position.setDeviceId(getDeviceId()); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setAltitude(parser.nextDouble()); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + DateBuilder dateBuilder = new DateBuilder() + .setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()) + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + position.setValid(parser.next().equals("1")); + + return position; + } return null; diff --git a/src/org/traccar/protocol/GpsmtaProtocol.java b/src/org/traccar/protocol/GpsmtaProtocol.java index fe586644a..d1eaa2fd3 100644 --- a/src/org/traccar/protocol/GpsmtaProtocol.java +++ b/src/org/traccar/protocol/GpsmtaProtocol.java @@ -35,8 +35,8 @@ public class GpsmtaProtocol extends BaseProtocol { serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new GpsmtaProtocolDecoder(GpsmtaProtocol.this)); } }); diff --git a/src/org/traccar/protocol/Gt06FrameDecoder.java b/src/org/traccar/protocol/Gt06FrameDecoder.java index ed0d8d548..67f42efb4 100644 --- a/src/org/traccar/protocol/Gt06FrameDecoder.java +++ b/src/org/traccar/protocol/Gt06FrameDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2014 - 2016 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. @@ -24,9 +24,7 @@ public class Gt06FrameDecoder extends FrameDecoder { @Override protected Object decode( - ChannelHandlerContext ctx, - Channel channel, - ChannelBuffer buf) throws Exception { + ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception { // Check minimum length if (buf.readableBytes() < 5) { @@ -37,6 +35,12 @@ public class Gt06FrameDecoder extends FrameDecoder { if (buf.getByte(buf.readerIndex()) == 0x78) { length += 1 + buf.getUnsignedByte(buf.readerIndex() + 2); + + int type = buf.getUnsignedByte(buf.readerIndex() + 3); + if (type == Gt06ProtocolDecoder.MSG_STATUS && length == 13) { + length += 2; // workaround for #1727 + } + } else { length += 2 + buf.getUnsignedShort(buf.readerIndex() + 2); } diff --git a/src/org/traccar/protocol/Gt06Protocol.java b/src/org/traccar/protocol/Gt06Protocol.java index eac9ad71a..4630342a5 100644 --- a/src/org/traccar/protocol/Gt06Protocol.java +++ b/src/org/traccar/protocol/Gt06Protocol.java @@ -37,8 +37,8 @@ public class Gt06Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new Gt06FrameDecoder()); - pipeline.addLast("objectDecoder", new Gt06ProtocolDecoder(Gt06Protocol.this)); pipeline.addLast("objectEncoder", new Gt06ProtocolEncoder()); + pipeline.addLast("objectDecoder", new Gt06ProtocolDecoder(Gt06Protocol.this)); } }); } diff --git a/src/org/traccar/protocol/HomtecsProtocol.java b/src/org/traccar/protocol/HomtecsProtocol.java new file mode 100644 index 000000000..1a2e98565 --- /dev/null +++ b/src/org/traccar/protocol/HomtecsProtocol.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 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.protocol; + +import org.jboss.netty.bootstrap.ConnectionlessBootstrap; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.TrackerServer; + +import java.util.List; + +public class HomtecsProtocol extends BaseProtocol { + + public HomtecsProtocol() { + super("homtecs"); + } + + @Override + public void initTrackerServers(List<TrackerServer> serverList) { + serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("stringDecoder", new StringDecoder()); + pipeline.addLast("objectDecoder", new HomtecsProtocolDecoder(HomtecsProtocol.this)); + } + }); + } + +} diff --git a/src/org/traccar/protocol/HomtecsProtocolDecoder.java b/src/org/traccar/protocol/HomtecsProtocolDecoder.java new file mode 100644 index 000000000..0b1cd99e9 --- /dev/null +++ b/src/org/traccar/protocol/HomtecsProtocolDecoder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 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.protocol; + +import org.jboss.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class HomtecsProtocolDecoder extends BaseProtocolDecoder { + + public HomtecsProtocolDecoder(HomtecsProtocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .expression("([^,]+),") // id + .number("(dd)(dd)(dd),") // date + .number("(dd)(dd)(dd).(d+),") // time + .number("(d+),") // satellites + .number("(dd)(dd.d+),") // latitude + .expression("([NS]),") + .number("(ddd)(dd.d+),") // longitude + .expression("([EW]),") + .number("(d+.?d*)?,") // speed + .number("(d+.?d*)?,") // course + .any() + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(); + position.setProtocol(getProtocolName()); + + if (!identify(parser.next(), channel, remoteAddress)) { + return null; + } + position.setDeviceId(getDeviceId()); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt(), parser.nextInt()); + + position.setTime(dateBuilder.getDate()); + + position.setValid(true); + position.set(Event.KEY_SATELLITES, parser.nextInt()); + + position.setLatitude(parser.nextCoordinate()); + position.setLongitude(parser.nextCoordinate()); + position.setSpeed(parser.nextDouble()); + position.setCourse(parser.nextDouble()); + + return position; + } + +} diff --git a/src/org/traccar/protocol/IntellitracProtocol.java b/src/org/traccar/protocol/IntellitracProtocol.java index c06585547..a6caf770f 100644 --- a/src/org/traccar/protocol/IntellitracProtocol.java +++ b/src/org/traccar/protocol/IntellitracProtocol.java @@ -36,8 +36,8 @@ public class IntellitracProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new IntellitracFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new IntellitracProtocolDecoder(IntellitracProtocol.this)); } }); diff --git a/src/org/traccar/protocol/KenjiProtocolDecoder.java b/src/org/traccar/protocol/KenjiProtocolDecoder.java index e1f8130fa..e190af948 100755 --- a/src/org/traccar/protocol/KenjiProtocolDecoder.java +++ b/src/org/traccar/protocol/KenjiProtocolDecoder.java @@ -47,7 +47,7 @@ public class KenjiProtocolDecoder extends BaseProtocolDecoder { .number("G(d+)") // satellites .any() .compile(); - + @Override protected Object decode( Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { diff --git a/src/org/traccar/protocol/KhdProtocol.java b/src/org/traccar/protocol/KhdProtocol.java index a2edbfd5e..93edb0415 100644 --- a/src/org/traccar/protocol/KhdProtocol.java +++ b/src/org/traccar/protocol/KhdProtocol.java @@ -39,8 +39,8 @@ public class KhdProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(512, 3, 2)); - pipeline.addLast("objectDecoder", new KhdProtocolDecoder(KhdProtocol.this)); pipeline.addLast("objectEncoder", new KhdProtocolEncoder()); + pipeline.addLast("objectDecoder", new KhdProtocolDecoder(KhdProtocol.this)); } }); } diff --git a/src/org/traccar/protocol/LaipacProtocol.java b/src/org/traccar/protocol/LaipacProtocol.java index fff7b1c32..c2e5c585e 100644 --- a/src/org/traccar/protocol/LaipacProtocol.java +++ b/src/org/traccar/protocol/LaipacProtocol.java @@ -37,8 +37,8 @@ public class LaipacProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new LaipacProtocolDecoder(LaipacProtocol.this)); } }); diff --git a/src/org/traccar/protocol/ManPowerProtocol.java b/src/org/traccar/protocol/ManPowerProtocol.java index 4fc194963..647a1bea7 100644 --- a/src/org/traccar/protocol/ManPowerProtocol.java +++ b/src/org/traccar/protocol/ManPowerProtocol.java @@ -37,8 +37,8 @@ public class ManPowerProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, ';')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new ManPowerProtocolDecoder(ManPowerProtocol.this)); } }); diff --git a/src/org/traccar/protocol/MegastekProtocol.java b/src/org/traccar/protocol/MegastekProtocol.java index cdf891afe..f61a4443c 100644 --- a/src/org/traccar/protocol/MegastekProtocol.java +++ b/src/org/traccar/protocol/MegastekProtocol.java @@ -36,8 +36,8 @@ public class MegastekProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new MegastekFrameDecoder()); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new MegastekProtocolDecoder(MegastekProtocol.this)); } }); diff --git a/src/org/traccar/protocol/MeiligaoProtocol.java b/src/org/traccar/protocol/MeiligaoProtocol.java index 05dd8834b..2e4226e19 100644 --- a/src/org/traccar/protocol/MeiligaoProtocol.java +++ b/src/org/traccar/protocol/MeiligaoProtocol.java @@ -43,15 +43,15 @@ public class MeiligaoProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new MeiligaoFrameDecoder()); - pipeline.addLast("objectDecoder", new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); pipeline.addLast("objectEncoder", new MeiligaoProtocolEncoder()); + pipeline.addLast("objectDecoder", new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); } }); serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("objectDecoder", new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); pipeline.addLast("objectEncoder", new MeiligaoProtocolEncoder()); + pipeline.addLast("objectDecoder", new MeiligaoProtocolDecoder(MeiligaoProtocol.this)); } }); } diff --git a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java index cb2139a00..5c17aab61 100644 --- a/src/org/traccar/protocol/MeiligaoProtocolDecoder.java +++ b/src/org/traccar/protocol/MeiligaoProtocolDecoder.java @@ -54,23 +54,19 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { .number("|(xxxx)?") // state .groupBegin() .number("|(xxxx),(xxxx)") // adc + .number("(?:,(xxxx),(xxxx),(xxxx),(xxxx),(xxxx),(xxxx))?") .groupBegin() - .number(",(xxxx),(xxxx),(xxxx),(xxxx),(xxxx),(xxxx)") - .groupEnd("?") - .groupBegin() - .text("|") - .groupBegin() - .number("(x{16})") // cell + .number("|x{16}") // cell .number("|(xx)") // gsm - .number("|(x{8})|") // odometer - .number("(x{9})") // odometer + .number("|(x{8})") // odometer + .or() + .number("|(x{9})") // odometer .groupBegin() .number("|(x{5,})") // rfid .groupEnd("?") .groupEnd("?") .groupEnd("?") .groupEnd("?") - .groupEnd("?") .any() .compile(); @@ -283,15 +279,14 @@ public class MeiligaoProtocolDecoder extends BaseProtocolDecoder { } } - position.set(Event.KEY_CID, parser.next()); - if (parser.hasNext()) { position.set(Event.KEY_GSM, parser.nextInt(16)); } if (parser.hasNext()) { position.set(Event.KEY_ODOMETER, parser.nextInt(16)); - } else if (parser.hasNext()) { + } + if (parser.hasNext()) { position.set(Event.KEY_ODOMETER, parser.nextInt(16)); } diff --git a/src/org/traccar/protocol/MeitrackProtocol.java b/src/org/traccar/protocol/MeitrackProtocol.java index c957d4ea1..e5b5ddf2b 100644 --- a/src/org/traccar/protocol/MeitrackProtocol.java +++ b/src/org/traccar/protocol/MeitrackProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -31,10 +31,12 @@ public class MeitrackProtocol extends BaseProtocol { public MeitrackProtocol() { super("meitrack"); setSupportedCommands( + Command.TYPE_POSITION_SINGLE, Command.TYPE_ENGINE_STOP, Command.TYPE_ENGINE_RESUME, Command.TYPE_ALARM_ARM, - Command.TYPE_ALARM_DISARM); + Command.TYPE_ALARM_DISARM, + Command.TYPE_REQUEST_PHOTO); } @Override @@ -44,6 +46,7 @@ public class MeitrackProtocol extends BaseProtocol { protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new MeitrackFrameDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("objectEncoder", new MeitrackProtocolEncoder()); pipeline.addLast("objectDecoder", new MeitrackProtocolDecoder(MeitrackProtocol.this)); } }; @@ -53,6 +56,7 @@ public class MeitrackProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("objectEncoder", new MeitrackProtocolEncoder()); pipeline.addLast("objectDecoder", new MeitrackProtocolDecoder(MeitrackProtocol.this)); } }; diff --git a/src/org/traccar/protocol/MeitrackProtocolDecoder.java b/src/org/traccar/protocol/MeitrackProtocolDecoder.java index e0ca01412..a920c6e76 100644 --- a/src/org/traccar/protocol/MeitrackProtocolDecoder.java +++ b/src/org/traccar/protocol/MeitrackProtocolDecoder.java @@ -24,6 +24,7 @@ import java.util.regex.Pattern; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.channel.Channel; import org.traccar.BaseProtocolDecoder; +import org.traccar.Context; import org.traccar.helper.DateBuilder; import org.traccar.helper.Parser; import org.traccar.helper.PatternBuilder; @@ -237,10 +238,17 @@ public class MeitrackProtocolDecoder extends BaseProtocolDecoder { index = buf.indexOf(index + 1, buf.writerIndex(), (byte) ','); String type = buf.toString(index + 1, 3, Charset.defaultCharset()); - if (type.equals("CCC")) { - return decodeBinaryMessage(channel, remoteAddress, buf); - } else { - return decodeRegularMessage(channel, remoteAddress, buf); + switch (type) { + case "D03": + if (channel != null) { + String imei = Context.getIdentityManager().getDeviceById(getDeviceId()).getUniqueId(); + channel.write("@@O46," + imei + ",D00,camera_picture.jpg,0*00\r\n"); + } + return null; + case "CCC": + return decodeBinaryMessage(channel, remoteAddress, buf); + default: + return decodeRegularMessage(channel, remoteAddress, buf); } } diff --git a/src/org/traccar/protocol/MeitrackProtocolEncoder.java b/src/org/traccar/protocol/MeitrackProtocolEncoder.java index bfda2b7d2..381935c58 100644 --- a/src/org/traccar/protocol/MeitrackProtocolEncoder.java +++ b/src/org/traccar/protocol/MeitrackProtocolEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -16,23 +16,36 @@ package org.traccar.protocol; import org.traccar.StringProtocolEncoder; +import org.traccar.helper.Checksum; import org.traccar.helper.Log; import org.traccar.model.Command; public class MeitrackProtocolEncoder extends StringProtocolEncoder { + private Object formatCommand(Command command, char dataId, String content) { + String uniqueId = getUniqueId(command.getDeviceId()); + int length = 1 + uniqueId.length() + 1 + content.length() + 5; + String result = String.format("@@%c%02d,%s,%s*", dataId, length, uniqueId, content); + result += Checksum.sum(result) + "\r\n"; + return result; + } + @Override protected Object encodeCommand(Command command) { switch (command.getType()) { + case Command.TYPE_POSITION_SINGLE: + return formatCommand(command, 'Q', "A10"); case Command.TYPE_ENGINE_STOP: - return formatCommand(command, "@@M33,{%s},C01,0,12222*18\r\n", Command.KEY_UNIQUE_ID); + return formatCommand(command, 'M', "C01,0,12222"); case Command.TYPE_ENGINE_RESUME: - return formatCommand(command, "@@M33,{%s},C01,0,02222*18\r\n", Command.KEY_UNIQUE_ID); + return formatCommand(command, 'M', "C01,0,02222"); case Command.TYPE_ALARM_ARM: - return formatCommand(command, "@@M33,{%s},C01,0,22122*18\r\n", Command.KEY_UNIQUE_ID); + return formatCommand(command, 'M', "C01,0,22122"); case Command.TYPE_ALARM_DISARM: - return formatCommand(command, "@@M33,{%s},C01,0,22022*18\r\n", Command.KEY_UNIQUE_ID); + return formatCommand(command, 'M', "C01,0,22022"); + case Command.TYPE_REQUEST_PHOTO: + return formatCommand(command, 'D', "D03,1,camera_picture.jpg"); default: Log.warning(new UnsupportedOperationException(command.getType())); break; diff --git a/src/org/traccar/protocol/Mta6Protocol.java b/src/org/traccar/protocol/Mta6Protocol.java index 8232b850c..607bf8118 100644 --- a/src/org/traccar/protocol/Mta6Protocol.java +++ b/src/org/traccar/protocol/Mta6Protocol.java @@ -35,8 +35,8 @@ public class Mta6Protocol extends BaseProtocol { serverList.add(new TrackerServer(new ServerBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("httpEncoder", new HttpResponseEncoder()); + pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("objectDecoder", new Mta6ProtocolDecoder( Mta6Protocol.this, !Context.getConfig().getBoolean(getName() + ".can"))); } diff --git a/src/org/traccar/protocol/MtxProtocol.java b/src/org/traccar/protocol/MtxProtocol.java index 4fba981a5..e0bef8350 100644 --- a/src/org/traccar/protocol/MtxProtocol.java +++ b/src/org/traccar/protocol/MtxProtocol.java @@ -37,8 +37,8 @@ public class MtxProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new MtxProtocolDecoder(MtxProtocol.this)); } }); diff --git a/src/org/traccar/protocol/MxtProtocolDecoder.java b/src/org/traccar/protocol/MxtProtocolDecoder.java index fedb7c715..53d5b8b09 100644 --- a/src/org/traccar/protocol/MxtProtocolDecoder.java +++ b/src/org/traccar/protocol/MxtProtocolDecoder.java @@ -114,7 +114,7 @@ public class MxtProtocolDecoder extends BaseProtocolDecoder { } if (BitUtil.check(infoGroups, 4)) { - position.set("hours", buf.readUnsignedInt()); + position.set(Event.KEY_HOURS, buf.readUnsignedInt()); } if (BitUtil.check(infoGroups, 5)) { diff --git a/src/org/traccar/protocol/OsmAndProtocol.java b/src/org/traccar/protocol/OsmAndProtocol.java index 1e60ae807..3b473c435 100644 --- a/src/org/traccar/protocol/OsmAndProtocol.java +++ b/src/org/traccar/protocol/OsmAndProtocol.java @@ -35,8 +35,8 @@ public class OsmAndProtocol extends BaseProtocol { serverList.add(new TrackerServer(new ServerBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("httpEncoder", new HttpResponseEncoder()); + pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("objectDecoder", new OsmAndProtocolDecoder(OsmAndProtocol.this)); } }); diff --git a/src/org/traccar/protocol/PathAwayProtocol.java b/src/org/traccar/protocol/PathAwayProtocol.java index dfa34abd5..d7efdfaf6 100644 --- a/src/org/traccar/protocol/PathAwayProtocol.java +++ b/src/org/traccar/protocol/PathAwayProtocol.java @@ -35,8 +35,8 @@ public class PathAwayProtocol extends BaseProtocol { serverList.add(new TrackerServer(new ServerBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("httpEncoder", new HttpResponseEncoder()); + pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("objectDecoder", new PathAwayProtocolDecoder(PathAwayProtocol.this)); } }); diff --git a/src/org/traccar/protocol/PiligrimProtocol.java b/src/org/traccar/protocol/PiligrimProtocol.java index 0478835d9..11aafa412 100644 --- a/src/org/traccar/protocol/PiligrimProtocol.java +++ b/src/org/traccar/protocol/PiligrimProtocol.java @@ -36,9 +36,9 @@ public class PiligrimProtocol extends BaseProtocol { serverList.add(new TrackerServer(new ServerBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("httpEncoder", new HttpResponseEncoder()); pipeline.addLast("httpDecoder", new HttpRequestDecoder()); pipeline.addLast("httpAggregator", new HttpChunkAggregator(16384)); - pipeline.addLast("httpEncoder", new HttpResponseEncoder()); pipeline.addLast("objectDecoder", new PiligrimProtocolDecoder(PiligrimProtocol.this)); } }); diff --git a/src/org/traccar/protocol/Pt3000Protocol.java b/src/org/traccar/protocol/Pt3000Protocol.java index bde8e7709..3a22be214 100644 --- a/src/org/traccar/protocol/Pt3000Protocol.java +++ b/src/org/traccar/protocol/Pt3000Protocol.java @@ -37,8 +37,8 @@ public class Pt3000Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, 'd')); // probably wrong - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Pt3000ProtocolDecoder(Pt3000Protocol.this)); } }); diff --git a/src/org/traccar/protocol/SuntechProtocol.java b/src/org/traccar/protocol/SuntechProtocol.java index d3f5d6009..e06ed2aac 100644 --- a/src/org/traccar/protocol/SuntechProtocol.java +++ b/src/org/traccar/protocol/SuntechProtocol.java @@ -41,10 +41,10 @@ public class SuntechProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '\r')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); - pipeline.addLast("objectDecoder", new SuntechProtocolDecoder(SuntechProtocol.this)); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectEncoder", new SuntechProtocolEncoder()); + pipeline.addLast("objectDecoder", new SuntechProtocolDecoder(SuntechProtocol.this)); } }); } diff --git a/src/org/traccar/protocol/T55Protocol.java b/src/org/traccar/protocol/T55Protocol.java index 21f1d3e6c..041413a65 100644 --- a/src/org/traccar/protocol/T55Protocol.java +++ b/src/org/traccar/protocol/T55Protocol.java @@ -37,8 +37,8 @@ public class T55Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new T55ProtocolDecoder(T55Protocol.this)); } }); diff --git a/src/org/traccar/protocol/T55ProtocolDecoder.java b/src/org/traccar/protocol/T55ProtocolDecoder.java index 9c47d1ab0..71f493318 100644 --- a/src/org/traccar/protocol/T55ProtocolDecoder.java +++ b/src/org/traccar/protocol/T55ProtocolDecoder.java @@ -42,7 +42,13 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { .expression("([EW]),") .number("(d+.?d*)?,") // speed .number("(d+.?d*)?,") // course - .number("(dd)(dd)(dd)") // date + .number("(dd)(dd)(dd),") // date + .expression("[^*]+") + .text("*") + .expression("[^,]+") + .number(",(d+)") // satellites + .number(",(d+)") // imei + .number(",(d+)").optional(3) .any() .compile(); @@ -84,7 +90,7 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { private Position position = null; - private Position decodeGprmc(String sentence, Channel channel) { + private Position decodeGprmc(String sentence, SocketAddress remoteAddress, Channel channel) { if (channel != null) { channel.write("OK1\r\n"); @@ -114,6 +120,14 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { dateBuilder.setDateReverse(parser.nextInt(), parser.nextInt(), parser.nextInt()); position.setTime(dateBuilder.getDate()); + if (parser.hasNext(3)) { + position.set(Event.KEY_SATELLITES, parser.next()); + if (!identify(parser.next(), channel, remoteAddress)) { + return null; + } + position.setDeviceId(getDeviceId()); + } + if (hasDeviceId()) { return position; } else { @@ -217,7 +231,7 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { } else if (sentence.startsWith("IMEI")) { identify(sentence.substring(5, sentence.length()), channel, remoteAddress); } else if (sentence.startsWith("$GPFID")) { - if (identify(sentence.substring(6, sentence.length()), channel, remoteAddress) && position != null) { + if (identify(sentence.substring(7, sentence.length()), channel, remoteAddress) && position != null) { Position position = this.position; position.setDeviceId(getDeviceId()); this.position = null; @@ -226,7 +240,7 @@ public class T55ProtocolDecoder extends BaseProtocolDecoder { } else if (Character.isDigit(sentence.charAt(0)) && sentence.length() == 15) { identify(sentence, channel, remoteAddress); } else if (sentence.startsWith("$GPRMC")) { - return decodeGprmc(sentence, channel); + return decodeGprmc(sentence, remoteAddress, channel); } else if (sentence.startsWith("$GPGGA") && hasDeviceId()) { return decodeGpgga(sentence); } else if (sentence.startsWith("$GPRMA") && hasDeviceId()) { diff --git a/src/org/traccar/protocol/TaipProtocol.java b/src/org/traccar/protocol/TaipProtocol.java index 439fe7696..a3b744e70 100644 --- a/src/org/traccar/protocol/TaipProtocol.java +++ b/src/org/traccar/protocol/TaipProtocol.java @@ -38,14 +38,15 @@ public class TaipProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '<')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new TaipProtocolDecoder(TaipProtocol.this, true)); } }); serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("stringEncoder", new StringEncoder()); pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new TaipProtocolDecoder(TaipProtocol.this, false)); } diff --git a/src/org/traccar/protocol/TelicFrameDecoder.java b/src/org/traccar/protocol/TelicFrameDecoder.java new file mode 100644 index 000000000..2a6e121cf --- /dev/null +++ b/src/org/traccar/protocol/TelicFrameDecoder.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 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.protocol; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +public class TelicFrameDecoder extends FrameDecoder { + + @Override + protected Object decode( + ChannelHandlerContext ctx, Channel channel, ChannelBuffer buf) throws Exception { + + if (buf.readableBytes() < 4) { + return null; + } + + long length = buf.getUnsignedInt(buf.readerIndex()); + + if (length < 1024) { + if (buf.readableBytes() >= length + 4) { + buf.readUnsignedInt(); + return buf.readBytes((int) length); + } + } else { + int endIndex = buf.indexOf(buf.readerIndex(), buf.writerIndex(), (byte) 0); + if (endIndex >= 0) { + ChannelBuffer frame = buf.readBytes(endIndex - buf.readerIndex()); + buf.readByte(); + if (frame.readableBytes() > 0) { + return frame; + } + } + } + + return null; + } + +} diff --git a/src/org/traccar/protocol/TelicProtocol.java b/src/org/traccar/protocol/TelicProtocol.java new file mode 100644 index 000000000..464892431 --- /dev/null +++ b/src/org/traccar/protocol/TelicProtocol.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 - 2016 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.protocol; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.handler.codec.string.StringDecoder; +import org.traccar.BaseProtocol; +import org.traccar.TrackerServer; + +import java.nio.ByteOrder; +import java.util.List; + +public class TelicProtocol extends BaseProtocol { + + public TelicProtocol() { + super("telic"); + } + + @Override + public void initTrackerServers(List<TrackerServer> serverList) { + TrackerServer server = new TrackerServer(new ServerBootstrap(), this.getName()) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("frameDecoder", new TelicFrameDecoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); + pipeline.addLast("objectDecoder", new TelicProtocolDecoder(TelicProtocol.this)); + } + }; + server.setEndianness(ByteOrder.LITTLE_ENDIAN); + serverList.add(server); + } + +} diff --git a/src/org/traccar/protocol/TelikProtocolDecoder.java b/src/org/traccar/protocol/TelicProtocolDecoder.java index 4171750d6..ba6d9c47e 100644 --- a/src/org/traccar/protocol/TelikProtocolDecoder.java +++ b/src/org/traccar/protocol/TelicProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 - 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2014 - 2016 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. @@ -25,9 +25,9 @@ import org.traccar.helper.PatternBuilder; import org.traccar.model.Event; import org.traccar.model.Position; -public class TelikProtocolDecoder extends BaseProtocolDecoder { +public class TelicProtocolDecoder extends BaseProtocolDecoder { - public TelikProtocolDecoder(TelikProtocol protocol) { + public TelicProtocolDecoder(TelicProtocol protocol) { super(protocol); } @@ -39,12 +39,19 @@ public class TelikProtocolDecoder extends BaseProtocolDecoder { .number("d+,") .number("(dd)(dd)(dd)") // date .number("(dd)(dd)(dd),") // time + .groupBegin() + .number("(ddd)(dd)(dddd),") // longitude + .number("(dd)(dd)(dddd),") // latitude + .or() .number("(-?d+),") // longitude .number("(-?d+),") // latitude + .groupEnd() .number("(d),") // validity .number("(d+),") // speed .number("(d+),") // course .number("(d+),") // satellites + .expression("(?:[^,]*,){7}") + .number("(d+),") .any() .compile(); @@ -72,13 +79,22 @@ public class TelikProtocolDecoder extends BaseProtocolDecoder { .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); position.setTime(dateBuilder.getDate()); - position.setLongitude(parser.nextDouble() / 10000); - position.setLatitude(parser.nextDouble() / 10000); + if (parser.hasNext(6)) { + position.setLongitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN)); + position.setLatitude(parser.nextCoordinate(Parser.CoordinateFormat.DEG_MIN_MIN)); + } + + if (parser.hasNext(2)) { + position.setLongitude(parser.nextDouble() / 10000); + position.setLatitude(parser.nextDouble() / 10000); + } + position.setValid(parser.nextInt() != 1); position.setSpeed(parser.nextDouble()); position.setCourse(parser.nextDouble()); position.set(Event.KEY_SATELLITES, parser.next()); + position.set(Event.KEY_BATTERY, parser.nextInt()); return position; } diff --git a/src/org/traccar/protocol/TeltonikaProtocol.java b/src/org/traccar/protocol/TeltonikaProtocol.java index 859b2a08c..f944c3003 100644 --- a/src/org/traccar/protocol/TeltonikaProtocol.java +++ b/src/org/traccar/protocol/TeltonikaProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -15,6 +15,7 @@ */ package org.traccar.protocol; +import org.jboss.netty.bootstrap.ConnectionlessBootstrap; import org.jboss.netty.bootstrap.ServerBootstrap; import org.jboss.netty.channel.ChannelPipeline; import org.traccar.BaseProtocol; @@ -37,6 +38,12 @@ public class TeltonikaProtocol extends BaseProtocol { pipeline.addLast("objectDecoder", new TeltonikaProtocolDecoder(TeltonikaProtocol.this)); } }); + serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { + @Override + protected void addSpecificHandlers(ChannelPipeline pipeline) { + pipeline.addLast("objectDecoder", new TeltonikaProtocolDecoder(TeltonikaProtocol.this)); + } + }); } } diff --git a/src/org/traccar/protocol/Tk102Protocol.java b/src/org/traccar/protocol/Tk102Protocol.java index 4e1d067f9..1f4eda730 100644 --- a/src/org/traccar/protocol/Tk102Protocol.java +++ b/src/org/traccar/protocol/Tk102Protocol.java @@ -37,8 +37,8 @@ public class Tk102Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, ']')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Tk102ProtocolDecoder(Tk102Protocol.this)); } }); diff --git a/src/org/traccar/protocol/Tk103Protocol.java b/src/org/traccar/protocol/Tk103Protocol.java index b14264091..324c01a31 100644 --- a/src/org/traccar/protocol/Tk103Protocol.java +++ b/src/org/traccar/protocol/Tk103Protocol.java @@ -38,16 +38,16 @@ public class Tk103Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, ')')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Tk103ProtocolDecoder(Tk103Protocol.this)); } }); serverList.add(new TrackerServer(new ConnectionlessBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Tk103ProtocolDecoder(Tk103Protocol.this)); } }); diff --git a/src/org/traccar/protocol/Tlt2hProtocol.java b/src/org/traccar/protocol/Tlt2hProtocol.java index 3a1bf99ff..14787537d 100644 --- a/src/org/traccar/protocol/Tlt2hProtocol.java +++ b/src/org/traccar/protocol/Tlt2hProtocol.java @@ -37,8 +37,8 @@ public class Tlt2hProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(32 * 1024, "##\r\n")); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Tlt2hProtocolDecoder(Tlt2hProtocol.this)); } }); diff --git a/src/org/traccar/protocol/TotemProtocol.java b/src/org/traccar/protocol/TotemProtocol.java index 097f6a593..4d7f0a135 100644 --- a/src/org/traccar/protocol/TotemProtocol.java +++ b/src/org/traccar/protocol/TotemProtocol.java @@ -41,10 +41,10 @@ public class TotemProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new TotemFrameDecoder()); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); - pipeline.addLast("objectDecoder", new TotemProtocolDecoder(TotemProtocol.this)); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectEncoder", new TotemProtocolEncoder()); + pipeline.addLast("objectDecoder", new TotemProtocolDecoder(TotemProtocol.this)); } }); } diff --git a/src/org/traccar/protocol/Tr20Protocol.java b/src/org/traccar/protocol/Tr20Protocol.java index 8c5a27ae2..4e55d5f98 100644 --- a/src/org/traccar/protocol/Tr20Protocol.java +++ b/src/org/traccar/protocol/Tr20Protocol.java @@ -37,8 +37,8 @@ public class Tr20Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Tr20ProtocolDecoder(Tr20Protocol.this)); } }); diff --git a/src/org/traccar/protocol/Tr900Protocol.java b/src/org/traccar/protocol/Tr900Protocol.java index 4db0a2239..d54e4cb3c 100644 --- a/src/org/traccar/protocol/Tr900Protocol.java +++ b/src/org/traccar/protocol/Tr900Protocol.java @@ -38,16 +38,16 @@ public class Tr900Protocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Tr900ProtocolDecoder(Tr900Protocol.this)); } }); serverList.add(new TrackerServer(new ConnectionlessBootstrap(), getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new Tr900ProtocolDecoder(Tr900Protocol.this)); } }); diff --git a/src/org/traccar/protocol/TrackboxProtocol.java b/src/org/traccar/protocol/TrackboxProtocol.java index 68554394e..5477de852 100644 --- a/src/org/traccar/protocol/TrackboxProtocol.java +++ b/src/org/traccar/protocol/TrackboxProtocol.java @@ -37,8 +37,8 @@ public class TrackboxProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new TrackboxProtocolDecoder(TrackboxProtocol.this)); } }); diff --git a/src/org/traccar/protocol/TrvProtocol.java b/src/org/traccar/protocol/TrvProtocol.java index 916b7d612..af598ea02 100644 --- a/src/org/traccar/protocol/TrvProtocol.java +++ b/src/org/traccar/protocol/TrvProtocol.java @@ -37,8 +37,8 @@ public class TrvProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '#')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new TrvProtocolDecoder(TrvProtocol.this)); } }); diff --git a/src/org/traccar/protocol/TrvProtocolDecoder.java b/src/org/traccar/protocol/TrvProtocolDecoder.java index 94796fa5e..8075515c2 100644 --- a/src/org/traccar/protocol/TrvProtocolDecoder.java +++ b/src/org/traccar/protocol/TrvProtocolDecoder.java @@ -105,7 +105,7 @@ public class TrvProtocolDecoder extends BaseProtocolDecoder { position.set(Event.KEY_GSM, parser.nextInt()); position.set(Event.KEY_SATELLITES, parser.nextInt()); - position.set(Event.KEY_BATTERY , parser.nextInt()); + position.set(Event.KEY_BATTERY, parser.nextInt()); position.set(Event.KEY_IGNITION, parser.nextInt() != 0); position.set("arm", parser.nextInt()); diff --git a/src/org/traccar/protocol/TelikProtocol.java b/src/org/traccar/protocol/Tt8850Protocol.java index b6b5ba14d..a409205c7 100644 --- a/src/org/traccar/protocol/TelikProtocol.java +++ b/src/org/traccar/protocol/Tt8850Protocol.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2016 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. @@ -24,10 +24,10 @@ import org.traccar.TrackerServer; import java.util.List; -public class TelikProtocol extends BaseProtocol { +public class Tt8850Protocol extends BaseProtocol { - public TelikProtocol() { - super("telik"); + public Tt8850Protocol() { + super("tt8850"); } @Override @@ -35,9 +35,9 @@ public class TelikProtocol extends BaseProtocol { serverList.add(new TrackerServer(new ServerBootstrap(), this.getName()) { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { - pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, '\0')); + pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, "$")); pipeline.addLast("stringDecoder", new StringDecoder()); - pipeline.addLast("objectDecoder", new TelikProtocolDecoder(TelikProtocol.this)); + pipeline.addLast("objectDecoder", new Tt8850ProtocolDecoder(Tt8850Protocol.this)); } }); } diff --git a/src/org/traccar/protocol/Tt8850ProtocolDecoder.java b/src/org/traccar/protocol/Tt8850ProtocolDecoder.java new file mode 100644 index 000000000..9979fd61d --- /dev/null +++ b/src/org/traccar/protocol/Tt8850ProtocolDecoder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016 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.protocol; + +import org.jboss.netty.channel.Channel; +import org.traccar.BaseProtocolDecoder; +import org.traccar.helper.DateBuilder; +import org.traccar.helper.Parser; +import org.traccar.helper.PatternBuilder; +import org.traccar.helper.UnitsConverter; +import org.traccar.model.Event; +import org.traccar.model.Position; + +import java.net.SocketAddress; +import java.util.regex.Pattern; + +public class Tt8850ProtocolDecoder extends BaseProtocolDecoder { + + public Tt8850ProtocolDecoder(Tt8850Protocol protocol) { + super(protocol); + } + + private static final Pattern PATTERN = new PatternBuilder() + .binary("0004,") + .number("xxxx,") + .expression("[01],") + .expression("GT...,") + .number("(?:[0-9A-Z]{2}xxxx)?,") // protocol version + .expression("([^,]+),") // imei + .any() + .number("(d{1,2})?,") // gps accuracy + .number("(d{1,3}.d)?,") // speed + .number("(d{1,3})?,") // course + .number("(-?d{1,5}.d)?,") // altitude + .number("(-?d{1,3}.d{6}),") // longitude + .number("(-?d{1,2}.d{6}),") // latitude + .number("(dddd)(dd)(dd)") // date + .number("(dd)(dd)(dd),") // time + .number("(0ddd)?,") // mcc + .number("(0ddd)?,") // mnc + .number("(xxxx)?,") // lac + .number("(xxxx)?,") // cell + .any() + .number("(dddd)(dd)(dd)") // date + .number("(dd)(dd)(dd),") // time + .number("(xxxx)") + .compile(); + + @Override + protected Object decode( + Channel channel, SocketAddress remoteAddress, Object msg) throws Exception { + + Parser parser = new Parser(PATTERN, (String) msg); + if (!parser.matches()) { + return null; + } + + Position position = new Position(); + position.setProtocol(getProtocolName()); + + if (!identify(parser.next(), channel, remoteAddress)) { + return null; + } + position.setDeviceId(getDeviceId()); + + position.setValid(parser.nextInt() < 20); + position.setSpeed(UnitsConverter.knotsFromKph(parser.nextDouble())); + position.setCourse(parser.nextDouble()); + position.setAltitude(parser.nextDouble()); + position.setLongitude(parser.nextDouble()); + position.setLatitude(parser.nextDouble()); + + DateBuilder dateBuilder = new DateBuilder() + .setDate(parser.nextInt(), parser.nextInt(), parser.nextInt()) + .setTime(parser.nextInt(), parser.nextInt(), parser.nextInt()); + position.setTime(dateBuilder.getDate()); + + if (parser.hasNext(4)) { + position.set(Event.KEY_MCC, parser.nextInt()); + position.set(Event.KEY_MNC, parser.nextInt()); + position.set(Event.KEY_LAC, parser.nextInt(16)); + position.set(Event.KEY_CID, parser.nextInt(16)); + } + + return position; + } + +} diff --git a/src/org/traccar/protocol/UlbotechProtocolDecoder.java b/src/org/traccar/protocol/UlbotechProtocolDecoder.java index bf0a9cc76..a5c608dd3 100644 --- a/src/org/traccar/protocol/UlbotechProtocolDecoder.java +++ b/src/org/traccar/protocol/UlbotechProtocolDecoder.java @@ -60,8 +60,8 @@ public class UlbotechProtocolDecoder extends BaseProtocolDecoder { while (buf.readerIndex() < end) { int parameterLength = buf.getUnsignedByte(buf.readerIndex()) >> 4; - position.add(ObdDecoder.decode(buf.readUnsignedByte() & 0x0F, buf.readUnsignedByte(), - ChannelBuffers.hexDump(buf.readBytes(parameterLength - 2)))); + int mode = buf.readUnsignedByte() & 0x0F; + position.add(ObdDecoder.decode(mode, ChannelBuffers.hexDump(buf.readBytes(parameterLength - 1)))); } } diff --git a/src/org/traccar/protocol/WatchProtocol.java b/src/org/traccar/protocol/WatchProtocol.java index 3a10f9c59..1198f689e 100644 --- a/src/org/traccar/protocol/WatchProtocol.java +++ b/src/org/traccar/protocol/WatchProtocol.java @@ -37,8 +37,8 @@ public class WatchProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, ']')); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new WatchProtocolDecoder(WatchProtocol.this)); } }); diff --git a/src/org/traccar/protocol/WialonProtocol.java b/src/org/traccar/protocol/WialonProtocol.java index 6fe924c2d..d0d3aa446 100644 --- a/src/org/traccar/protocol/WialonProtocol.java +++ b/src/org/traccar/protocol/WialonProtocol.java @@ -37,8 +37,8 @@ public class WialonProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(4 * 1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new WialonProtocolDecoder(WialonProtocol.this)); } }); diff --git a/src/org/traccar/protocol/XexunProtocolDecoder.java b/src/org/traccar/protocol/XexunProtocolDecoder.java index 0929ee99b..4d4831a20 100644 --- a/src/org/traccar/protocol/XexunProtocolDecoder.java +++ b/src/org/traccar/protocol/XexunProtocolDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2012 - 2016 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. @@ -38,8 +38,8 @@ public class XexunProtocolDecoder extends BaseProtocolDecoder { .expression("G[PN]RMC,") .number("(dd)(dd)(dd).(d+),") // time .expression("([AV]),") // validity - .number("(d+)(dd.d+),([NS]),") // latitude - .number("(d+)(dd.d+),([EW])?,") // longitude + .number("(d*?)(d?d.d+),([NS]),") // latitude + .number("(d*?)(d?d.d+),([EW])?,") // longitude .number("(d+.?d*),") // speed .number("(d+.?d*)?,") // course .number("(dd)(dd)(dd),") // date diff --git a/src/org/traccar/protocol/XirgoProtocol.java b/src/org/traccar/protocol/XirgoProtocol.java index 83c373175..f186c731f 100644 --- a/src/org/traccar/protocol/XirgoProtocol.java +++ b/src/org/traccar/protocol/XirgoProtocol.java @@ -37,8 +37,8 @@ public class XirgoProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new CharacterDelimiterFrameDecoder(1024, "##")); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new XirgoProtocolDecoder(XirgoProtocol.this)); } }); diff --git a/src/org/traccar/protocol/YwtProtocol.java b/src/org/traccar/protocol/YwtProtocol.java index e9cdb1615..3fa154ec0 100644 --- a/src/org/traccar/protocol/YwtProtocol.java +++ b/src/org/traccar/protocol/YwtProtocol.java @@ -37,8 +37,8 @@ public class YwtProtocol extends BaseProtocol { @Override protected void addSpecificHandlers(ChannelPipeline pipeline) { pipeline.addLast("frameDecoder", new LineBasedFrameDecoder(1024)); - pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("stringEncoder", new StringEncoder()); + pipeline.addLast("stringDecoder", new StringDecoder()); pipeline.addLast("objectDecoder", new YwtProtocolDecoder(YwtProtocol.this)); } }); diff --git a/src/org/traccar/web/AsyncServlet.java b/src/org/traccar/web/AsyncServlet.java index b10df5408..e4cb64c57 100644 --- a/src/org/traccar/web/AsyncServlet.java +++ b/src/org/traccar/web/AsyncServlet.java @@ -226,7 +226,7 @@ public class AsyncServlet extends BaseServlet { synchronized (ASYNC_SESSIONS) { if (Boolean.parseBoolean(req.getParameter("first")) || !ASYNC_SESSIONS.containsKey(userId)) { - Collection<Long> devices = Context.getPermissionsManager().allowedDevices(userId); + Collection<Long> devices = Context.getPermissionsManager().getDevicePermissions(userId); ASYNC_SESSIONS.put(userId, new AsyncSession(userId, devices)); } diff --git a/src/org/traccar/web/BaseServlet.java b/src/org/traccar/web/BaseServlet.java index 8b022d556..17f4f19cd 100644 --- a/src/org/traccar/web/BaseServlet.java +++ b/src/org/traccar/web/BaseServlet.java @@ -53,7 +53,7 @@ public abstract class BaseServlet extends HttpServlet { String origin = req.getHeader(HttpHeaders.Names.ORIGIN); String allowed = Context.getConfig().getString("web.origin"); - if (allowed == null) { + if (allowed == null || origin == null) { resp.setHeader(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW_ORIGIN_VALUE); } else if (allowed.contains(origin)) { String originSafe = URLEncoder.encode(origin, StandardCharsets.UTF_8.name()); diff --git a/src/org/traccar/web/WebServer.java b/src/org/traccar/web/WebServer.java index d6cd19d87..4e764d5d6 100644 --- a/src/org/traccar/web/WebServer.java +++ b/src/org/traccar/web/WebServer.java @@ -42,7 +42,9 @@ import org.traccar.api.ResourceErrorHandler; import org.traccar.api.SecurityRequestFilter; import org.traccar.api.resource.CommandResource; import org.traccar.api.resource.DeviceResource; -import org.traccar.api.resource.PermissionResource; +import org.traccar.api.resource.DevicePermissionResource; +import org.traccar.api.resource.GroupPermissionResource; +import org.traccar.api.resource.GroupResource; import org.traccar.api.resource.PositionResource; import org.traccar.api.resource.ServerResource; import org.traccar.api.resource.SessionResource; @@ -140,7 +142,8 @@ public class WebServer { resourceConfig.register(SecurityRequestFilter.class); resourceConfig.register(CorsResponseFilter.class); resourceConfig.registerClasses(ServerResource.class, SessionResource.class, CommandResource.class, - PermissionResource.class, DeviceResource.class, UserResource.class, PositionResource.class); + GroupPermissionResource.class, DevicePermissionResource.class, UserResource.class, + GroupResource.class, DeviceResource.class, PositionResource.class); servletHandler.addServlet(new ServletHolder(new ServletContainer(resourceConfig)), "/*"); handlers.addHandler(servletHandler); diff --git a/test/org/traccar/ProtocolTest.java b/test/org/traccar/ProtocolTest.java index 75c12b171..d7104edba 100644 --- a/test/org/traccar/ProtocolTest.java +++ b/test/org/traccar/ProtocolTest.java @@ -194,7 +194,7 @@ public class ProtocolTest { if (attributes.containsKey(Event.KEY_LAC) || attributes.containsKey(Event.KEY_CID)) { checkInteger(attributes.get(Event.KEY_LAC), 1, 65535); - checkInteger(attributes.get(Event.KEY_CID), 1, 268435455); + checkInteger(attributes.get(Event.KEY_CID), 0, 268435455); } if (attributes.containsKey(Event.KEY_MCC) || attributes.containsKey(Event.KEY_MNC)) { diff --git a/test/org/traccar/database/GroupTreeTest.java b/test/org/traccar/database/GroupTreeTest.java new file mode 100644 index 000000000..f73cb75b6 --- /dev/null +++ b/test/org/traccar/database/GroupTreeTest.java @@ -0,0 +1,55 @@ +package org.traccar.database; + +import org.junit.Assert; +import org.junit.Test; +import org.traccar.model.Device; +import org.traccar.model.Group; + +import java.util.ArrayList; +import java.util.Collection; + +public class GroupTreeTest { + + private static Group createGroup(long id, String name, long parent) { + Group group = new Group(); + group.setId(id); + group.setName(name); + group.setGroupId(parent); + return group; + } + + private static Device createDevice(long id, String name, long parent) { + Device device = new Device(); + device.setId(id); + device.setName(name); + device.setGroupId(parent); + return device; + } + + @Test + public void testGetDescendants() { + Collection<Group> groups = new ArrayList<>(); + groups.add(createGroup(1, "First", 0)); + groups.add(createGroup(2, "Second", 1)); + groups.add(createGroup(3, "Third", 2)); + groups.add(createGroup(4, "Fourth", 2)); + groups.add(createGroup(5, "Fifth", 4)); + + Collection<Device> devices = new ArrayList<>(); + devices.add(createDevice(1, "One", 3)); + devices.add(createDevice(2, "Two", 5)); + devices.add(createDevice(3, "One", 5)); + + GroupTree groupTree = new GroupTree(groups, devices); + + Assert.assertEquals(4, groupTree.getGroups(1).size()); + Assert.assertEquals(3, groupTree.getGroups(2).size()); + Assert.assertEquals(0, groupTree.getGroups(3).size()); + Assert.assertEquals(1, groupTree.getGroups(4).size()); + + Assert.assertEquals(3, groupTree.getDevices(1).size()); + Assert.assertEquals(1, groupTree.getDevices(3).size()); + Assert.assertEquals(2, groupTree.getDevices(4).size()); + } + +} diff --git a/test/org/traccar/helper/ObdDecoderTest.java b/test/org/traccar/helper/ObdDecoderTest.java index 238f3e0a6..62881b66c 100644 --- a/test/org/traccar/helper/ObdDecoderTest.java +++ b/test/org/traccar/helper/ObdDecoderTest.java @@ -8,11 +8,11 @@ public class ObdDecoderTest { @Test public void testDecode() { - Assert.assertEquals(83, ObdDecoder.decode(0x01, 0x05, "7b").getValue()); - Assert.assertEquals(1225, ObdDecoder.decode(0x01, 0x0C, "1324").getValue()); - Assert.assertEquals(20, ObdDecoder.decode(0x01, 0x0D, "14").getValue()); - Assert.assertEquals(64050, ObdDecoder.decode(0x01, 0x31, "fa32").getValue()); - Assert.assertEquals(25, ObdDecoder.decode(0x01, 0x2F, "41").getValue()); + Assert.assertEquals(83, ObdDecoder.decode(0x01, "057b").getValue()); + Assert.assertEquals(1225, ObdDecoder.decode(0x01, "0C1324").getValue()); + Assert.assertEquals(20, ObdDecoder.decode(0x01, "0D14").getValue()); + Assert.assertEquals(64050, ObdDecoder.decode(0x01, "31fa32").getValue()); + Assert.assertEquals(25, ObdDecoder.decode(0x01, "2F41").getValue()); } diff --git a/test/org/traccar/protocol/AplicomProtocolDecoderTest.java b/test/org/traccar/protocol/AplicomProtocolDecoderTest.java index 103718941..26844994e 100644 --- a/test/org/traccar/protocol/AplicomProtocolDecoderTest.java +++ b/test/org/traccar/protocol/AplicomProtocolDecoderTest.java @@ -10,6 +10,15 @@ public class AplicomProtocolDecoderTest extends ProtocolTest { AplicomProtocolDecoder decoder = new AplicomProtocolDecoder(new AplicomProtocol());
+ verifyPosition(decoder, binary(
+ "44c3014645e8e91b66002300a21f0b01f056d3e62856d3e626031f845f00c6ee440800000000000000000017bd1cb30000"));
+
+ verifyPosition(decoder, binary(
+ "44c3014645e8e91b66002300a21f0b00f056d3e64756d3e646031f845f00c6ee440800000000000000000017bd1cb30001"));
+
+ verifyPosition(decoder, binary(
+ "44c3014645e8e91b66001f00221f0b01f456ba1e0d56ba1e0b031f842200c6ef550c000000000017bd1cb30004"));
+
verifyNothing(decoder, binary(
"44c3014645e8e9bada003e03fff7070055a4f24200000081000000000000000000000000000000000000000000000000000000000000000000000000000000ff00000001000000000000000044c3014645e8e9bada003e03fff77bff55a4f24300000081000000000000000000000000000000000000000000000000000000000000000000000000000000ff00300002000000000000000044c3014645e8e9bada003e03fff7690655a4f24500000081000000000000000000000000000000000000000000000000000000000000000000000000000000ff003000030000000000000000"));
diff --git a/test/org/traccar/protocol/AstraProtocolDecoderTest.java b/test/org/traccar/protocol/AstraProtocolDecoderTest.java new file mode 100644 index 000000000..93a939879 --- /dev/null +++ b/test/org/traccar/protocol/AstraProtocolDecoderTest.java @@ -0,0 +1,18 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class AstraProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + AstraProtocolDecoder decoder = new AstraProtocolDecoder(new AstraProtocol()); + + verifyPositions(decoder, binary( + "4b013c02213aec35c501ad031368b8ffcd1ad043e5c4500c79000100003101005c490c001c0009020200020015069600ae03136801ffcd1af143e5c452125e000100003101005c491200090011010000020015068500af0313629effcd1f4b43e5c45d1e46000100003101005c491e00080409040500040015068700b0031359d5ffcd35ad43e5c47b2a3b000001003101005c492a1b1a0d0b0f0b00080015068700b103134984ffcd4b1e43e5c4913354000100003101005c49340b0103090606000f0015067700b203132e1affcd5a5a43e5c4af3348000001003101005c4935070a08000a070017001505f700b30313192cffcd7af143e5c4cd3733000001003101005c4937091206050a0800200015058600b403130debffcda88743e5c4eb2c3e000001003101005c493707030601080600290015058600b377")); + + } + +} diff --git a/test/org/traccar/protocol/AutoFonProtocolDecoderTest.java b/test/org/traccar/protocol/AutoFonProtocolDecoderTest.java index 4a0a42a7b..f84873c6e 100644 --- a/test/org/traccar/protocol/AutoFonProtocolDecoderTest.java +++ b/test/org/traccar/protocol/AutoFonProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class AutoFonProtocolDecoderTest extends ProtocolTest { AutoFonProtocolDecoder decoder = new AutoFonProtocolDecoder(new AutoFonProtocol());
+ verifyPosition(decoder, binary(
+ "02080000251848470afa010262daa690013aa4046da83745f8812560df010001126a"));
+
verifyNothing(decoder, binary(
"10556103592310314825728F"));
diff --git a/test/org/traccar/protocol/Gl100ProtocolDecoderTest.java b/test/org/traccar/protocol/Gl100ProtocolDecoderTest.java index 1e4490f09..803d076b9 100644 --- a/test/org/traccar/protocol/Gl100ProtocolDecoderTest.java +++ b/test/org/traccar/protocol/Gl100ProtocolDecoderTest.java @@ -12,6 +12,9 @@ public class Gl100ProtocolDecoderTest extends ProtocolTest { Gl100ProtocolDecoder decoder = new Gl100ProtocolDecoder(new Gl100Protocol()); verifyPosition(decoder, text( + "+RESP:GTLGL,359464030492644,1,2,1,0,0.4,0,299.7,1,5.455551,51.449776,20160311083229,0204,0016,03EC,BD94,00,0036,0102090501")); + + verifyPosition(decoder, text( "+RESP:GTTRI,359464030439249,1,0,61,1,0.0,346,-2.7,2,-80.392825,26.122424,20151214000354,0310,0260,72BC,35F5,00,04B6,0102070407")); verifyPosition(decoder, text( diff --git a/test/org/traccar/protocol/Gl200ProtocolDecoderTest.java b/test/org/traccar/protocol/Gl200ProtocolDecoderTest.java index b06715e29..bf7e3f175 100644 --- a/test/org/traccar/protocol/Gl200ProtocolDecoderTest.java +++ b/test/org/traccar/protocol/Gl200ProtocolDecoderTest.java @@ -10,13 +10,25 @@ public class Gl200ProtocolDecoderTest extends ProtocolTest { Gl200ProtocolDecoder decoder = new Gl200ProtocolDecoder(new Gl200Protocol()); + verifyPositions(decoder, text( + "+RESP:GTFRI,060228,862894020180553,,,10,1,1,20.0,147,329.7,-62.899703,-17.720434,20160309113548,0736,0003,6AAE,3381,00,913.3,,,,0,220101,,,,20160309073554,132B$")); + + verifyPositions(decoder, text( + "+BUFF:GTFRI,060402,862894021808798,,,10,1,1,0.0,349,394.3,-63.287717,-17.662410,20160116234031,0736,0003,6ABA,8305,00,3326.8,,,,94,220100,,,,20160116194035,4D83")); + verifyPosition(decoder, text( + "+RESP:GTFRI,2C0204,867162020003125,GL300W,0,0,2,1,1.7,205,2867.0,-78.481127,-0.206828,20160215210433,0740,0000,7596,5891C,0.0,1,1.7,205,2867.0,-78.481127,-0.206828,20160215210503,0740,0000,7596,5891C,0.0,88,20160215210506,1E78$")); + + verifyPositions(decoder, text( + "+RESP:GTFRI,060228,862894020178276,,15153,10,1,1,0.0,0,431.7,-63.169571,-17.776235,20160210153458,0736,0003,6AD4,80EF,00,34.9,00117:31:26,13442,15163,0,210101,,,,20160210113503,38EE$")); + + verifyPositions(decoder, text( "+RESP:GTFRI,110100,A5868800000015,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20110214013254,0460,0000,18d8,6141,00,80,20110214013254,000C")); - verifyNothing(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,210102,A10000458356CE,,0,1,1,15,1.4,0,190.6,-85.765763,42.894896,20160208164505,4126,210,0,18673,00,92,20160208164507,00A6")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+BUFF:GTFRI,060402,862894021808798,,,10,1,1,0.0,349,394.3,-63.287717,-17.662410,20160116234031,0736,0003,6ABA,8305,00,3326.8,,,,94,220100,,,,20160116194035,4D83")); verifyPosition(decoder, text( @@ -25,10 +37,10 @@ public class Gl200ProtocolDecoderTest extends ProtocolTest { verifyAttributes(decoder, text( "+RESP:GTINF,1F0101,135790246811220,1G1JC5444R7252367,,16,898600810906F8048812,16,0,1,12000,,4.2,0,0,,,20090214013254,,,,,,+0800,0,20090214093254,11F0$")); - verifyAttributes(decoder, text( + verifyPositions(decoder, false, text( "+RESP:GTFRI,120113,555564055560555,,1,1,1,,,,,,,,0282,0380,f080,cabf,6900,79,20140824165629,0001$")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,0F0106,862193020451183,,,10,1,1,0.0,163,,-57.513617,-25.368191,20150918182145,,,,,,21235.0,,,,0,210100,,,,20150918182149,00B8$")); verifyPosition(decoder, text( @@ -40,31 +52,31 @@ public class Gl200ProtocolDecoderTest extends ProtocolTest { verifyPosition(decoder, text( "+RESP:GTSTT,1A0401,860599000508846,,41,0,0.0,84,107.5,-76.657998,39.497203,20150623160622,0310,0260,B435,3B81,,20150623160622,0F54$")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,1A0401,860599000508846,,0,0,1,1,134.8,154,278.7,-76.671089,39.778885,20150623154301,0310,0260,043F,7761,,99,20150623154314,0F24$")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,1A0200,860599000165464,CRI001,0,0,1,2,,41,,-71.153137,42.301634,20150328020301,,,,,280.3,55,20150327220351,320C")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,02010D,867844001675407,,0,0,1,2,0.0,0,28.9,8.591011,56.476397,20140915213209,0238,0001,03CB,2871,,97,20140915213459,009A")); verifyNothing(decoder, text( "+RESP:GTINF,359464030073766,8938003990320469804f,18,99,100,1,0,+2.00,0,20131018084015,00EE,0103090402")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,04040C,359231038939904,,,10,1,2,0.0,117,346.0,8.924243,50.798077,20130618122040,0262,0002,0299,109C,00,0.0,,,,,,,,,20130618122045,00F6")); verifyPosition(decoder, text( "+RESP:GTSTT,04040C,359231038939904,,42,0,0.0,117,346.0,8.924243,50.798077,20130618125152,0262,0002,0299,109C,00,20130618125154,017A")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,020102,000035988863964,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,,20090214093254,11F0")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,020102,135790246811220,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,,20090214093254,11F0")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,020102,135790246811220,,0,0,2,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,0,4.3,92,70.0,121.354335,31.222073,20090101000000,0460,0000,18d8,6141,00,,20090214093254,11F0")); verifyPosition(decoder, text( @@ -78,72 +90,27 @@ public class Gl200ProtocolDecoderTest extends ProtocolTest { verifyPosition(decoder, text( "+RESP:GTFRI,07000D,868487001005941,,0,0,1,1,0.0,0,46.3,-77.039627,38.907573,20120731175232,0310,0260,B44B,EBC9,0015e96913a7,-58,,100,20120731175244,0114")); - - verifyPosition(decoder, text( - "+RESP:GTTOW,0F0100,135790246811220,,,10,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTDIS,0F0100,135790246811220,,,20,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTIOB,0F0100,135790246811220,,,10,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTGEO,0F0100,135790246811220,,,00,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTSPD,0F0100,135790246811220,,,00,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTSOS,0F0100,135790246811220,,,00,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTRTL,0F0100,135790246811220,,,00,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTDOG,0F0100,135790246811220,,,01,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "+RESP:GTIGL,0F0100,135790246811220,,,00,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); verifyPosition(decoder, text( "+RESP:GTHBM,0F0100,135790246811220,,,10,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); verifyPosition(decoder, text( "+RESP:GTHBM,0F0100,135790246811220,,,11,1,1,24.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,20090214093254,11F0$")); - - verifyPosition(decoder, text( + + verifyPositions(decoder, text( "+RESP:GTFRI,02010C,867844001274144,,0,0,1,1,18.0,233,118.1,7.615551,51.515600,20140106130516,0262,0007,79E6,B956,,72,20140106140524,09CE$")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,02010C,867844001274649,,0,0,1,1,0.0,0,122.5,7.684216,51.524512,20140106233722,0262,0007,79EE,1D22,,93,20140107003805,03C4$")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+BUFF:GTFRI,210101,863286020016706,,,10,1,1,,,,49.903915,40.391669,20140818105815,,,,,,,,,,,210100,,,,,000C$")); - verifyPosition(decoder, text( + verifyPositions(decoder, text( "+RESP:GTFRI,240100,135790246811220,,,10,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,2000.0,12345:12:34,,80,,,,,,20090214093254,11F0$")); - verifyPosition(decoder, text( - "+RESP:GTFRI,240100,135790246811220,,,10,2,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,0,4.3,92,70.0,121.354335,31.222073,20090101000000,0460,0000,18d8,6141,00,2000.0,12345:12:34,,,80,,,,,,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "\u0000\u0004,005F,0,GTFRI,020100,135790246811220,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,90,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "\u0000\u0004,005F,0,GTGEO,020100,135790246811220,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,90,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "\u0000\u0004,005F,0,GTNMR,020100,135790246811220,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,90,20090214093254,11F0$")); - - verifyPosition(decoder, text( - "\u0000\u0004,0017,0,GTNMR,,867844000400914,,0,41,1,2,0.0,0,1504.2,-75.569202,6.242850,20150404162835,,,,,97,20150404162836,05EF$")); - - verifyNothing(decoder, text( - "\u0000\u0004,0017,0,GTPNA,,867844000400914,,0,0,1,0,,,,0,0,,,,,,99,20150404190153,0601$")); - - verifyPosition(decoder, text( - "\u0000\u0004,0017,0,GTEPN,,867844000400914,,0,0,1,0,0.0,0,1717.4,-75.598445,6.278578,20150405003116,,,,,95,20150405003358,0607$")); + verifyPositions(decoder, text( + "+RESP:GTFRI,240100,135790246811220,,,10,2,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,00,0,4.3,92,70.0,121.354335,31.222073,20090101000000,0460,0000,18d8,6141,00,2000.0,12345:12:34,,,80,,,,,20090214093254,11F0$")); verifyPosition(decoder, text( "+RESP:GTSTT,280100,A1000043D20139,,42,0,0.1,321,228.6,-76.660884,39.832552,20150615120628,0310,0484,00600019,0A52,,20150615085741,0320$")); diff --git a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java index ae3d2fa55..ab59ba331 100644 --- a/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java +++ b/test/org/traccar/protocol/Gps103ProtocolDecoderTest.java @@ -10,6 +10,9 @@ public class Gps103ProtocolDecoderTest extends ProtocolTest { Gps103ProtocolDecoder decoder = new Gps103ProtocolDecoder(new Gps103Protocol()); + verifyPosition(decoder, text( + "359769031878322imei:359769031878322,tracker,1602160718,2,F,221811.000,A,1655.2193,S,14546.6722,E,0.00,,")); + verifyNothing(decoder, text( "imei:865328021049167,OBD,141118115036,,,0.0,,000,0.0%,+,0.0%,00000,,,,,")); diff --git a/test/org/traccar/protocol/GpsGateProtocolDecoderTest.java b/test/org/traccar/protocol/GpsGateProtocolDecoderTest.java index c4ed38656..788830e52 100644 --- a/test/org/traccar/protocol/GpsGateProtocolDecoderTest.java +++ b/test/org/traccar/protocol/GpsGateProtocolDecoderTest.java @@ -11,6 +11,12 @@ public class GpsGateProtocolDecoderTest extends ProtocolTest { GpsGateProtocolDecoder decoder = new GpsGateProtocolDecoder(new GpsGateProtocol()); verifyNothing(decoder, text( + "$FRCMD,862950025974620,_Ping,voltage=4*4F")); + + verifyPosition(decoder, text( + "$FRCMD,862950025974620,_SendMessage, ,2721.5781,S,15259.145,E,61,0.00,61,080316,092612,1,SosButton=0,voltage=4*60")); + + verifyNothing(decoder, text( "$FRLIN,,user1,8IVHF*7A")); verifyNothing(decoder, text( diff --git a/test/org/traccar/protocol/Gt06FrameDecoderTest.java b/test/org/traccar/protocol/Gt06FrameDecoderTest.java index 9a4c709e8..12ee6bb60 100644 --- a/test/org/traccar/protocol/Gt06FrameDecoderTest.java +++ b/test/org/traccar/protocol/Gt06FrameDecoderTest.java @@ -12,6 +12,18 @@ public class Gt06FrameDecoderTest extends ProtocolTest { Gt06FrameDecoder decoder = new Gt06FrameDecoder(); Assert.assertEquals( + binary("78781f1210020e140613cc04770690003e3f2e3414b20000000000000000044c446a0d0a"), + decoder.decode(null, null, binary("78781f1210020e140613cc04770690003e3f2e3414b20000000000000000044c446a0d0a"))); + + Assert.assertEquals( + binary("787808134606020002044dc5050d0a"), + decoder.decode(null, null, binary("787808134606020002044dc5050d0a"))); + + Assert.assertEquals( + binary("78781f1210020e14061dcc0476fcd0003e3faf3e14b20000000000000000044ef6740d0a"), + decoder.decode(null, null, binary("78781f1210020e14061dcc0476fcd0003e3faf3e14b20000000000000000044ef6740d0a"))); + + Assert.assertEquals( binary("78780d010352887071911998000479d00d0a"), decoder.decode(null, null, binary("78780d010352887071911998000479d00d0a"))); diff --git a/test/org/traccar/protocol/HomtecsProtocolDecoderTest.java b/test/org/traccar/protocol/HomtecsProtocolDecoderTest.java new file mode 100644 index 000000000..5b70453e0 --- /dev/null +++ b/test/org/traccar/protocol/HomtecsProtocolDecoderTest.java @@ -0,0 +1,18 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class HomtecsProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + HomtecsProtocolDecoder decoder = new HomtecsProtocolDecoder(new HomtecsProtocol()); + + verifyPosition(decoder, text( + "strommabus939_R01272028,160217,191003.00,06,5540.12292,N,01237.49814,E,0.391,,1,1.27,1.2")); + + } + +} diff --git a/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java b/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java index 2dbb24d9e..dd497858b 100644 --- a/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java +++ b/test/org/traccar/protocol/MeiligaoProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class MeiligaoProtocolDecoderTest extends ProtocolTest { MeiligaoProtocolDecoder decoder = new MeiligaoProtocolDecoder(new MeiligaoProtocol()); verifyPosition(decoder, binary( + "24240072190820157fffff99553039343335342e3030302c412c313930372e303631392c4e2c30373235312e333235312c452c3031302e312c3138382e352c3234303231362c2c2c412a36427c302e387c36352e327c303830307c303030302c303030307c303336343838373532c73f0d0a")); + + verifyPosition(decoder, binary( "242400680790209818ffff99553038333235382e3030302c412c303131352e393338302c532c30333634382e313430392c452c302e30302c3331352e35352c3132303131367c302e37347c313930322e337c303430307c303030302c303030307c302e30f41b0d0a")); verifyNothing(decoder, binary( diff --git a/test/org/traccar/protocol/MeitrackProtocolDecoderTest.java b/test/org/traccar/protocol/MeitrackProtocolDecoderTest.java index dcefb1637..f74914461 100644 --- a/test/org/traccar/protocol/MeitrackProtocolDecoderTest.java +++ b/test/org/traccar/protocol/MeitrackProtocolDecoderTest.java @@ -11,6 +11,9 @@ public class MeitrackProtocolDecoderTest extends ProtocolTest { MeitrackProtocolDecoder decoder = new MeitrackProtocolDecoder(new MeitrackProtocol()); + verifyNothing(decoder, buffer( + "$$D28,353358017784062,D03,OK*F3")); + verifyPosition(decoder, buffer( "$$A158,79007001520234,AAA,35,40.996370,-8.575065,150730184834,A,8,24,0,1,1.3,173,32573389,31405012,268|3|2BC0|250B,2000,|||0A2D|0000,00000001,,50,,,,,,,,,,,,,*4A"), position("2015-07-30 18:48:34.000", true, 40.99637, -8.57507)); diff --git a/test/org/traccar/protocol/MeitrackProtocolEncoderTest.java b/test/org/traccar/protocol/MeitrackProtocolEncoderTest.java new file mode 100644 index 000000000..64812c1da --- /dev/null +++ b/test/org/traccar/protocol/MeitrackProtocolEncoderTest.java @@ -0,0 +1,23 @@ +package org.traccar.protocol; + +import org.junit.Assert; +import org.junit.Test; +import org.traccar.ProtocolTest; +import org.traccar.model.Command; + +public class MeitrackProtocolEncoderTest extends ProtocolTest { + + @Test + public void testEncode() throws Exception { + + MeitrackProtocolEncoder encoder = new MeitrackProtocolEncoder(); + + Command command = new Command(); + command.setDeviceId(1); + command.setType(Command.TYPE_POSITION_SINGLE); + + Assert.assertEquals("@@Q25,123456789012345,A10*68\r\n", encoder.encodeCommand(command)); + + } + +} diff --git a/test/org/traccar/protocol/T55ProtocolDecoderTest.java b/test/org/traccar/protocol/T55ProtocolDecoderTest.java index 20d30ce73..4160758fc 100644 --- a/test/org/traccar/protocol/T55ProtocolDecoderTest.java +++ b/test/org/traccar/protocol/T55ProtocolDecoderTest.java @@ -10,6 +10,9 @@ public class T55ProtocolDecoderTest extends ProtocolTest { T55ProtocolDecoder decoder = new T55ProtocolDecoder(new T55Protocol()); + verifyPosition(decoder, text( + "$GPRMC,073501.000,A,1255.5125,N,07738.2948,E,0.00,0.53,080316,,,D*73,12,865733027593268,10011")); + verifyNothing(decoder, text( "$GPFID,ID123456ABC")); diff --git a/test/org/traccar/protocol/TelicFrameDecoderTest.java b/test/org/traccar/protocol/TelicFrameDecoderTest.java new file mode 100644 index 000000000..64f8b55e6 --- /dev/null +++ b/test/org/traccar/protocol/TelicFrameDecoderTest.java @@ -0,0 +1,30 @@ +package org.traccar.protocol; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.junit.Assert; +import org.junit.Test; +import org.traccar.ProtocolTest; + +import java.nio.ByteOrder; + +public class TelicFrameDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + TelicFrameDecoder decoder = new TelicFrameDecoder(); + + Assert.assertNull( + decoder.decode(null, null, binary(ByteOrder.LITTLE_ENDIAN, "00303032363937393238317c3233327c30337c30303230303430313000"))); + + Assert.assertEquals( + binary(ByteOrder.LITTLE_ENDIAN, "303032363937393238317c3233327c30337c303032303034303130"), + decoder.decode(null, null, binary(ByteOrder.LITTLE_ENDIAN, "303032363937393238317c3233327c30337c30303230303430313000"))); + + Assert.assertEquals( + binary(ByteOrder.LITTLE_ENDIAN, "3030323039373932383139392c3231303231363038313930302c302c3231303231363038313835392c3031333839333338352c34363635383639352c332c302c302c382c2c2c3534312c36313239382c2c303030302c30302c302c3139362c302c30343037"), + decoder.decode(null, null, binary(ByteOrder.LITTLE_ENDIAN, "650000003030323039373932383139392c3231303231363038313930302c302c3231303231363038313835392c3031333839333338352c34363635383639352c332c302c302c382c2c2c3534312c36313239382c2c303030302c30302c302c3139362c302c3034303700"))); + + } + +} diff --git a/test/org/traccar/protocol/TelicProtocolDecoderTest.java b/test/org/traccar/protocol/TelicProtocolDecoderTest.java new file mode 100644 index 000000000..a922c390d --- /dev/null +++ b/test/org/traccar/protocol/TelicProtocolDecoderTest.java @@ -0,0 +1,37 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class TelicProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + TelicProtocolDecoder decoder = new TelicProtocolDecoder(new TelicProtocol()); + + verifyPosition(decoder, text( + "002017297899,220216111100,0,220216111059,014306446,46626713,3,7,137,7,,,448,266643,,0000,00,0,206,0,0407")); + + verifyPosition(decoder, text( + "003097061325,210216112630,0,210216001405,246985,594078,3,0,283,12,,,23,4418669,,0010,00,117,0,0,0108")); + + verifyNothing(decoder, text( + "0026970613|248|01|004006011")); + + verifyPosition(decoder, text( + "032097061399,210216112800,0,210216112759,246912,594076,3,47,291,10,,,46,4419290,,0010,00,100,0,0,0108")); + + verifyPosition(decoder, text( + "002017297899,190216202500,0,190216202459,014221890,46492170,3,0,0,6,,,1034,43841,,0000,00,0,209,0,0407")); + + verifyPosition(decoder, text( + "182043672999,010100001301,0,270613041652,166653,475341,3,0,355,6,2,1,231,8112432,23201,01,00,217,0,0,0,0,7"), + position("2013-06-27 04:16:52.000", true, 47.53410, 16.66530)); + + verifyPosition(decoder, text( + "182043672999,010100001301,0,270613041652,166653,475341,3,0,355,6,2,1,231,8112432,23201,01,00,217,0,0,0,0,7")); + + } + +} diff --git a/test/org/traccar/protocol/TelikProtocolDecoderTest.java b/test/org/traccar/protocol/TelikProtocolDecoderTest.java deleted file mode 100644 index 180ab1b3d..000000000 --- a/test/org/traccar/protocol/TelikProtocolDecoderTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.traccar.protocol; - -import org.junit.Test; -import org.traccar.ProtocolTest; - -public class TelikProtocolDecoderTest extends ProtocolTest { - - @Test - public void testDecode() throws Exception { - - TelikProtocolDecoder decoder = new TelikProtocolDecoder(new TelikProtocol()); - - verifyNothing(decoder, text( - "0026436729|232|01|003002030")); - - verifyPosition(decoder, text( - "182043672999,010100001301,0,270613041652,166653,475341,3,0,355,6,2,1,231,8112432,23201,01,00,217,0,0,0,0,7"), - position("2013-06-27 04:16:52.000", true, 47.53410, 16.66530)); - - verifyPosition(decoder, text( - "182043672999,010100001301,0,270613041652,166653,475341,3,0,355,6,2,1,231,8112432,23201,01,00,217,0,0,0,0,7")); - - } - -} diff --git a/test/org/traccar/protocol/TotemProtocolDecoderTest.java b/test/org/traccar/protocol/TotemProtocolDecoderTest.java index de554b7bc..237e77612 100644 --- a/test/org/traccar/protocol/TotemProtocolDecoderTest.java +++ b/test/org/traccar/protocol/TotemProtocolDecoderTest.java @@ -10,6 +10,9 @@ public class TotemProtocolDecoderTest extends ProtocolTest { TotemProtocolDecoder decoder = new TotemProtocolDecoder(new TotemProtocol()); + verifyPosition(decoder, text( + "$$0108AA863835024426319|18004000160216160756411100007DCD0000111000000000.800000000316.3519N10228.5086E126522")); + // $$0128AA867521029231005|1880100015101802314842140000000000000000000000001AB48366093127600000.900000000806.1947N09818.4795E080355 verifyPosition(decoder, text( diff --git a/test/org/traccar/protocol/Tt8850ProtocolDecoderTest.java b/test/org/traccar/protocol/Tt8850ProtocolDecoderTest.java new file mode 100644 index 000000000..b2b7c4fe5 --- /dev/null +++ b/test/org/traccar/protocol/Tt8850ProtocolDecoderTest.java @@ -0,0 +1,33 @@ +package org.traccar.protocol; + +import org.junit.Test; +import org.traccar.ProtocolTest; + +public class Tt8850ProtocolDecoderTest extends ProtocolTest { + + @Test + public void testDecode() throws Exception { + + Tt8850ProtocolDecoder decoder = new Tt8850ProtocolDecoder(new Tt8850Protocol()); + + verifyPosition(decoder, text( + "\u0000\u0004,005F,0,GTFRI,020100,135790246811220,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,90,20090214093254,11F0")); + + verifyPosition(decoder, text( + "\u0000\u0004,005F,0,GTGEO,020100,135790246811220,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,90,20090214093254,11F0")); + + verifyPosition(decoder, text( + "\u0000\u0004,005F,0,GTNMR,020100,135790246811220,,0,0,1,1,4.3,92,70.0,121.354335,31.222073,20090214013254,0460,0000,18d8,6141,90,20090214093254,11F0")); + + verifyPosition(decoder, text( + "\u0000\u0004,0017,0,GTNMR,,867844000400914,,0,41,1,2,0.0,0,1504.2,-75.569202,6.242850,20150404162835,,,,,97,20150404162836,05EF")); + + verifyNothing(decoder, text( + "\u0000\u0004,0017,0,GTPNA,,867844000400914,,0,0,1,0,,,,0,0,,,,,,99,20150404190153,0601")); + + verifyPosition(decoder, text( + "\u0000\u0004,0017,0,GTEPN,,867844000400914,,0,0,1,0,0.0,0,1717.4,-75.598445,6.278578,20150405003116,,,,,95,20150405003358,0607")); + + } + +} diff --git a/test/org/traccar/protocol/UlbotechProtocolDecoderTest.java b/test/org/traccar/protocol/UlbotechProtocolDecoderTest.java index e87e95efe..edee9930d 100644 --- a/test/org/traccar/protocol/UlbotechProtocolDecoderTest.java +++ b/test/org/traccar/protocol/UlbotechProtocolDecoderTest.java @@ -10,6 +10,9 @@ public class UlbotechProtocolDecoderTest extends ProtocolTest { UlbotechProtocolDecoder decoder = new UlbotechProtocolDecoder(new UlbotechProtocol()); + verifyPosition(decoder, binary( + "f8010103515790566431569e5fbb9d010e015ee2b906bde4a000000000009f03040a4000000404000115fe05060340173f22030711310583410c0000310d00312f834131000008040000b78c09077320290082c021100101120af8")); + verifyNothing(decoder, binary( "2a545330312c33353430343330353133383934363023")); diff --git a/test/org/traccar/protocol/XexunProtocolDecoderTest.java b/test/org/traccar/protocol/XexunProtocolDecoderTest.java index 76c516caf..ac90daec5 100644 --- a/test/org/traccar/protocol/XexunProtocolDecoderTest.java +++ b/test/org/traccar/protocol/XexunProtocolDecoderTest.java @@ -12,6 +12,12 @@ public class XexunProtocolDecoderTest extends ProtocolTest { XexunProtocolDecoder decoder = new XexunProtocolDecoder(new XexunProtocol(), false); verifyPosition(decoder, text( + "GPRMC,113518.000,A,5303.4150,N,10.2368,E,60.73,207.42,260216,00,0000.0,A*74,F,,imei:351525018007873,")); + + verifyPosition(decoder, text( + "GPRMC,215853.000,A,5304.9600,N,6.7907,E,1.43,80.67,250216,00,0000.0,A*47,F,,imei:351525018007873,")); + + verifyPosition(decoder, text( "GPRMC,121535.000,A,5417.2666,N,04822.1264,E,1.452,30.42,031014,0.0,A*4D\r\n,L,imei:355227042011730,")); verifyPosition(decoder, text( diff --git a/tools/test-integration.py b/tools/test-integration.py index 928464895..3f1f519be 100755 --- a/tools/test-integration.py +++ b/tools/test-integration.py @@ -27,12 +27,11 @@ messages = { 'meitrack' : '$$d138,123456789012345,AAA,35,60.000000,130.000000,120101122000,A,7,18,0,0,0,49,3800,24965,510|10|0081|4F4F,0000,000D|0010|0012|0963|0000,,*BF\r\n', 'megastek' : 'STX,102110830074542,$GPRMC,122400.000,A,6000.0000,N,13000.0000,E,0.00,0.00,010112,,,A*64,F,LowBattery,imei:123456789012345,03,113.1,Battery=24%,,1,460,01,2531,647E;57\r\n', 'gpsgate' : '$FRLIN,IMEI,123456789012345,*7B\r\n$GPRMC,122600.000,A,6000.00000,N,13000.00000,E,0.000,0.0,010112,,*0A\r\n', - 'tlt2h' : '#123456789012345#V500#0000#AUTO#1\r\n#$GPRMC,123000.000,A,6000.0000,N,13000.0000,E,0.00,0.00,010112,,,D*70\r\n##', + 'tlt2h' : '#123456789012345#V500#0000#AUTO#1\r\n#$GPRMC,123000.000,A,6000.0000,N,13000.0000,E,0.00,0.00,010112,,,D*70\r\n##\r\n', 'taip' : '>REV481669045060+6000000-1300000000000012;ID=123456789012345<', 'wondex' : '123456789012345,20120101123200,130.000000,60.000000,0,000,0,0,2\r\n', 'ywt' : '%RP,123456789012345:0,120101123500,E130.000000,N60.000000,,0,0,4,0,00\r\n', 'tk102' : '[!0000000081r(123456789012345,TK102-W998_01_V1.1.001_130219,255,001,255,001,0,100,100,0,internet,0000,0000,0,0,255,0,4,1,11,00)][=00000000836(ITV123600A6000.0000N13000.0000E000.00001011210010000)]', - 'intellitrac' : '123456789012345,20120101123700,130.000000,60.000000,0,0,0,7,0,11,15\r\n', 'wialon' : '#L#123456789012345;test\r\n#SD#010112;123900;6000.0000;N;13000.0000;E;0;0;0;4\r\n', 'carscop' : '*040331141830UB05123456789012345010112A6000.0000N13000.0000E000.0124000000.0000000000L000000^', 'manpower' : 'simei:123456789012345,,,tracker,51,24,1.73,120101124200,A,6000.0000,N,13000.0000,E,0.00,28B9,1DED,425,01,1x0x0*0x1*60x+2,en-us,;', @@ -50,7 +49,7 @@ messages = { 'haicom' : '$GPRS123456789012345,T100001,150618,230031,5402267400332464,0004,2014,000001,,,1,00#V040*', 'box' : 'H,BT,123456789012345,081028142432,F5813D19,6D6E6DC2\rL,081028142429,G,52.51084,-1.70849,0,170,0,1,0\r', 'freedom' : 'IMEI,123456789012345,2014/05/22, 20:49:32, N, Lat:4725.9624, E, Lon:01912.5483, Spd:5.05\r\n', - 'telik' : '182012345699,010100001301,0,270613041652,166653,475341,3,0,355,6,2,1,231,8112432,23201,01,00,217,0,0,0,0,7\0', + 'telic' : '182012345699,010100001301,0,270613041652,166653,475341,3,0,355,6,2,1,231,8112432,23201,01,00,217,0,0,0,0,7\0', 'trackbox' : 'a=connect&v=11&i=123456789012345\r\n183457.999,5126.0247N,00002.8686E,5.2,70.4,3,57.63,32.11,17.32,150507,05\r\n', 'visiontek' : '$1,AP09BU9397,123456789012345,20,06,14,15,03,28,17267339N,078279407E,060.0,073,0550,11,0,1,0,0,1,1,26,A,0000000000#', 'tr900' : '>123456,4,1,150626,131252,W05830.2978,S3137.2783,,00,348,18,00,003-000,0,3,11111011*3b!\r\n', diff --git a/web/app.css b/web/app.css index eb0fdf136..0dbf94ca6 100644 --- a/web/app.css +++ b/web/app.css @@ -8,6 +8,10 @@ background-color: rgba(255, 84, 104, 0.3); } +.x-tree-icon { + display: none !important; +} + .state-indicator { position: absolute; top: -999em; diff --git a/web/app/Application.js b/web/app/Application.js index fc4344a08..69ce8f891 100644 --- a/web/app/Application.js +++ b/web/app/Application.js @@ -26,6 +26,7 @@ Ext.define('Traccar.Application', { models: [ 'Server', 'User', + 'Group', 'Device', 'Position', 'Attribute', @@ -33,7 +34,9 @@ Ext.define('Traccar.Application', { ], stores: [ + 'Groups', 'Devices', + 'AllGroups', 'AllDevices', 'Positions', 'LatestPositions', diff --git a/web/app/controller/Root.js b/web/app/controller/Root.js index 23ca94972..5bd567619 100644 --- a/web/app/controller/Root.js +++ b/web/app/controller/Root.js @@ -73,6 +73,7 @@ Ext.define('Traccar.controller.Root', { }, loadApp: function () { + Ext.getStore('Groups').load(); Ext.getStore('Devices').load(); Ext.get('attribution').remove(); if (this.isPhone) { @@ -86,7 +87,7 @@ Ext.define('Traccar.controller.Root', { asyncUpdate: function (first) { var protocol, socket, self = this; protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; - socket = new WebSocket(protocol + window.location.host + '/api/socket'); + socket = new WebSocket(protocol + '//' + window.location.host + '/api/socket'); socket.onclose = function (event) { self.asyncUpdate(false); diff --git a/web/app/model/Device.js b/web/app/model/Device.js index b8434d6ad..588d53c1f 100644 --- a/web/app/model/Device.js +++ b/web/app/model/Device.js @@ -33,6 +33,9 @@ Ext.define('Traccar.model.Device', { }, { name: 'lastUpdate', type: 'date', - dateWriteFormat: 'c' + dateFormat: 'c' + }, { + name: 'groupId', + type: 'int' }] }); diff --git a/web/app/model/Group.js b/web/app/model/Group.js new file mode 100644 index 000000000..a28897feb --- /dev/null +++ b/web/app/model/Group.js @@ -0,0 +1,31 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.model.Group', { + extend: 'Ext.data.Model', + identifier: 'negative', + + fields: [{ + name: 'id', + type: 'int' + }, { + name: 'name', + type: 'string' + }, { + name: 'groupId', + type: 'int' + }] +}); diff --git a/web/app/model/Position.js b/web/app/model/Position.js index 365b06115..e559a7eab 100644 --- a/web/app/model/Position.js +++ b/web/app/model/Position.js @@ -29,13 +29,16 @@ Ext.define('Traccar.model.Position', { type: 'int' }, { name: 'serverTime', - type: 'date' + type: 'date', + dateFormat: 'c' }, { name: 'deviceTime', - type: 'date' + type: 'date', + dateFormat: 'c' }, { name: 'fixTime', - type: 'date' + type: 'date', + dateFormat: 'c' }, { name: 'valid', type: 'boolean' diff --git a/web/app/store/AllGroups.js b/web/app/store/AllGroups.js new file mode 100644 index 000000000..880ccc8f5 --- /dev/null +++ b/web/app/store/AllGroups.js @@ -0,0 +1,28 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.store.AllGroups', { + extend: 'Ext.data.Store', + model: 'Traccar.model.Group', + + proxy: { + type: 'rest', + url: '/api/groups', + extraParams: { + all: true + } + } +}); diff --git a/web/app/store/Groups.js b/web/app/store/Groups.js new file mode 100644 index 000000000..938abed64 --- /dev/null +++ b/web/app/store/Groups.js @@ -0,0 +1,28 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.store.Groups', { + extend: 'Ext.data.Store', + model: 'Traccar.model.Group', + + proxy: { + type: 'rest', + url: '/api/groups', + writer: { + writeAllFields: true + } + } +}); diff --git a/web/app/view/DeviceDialog.js b/web/app/view/DeviceDialog.js index c42af95d0..4a22ca008 100644 --- a/web/app/view/DeviceDialog.js +++ b/web/app/view/DeviceDialog.js @@ -29,13 +29,20 @@ Ext.define('Traccar.view.DeviceDialog', { items: [{ xtype: 'textfield', name: 'name', - fieldLabel: Strings.deviceName, + fieldLabel: Strings.sharedName, allowBlank: false }, { xtype: 'textfield', name: 'uniqueId', fieldLabel: Strings.deviceIdentifier, allowBlank: false + }, { + xtype: 'combobox', + name: 'groupId', + fieldLabel: Strings.groupParent, + store: 'Groups', + displayField: 'name', + valueField: 'id' }] } }); diff --git a/web/app/view/Devices.js b/web/app/view/Devices.js index 66c4e813b..ebe3ca195 100644 --- a/web/app/view/Devices.js +++ b/web/app/view/Devices.js @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -15,7 +15,7 @@ */ Ext.define('Traccar.view.Devices', { - extend: 'Ext.grid.Panel', + extend: 'Ext.tree.Panel', xtype: 'devicesView', requires: [ @@ -25,7 +25,17 @@ Ext.define('Traccar.view.Devices', { ], controller: 'devices', - store: 'Devices', + rootVisible: false, + store: { + type: 'tree', + parentIdProperty: 'groupId', + proxy: { + type: 'memory', + reader: { + type: 'json' + } + } + }, title: Strings.deviceTitle, selType: 'rowmodel', @@ -53,11 +63,13 @@ Ext.define('Traccar.view.Devices', { }, listeners: { - selectionchange: 'onSelectionChange' + selectionchange: 'onSelectionChange', + beforeselect: 'onBeforeSelect' }, columns: [{ - text: Strings.deviceName, + xtype: 'treecolumn', + text: Strings.sharedName, dataIndex: 'name', flex: 1 }, { @@ -65,19 +77,20 @@ Ext.define('Traccar.view.Devices', { dataIndex: 'lastUpdate', flex: 1, renderer: function (value, metaData, record) { - var status = record.get('status'); - switch (status) { - case 'online': - metaData.tdCls = 'status-color-online'; - break; - case 'offline': - metaData.tdCls = 'status-color-offline'; - break; - default: - metaData.tdCls = 'status-color-unknown'; - break; + if (record.get('leaf')) { + switch (record.get('status')) { + case 'online': + metaData.tdCls = 'status-color-online'; + break; + case 'offline': + metaData.tdCls = 'status-color-offline'; + break; + default: + metaData.tdCls = 'status-color-unknown'; + break; + } + return Ext.Date.format(value, Traccar.Style.dateTimeFormat); } - return Ext.Date.format(value, Traccar.Style.dateTimeFormat); } }] diff --git a/web/app/view/DevicesController.js b/web/app/view/DevicesController.js index 8c6c72725..4d8231d94 100644 --- a/web/app/view/DevicesController.js +++ b/web/app/view/DevicesController.js @@ -1,5 +1,5 @@ /* - * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) + * Copyright 2015 - 2016 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. @@ -30,9 +30,54 @@ Ext.define('Traccar.view.DevicesController', { selectDevice: 'selectDevice', selectReport: 'selectReport' } + }, + store: { + '#Groups': { + datachanged: 'storeUpdate', + update: 'storeUpdate' + }, + '#Devices': { + datachanged: 'storeUpdate', + update: 'storeUpdate' + } } } }, + + storeUpdate: function () { + var nodes = []; + Ext.getStore('Groups').each(function (record) { + var groupId, node = { + id: 'g' + record.get('id'), + original: record, + name: record.get('name') + }; + groupId = record.get('groupId'); + if (groupId !== 0) { + node.groupId = 'g' + groupId; + } + nodes.push(node); + }, this); + Ext.getStore('Devices').each(function (record) { + var groupId, node = { + id: 'd' + record.get('id'), + original: record, + name: record.get('name'), + status: record.get('status'), + lastUpdate: record.get('lastUpdate'), + leaf: true + }; + groupId = record.get('groupId'); + if (groupId !== 0) { + node.groupId = 'g' + groupId; + } + nodes.push(node); + }, this); + this.getView().getStore().getProxy().setData(nodes); + this.getView().getStore().load(); + this.getView().expandAll(); + }, + init: function () { var readonly = Traccar.app.getServer().get('readonly') && !Traccar.app.getUser().get('admin'); this.lookupReference('toolbarAddButton').setVisible(!readonly); @@ -43,7 +88,7 @@ Ext.define('Traccar.view.DevicesController', { onAddClick: function () { var device, dialog; device = Ext.create('Traccar.model.Device'); - device.store = this.getView().getStore(); + device.store = Ext.getStore('Devices'); dialog = Ext.create('Traccar.view.DeviceDialog'); dialog.down('form').loadRecord(device); dialog.show(); @@ -51,14 +96,14 @@ Ext.define('Traccar.view.DevicesController', { onEditClick: function () { var device, dialog; - device = this.getView().getSelectionModel().getSelection()[0]; + device = this.getView().getSelectionModel().getSelection()[0].get('original'); dialog = Ext.create('Traccar.view.DeviceDialog'); dialog.down('form').loadRecord(device); dialog.show(); }, onRemoveClick: function () { - var device = this.getView().getSelectionModel().getSelection()[0]; + var device = this.getView().getSelectionModel().getSelection()[0].get('original'); Ext.Msg.show({ title: Strings.deviceDialog, message: Strings.sharedRemoveConfirm, @@ -80,7 +125,7 @@ Ext.define('Traccar.view.DevicesController', { onCommandClick: function () { var device, command, dialog; - device = this.getView().getSelectionModel().getSelection()[0]; + device = this.getView().getSelectionModel().getSelection()[0].get('original'); command = Ext.create('Traccar.model.Command'); command.set('deviceId', device.get('id')); dialog = Ext.create('Traccar.view.CommandDialog'); @@ -89,17 +134,22 @@ Ext.define('Traccar.view.DevicesController', { }, onSelectionChange: function (selected) { - var empty = selected.getCount() === 0; + var empty = selected.getCount() === 0 || !this.getView().getSelectionModel().getSelection()[0].get('leaf'); this.lookupReference('toolbarEditButton').setDisabled(empty); this.lookupReference('toolbarRemoveButton').setDisabled(empty); this.lookupReference('deviceCommandButton').setDisabled(empty); if (!empty) { - this.fireEvent('selectDevice', selected.getLastSelected(), true); + this.fireEvent('selectDevice', selected.getLastSelected().get('original'), true); } }, + onBeforeSelect: function (row, record) { + return record.get('leaf'); + }, + selectDevice: function (device, center) { - this.getView().getSelectionModel().select([device], false, true); + var node = this.getView().getStore().getNodeById('d' + device.get('id')); + this.getView().getSelectionModel().select([node], false, true); }, selectReport: function (position) { diff --git a/web/app/view/GroupDialog.js b/web/app/view/GroupDialog.js new file mode 100644 index 000000000..2cca61ef5 --- /dev/null +++ b/web/app/view/GroupDialog.js @@ -0,0 +1,43 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.view.GroupDialog', { + extend: 'Traccar.view.BaseEditDialog', + + requires: [ + 'Traccar.view.BaseEditDialogController' + ], + + controller: 'baseEditDialog', + title: Strings.groupDialog, + + items: { + xtype: 'form', + items: [{ + xtype: 'textfield', + name: 'name', + fieldLabel: Strings.sharedName, + allowBlank: false + }, { + xtype: 'combobox', + name: 'groupId', + fieldLabel: Strings.groupParent, + store: 'Groups', + displayField: 'name', + valueField: 'id' + }] + } +}); diff --git a/web/app/view/Groups.js b/web/app/view/Groups.js new file mode 100644 index 000000000..8404c59a9 --- /dev/null +++ b/web/app/view/Groups.js @@ -0,0 +1,44 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.view.Groups', { + extend: 'Ext.grid.Panel', + xtype: 'groupsView', + + requires: [ + 'Traccar.view.GroupsController', + 'Traccar.view.EditToolbar' + ], + + controller: 'groups', + store: 'Groups', + + selType: 'rowmodel', + + tbar: { + xtype: 'editToolbar' + }, + + listeners: { + selectionchange: 'onSelectionChange' + }, + + columns: [{ + text: Strings.sharedName, + dataIndex: 'name', + flex: 1 + }] +}); diff --git a/web/app/view/GroupsController.js b/web/app/view/GroupsController.js new file mode 100644 index 000000000..6cc568ea2 --- /dev/null +++ b/web/app/view/GroupsController.js @@ -0,0 +1,63 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.view.GroupsController', { + extend: 'Ext.app.ViewController', + alias: 'controller.groups', + + onAddClick: function () { + var group, dialog; + group = Ext.create('Traccar.model.Group'); + group.store = this.getView().getStore(); + dialog = Ext.create('Traccar.view.GroupDialog'); + dialog.down('form').loadRecord(group); + dialog.show(); + }, + + onEditClick: function () { + var group, dialog; + group = this.getView().getSelectionModel().getSelection()[0]; + dialog = Ext.create('Traccar.view.GroupDialog'); + dialog.down('form').loadRecord(group); + dialog.show(); + }, + + onRemoveClick: function () { + var group = this.getView().getSelectionModel().getSelection()[0]; + Ext.Msg.show({ + title: Strings.groupDialog, + message: Strings.sharedRemoveConfirm, + buttons: Ext.Msg.YESNO, + buttonText: { + yes: Strings.sharedRemove, + no: Strings.sharedCancel + }, + fn: function (btn) { + var store = Ext.getStore('Groups'); + if (btn === 'yes') { + store.remove(group); + store.sync(); + } + } + }); + }, + + onSelectionChange: function (selected) { + var disabled = selected.length > 0; + this.lookupReference('toolbarEditButton').setDisabled(disabled); + this.lookupReference('toolbarRemoveButton').setDisabled(disabled); + } +}); diff --git a/web/app/view/MapController.js b/web/app/view/MapController.js index ce420c2ec..918f81390 100644 --- a/web/app/view/MapController.js +++ b/web/app/view/MapController.js @@ -113,29 +113,31 @@ Ext.define('Traccar.view.MapController', { deviceId = position.get('deviceId'); device = Ext.getStore('Devices').findRecord('id', deviceId, 0, false, false, true); - geometry = new ol.geom.Point(ol.proj.fromLonLat([ - position.get('longitude'), - position.get('latitude') - ])); - - if (deviceId in this.latestMarkers) { - marker = this.latestMarkers[deviceId]; - marker.setGeometry(geometry); - } else { - marker = new ol.Feature(geometry); - marker.set('record', device); - this.latestMarkers[deviceId] = marker; - this.getView().getLatestSource().addFeature(marker); - - style = this.getLatestMarker(this.getDeviceColor(device)); - style.getText().setText(device.get('name')); - marker.setStyle(style); - } + if (device) { + geometry = new ol.geom.Point(ol.proj.fromLonLat([ + position.get('longitude'), + position.get('latitude') + ])); + + if (deviceId in this.latestMarkers) { + marker = this.latestMarkers[deviceId]; + marker.setGeometry(geometry); + } else { + marker = new ol.Feature(geometry); + marker.set('record', device); + this.latestMarkers[deviceId] = marker; + this.getView().getLatestSource().addFeature(marker); + + style = this.getLatestMarker(this.getDeviceColor(device)); + style.getText().setText(device.get('name')); + marker.setStyle(style); + } - marker.getStyle().getImage().setRotation(position.get('course') * Math.PI / 180); + marker.getStyle().getImage().setRotation(position.get('course') * Math.PI / 180); - if (marker === this.selectedMarker && this.followSelected()) { - this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates()); + if (marker === this.selectedMarker && this.followSelected()) { + this.getView().getMapView().setCenter(marker.getGeometry().getCoordinates()); + } } } }, diff --git a/web/app/view/Register.js b/web/app/view/Register.js index 7c2881d62..198e10b8f 100644 --- a/web/app/view/Register.js +++ b/web/app/view/Register.js @@ -33,7 +33,7 @@ Ext.define('Traccar.view.Register', { items: [{ xtype: 'textfield', name: 'name', - fieldLabel: Strings.userName, + fieldLabel: Strings.sharedName, allowBlank: false }, { xtype: 'textfield', diff --git a/web/app/view/SettingsMenu.js b/web/app/view/SettingsMenu.js index c828b37af..1ada94359 100644 --- a/web/app/view/SettingsMenu.js +++ b/web/app/view/SettingsMenu.js @@ -32,6 +32,9 @@ Ext.define('Traccar.view.SettingsMenu', { text: Strings.settingsUser, handler: 'onUserClick' }, { + text: Strings.settingsGroups, + handler: 'onGroupsClick' + }, { text: Strings.settingsServer, hidden: true, handler: 'onServerClick', diff --git a/web/app/view/SettingsMenuController.js b/web/app/view/SettingsMenuController.js index c52f0a75e..6d767e3a9 100644 --- a/web/app/view/SettingsMenuController.js +++ b/web/app/view/SettingsMenuController.js @@ -23,6 +23,7 @@ Ext.define('Traccar.view.SettingsMenuController', { 'Traccar.view.UserDialog', 'Traccar.view.ServerDialog', 'Traccar.view.Users', + 'Traccar.view.Groups', 'Traccar.view.BaseWindow' ], @@ -39,6 +40,16 @@ Ext.define('Traccar.view.SettingsMenuController', { dialog.show(); }, + onGroupsClick: function () { + Ext.create('Traccar.view.BaseWindow', { + title: Strings.settingsGroups, + modal: false, + items: { + xtype: 'groupsView' + } + }).show(); + }, + onServerClick: function () { var dialog = Ext.create('Traccar.view.ServerDialog'); dialog.down('form').loadRecord(Traccar.app.getServer()); diff --git a/web/app/view/UserDevices.js b/web/app/view/UserDevices.js index f9ab48266..fe16dd93a 100644 --- a/web/app/view/UserDevices.js +++ b/web/app/view/UserDevices.js @@ -37,10 +37,12 @@ Ext.define('Traccar.view.UserDevices', { }, columns: [{ - text: Strings.deviceName, - dataIndex: 'name', flex: 1 + text: Strings.sharedName, + dataIndex: 'name', + flex: 1 }, { text: Strings.deviceIdentifier, - dataIndex: 'uniqueId', flex: 1 + dataIndex: 'uniqueId', + flex: 1 }] }); diff --git a/web/app/view/UserDevicesController.js b/web/app/view/UserDevicesController.js index 4f013fd64..e5dbbdbb8 100644 --- a/web/app/view/UserDevicesController.js +++ b/web/app/view/UserDevicesController.js @@ -47,7 +47,7 @@ Ext.define('Traccar.view.UserDevicesController', { onBeforeSelect: function (object, record, index) { Ext.Ajax.request({ scope: this, - url: '/api/permissions', + url: '/api/permissions/devices', jsonData: { userId: this.userId, deviceId: record.getData().id @@ -64,7 +64,7 @@ Ext.define('Traccar.view.UserDevicesController', { Ext.Ajax.request({ scope: this, method: 'DELETE', - url: '/api/permissions', + url: '/api/permissions/devices', jsonData: { userId: this.userId, deviceId: record.getData().id diff --git a/web/app/view/UserDialog.js b/web/app/view/UserDialog.js index 783ddd159..c1ed2fece 100644 --- a/web/app/view/UserDialog.js +++ b/web/app/view/UserDialog.js @@ -30,7 +30,7 @@ Ext.define('Traccar.view.UserDialog', { items: [{ xtype: 'textfield', name: 'name', - fieldLabel: Strings.userName + fieldLabel: Strings.sharedName }, { xtype: 'textfield', name: 'email', diff --git a/web/app/view/UserGroups.js b/web/app/view/UserGroups.js new file mode 100644 index 000000000..cb0f0bd5d --- /dev/null +++ b/web/app/view/UserGroups.js @@ -0,0 +1,44 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.view.UserGroups', { + extend: 'Ext.grid.Panel', + xtype: 'userGroupsView', + + requires: [ + 'Traccar.view.UserGroupsController' + ], + + controller: 'userGroups', + store: 'AllGroups', + + selModel: { + selType: 'checkboxmodel', + checkOnly: true, + showHeaderCheckbox: false + }, + + listeners: { + beforedeselect: 'onBeforeDeselect', + beforeselect: 'onBeforeSelect' + }, + + columns: [{ + text: Strings.sharedName, + dataIndex: 'name', + flex: 1 + }] +}); diff --git a/web/app/view/UserGroupsController.js b/web/app/view/UserGroupsController.js new file mode 100644 index 000000000..662508f0a --- /dev/null +++ b/web/app/view/UserGroupsController.js @@ -0,0 +1,79 @@ +/* + * Copyright 2016 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. + */ + +Ext.define('Traccar.view.UserGroupsController', { + extend: 'Ext.app.ViewController', + alias: 'controller.userGroups', + + init: function () { + this.userId = this.getView().user.getData().id; + this.getView().getStore().load({ + scope: this, + callback: function (records, operation, success) { + var userStore = Ext.create('Traccar.store.Groups'); + + userStore.load({ + params: { + userId: this.userId + }, + scope: this, + callback: function (records, operation, success) { + var i, index; + if (success) { + for (i = 0; i < records.length; i++) { + index = this.getView().getStore().find('id', records[i].getData().id); + this.getView().getSelectionModel().select(index, true, true); + } + } + } + }); + } + }); + }, + + onBeforeSelect: function (object, record, index) { + Ext.Ajax.request({ + scope: this, + url: '/api/permissions/groups', + jsonData: { + userId: this.userId, + groupId: record.getData().id + }, + callback: function (options, success, response) { + if (!success) { + Traccar.app.showError(response); + } + } + }); + }, + + onBeforeDeselect: function (object, record, index) { + Ext.Ajax.request({ + scope: this, + method: 'DELETE', + url: '/api/permissions/groups', + jsonData: { + userId: this.userId, + groupId: record.getData().id + }, + callback: function (options, success, response) { + if (!success) { + Traccar.app.showError(response); + } + } + }); + } +}); diff --git a/web/app/view/Users.js b/web/app/view/Users.js index f4ef332b4..408a70885 100644 --- a/web/app/view/Users.js +++ b/web/app/view/Users.js @@ -35,6 +35,11 @@ Ext.define('Traccar.view.Users', { disabled: true, handler: 'onDevicesClick', reference: 'userDevicesButton' + }, { + text: Strings.settingsGroups, + disabled: true, + handler: 'onGroupsClick', + reference: 'userGroupsButton' }] }, @@ -43,7 +48,7 @@ Ext.define('Traccar.view.Users', { }, columns: [{ - text: Strings.userName, + text: Strings.sharedName, dataIndex: 'name', flex: 1 }, { diff --git a/web/app/view/UsersController.js b/web/app/view/UsersController.js index 3d0e813e8..c48f57cf4 100644 --- a/web/app/view/UsersController.js +++ b/web/app/view/UsersController.js @@ -21,6 +21,7 @@ Ext.define('Traccar.view.UsersController', { requires: [ 'Traccar.view.UserDialog', 'Traccar.view.UserDevices', + 'Traccar.view.UserGroups', 'Traccar.view.BaseWindow' ], @@ -75,10 +76,22 @@ Ext.define('Traccar.view.UsersController', { }).show(); }, + onGroupsClick: function () { + var user = this.getView().getSelectionModel().getSelection()[0]; + Ext.create('Traccar.view.BaseWindow', { + title: Strings.settingsGroups, + items: { + xtype: 'userGroupsView', + user: user + } + }).show(); + }, + onSelectionChange: function (selected) { var disabled = selected.length > 0; this.lookupReference('toolbarEditButton').setDisabled(disabled); this.lookupReference('toolbarRemoveButton').setDisabled(disabled); this.lookupReference('userDevicesButton').setDisabled(disabled); + this.lookupReference('userGroupsButton').setDisabled(disabled); } }); diff --git a/web/arrowstyle.js b/web/arrowstyle.js index c1eb88909..ad0ba08ee 100644 --- a/web/arrowstyle.js +++ b/web/arrowstyle.js @@ -1,12 +1,10 @@ goog.provide('ol.style.Arrow'); goog.require('goog.asserts'); -goog.require('goog.dom'); goog.require('ol'); goog.require('ol.color'); goog.require('ol.has'); goog.require('ol.render.canvas'); -goog.require('ol.structs.IHasChecksum'); goog.require('ol.style.AtlasManager'); goog.require('ol.style.Fill'); goog.require('ol.style.Image'); @@ -22,7 +20,6 @@ goog.require('ol.style.Stroke'); * @constructor * @param {olx.style.ArrowOptions} options Options. * @extends {ol.style.Image} - * @implements {ol.structs.IHasChecksum} * @api */ ol.style.Arrow = function(options) { @@ -119,9 +116,15 @@ ol.style.Arrow = function(options) { var snapToPixel = options.snapToPixel !== undefined ? options.snapToPixel : true; + /** + * @type {boolean} + */ + var rotateWithView = options.rotateWithView !== undefined ? + options.rotateWithView : false; + goog.base(this, { opacity: 1, - rotateWithView: false, + rotateWithView: rotateWithView, rotation: options.rotation !== undefined ? options.rotation : 0, scale: 1, snapToPixel: snapToPixel @@ -283,7 +286,7 @@ ol.style.Arrow.RenderOptions; /** * @private - * @param {ol.style.AtlasManager|undefined} atlasManager + * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager. */ ol.style.Arrow.prototype.render_ = function(atlasManager) { var imageSize; @@ -334,7 +337,7 @@ ol.style.Arrow.prototype.render_ = function(atlasManager) { if (atlasManager === undefined) { // no atlas manager is used, create a new canvas this.canvas_ = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement('CANVAS')); + (document.createElement('CANVAS')); this.canvas_.height = size; this.canvas_.width = size; @@ -357,12 +360,12 @@ ol.style.Arrow.prototype.render_ = function(atlasManager) { if (hasCustomHitDetectionImage) { // render the hit-detection image into a separate atlas image renderHitDetectionCallback = - goog.bind(this.drawHitDetectionCanvas_, this, renderOptions); + this.drawHitDetectionCanvas_.bind(this, renderOptions); } var id = this.getChecksum(); var info = atlasManager.add( - id, size, size, goog.bind(this.draw_, this, renderOptions), + id, size, size, this.draw_.bind(this, renderOptions), renderHitDetectionCallback); goog.asserts.assert(info, 'arrow size is too large'); @@ -388,8 +391,8 @@ ol.style.Arrow.prototype.render_ = function(atlasManager) { /** * @private - * @param {ol.style.Arrow.RenderOptions} renderOptions - * @param {CanvasRenderingContext2D} context + * @param {ol.style.Arrow.RenderOptions} renderOptions Render options. + * @param {CanvasRenderingContext2D} context The rendering context. * @param {number} x The origin for the symbol (x). * @param {number} y The origin for the symbol (y). */ @@ -419,7 +422,7 @@ ol.style.Arrow.prototype.draw_ = function(renderOptions, context, x, y) { lineTo(this.radius_, 0); if (this.fill_) { - context.fillStyle = ol.color.asString(this.fill_.getColor()); + context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor()); context.fill(); } if (this.stroke_) { @@ -439,10 +442,9 @@ ol.style.Arrow.prototype.draw_ = function(renderOptions, context, x, y) { /** * @private - * @param {ol.style.Arrow.RenderOptions} renderOptions + * @param {ol.style.Arrow.RenderOptions} renderOptions Render options. */ -ol.style.Arrow.prototype.createHitDetectionCanvas_ = - function(renderOptions) { +ol.style.Arrow.prototype.createHitDetectionCanvas_ = function(renderOptions) { this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size]; if (this.fill_) { this.hitDetectionCanvas_ = this.canvas_; @@ -452,7 +454,7 @@ ol.style.Arrow.prototype.createHitDetectionCanvas_ = // if no fill style is set, create an extra hit-detection image with a // default fill style this.hitDetectionCanvas_ = /** @type {HTMLCanvasElement} */ - (goog.dom.createElement('CANVAS')); + (document.createElement('CANVAS')); var canvas = this.hitDetectionCanvas_; canvas.height = renderOptions.size; @@ -466,13 +468,12 @@ ol.style.Arrow.prototype.createHitDetectionCanvas_ = /** * @private - * @param {ol.style.Arrow.RenderOptions} renderOptions - * @param {CanvasRenderingContext2D} context + * @param {ol.style.Arrow.RenderOptions} renderOptions Render options. + * @param {CanvasRenderingContext2D} context The context. * @param {number} x The origin for the symbol (x). * @param {number} y The origin for the symbol (y). */ -ol.style.Arrow.prototype.drawHitDetectionCanvas_ = - function(renderOptions, context, x, y) { +ol.style.Arrow.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) { var innerRadius = this.radius_ / Math.sin(Math.PI - this.backAngle_ / 2) * Math.sin(this.backAngle_ / 2 - this.frontAngle_); @@ -511,7 +512,7 @@ ol.style.Arrow.prototype.drawHitDetectionCanvas_ = /** - * @inheritDoc + * @return {string} The checksum. */ ol.style.Arrow.prototype.getChecksum = function() { var strokeChecksum = this.stroke_ ? diff --git a/web/debug.html b/web/debug.html index 3608930c0..351bb9f9f 100644 --- a/web/debug.html +++ b/web/debug.html @@ -5,7 +5,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>Traccar</title> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/resources/theme-neptune-all.css"> -<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol.css"> +<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol.css"> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"> <link rel="stylesheet" href="app.css"> </head> @@ -14,8 +14,11 @@ <div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div> <script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all-debug.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/theme-neptune.js"></script> -<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol-debug.js"></script> +<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol-debug.js"></script> <script src="arrowstyle.js"></script> +<script type="text/javascript"> +Ext.Loader.setConfig({ disableCaching: false }); +</script> <script src="locale.js"></script> <script src="app.js"></script> </body> diff --git a/web/l10n/en.json b/web/l10n/en.json index ec8018863..cabd76362 100644 --- a/web/l10n/en.json +++ b/web/l10n/en.json @@ -13,10 +13,10 @@ "sharedHour": "Hour", "sharedMinute": "Minute", "sharedSecond": "Second", + "sharedName": "Name", "errorTitle": "Error", "errorUnknown": "Unknown error", "errorConnection": "Connection error", - "userName": "Name", "userEmail": "Email", "userPassword": "Password", "userAdmin": "Admin", @@ -30,13 +30,15 @@ "devicesAndState": "Devices and State", "deviceDialog": "Device", "deviceTitle": "Devices", - "deviceName": "Name", "deviceIdentifier": "Identifier", "deviceLastUpdate": "Last Update", "deviceCommand": "Command", "deviceFollow": "Follow", + "groupDialog": "Group", + "groupParent": "Group", "settingsTitle": "Settings", "settingsUser": "Account", + "settingsGroups": "Groups", "settingsServer": "Server", "settingsUsers": "Users", "settingsDistanceUnit": "Distance", diff --git a/web/l10n/es.json b/web/l10n/es.json index 2ace91caf..7cca1c8f2 100644 --- a/web/l10n/es.json +++ b/web/l10n/es.json @@ -1,6 +1,6 @@ { "sharedLoading": "Cargando...", - "sharedSave": "Grabar", + "sharedSave": "Guardar", "sharedCancel": "Cancelar", "sharedAdd": "Agregar", "sharedEdit": "Editar", @@ -12,14 +12,14 @@ "sharedMph": "MPH", "sharedHour": "Hora", "sharedMinute": "Minuto", - "sharedSecond": "Segundos", + "sharedSecond": "Segundo", "errorTitle": "Error", "errorUnknown": "Error Desconocido", "errorConnection": "Error de Conexión", "userName": "Nombre", "userEmail": "Email", "userPassword": "Contraseña", - "userAdmin": "Admin", + "userAdmin": "Administrador", "loginTitle": "Ingresar", "loginLanguage": "Idioma", "loginRegister": "Registrar", @@ -27,14 +27,14 @@ "loginFailed": "Dirección de Correo o Contraseña Incorrecta", "loginCreated": "Nuevo Usuario ha sido registrado", "loginLogout": "Salir", - "devicesAndState": "Devices and State", + "devicesAndState": "Dispositivos y Estado", "deviceDialog": "Dispositivo", "deviceTitle": "Dispositivos", "deviceName": "Nombre", "deviceIdentifier": "Identificador", - "deviceLastUpdate": "Last Update", + "deviceLastUpdate": "Última Actualización", "deviceCommand": "Comando", - "deviceFollow": "Follow", + "deviceFollow": "Seguir", "settingsTitle": "Preferencias", "settingsUser": "Cuenta", "settingsServer": "Servidor", @@ -59,7 +59,7 @@ "serverTitle": "Preferencias Servidor", "serverZoom": "Zoom", "serverRegistration": "Registrar", - "serverReadonly": "Readonly", + "serverReadonly": "Sólo Lectura", "mapTitle": "Mapa", "mapLayer": "Capa de Mapa", "mapCustom": "Mapa Personalizado", diff --git a/web/l10n/fa.json b/web/l10n/fa.json new file mode 100644 index 000000000..3497dff23 --- /dev/null +++ b/web/l10n/fa.json @@ -0,0 +1,83 @@ +{ + "sharedLoading": "در حال بارگزارى ...", + "sharedSave": "ذخيره", + "sharedCancel": "Cancel", + "sharedAdd": "اضافه كردن", + "sharedEdit": "Edit", + "sharedRemove": "Remove", + "sharedRemoveConfirm": "Remove item?", + "sharedKm": "km", + "sharedMi": "mi", + "sharedKmh": "km/h", + "sharedMph": "mph", + "sharedHour": "Hour", + "sharedMinute": "دقيقه", + "sharedSecond": "ثانيه", + "errorTitle": "Error", + "errorUnknown": "Unknown error", + "errorConnection": "Connection error", + "userName": "نام", + "userEmail": "نام كاربرى ( ايميل )", + "userPassword": "گذرواژه", + "userAdmin": "Admin", + "loginTitle": "ورود", + "loginLanguage": "انتخاب زبان", + "loginRegister": "ثبت نام", + "loginLogin": "ورود", + "loginFailed": "نام كاربرى ( ايميل ) يا گذرواژه اشتباه است", + "loginCreated": "ثبت نام با موفقيت انجام شد", + "loginLogout": "خروج", + "devicesAndState": "Devices and State", + "deviceDialog": "دستگاه", + "deviceTitle": "دستگاه ها", + "deviceName": "نام خودرو", + "deviceIdentifier": "سريال دستگاه", + "deviceLastUpdate": "آخرين بروزرسانى", + "deviceCommand": "Command", + "deviceFollow": "Follow", + "settingsTitle": "تنظيمات", + "settingsUser": "حساب كاربرى", + "settingsServer": "Server", + "settingsUsers": "Users", + "settingsDistanceUnit": "Distance", + "settingsSpeedUnit": "سرعت", + "reportTitle": "گزارشات ", + "reportDevice": "دستگاه", + "reportFrom": "از", + "reportTo": "To", + "reportShow": "Show", + "reportClear": "Clear", + "positionFixTime": "Time", + "positionValid": "Valid", + "positionLatitude": "عرض جغرافيايى", + "positionLongitude": "طول جغرافيايى", + "positionAltitude": "Altitude", + "positionSpeed": "سرعت", + "positionCourse": "Course", + "positionAddress": "Address", + "positionProtocol": "Protocol", + "serverTitle": "Server Settings", + "serverZoom": "Zoom", + "serverRegistration": "Registration", + "serverReadonly": "Readonly", + "mapTitle": "Map", + "mapLayer": "Map Layer", + "mapCustom": "Custom Map", + "mapOsm": "Open Street Map", + "mapBingKey": "Bing Maps Key", + "mapBingRoad": "Bing Maps Road", + "mapBingAerial": "Bing Maps Aerial", + "stateTitle": "State", + "stateName": "Attribute", + "stateValue": "Value", + "commandTitle": "Command", + "commandSend": "ارسال", + "commandType": "Type", + "commandSent": "Command has been sent", + "commandPositionPeriodic": "Periodic Reporting", + "commandPositionStop": "Stop Reporting", + "commandEngineStop": "Engine Stop", + "commandEngineResume": "Engine Resume", + "commandFrequency": "Frequency", + "commandUnit": "Unit" +}
\ No newline at end of file diff --git a/web/l10n/fi.json b/web/l10n/fi.json new file mode 100644 index 000000000..dc44cb03a --- /dev/null +++ b/web/l10n/fi.json @@ -0,0 +1,83 @@ +{ + "sharedLoading": "Ladataan...", + "sharedSave": "Tallenna", + "sharedCancel": "Peruuta", + "sharedAdd": "Lisää", + "sharedEdit": "Muokkaa", + "sharedRemove": "Poista", + "sharedRemoveConfirm": "Poista kohde?", + "sharedKm": "km", + "sharedMi": "mi", + "sharedKmh": "km/h", + "sharedMph": "mph", + "sharedHour": "Tunti", + "sharedMinute": "Minuutti", + "sharedSecond": "Sekunti", + "errorTitle": "Virhe", + "errorUnknown": "Tuntematon virhe", + "errorConnection": "Yhteysvirhe", + "userName": "Nimi", + "userEmail": "Email", + "userPassword": "Salasana", + "userAdmin": "Ylläpito", + "loginTitle": "Kirjaudu", + "loginLanguage": "Kieli", + "loginRegister": "Rekisteröidy", + "loginLogin": "Kirjaudu", + "loginFailed": "Virheellinen email tai salasana", + "loginCreated": "Uusi käyttäjä on rekisteröitynyt", + "loginLogout": "Kirjaudu ulos", + "devicesAndState": "Laitteet ja Tilat", + "deviceDialog": "Laite", + "deviceTitle": "Laitteet", + "deviceName": "Nimi", + "deviceIdentifier": "Tunniste", + "deviceLastUpdate": "Viimeisin päivitys", + "deviceCommand": "Komento", + "deviceFollow": "Seuraa", + "settingsTitle": "Asetukset", + "settingsUser": "Tili", + "settingsServer": "Palvelin", + "settingsUsers": "Käyttäjät", + "settingsDistanceUnit": "Etäisyys", + "settingsSpeedUnit": "Nopeus", + "reportTitle": "Raportit", + "reportDevice": "Laite", + "reportFrom": "Mistä", + "reportTo": "Mihin", + "reportShow": "Näytä", + "reportClear": "Tyhjennä", + "positionFixTime": "Aika", + "positionValid": "Kelvollinen", + "positionLatitude": "Latitude", + "positionLongitude": "Longitude", + "positionAltitude": "Korkeus", + "positionSpeed": "Nopeus", + "positionCourse": "Suunta", + "positionAddress": "Osoite", + "positionProtocol": "Protokolla", + "serverTitle": "Palvelinasetukset", + "serverZoom": "Lähennä", + "serverRegistration": "Rekisteröinti", + "serverReadonly": "Vain luku", + "mapTitle": "Kartta", + "mapLayer": "Karttataso", + "mapCustom": "Oma kartta", + "mapOsm": "Open Street Map", + "mapBingKey": "Bing Maps avain", + "mapBingRoad": "Bign Maps tiet", + "mapBingAerial": "Bing Maps ilmakuva", + "stateTitle": "Tila", + "stateName": "Ominaisuus", + "stateValue": "Arvo", + "commandTitle": "Komento", + "commandSend": "Lähetä", + "commandType": "Tyyppi", + "commandSent": "Komento on lähetetty", + "commandPositionPeriodic": "Määräaikaisraportointi", + "commandPositionStop": "Lopeta raportointi", + "commandEngineStop": "Sammuta moottori", + "commandEngineResume": "Palauta moottori", + "commandFrequency": "Taajuus", + "commandUnit": "Yksikkö" +}
\ No newline at end of file diff --git a/web/l10n/lt.json b/web/l10n/lt.json index 3b32a9c3d..77e7b95a5 100644 --- a/web/l10n/lt.json +++ b/web/l10n/lt.json @@ -27,14 +27,14 @@ "loginFailed": "Neteisingas el.paštas ir/ar slaptažodis", "loginCreated": "Registracija sėmkinga", "loginLogout": "Atsijungti", - "devicesAndState": "Devices and State", + "devicesAndState": "Prietaisai ir Statusas", "deviceDialog": "Prietaisas", "deviceTitle": "Prietaisai", "deviceName": "Pavadinimas", "deviceIdentifier": "Identifikacinis kodas", "deviceLastUpdate": "Naujausias atnaujinimas", "deviceCommand": "Komanda", - "deviceFollow": "Follow", + "deviceFollow": "Sekti", "settingsTitle": "Nustatymai", "settingsUser": "Paskyra", "settingsServer": "Serveris", @@ -59,7 +59,7 @@ "serverTitle": "Serverio nustatymai", "serverZoom": "Priartinimas", "serverRegistration": "Registracija", - "serverReadonly": "Readonly", + "serverReadonly": "Tik skaityti", "mapTitle": "Žemėlapis", "mapLayer": "Žemėlapio sluoksnis", "mapCustom": "Pasirinktinis Žemėlapis", diff --git a/web/l10n/ml.json b/web/l10n/ml.json new file mode 100644 index 000000000..02bd39b00 --- /dev/null +++ b/web/l10n/ml.json @@ -0,0 +1,83 @@ +{ + "sharedLoading": "ലോഡുചെയ്യുന്നു ..", + "sharedSave": "Save", + "sharedCancel": "റദ്ദാക്കുക", + "sharedAdd": "ചേര്ക്കുക", + "sharedEdit": "തിരുത്തുക", + "sharedRemove": "നീക്കം ചെയ്യുക", + "sharedRemoveConfirm": "വിഷയം നീക്കം ചെയ്യുക", + "sharedKm": "കിലോമീറ്റർ", + "sharedMi": "mi", + "sharedKmh": "കിലോമീറ്റർ / മണിക്കൂർ", + "sharedMph": "mph", + "sharedHour": "മണിക്കൂര്", + "sharedMinute": "മിനിറ്റ്", + "sharedSecond": "സെക്കന്റ്", + "errorTitle": "പിശക്", + "errorUnknown": "അജ്ഞാത പിശക്", + "errorConnection": "കണക്ഷൻ പിശക്", + "userName": "പേര്", + "userEmail": "ഇമെയിൽ", + "userPassword": "രഹസ്യ കോഡ്", + "userAdmin": "Admin", + "loginTitle": "അകത്തു പ്രവേശിക്കുക", + "loginLanguage": "ഭാഷ", + "loginRegister": "Register", + "loginLogin": "അകത്തു പ്രവേശിക്കുക", + "loginFailed": "തെറ്റായ ഇമെയിൽ വിലാസവും പാസ്വേഡും", + "loginCreated": "പുതിയ ഉപയോക്താവ് രജിസ്റ്റർ ചെയ്തു", + "loginLogout": "പുറത്തുകടക്കുക", + "devicesAndState": "Devices and State", + "deviceDialog": "ഉപകരണം", + "deviceTitle": "Devices", + "deviceName": "പേര്", + "deviceIdentifier": "Identifier", + "deviceLastUpdate": "Last Update", + "deviceCommand": "Command", + "deviceFollow": "Follow", + "settingsTitle": "Settings", + "settingsUser": "Account", + "settingsServer": "Server", + "settingsUsers": "Users", + "settingsDistanceUnit": "Distance", + "settingsSpeedUnit": "വേഗം", + "reportTitle": "Reports", + "reportDevice": "ഉപകരണം", + "reportFrom": "From", + "reportTo": "To", + "reportShow": "Show", + "reportClear": "Clear", + "positionFixTime": "സമയം", + "positionValid": "Valid", + "positionLatitude": "അക്ഷാംശം", + "positionLongitude": "രേഖാംശം", + "positionAltitude": "Altitude", + "positionSpeed": "വേഗം", + "positionCourse": "Course", + "positionAddress": "Address", + "positionProtocol": "Protocol", + "serverTitle": "Server Settings", + "serverZoom": "വലുതാക്കിയോ ചെറുതാക്കിയോ കാണിക്കുക", + "serverRegistration": "രജിസ്ട്രേഷൻ", + "serverReadonly": "Readonly", + "mapTitle": "ഭൂപടം", + "mapLayer": "Map Layer", + "mapCustom": "Custom Map", + "mapOsm": "Open Street Map", + "mapBingKey": "Bing Maps Key", + "mapBingRoad": "Bing Maps Road", + "mapBingAerial": "Bing Maps Aerial", + "stateTitle": "State", + "stateName": "Attribute", + "stateValue": "Value", + "commandTitle": "Command", + "commandSend": "Send", + "commandType": "Type", + "commandSent": "Command has been sent", + "commandPositionPeriodic": "Periodic Reporting", + "commandPositionStop": "Stop Reporting", + "commandEngineStop": "Engine Stop", + "commandEngineResume": "Engine Resume", + "commandFrequency": "Frequency", + "commandUnit": "Unit" +}
\ No newline at end of file diff --git a/web/l10n/ms.json b/web/l10n/ms.json new file mode 100644 index 000000000..ff5f02f01 --- /dev/null +++ b/web/l10n/ms.json @@ -0,0 +1,83 @@ +{ + "sharedLoading": "Memuatkan...", + "sharedSave": "Simpan", + "sharedCancel": "Batal", + "sharedAdd": "Tambah", + "sharedEdit": "Ubah", + "sharedRemove": "Hapus", + "sharedRemoveConfirm": "Hapuskan item?", + "sharedKm": "km", + "sharedMi": "mi", + "sharedKmh": "km/h", + "sharedMph": "mph", + "sharedHour": "Jam", + "sharedMinute": "Minit", + "sharedSecond": "Saat", + "errorTitle": "Ralat", + "errorUnknown": "Ralat tidak diketahui", + "errorConnection": "Ralat penyambungan", + "userName": "Nama", + "userEmail": "Emel", + "userPassword": "Katalaluan", + "userAdmin": "Admin", + "loginTitle": "Log masuk", + "loginLanguage": "Bahasa", + "loginRegister": "Daftar", + "loginLogin": "Log masuk", + "loginFailed": "Kesalahan emel atau katalaluan", + "loginCreated": "Pengguna baru telah didaftarkan", + "loginLogout": "Keluar", + "devicesAndState": "Peranti dan State", + "deviceDialog": "Peranti", + "deviceTitle": "Peranti", + "deviceName": "Nama", + "deviceIdentifier": "IMEI/ID", + "deviceLastUpdate": "Kemaskini Terakhir", + "deviceCommand": "Arahan", + "deviceFollow": "Ikut", + "settingsTitle": "Tetapan", + "settingsUser": "Akaun", + "settingsServer": "Server", + "settingsUsers": "Pengguna", + "settingsDistanceUnit": "Jarak", + "settingsSpeedUnit": "Kelajuan", + "reportTitle": "Laporan", + "reportDevice": "Peranti", + "reportFrom": "Daripada", + "reportTo": "Ke", + "reportShow": "Papar", + "reportClear": "Kosongkan", + "positionFixTime": "Masa", + "positionValid": "Sah", + "positionLatitude": "Latitud", + "positionLongitude": "Longitud", + "positionAltitude": "Altitud", + "positionSpeed": "Kelajuan", + "positionCourse": "Course", + "positionAddress": "Alamat", + "positionProtocol": "Protokol", + "serverTitle": "Tetapan Server", + "serverZoom": "Besarkan", + "serverRegistration": "Pendaftaran", + "serverReadonly": "Baca Sahaja", + "mapTitle": "Peta", + "mapLayer": "Map Layer", + "mapCustom": "Peta Lain", + "mapOsm": "Open Street Map", + "mapBingKey": "Bing Maps Key", + "mapBingRoad": "Bing Maps Road", + "mapBingAerial": "Bing Maps Aerial", + "stateTitle": "Negeri", + "stateName": "Atribut", + "stateValue": "Nilai", + "commandTitle": "Arahan", + "commandSend": "Hantar", + "commandType": "Jenis", + "commandSent": "Arahan telah dihantar", + "commandPositionPeriodic": "Laporan Berkala", + "commandPositionStop": "Hentikan Laporan", + "commandEngineStop": "Matikan Enjin", + "commandEngineResume": "Hidupkan Enjin", + "commandFrequency": "Frekuensi", + "commandUnit": "Unit" +}
\ No newline at end of file diff --git a/web/l10n/no.json b/web/l10n/no.json index dcfef54ce..d3dbb003e 100644 --- a/web/l10n/no.json +++ b/web/l10n/no.json @@ -67,7 +67,7 @@ "mapBingKey": "Bing Maps Nøkkel", "mapBingRoad": "Bing Maps Vei", "mapBingAerial": "Bing Maps Fly", - "stateTitle": "Stat", + "stateTitle": "Status", "stateName": "Egenskap", "stateValue": "Verdi", "commandTitle": "Kommando", diff --git a/web/l10n/tr.json b/web/l10n/tr.json new file mode 100644 index 000000000..6eacb12d0 --- /dev/null +++ b/web/l10n/tr.json @@ -0,0 +1,83 @@ +{ + "sharedLoading": "Yükleniyor...", + "sharedSave": "Kaydet", + "sharedCancel": "İptal", + "sharedAdd": "Ekle", + "sharedEdit": "Düzenle", + "sharedRemove": "Kaldır", + "sharedRemoveConfirm": "Öğeyi kaldır", + "sharedKm": "km", + "sharedMi": "mil", + "sharedKmh": "km/s", + "sharedMph": "mil/s", + "sharedHour": "Saat", + "sharedMinute": "Dakika", + "sharedSecond": "Saniye", + "errorTitle": "Hata", + "errorUnknown": "Bilinmeyen hata ", + "errorConnection": "Bağlantı Hatası", + "userName": "Ad", + "userEmail": "Eposta", + "userPassword": "Şifre", + "userAdmin": "Yönetici", + "loginTitle": "Oturum aç", + "loginLanguage": "Lisan", + "loginRegister": "Kayıt", + "loginLogin": "Oturumu aç", + "loginFailed": "Geçersiz eposta veya şifre", + "loginCreated": "Yeni kullanıcı kaydedildi", + "loginLogout": "Oturumu sonlandır", + "devicesAndState": "Cihazlar ve Bölge", + "deviceDialog": "Cihaz", + "deviceTitle": "Cihazlar", + "deviceName": "İsim", + "deviceIdentifier": "Kimlik", + "deviceLastUpdate": "Son Güncelleme", + "deviceCommand": "Komut", + "deviceFollow": "Takip", + "settingsTitle": "Ayarlar", + "settingsUser": "Hesap", + "settingsServer": "Sunucu", + "settingsUsers": "Kullanıcı", + "settingsDistanceUnit": "Mesafe", + "settingsSpeedUnit": "Hız", + "reportTitle": "Raporlar", + "reportDevice": "Aygıt", + "reportFrom": "Başlangıç", + "reportTo": "Varış", + "reportShow": "Göster", + "reportClear": "Temizle", + "positionFixTime": "Süre", + "positionValid": "Geçerli", + "positionLatitude": "Enlem", + "positionLongitude": "Boylam", + "positionAltitude": "Rakım", + "positionSpeed": "Sürat", + "positionCourse": "Yön", + "positionAddress": "Adres", + "positionProtocol": "Protokol", + "serverTitle": "Sunucu Ayarları", + "serverZoom": "Yakınlaştırma", + "serverRegistration": "Kayıt", + "serverReadonly": "Saltokunur", + "mapTitle": "Harita", + "mapLayer": "Harita Katmanı", + "mapCustom": "Özelleştirilmiş Harita", + "mapOsm": "Open Street Map", + "mapBingKey": "Bing Maps Key", + "mapBingRoad": "Bing Maps Road", + "mapBingAerial": "Bing Maps Aerial", + "stateTitle": "Bölge", + "stateName": "Özellik", + "stateValue": "Değer", + "commandTitle": "Komut", + "commandSend": "Gönder", + "commandType": "Tip", + "commandSent": "Komut gönderildi", + "commandPositionPeriodic": "Periyodik Rapor", + "commandPositionStop": "Raporlamayı Durdur", + "commandEngineStop": "Motoru Durdur", + "commandEngineResume": "Motoru Çalıştır", + "commandFrequency": "Frekans", + "commandUnit": "Ünite" +}
\ No newline at end of file diff --git a/web/locale.js b/web/locale.js index 7dc94ec45..c0b3af90b 100644 --- a/web/locale.js +++ b/web/locale.js @@ -16,9 +16,6 @@ var Locale = {}; -Ext.Ajax.disableCaching = false; -Ext.Loader.disableCaching = false; - Locale.languages = { 'ar': { name: 'العربية', code: 'en' }, 'bg': { name: 'Български', code: 'bg' }, @@ -28,12 +25,16 @@ Locale.languages = { 'el': { name: 'Ελληνικά', code: 'el' }, 'en': { name: 'English', code: 'en' }, 'es': { name: 'Español', code: 'es' }, + 'fa': { name: 'فارسی', code: 'fa' }, + 'fi': { name: 'Suomi', code: 'fi' }, 'fr': { name: 'Français', code: 'fr' }, 'hu': { name: 'Magyar', code: 'hu' }, 'id': { name: 'Bahasa Indonesia', code: 'id' }, 'it': { name: 'Italiano', code: 'it' }, 'ka': { name: 'ქართული', code: 'en' }, 'lt': { name: 'Lietuvių', code: 'lt' }, + 'ml': { name: 'മലയാളം', code: 'en' }, + 'ms': { name: 'بهاس ملايو', code: 'en' }, 'nl': { name: 'Nederlands', code: 'nl' }, 'no': { name: 'Norsk', code: 'no_NB' }, 'pl': { name: 'Polski', code: 'pl' }, @@ -47,6 +48,7 @@ Locale.languages = { 'sr': { name: 'Srpski', code: 'sr' }, 'ta': { name: 'தமிழ்', code: 'en' }, 'th': { name: 'ไทย', code: 'th' }, + 'tr': { name: 'Türkçe', code: 'tr' }, 'uk': { name: 'Українська', code: 'ukr' }, 'vi': { name: 'Tiếng Việt', code: 'en' }, 'zh': { name: '中文', code: 'zh_CN' } diff --git a/web/release.html b/web/release.html index d5fc60e2b..155e13f1a 100644 --- a/web/release.html +++ b/web/release.html @@ -5,7 +5,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>Traccar</title> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/resources/theme-neptune-all.css"> -<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol.min.css"> +<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol.min.css"> <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css"> <link rel="stylesheet" href="app.css"> </head> @@ -14,7 +14,7 @@ <div id="attribution">Powered by <a href="https://www.traccar.org/">Traccar GPS Tracking System</a></div> <script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-neptune/theme-neptune.js"></script> -<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.12.1/ol-debug.min.js"></script> +<script src="//cdnjs.cloudflare.com/ajax/libs/ol3/3.14.1/ol-debug.min.js"></script> <script src="arrowstyle.js"></script> <script src="locale.js"></script> <script type="text/javascript"> |