Pārlūkot izejas kodu

Binary protocol, db connection, testing

Lukas Cerny 2 gadi atpakaļ
vecāks
revīzija
ebcc77d42c
33 mainītis faili ar 2939 papildinājumiem un 129 dzēšanām
  1. 1 0
      build.gradle
  2. BIN
      doc/telemetry_model-v2.png
  3. 12 1
      docker-compose.yaml
  4. 845 7
      sql/init.sql
  5. 849 0
      sql/telemetry-model-v2.sql
  6. 3 3
      src/main/java/cz/senslog/telemetry/app/PropertyConfig.java
  7. 90 0
      src/main/java/cz/senslog/telemetry/database/domain/ObsTelemetry.java
  8. 1 1
      src/main/java/cz/senslog/telemetry/database/domain/Post.java
  9. 20 0
      src/main/java/cz/senslog/telemetry/database/domain/Sensor.java
  10. 49 0
      src/main/java/cz/senslog/telemetry/database/domain/Unit.java
  11. 8 7
      src/main/java/cz/senslog/telemetry/database/repository/PostRepository.java
  12. 57 0
      src/main/java/cz/senslog/telemetry/database/repository/SensorNetworkRepository.java
  13. 72 0
      src/main/java/cz/senslog/telemetry/database/repository/TelemetryRepository.java
  14. 24 0
      src/main/java/cz/senslog/telemetry/domain/IOProperty.java
  15. 94 0
      src/main/java/cz/senslog/telemetry/domain/TelemetryAVLRecord.java
  16. 23 0
      src/main/java/cz/senslog/telemetry/protocol/CodecType.java
  17. 118 0
      src/main/java/cz/senslog/telemetry/protocol/Fm4ex.java
  18. 99 0
      src/main/java/cz/senslog/telemetry/server/Fm4exSocketHandler.java
  19. 2 2
      src/main/java/cz/senslog/telemetry/server/HttpVertxServer.java
  20. 0 27
      src/main/java/cz/senslog/telemetry/server/LoopCascadeInvoker.java
  21. 0 28
      src/main/java/cz/senslog/telemetry/server/LoopCascadeInvokerInitializer.java
  22. 74 0
      src/main/java/cz/senslog/telemetry/server/LoopInvoker.java
  23. 5 5
      src/main/java/cz/senslog/telemetry/server/OpenAPIHandler.java
  24. 0 38
      src/main/java/cz/senslog/telemetry/server/SocketHandler.java
  25. 7 5
      src/main/java/cz/senslog/telemetry/server/TCPVertxServer.java
  26. 5 5
      src/main/java/cz/senslog/telemetry/server/TestHandler.java
  27. 65 0
      src/main/java/cz/senslog/telemetry/utils/CRC16.java
  28. 24 0
      src/main/java/cz/senslog/telemetry/utils/Tuple.java
  29. 26 0
      src/test/java/cz/senslog/telemetry/BinaryDataSet.java
  30. 114 0
      src/test/java/cz/senslog/telemetry/DataSet.java
  31. 84 0
      src/test/java/cz/senslog/telemetry/database/repository/TelemetryRepositoryTest.java
  32. 113 0
      src/test/java/cz/senslog/telemetry/protocol/Fm4exTest.java
  33. 55 0
      src/test/java/cz/senslog/telemetry/server/Fm4exSocketHandlerTest.java

+ 1 - 0
build.gradle

@@ -58,6 +58,7 @@ dependencies {
 
     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
+    testImplementation 'org.mockito:mockito-core:5.3.1'
     testImplementation 'io.vertx:vertx-junit5:4.4.0'
     testImplementation 'org.assertj:assertj-core:3.24.2'
     testImplementation 'org.openapi4j:openapi-parser:1.0.7'

BIN
doc/telemetry_model-v2.png


+ 12 - 1
docker-compose.yaml

@@ -29,4 +29,15 @@ services:
     ports:
       - '5432:5432'
     volumes:
-      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
+      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
+  db:
+    image: postgis/postgis:15-3.3-alpine
+    container_name: telemetry_db
+    environment:
+      - POSTGRES_USER=postgres
+      - POSTGRES_PASSWORD=postgres
+    ports:
+      - '5432:5432'
+    volumes:
+      - ./db:/var/lib/postgresql/data
+      - ./sql/init.sql:/docker-entrypoint-initdb.d/create_tables.sql

+ 845 - 7
sql/init.sql

@@ -1,12 +1,850 @@
+--
+-- PostgreSQL database dump
+--
 
-CREATE DATABASE telemetry;
-CREATE EXTENSION IF NOT EXISTS timescaledb;
+-- Dumped from database version 11.12
+-- Dumped by pg_dump version 15.2
 
-\connect telemetry
+-- Started on 2023-05-05 01:10:04
 
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = on;
+SELECT pg_catalog.set_config('search_path', '', false);
+SET check_function_bodies = false;
+SET xmloption = content;
+SET client_min_messages = warning;
+SET row_security = off;
 
-CREATE TABLE IF NOT EXISTS post(
-        id serial PRIMARY KEY,
-        text VARCHAR (255) NOT NULL,
-        created_on TIMESTAMP NOT NULL DEFAULT NOW()
+
+CREATE ROLE senslog NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
+CREATE ROLE maplog_app NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN PASSWORD 'MAPlog';
+GRANT senslog TO maplog_app;
+
+--
+-- TOC entry 3861 (class 1262 OID 54507)
+-- Name: maplog; Type: DATABASE; Schema: -; Owner: postgres
+--
+
+
+
+CREATE DATABASE maplog WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.UTF-8';
+
+
+ALTER DATABASE maplog OWNER TO senslog;
+
+\connect maplog
+
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = on;
+SELECT pg_catalog.set_config('search_path', '', false);
+SET check_function_bodies = false;
+SET xmloption = content;
+SET client_min_messages = warning;
+SET row_security = off;
+
+--
+-- TOC entry 9 (class 2615 OID 55528)
+-- Name: maplog; Type: SCHEMA; Schema: -; Owner: postgres
+--
+
+CREATE SCHEMA maplog;
+
+
+ALTER SCHEMA maplog OWNER TO senslog;
+
+--
+-- TOC entry 7 (class 2615 OID 2200)
+-- Name: public; Type: SCHEMA; Schema: -; Owner: postgres
+--
+
+-- *not* creating schema, since initdb creates it
+
+
+ALTER SCHEMA public OWNER TO senslog;
+
+--
+-- TOC entry 2 (class 3079 OID 54508)
+-- Name: postgis; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA public;
+
+
+--
+-- TOC entry 3863 (class 0 OID 0)
+-- Dependencies: 2
+-- Name: EXTENSION postgis; Type: COMMENT; Schema: -; Owner:
+--
+
+COMMENT ON EXTENSION postgis IS 'PostGIS geometry and geography spatial types and functions';
+
+
+SET default_tablespace = '';
+
+--
+-- TOC entry 216 (class 1259 OID 62701)
+-- Name: campaign; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.campaign (
+                                 campaign_id integer NOT NULL,
+                                 description text NOT NULL,
+                                 from_time timestamp with time zone NOT NULL,
+                                 to_time timestamp with time zone NOT NULL
 );
+
+
+ALTER TABLE maplog.campaign OWNER TO senslog;
+
+--
+-- TOC entry 215 (class 1259 OID 62699)
+-- Name: campaign_campaign_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.campaign_campaign_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.campaign_campaign_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3864 (class 0 OID 0)
+-- Dependencies: 215
+-- Name: campaign_campaign_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.campaign_campaign_id_seq OWNED BY maplog.campaign.campaign_id;
+
+
+--
+-- TOC entry 224 (class 1259 OID 62784)
+-- Name: groups; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.groups (
+                               group_id integer NOT NULL,
+                               group_name text,
+                               parent_group_id integer,
+                               has_children boolean DEFAULT false
+);
+
+
+ALTER TABLE maplog.groups OWNER TO senslog;
+
+--
+-- TOC entry 223 (class 1259 OID 62782)
+-- Name: group_group_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.group_group_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.group_group_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3865 (class 0 OID 0)
+-- Dependencies: 223
+-- Name: group_group_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.group_group_id_seq OWNED BY maplog.groups.group_id;
+
+
+--
+-- TOC entry 212 (class 1259 OID 55614)
+-- Name: obs_telemetry; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.obs_telemetry (
+                                      obs_id bigint NOT NULL,
+                                      time_stamp timestamp with time zone NOT NULL,
+                                      unit_id bigint NOT NULL,
+                                      observed_values jsonb NOT NULL,
+                                      the_geom public.geometry(Point,4326),
+                                      time_received timestamp with time zone DEFAULT now() NOT NULL
+);
+
+
+ALTER TABLE maplog.obs_telemetry OWNER TO senslog;
+
+--
+-- TOC entry 211 (class 1259 OID 55612)
+-- Name: obs_telemetry_obs_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.obs_telemetry_obs_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.obs_telemetry_obs_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3866 (class 0 OID 0)
+-- Dependencies: 211
+-- Name: obs_telemetry_obs_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.obs_telemetry_obs_id_seq OWNED BY maplog.obs_telemetry.obs_id;
+
+
+--
+-- TOC entry 209 (class 1259 OID 55579)
+-- Name: phenomenon; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.phenomenon (
+                                   phenomenon_id integer NOT NULL,
+                                   phenomenon_name text NOT NULL,
+                                   uom character varying(30) NOT NULL,
+                                   uom_link text
+);
+
+
+ALTER TABLE maplog.phenomenon OWNER TO senslog;
+
+--
+-- TOC entry 208 (class 1259 OID 55577)
+-- Name: phenomenon_phenomenon_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.phenomenon_phenomenon_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.phenomenon_phenomenon_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3867 (class 0 OID 0)
+-- Dependencies: 208
+-- Name: phenomenon_phenomenon_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.phenomenon_phenomenon_id_seq OWNED BY maplog.phenomenon.phenomenon_id;
+
+
+--
+-- TOC entry 207 (class 1259 OID 55566)
+-- Name: sensor; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.sensor (
+                               sensor_id bigint NOT NULL,
+                               sensor_name character varying(100),
+                               sensor_type text,
+                               sensor_type_id integer,
+                               min_range text,
+                               max_range text,
+                               phenomenon_id integer NOT NULL
+);
+
+
+ALTER TABLE maplog.sensor OWNER TO senslog;
+
+--
+-- TOC entry 206 (class 1259 OID 55564)
+-- Name: sensor_sensor_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.sensor_sensor_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.sensor_sensor_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3868 (class 0 OID 0)
+-- Dependencies: 206
+-- Name: sensor_sensor_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.sensor_sensor_id_seq OWNED BY maplog.sensor.sensor_id;
+
+
+--
+-- TOC entry 213 (class 1259 OID 55644)
+-- Name: system_user; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.system_user (
+                                    user_id integer NOT NULL,
+                                    user_name text NOT NULL,
+                                    user_real_name text,
+                                    user_password text,
+                                    group_id integer,
+                                    rights_id integer DEFAULT 0
+);
+
+
+ALTER TABLE maplog.system_user OWNER TO senslog;
+
+--
+-- TOC entry 204 (class 1259 OID 55540)
+-- Name: unit; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit (
+                             unit_id bigint NOT NULL,
+                             imei character varying(20) NOT NULL,
+                             description text,
+                             is_mobile boolean DEFAULT true NOT NULL,
+                             unit_type_id character varying(2) DEFAULT 'X'::character varying NOT NULL
+);
+
+
+ALTER TABLE maplog.unit OWNER TO senslog;
+
+--
+-- TOC entry 218 (class 1259 OID 62712)
+-- Name: unit_to_campaign; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_to_campaign (
+                                         id integer NOT NULL,
+                                         camp_id integer NOT NULL,
+                                         unit_id bigint NOT NULL,
+                                         from_time timestamp with time zone NOT NULL,
+                                         to_time timestamp with time zone NOT NULL
+);
+
+
+ALTER TABLE maplog.unit_to_campaign OWNER TO senslog;
+
+--
+-- TOC entry 217 (class 1259 OID 62710)
+-- Name: unit_to_campaign_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.unit_to_campaign_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.unit_to_campaign_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3869 (class 0 OID 0)
+-- Dependencies: 217
+-- Name: unit_to_campaign_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.unit_to_campaign_id_seq OWNED BY maplog.unit_to_campaign.id;
+
+
+--
+-- TOC entry 226 (class 1259 OID 62796)
+-- Name: unit_to_group; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_to_group (
+                                      id integer NOT NULL,
+                                      group_id integer,
+                                      unit_id bigint
+);
+
+
+ALTER TABLE maplog.unit_to_group OWNER TO senslog;
+
+--
+-- TOC entry 225 (class 1259 OID 62794)
+-- Name: unit_to_group_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.unit_to_group_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.unit_to_group_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3870 (class 0 OID 0)
+-- Dependencies: 225
+-- Name: unit_to_group_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.unit_to_group_id_seq OWNED BY maplog.unit_to_group.id;
+
+
+--
+-- TOC entry 210 (class 1259 OID 55594)
+-- Name: unit_to_sensor; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_to_sensor (
+                                       sensor_id bigint NOT NULL,
+                                       unit_id bigint NOT NULL,
+                                       first_obs_ts timestamp with time zone,
+                                       last_obs_ts timestamp with time zone,
+                                       last_obs_value double precision DEFAULT 'NaN'::double precision
+);
+
+
+ALTER TABLE maplog.unit_to_sensor OWNER TO senslog;
+
+--
+-- TOC entry 205 (class 1259 OID 55548)
+-- Name: unit_type; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_type (
+                                  unit_type_id character varying(2) NOT NULL,
+                                  type_name character varying(20) NOT NULL,
+                                  description text
+);
+
+
+ALTER TABLE maplog.unit_type OWNER TO senslog;
+
+--
+-- TOC entry 214 (class 1259 OID 55654)
+-- Name: user_to_campaign; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.user_to_campaign (
+                                         user_id integer NOT NULL,
+                                         campaign_id integer NOT NULL
+);
+
+
+ALTER TABLE maplog.user_to_campaign OWNER TO senslog;
+
+
+--
+-- TOC entry 3638 (class 2604 OID 62704)
+-- Name: campaign campaign_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.campaign ALTER COLUMN campaign_id SET DEFAULT nextval('maplog.campaign_campaign_id_seq'::regclass);
+
+
+--
+-- TOC entry 3643 (class 2604 OID 62787)
+-- Name: groups group_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.groups ALTER COLUMN group_id SET DEFAULT nextval('maplog.group_group_id_seq'::regclass);
+
+
+--
+-- TOC entry 3635 (class 2604 OID 55617)
+-- Name: obs_telemetry obs_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.obs_telemetry ALTER COLUMN obs_id SET DEFAULT nextval('maplog.obs_telemetry_obs_id_seq'::regclass);
+
+
+--
+-- TOC entry 3633 (class 2604 OID 55582)
+-- Name: phenomenon phenomenon_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.phenomenon ALTER COLUMN phenomenon_id SET DEFAULT nextval('maplog.phenomenon_phenomenon_id_seq'::regclass);
+
+
+--
+-- TOC entry 3632 (class 2604 OID 55569)
+-- Name: sensor sensor_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor ALTER COLUMN sensor_id SET DEFAULT nextval('maplog.sensor_sensor_id_seq'::regclass);
+
+
+--
+-- TOC entry 3639 (class 2604 OID 62715)
+-- Name: unit_to_campaign id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign ALTER COLUMN id SET DEFAULT nextval('maplog.unit_to_campaign_id_seq'::regclass);
+
+
+--
+-- TOC entry 3645 (class 2604 OID 62799)
+-- Name: unit_to_group id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group ALTER COLUMN id SET DEFAULT nextval('maplog.unit_to_group_id_seq'::regclass);
+
+
+--
+-- TOC entry 3677 (class 2606 OID 62709)
+-- Name: campaign campaign_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.campaign
+    ADD CONSTRAINT campaign_pkey PRIMARY KEY (campaign_id);
+
+
+--
+-- TOC entry 3689 (class 2606 OID 62793)
+-- Name: groups group_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.groups
+    ADD CONSTRAINT group_pkey PRIMARY KEY (group_id);
+
+
+--
+-- TOC entry 3667 (class 2606 OID 55623)
+-- Name: obs_telemetry obs_telemetry_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.obs_telemetry
+    ADD CONSTRAINT obs_telemetry_pkey PRIMARY KEY (obs_id);
+
+
+--
+-- TOC entry 3660 (class 2606 OID 55587)
+-- Name: phenomenon phenomenon_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.phenomenon
+    ADD CONSTRAINT phenomenon_pkey PRIMARY KEY (phenomenon_id);
+
+
+--
+-- TOC entry 3656 (class 2606 OID 55574)
+-- Name: sensor sensor_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor
+    ADD CONSTRAINT sensor_pkey PRIMARY KEY (sensor_id);
+
+
+--
+-- TOC entry 3658 (class 2606 OID 55576)
+-- Name: sensor sensor_sensor_name_key; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor
+    ADD CONSTRAINT sensor_sensor_name_key UNIQUE (sensor_name);
+
+
+--
+-- TOC entry 3670 (class 2606 OID 55653)
+-- Name: system_user systemuser_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.system_user
+    ADD CONSTRAINT systemuser_pk PRIMARY KEY (user_id);
+
+
+--
+-- TOC entry 3651 (class 2606 OID 55557)
+-- Name: unit unit_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit
+    ADD CONSTRAINT unit_pk PRIMARY KEY (unit_id);
+
+
+--
+-- TOC entry 3681 (class 2606 OID 62717)
+-- Name: unit_to_campaign unit_to_campaign_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign
+    ADD CONSTRAINT unit_to_campaign_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 3693 (class 2606 OID 62801)
+-- Name: unit_to_group unit_to_group_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group
+    ADD CONSTRAINT unit_to_group_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 3653 (class 2606 OID 55555)
+-- Name: unit_type unittype_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_type
+    ADD CONSTRAINT unittype_pk PRIMARY KEY (unit_type_id);
+
+
+--
+-- TOC entry 3675 (class 2606 OID 55658)
+-- Name: user_to_campaign user_to_campaign_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.user_to_campaign
+    ADD CONSTRAINT user_to_campaign_pkey PRIMARY KEY (user_id, campaign_id);
+
+
+--
+-- TOC entry 3664 (class 2606 OID 55599)
+-- Name: unit_to_sensor uts_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_sensor
+    ADD CONSTRAINT uts_pk PRIMARY KEY (unit_id, sensor_id);
+
+
+--
+-- TOC entry 3665 (class 1259 OID 55629)
+-- Name: fki_obss_unitid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_obss_unitid_fk ON maplog.obs_telemetry USING btree (unit_id);
+
+
+--
+-- TOC entry 3654 (class 1259 OID 55593)
+-- Name: fki_sens_phenom_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_sens_phenom_fk ON maplog.sensor USING btree (phenomenon_id);
+
+
+--
+-- TOC entry 3671 (class 1259 OID 62723)
+-- Name: fki_u2c_campid; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_u2c_campid ON maplog.user_to_campaign USING btree (campaign_id);
+
+
+--
+-- TOC entry 3672 (class 1259 OID 55670)
+-- Name: fki_u2c_campid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_u2c_campid_fk ON maplog.user_to_campaign USING btree (campaign_id);
+
+
+--
+-- TOC entry 3673 (class 1259 OID 55664)
+-- Name: fki_u2c_userid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_u2c_userid_fk ON maplog.user_to_campaign USING btree (user_id);
+
+
+--
+-- TOC entry 3678 (class 1259 OID 62735)
+-- Name: fki_un2c_campid; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_un2c_campid ON maplog.unit_to_campaign USING btree (camp_id);
+
+
+--
+-- TOC entry 3679 (class 1259 OID 62729)
+-- Name: fki_un2c_unitid; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_un2c_unitid ON maplog.unit_to_campaign USING btree (unit_id);
+
+
+--
+-- TOC entry 3690 (class 1259 OID 62813)
+-- Name: fki_unit2group_group_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_unit2group_group_fk ON maplog.unit_to_group USING btree (group_id);
+
+
+--
+-- TOC entry 3691 (class 1259 OID 62819)
+-- Name: fki_unit2group_unit_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_unit2group_unit_fk ON maplog.unit_to_group USING btree (unit_id);
+
+
+--
+-- TOC entry 3649 (class 1259 OID 55563)
+-- Name: fki_unit_unittype_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_unit_unittype_fk ON maplog.unit USING btree (unit_type_id);
+
+
+--
+-- TOC entry 3668 (class 1259 OID 62807)
+-- Name: fki_user2group_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_user2group_fk ON maplog.system_user USING btree (group_id);
+
+
+--
+-- TOC entry 3661 (class 1259 OID 55611)
+-- Name: fki_uts_sensorid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_uts_sensorid_fk ON maplog.unit_to_sensor USING btree (sensor_id);
+
+
+--
+-- TOC entry 3662 (class 1259 OID 55605)
+-- Name: fki_uts_unitid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_uts_unitid_fk ON maplog.unit_to_sensor USING btree (unit_id);
+
+
+--
+-- TOC entry 3700 (class 2606 OID 55624)
+-- Name: obs_telemetry obss_unitid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.obs_telemetry
+    ADD CONSTRAINT obss_unitid_fk FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3697 (class 2606 OID 55588)
+-- Name: sensor sens_phenom_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor
+    ADD CONSTRAINT sens_phenom_fk FOREIGN KEY (phenomenon_id) REFERENCES maplog.phenomenon(phenomenon_id);
+
+
+--
+-- TOC entry 3702 (class 2606 OID 62718)
+-- Name: user_to_campaign u2c_campid; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.user_to_campaign
+    ADD CONSTRAINT u2c_campid FOREIGN KEY (campaign_id) REFERENCES maplog.campaign(campaign_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3703 (class 2606 OID 55659)
+-- Name: user_to_campaign u2c_userid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.user_to_campaign
+    ADD CONSTRAINT u2c_userid_fk FOREIGN KEY (user_id) REFERENCES maplog.system_user(user_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3704 (class 2606 OID 62730)
+-- Name: unit_to_campaign un2c_campid; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign
+    ADD CONSTRAINT un2c_campid FOREIGN KEY (camp_id) REFERENCES maplog.campaign(campaign_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3705 (class 2606 OID 62724)
+-- Name: unit_to_campaign un2c_unitid; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign
+    ADD CONSTRAINT un2c_unitid FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3706 (class 2606 OID 62808)
+-- Name: unit_to_group unit2group_group_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group
+    ADD CONSTRAINT unit2group_group_fk FOREIGN KEY (group_id) REFERENCES maplog.groups(group_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3707 (class 2606 OID 62814)
+-- Name: unit_to_group unit2group_unit_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group
+    ADD CONSTRAINT unit2group_unit_fk FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3696 (class 2606 OID 55558)
+-- Name: unit unit_unittype_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit
+    ADD CONSTRAINT unit_unittype_fk FOREIGN KEY (unit_type_id) REFERENCES maplog.unit_type(unit_type_id);
+
+
+--
+-- TOC entry 3701 (class 2606 OID 62802)
+-- Name: system_user user2group_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.system_user
+    ADD CONSTRAINT user2group_fk FOREIGN KEY (group_id) REFERENCES maplog.groups(group_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3698 (class 2606 OID 55606)
+-- Name: unit_to_sensor uts_sensorid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_sensor
+    ADD CONSTRAINT uts_sensorid_fk FOREIGN KEY (sensor_id) REFERENCES maplog.sensor(sensor_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3699 (class 2606 OID 55600)
+-- Name: unit_to_sensor uts_unitid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_sensor
+    ADD CONSTRAINT uts_unitid_fk FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3862 (class 0 OID 0)
+-- Dependencies: 7
+-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres
+--
+
+REVOKE USAGE ON SCHEMA public FROM PUBLIC;
+GRANT ALL ON SCHEMA public TO PUBLIC;
+
+
+-- Completed on 2023-05-05 01:10:04
+
+--
+-- PostgreSQL database dump complete
+--
+

+ 849 - 0
sql/telemetry-model-v2.sql

@@ -0,0 +1,849 @@
+--
+-- PostgreSQL database dump
+--
+
+-- Dumped from database version 11.12
+-- Dumped by pg_dump version 15.2
+
+-- Started on 2023-05-05 01:10:04
+
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = on;
+SELECT pg_catalog.set_config('search_path', '', false);
+SET check_function_bodies = false;
+SET xmloption = content;
+SET client_min_messages = warning;
+SET row_security = off;
+
+
+CREATE ROLE senslog NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
+CREATE ROLE maplog_app NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN PASSWORD 'MAPlog';
+GRANT senslog TO maplog_app;
+
+--
+-- TOC entry 3861 (class 1262 OID 54507)
+-- Name: maplog; Type: DATABASE; Schema: -; Owner: postgres
+--
+
+
+
+CREATE DATABASE maplog WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.UTF-8';
+
+
+ALTER DATABASE maplog OWNER TO senslog;
+
+\connect maplog
+
+SET statement_timeout = 0;
+SET lock_timeout = 0;
+SET idle_in_transaction_session_timeout = 0;
+SET client_encoding = 'UTF8';
+SET standard_conforming_strings = on;
+SELECT pg_catalog.set_config('search_path', '', false);
+SET check_function_bodies = false;
+SET xmloption = content;
+SET client_min_messages = warning;
+SET row_security = off;
+
+--
+-- TOC entry 9 (class 2615 OID 55528)
+-- Name: maplog; Type: SCHEMA; Schema: -; Owner: postgres
+--
+
+CREATE SCHEMA maplog;
+
+
+ALTER SCHEMA maplog OWNER TO senslog;
+
+--
+-- TOC entry 7 (class 2615 OID 2200)
+-- Name: public; Type: SCHEMA; Schema: -; Owner: postgres
+--
+
+-- *not* creating schema, since initdb creates it
+
+
+ALTER SCHEMA public OWNER TO senslog;
+
+--
+-- TOC entry 2 (class 3079 OID 54508)
+-- Name: postgis; Type: EXTENSION; Schema: -; Owner: -
+--
+
+CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA public;
+
+
+--
+-- TOC entry 3863 (class 0 OID 0)
+-- Dependencies: 2
+-- Name: EXTENSION postgis; Type: COMMENT; Schema: -; Owner: 
+--
+
+COMMENT ON EXTENSION postgis IS 'PostGIS geometry and geography spatial types and functions';
+
+
+SET default_tablespace = '';
+
+--
+-- TOC entry 216 (class 1259 OID 62701)
+-- Name: campaign; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.campaign (
+    campaign_id integer NOT NULL,
+    description text NOT NULL,
+    from_time timestamp with time zone NOT NULL,
+    to_time timestamp with time zone NOT NULL
+);
+
+
+ALTER TABLE maplog.campaign OWNER TO senslog;
+
+--
+-- TOC entry 215 (class 1259 OID 62699)
+-- Name: campaign_campaign_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.campaign_campaign_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.campaign_campaign_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3864 (class 0 OID 0)
+-- Dependencies: 215
+-- Name: campaign_campaign_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.campaign_campaign_id_seq OWNED BY maplog.campaign.campaign_id;
+
+
+--
+-- TOC entry 224 (class 1259 OID 62784)
+-- Name: groups; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.groups (
+    group_id integer NOT NULL,
+    group_name text,
+    parent_group_id integer,
+    has_children boolean DEFAULT false
+);
+
+
+ALTER TABLE maplog.groups OWNER TO senslog;
+
+--
+-- TOC entry 223 (class 1259 OID 62782)
+-- Name: group_group_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.group_group_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.group_group_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3865 (class 0 OID 0)
+-- Dependencies: 223
+-- Name: group_group_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.group_group_id_seq OWNED BY maplog.groups.group_id;
+
+
+--
+-- TOC entry 212 (class 1259 OID 55614)
+-- Name: obs_telemetry; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.obs_telemetry (
+    obs_id bigint NOT NULL,
+    time_stamp timestamp with time zone NOT NULL,
+    unit_id bigint NOT NULL,
+    observed_values jsonb NOT NULL,
+    the_geom public.geometry(Point,4326),
+    time_received timestamp with time zone DEFAULT now() NOT NULL
+);
+
+
+ALTER TABLE maplog.obs_telemetry OWNER TO senslog;
+
+--
+-- TOC entry 211 (class 1259 OID 55612)
+-- Name: obs_telemetry_obs_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.obs_telemetry_obs_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.obs_telemetry_obs_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3866 (class 0 OID 0)
+-- Dependencies: 211
+-- Name: obs_telemetry_obs_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.obs_telemetry_obs_id_seq OWNED BY maplog.obs_telemetry.obs_id;
+
+
+--
+-- TOC entry 209 (class 1259 OID 55579)
+-- Name: phenomenon; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.phenomenon (
+    phenomenon_id integer NOT NULL,
+    phenomenon_name text NOT NULL,
+    uom character varying(30) NOT NULL,
+    uom_link text
+);
+
+
+ALTER TABLE maplog.phenomenon OWNER TO senslog;
+
+--
+-- TOC entry 208 (class 1259 OID 55577)
+-- Name: phenomenon_phenomenon_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.phenomenon_phenomenon_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.phenomenon_phenomenon_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3867 (class 0 OID 0)
+-- Dependencies: 208
+-- Name: phenomenon_phenomenon_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.phenomenon_phenomenon_id_seq OWNED BY maplog.phenomenon.phenomenon_id;
+
+
+--
+-- TOC entry 207 (class 1259 OID 55566)
+-- Name: sensor; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.sensor (
+    sensor_id bigint NOT NULL,
+    sensor_name character varying(100),
+    sensor_type text,
+    sensor_type_id integer,
+    min_range text,
+    max_range text,
+    phenomenon_id integer NOT NULL
+);
+
+
+ALTER TABLE maplog.sensor OWNER TO senslog;
+
+--
+-- TOC entry 206 (class 1259 OID 55564)
+-- Name: sensor_sensor_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.sensor_sensor_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.sensor_sensor_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3868 (class 0 OID 0)
+-- Dependencies: 206
+-- Name: sensor_sensor_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.sensor_sensor_id_seq OWNED BY maplog.sensor.sensor_id;
+
+
+--
+-- TOC entry 213 (class 1259 OID 55644)
+-- Name: system_user; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.system_user (
+    user_id integer NOT NULL,
+    user_name text NOT NULL,
+    user_real_name text,
+    user_password text,
+    group_id integer,
+    rights_id integer DEFAULT 0
+);
+
+
+ALTER TABLE maplog.system_user OWNER TO senslog;
+
+--
+-- TOC entry 204 (class 1259 OID 55540)
+-- Name: unit; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit (
+    unit_id bigint NOT NULL,
+    description text,
+    is_mobile boolean DEFAULT true NOT NULL,
+    unit_type_id character varying(2) DEFAULT 'X'::character varying NOT NULL
+);
+
+
+ALTER TABLE maplog.unit OWNER TO senslog;
+
+--
+-- TOC entry 218 (class 1259 OID 62712)
+-- Name: unit_to_campaign; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_to_campaign (
+    id integer NOT NULL,
+    camp_id integer NOT NULL,
+    unit_id bigint NOT NULL,
+    from_time timestamp with time zone NOT NULL,
+    to_time timestamp with time zone NOT NULL
+);
+
+
+ALTER TABLE maplog.unit_to_campaign OWNER TO senslog;
+
+--
+-- TOC entry 217 (class 1259 OID 62710)
+-- Name: unit_to_campaign_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.unit_to_campaign_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.unit_to_campaign_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3869 (class 0 OID 0)
+-- Dependencies: 217
+-- Name: unit_to_campaign_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.unit_to_campaign_id_seq OWNED BY maplog.unit_to_campaign.id;
+
+
+--
+-- TOC entry 226 (class 1259 OID 62796)
+-- Name: unit_to_group; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_to_group (
+    id integer NOT NULL,
+    group_id integer,
+    unit_id bigint
+);
+
+
+ALTER TABLE maplog.unit_to_group OWNER TO senslog;
+
+--
+-- TOC entry 225 (class 1259 OID 62794)
+-- Name: unit_to_group_id_seq; Type: SEQUENCE; Schema: maplog; Owner: postgres
+--
+
+CREATE SEQUENCE maplog.unit_to_group_id_seq
+    AS integer
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+ALTER TABLE maplog.unit_to_group_id_seq OWNER TO senslog;
+
+--
+-- TOC entry 3870 (class 0 OID 0)
+-- Dependencies: 225
+-- Name: unit_to_group_id_seq; Type: SEQUENCE OWNED BY; Schema: maplog; Owner: postgres
+--
+
+ALTER SEQUENCE maplog.unit_to_group_id_seq OWNED BY maplog.unit_to_group.id;
+
+
+--
+-- TOC entry 210 (class 1259 OID 55594)
+-- Name: unit_to_sensor; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_to_sensor (
+    sensor_id bigint NOT NULL,
+    unit_id bigint NOT NULL,
+    first_obs_ts timestamp with time zone,
+    last_obs_ts timestamp with time zone,
+    last_obs_value double precision DEFAULT 'NaN'::double precision
+);
+
+
+ALTER TABLE maplog.unit_to_sensor OWNER TO senslog;
+
+--
+-- TOC entry 205 (class 1259 OID 55548)
+-- Name: unit_type; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.unit_type (
+    unit_type_id character varying(2) NOT NULL,
+    type_name character varying(20) NOT NULL,
+    description text
+);
+
+
+ALTER TABLE maplog.unit_type OWNER TO senslog;
+
+--
+-- TOC entry 214 (class 1259 OID 55654)
+-- Name: user_to_campaign; Type: TABLE; Schema: maplog; Owner: postgres
+--
+
+CREATE TABLE maplog.user_to_campaign (
+    user_id integer NOT NULL,
+    campaign_id integer NOT NULL
+);
+
+
+ALTER TABLE maplog.user_to_campaign OWNER TO senslog;
+
+
+--
+-- TOC entry 3638 (class 2604 OID 62704)
+-- Name: campaign campaign_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.campaign ALTER COLUMN campaign_id SET DEFAULT nextval('maplog.campaign_campaign_id_seq'::regclass);
+
+
+--
+-- TOC entry 3643 (class 2604 OID 62787)
+-- Name: groups group_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.groups ALTER COLUMN group_id SET DEFAULT nextval('maplog.group_group_id_seq'::regclass);
+
+
+--
+-- TOC entry 3635 (class 2604 OID 55617)
+-- Name: obs_telemetry obs_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.obs_telemetry ALTER COLUMN obs_id SET DEFAULT nextval('maplog.obs_telemetry_obs_id_seq'::regclass);
+
+
+--
+-- TOC entry 3633 (class 2604 OID 55582)
+-- Name: phenomenon phenomenon_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.phenomenon ALTER COLUMN phenomenon_id SET DEFAULT nextval('maplog.phenomenon_phenomenon_id_seq'::regclass);
+
+
+--
+-- TOC entry 3632 (class 2604 OID 55569)
+-- Name: sensor sensor_id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor ALTER COLUMN sensor_id SET DEFAULT nextval('maplog.sensor_sensor_id_seq'::regclass);
+
+
+--
+-- TOC entry 3639 (class 2604 OID 62715)
+-- Name: unit_to_campaign id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign ALTER COLUMN id SET DEFAULT nextval('maplog.unit_to_campaign_id_seq'::regclass);
+
+
+--
+-- TOC entry 3645 (class 2604 OID 62799)
+-- Name: unit_to_group id; Type: DEFAULT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group ALTER COLUMN id SET DEFAULT nextval('maplog.unit_to_group_id_seq'::regclass);
+
+
+--
+-- TOC entry 3677 (class 2606 OID 62709)
+-- Name: campaign campaign_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.campaign
+    ADD CONSTRAINT campaign_pkey PRIMARY KEY (campaign_id);
+
+
+--
+-- TOC entry 3689 (class 2606 OID 62793)
+-- Name: groups group_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.groups
+    ADD CONSTRAINT group_pkey PRIMARY KEY (group_id);
+
+
+--
+-- TOC entry 3667 (class 2606 OID 55623)
+-- Name: obs_telemetry obs_telemetry_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.obs_telemetry
+    ADD CONSTRAINT obs_telemetry_pkey PRIMARY KEY (obs_id);
+
+
+--
+-- TOC entry 3660 (class 2606 OID 55587)
+-- Name: phenomenon phenomenon_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.phenomenon
+    ADD CONSTRAINT phenomenon_pkey PRIMARY KEY (phenomenon_id);
+
+
+--
+-- TOC entry 3656 (class 2606 OID 55574)
+-- Name: sensor sensor_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor
+    ADD CONSTRAINT sensor_pkey PRIMARY KEY (sensor_id);
+
+
+--
+-- TOC entry 3658 (class 2606 OID 55576)
+-- Name: sensor sensor_sensor_name_key; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor
+    ADD CONSTRAINT sensor_sensor_name_key UNIQUE (sensor_name);
+
+
+--
+-- TOC entry 3670 (class 2606 OID 55653)
+-- Name: system_user systemuser_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.system_user
+    ADD CONSTRAINT systemuser_pk PRIMARY KEY (user_id);
+
+
+--
+-- TOC entry 3651 (class 2606 OID 55557)
+-- Name: unit unit_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit
+    ADD CONSTRAINT unit_pk PRIMARY KEY (unit_id);
+
+
+--
+-- TOC entry 3681 (class 2606 OID 62717)
+-- Name: unit_to_campaign unit_to_campaign_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign
+    ADD CONSTRAINT unit_to_campaign_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 3693 (class 2606 OID 62801)
+-- Name: unit_to_group unit_to_group_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group
+    ADD CONSTRAINT unit_to_group_pkey PRIMARY KEY (id);
+
+
+--
+-- TOC entry 3653 (class 2606 OID 55555)
+-- Name: unit_type unittype_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_type
+    ADD CONSTRAINT unittype_pk PRIMARY KEY (unit_type_id);
+
+
+--
+-- TOC entry 3675 (class 2606 OID 55658)
+-- Name: user_to_campaign user_to_campaign_pkey; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.user_to_campaign
+    ADD CONSTRAINT user_to_campaign_pkey PRIMARY KEY (user_id, campaign_id);
+
+
+--
+-- TOC entry 3664 (class 2606 OID 55599)
+-- Name: unit_to_sensor uts_pk; Type: CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_sensor
+    ADD CONSTRAINT uts_pk PRIMARY KEY (unit_id, sensor_id);
+
+
+--
+-- TOC entry 3665 (class 1259 OID 55629)
+-- Name: fki_obss_unitid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_obss_unitid_fk ON maplog.obs_telemetry USING btree (unit_id);
+
+
+--
+-- TOC entry 3654 (class 1259 OID 55593)
+-- Name: fki_sens_phenom_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_sens_phenom_fk ON maplog.sensor USING btree (phenomenon_id);
+
+
+--
+-- TOC entry 3671 (class 1259 OID 62723)
+-- Name: fki_u2c_campid; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_u2c_campid ON maplog.user_to_campaign USING btree (campaign_id);
+
+
+--
+-- TOC entry 3672 (class 1259 OID 55670)
+-- Name: fki_u2c_campid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_u2c_campid_fk ON maplog.user_to_campaign USING btree (campaign_id);
+
+
+--
+-- TOC entry 3673 (class 1259 OID 55664)
+-- Name: fki_u2c_userid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_u2c_userid_fk ON maplog.user_to_campaign USING btree (user_id);
+
+
+--
+-- TOC entry 3678 (class 1259 OID 62735)
+-- Name: fki_un2c_campid; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_un2c_campid ON maplog.unit_to_campaign USING btree (camp_id);
+
+
+--
+-- TOC entry 3679 (class 1259 OID 62729)
+-- Name: fki_un2c_unitid; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_un2c_unitid ON maplog.unit_to_campaign USING btree (unit_id);
+
+
+--
+-- TOC entry 3690 (class 1259 OID 62813)
+-- Name: fki_unit2group_group_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_unit2group_group_fk ON maplog.unit_to_group USING btree (group_id);
+
+
+--
+-- TOC entry 3691 (class 1259 OID 62819)
+-- Name: fki_unit2group_unit_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_unit2group_unit_fk ON maplog.unit_to_group USING btree (unit_id);
+
+
+--
+-- TOC entry 3649 (class 1259 OID 55563)
+-- Name: fki_unit_unittype_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_unit_unittype_fk ON maplog.unit USING btree (unit_type_id);
+
+
+--
+-- TOC entry 3668 (class 1259 OID 62807)
+-- Name: fki_user2group_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_user2group_fk ON maplog.system_user USING btree (group_id);
+
+
+--
+-- TOC entry 3661 (class 1259 OID 55611)
+-- Name: fki_uts_sensorid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_uts_sensorid_fk ON maplog.unit_to_sensor USING btree (sensor_id);
+
+
+--
+-- TOC entry 3662 (class 1259 OID 55605)
+-- Name: fki_uts_unitid_fk; Type: INDEX; Schema: maplog; Owner: postgres
+--
+
+CREATE INDEX fki_uts_unitid_fk ON maplog.unit_to_sensor USING btree (unit_id);
+
+
+--
+-- TOC entry 3700 (class 2606 OID 55624)
+-- Name: obs_telemetry obss_unitid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.obs_telemetry
+    ADD CONSTRAINT obss_unitid_fk FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3697 (class 2606 OID 55588)
+-- Name: sensor sens_phenom_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.sensor
+    ADD CONSTRAINT sens_phenom_fk FOREIGN KEY (phenomenon_id) REFERENCES maplog.phenomenon(phenomenon_id);
+
+
+--
+-- TOC entry 3702 (class 2606 OID 62718)
+-- Name: user_to_campaign u2c_campid; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.user_to_campaign
+    ADD CONSTRAINT u2c_campid FOREIGN KEY (campaign_id) REFERENCES maplog.campaign(campaign_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3703 (class 2606 OID 55659)
+-- Name: user_to_campaign u2c_userid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.user_to_campaign
+    ADD CONSTRAINT u2c_userid_fk FOREIGN KEY (user_id) REFERENCES maplog.system_user(user_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3704 (class 2606 OID 62730)
+-- Name: unit_to_campaign un2c_campid; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign
+    ADD CONSTRAINT un2c_campid FOREIGN KEY (camp_id) REFERENCES maplog.campaign(campaign_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3705 (class 2606 OID 62724)
+-- Name: unit_to_campaign un2c_unitid; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_campaign
+    ADD CONSTRAINT un2c_unitid FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3706 (class 2606 OID 62808)
+-- Name: unit_to_group unit2group_group_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group
+    ADD CONSTRAINT unit2group_group_fk FOREIGN KEY (group_id) REFERENCES maplog.groups(group_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3707 (class 2606 OID 62814)
+-- Name: unit_to_group unit2group_unit_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_group
+    ADD CONSTRAINT unit2group_unit_fk FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3696 (class 2606 OID 55558)
+-- Name: unit unit_unittype_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit
+    ADD CONSTRAINT unit_unittype_fk FOREIGN KEY (unit_type_id) REFERENCES maplog.unit_type(unit_type_id);
+
+
+--
+-- TOC entry 3701 (class 2606 OID 62802)
+-- Name: system_user user2group_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.system_user
+    ADD CONSTRAINT user2group_fk FOREIGN KEY (group_id) REFERENCES maplog.groups(group_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3698 (class 2606 OID 55606)
+-- Name: unit_to_sensor uts_sensorid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_sensor
+    ADD CONSTRAINT uts_sensorid_fk FOREIGN KEY (sensor_id) REFERENCES maplog.sensor(sensor_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3699 (class 2606 OID 55600)
+-- Name: unit_to_sensor uts_unitid_fk; Type: FK CONSTRAINT; Schema: maplog; Owner: postgres
+--
+
+ALTER TABLE ONLY maplog.unit_to_sensor
+    ADD CONSTRAINT uts_unitid_fk FOREIGN KEY (unit_id) REFERENCES maplog.unit(unit_id) ON UPDATE CASCADE ON DELETE CASCADE;
+
+
+--
+-- TOC entry 3862 (class 0 OID 0)
+-- Dependencies: 7
+-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres
+--
+
+REVOKE USAGE ON SCHEMA public FROM PUBLIC;
+GRANT ALL ON SCHEMA public TO PUBLIC;
+
+
+-- Completed on 2023-05-05 01:10:04
+
+--
+-- PostgreSQL database dump complete
+--
+

+ 3 - 3
src/main/java/cz/senslog/telemetry/app/PropertyConfig.java

@@ -42,15 +42,15 @@ public final class PropertyConfig {
         }
 
         public String getDatabase() {
-            return "telemetry";
+            return "maplog";
         }
 
         public String getUser() {
-            return "postgres";
+            return "maplog_app";
         }
 
         public String getPassword() {
-            return "telemetry";
+            return "MAPlog";
         }
 
         public int getPoolSize() {

+ 90 - 0
src/main/java/cz/senslog/telemetry/database/domain/ObsTelemetry.java

@@ -0,0 +1,90 @@
+package cz.senslog.telemetry.database.domain;
+
+import io.vertx.core.json.JsonObject;
+
+import java.time.OffsetDateTime;
+
+public class ObsTelemetry {
+
+    private long id;
+    private OffsetDateTime timestamp;
+    private long unitId;
+    private JsonObject observedValues;
+    private int longitude;
+    private int latitude;
+    private int altitude;
+    private int angle;
+    private int speed;
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public OffsetDateTime getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(OffsetDateTime timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public long getUnitId() {
+        return unitId;
+    }
+
+    public void setUnitId(long unitId) {
+        this.unitId = unitId;
+    }
+
+    public JsonObject getObservedValues() {
+        return observedValues;
+    }
+
+    public void setObservedValues(JsonObject observedValues) {
+        this.observedValues = observedValues;
+    }
+
+    public int getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(int longitude) {
+        this.longitude = longitude;
+    }
+
+    public int getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(int latitude) {
+        this.latitude = latitude;
+    }
+
+    public int getAltitude() {
+        return altitude;
+    }
+
+    public void setAltitude(int altitude) {
+        this.altitude = altitude;
+    }
+
+    public int getAngle() {
+        return angle;
+    }
+
+    public void setAngle(int angle) {
+        this.angle = angle;
+    }
+
+    public int getSpeed() {
+        return speed;
+    }
+
+    public void setSpeed(int speed) {
+        this.speed = speed;
+    }
+}

+ 1 - 1
src/main/java/cz/senslog/telemetry/domain/Post.java → src/main/java/cz/senslog/telemetry/database/domain/Post.java

@@ -1,4 +1,4 @@
-package cz.senslog.telemetry.domain;
+package cz.senslog.telemetry.database.domain;
 
 
 import io.vertx.core.json.JsonObject;

+ 20 - 0
src/main/java/cz/senslog/telemetry/database/domain/Sensor.java

@@ -0,0 +1,20 @@
+package cz.senslog.telemetry.database.domain;
+
+public class Sensor {
+
+    private long sensorId;
+
+    public static Sensor of(long sensorId) {
+        Sensor s = new Sensor();
+        s.setSensorId(sensorId);
+        return s;
+    }
+
+    public long getSensorId() {
+        return sensorId;
+    }
+
+    public void setSensorId(long sensorId) {
+        this.sensorId = sensorId;
+    }
+}

+ 49 - 0
src/main/java/cz/senslog/telemetry/database/domain/Unit.java

@@ -0,0 +1,49 @@
+package cz.senslog.telemetry.database.domain;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Unit {
+
+    private long unitId;
+
+    private String imei;
+
+    private List<Sensor> sensors;
+
+    public static Unit of(long unitId, String imei, List<Sensor> sensors) {
+        Unit u = new Unit();
+        u.setUnitId(unitId);
+        u.setImei(imei);
+        u.setSensors(sensors);
+        return u;
+    }
+
+    public static Unit of(long unitId, String imei) {
+        return of(unitId, imei, Collections.emptyList());
+    }
+
+    public long getUnitId() {
+        return unitId;
+    }
+
+    public void setUnitId(long unitId) {
+        this.unitId = unitId;
+    }
+
+    public String getImei() {
+        return imei;
+    }
+
+    public void setImei(String imei) {
+        this.imei = imei;
+    }
+
+    public List<Sensor> getSensors() {
+        return sensors;
+    }
+
+    public void setSensors(List<Sensor> sensors) {
+        this.sensors = sensors;
+    }
+}

+ 8 - 7
src/main/java/cz/senslog/telemetry/database/TelemetryRepository.java → src/main/java/cz/senslog/telemetry/database/repository/PostRepository.java

@@ -1,6 +1,7 @@
-package cz.senslog.telemetry.database;
+package cz.senslog.telemetry.database.repository;
 
-import cz.senslog.telemetry.domain.Post;
+import cz.senslog.telemetry.database.DataNotFoundException;
+import cz.senslog.telemetry.database.domain.Post;
 import io.vertx.core.Future;
 import io.vertx.pgclient.PgPool;
 import io.vertx.sqlclient.Row;
@@ -16,9 +17,9 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
-public class TelemetryRepository {
+public class PostRepository {
 
-    private static final Logger logger = LogManager.getLogger(TelemetryRepository.class);
+    private static final Logger logger = LogManager.getLogger(PostRepository.class);
 
     private static Function<Row, Post> MAPPER = (row) ->
             Post.of(
@@ -28,12 +29,12 @@ public class TelemetryRepository {
             );
 
     private final PgPool client;
-    private TelemetryRepository(PgPool client) {
+    private PostRepository(PgPool client) {
         this.client = client;
     }
 
-    public static TelemetryRepository create(PgPool client) {
-        return new TelemetryRepository(client);
+    public static PostRepository create(PgPool client) {
+        return new PostRepository(client);
     }
 
     public Future<List<Post>> findAll() {

+ 57 - 0
src/main/java/cz/senslog/telemetry/database/repository/SensorNetworkRepository.java

@@ -0,0 +1,57 @@
+package cz.senslog.telemetry.database.repository;
+
+import cz.senslog.telemetry.database.DataNotFoundException;
+import cz.senslog.telemetry.database.domain.Unit;
+import io.vertx.core.Future;
+import io.vertx.pgclient.PgPool;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowSet;
+import io.vertx.sqlclient.Tuple;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class SensorNetworkRepository {
+
+    private static final Logger logger = LogManager.getLogger(SensorNetworkRepository.class);
+
+    private static final Function<Row, Unit> ROW_TO_UNIT = (row) ->
+            Unit.of(row.getLong("unit_id"), row.getString("imei"));
+
+    private static final Function<Object, Tuple> UNIT_TO_TUPLE = (data) ->
+            Tuple.of(null);
+
+    private final PgPool client;
+
+    private SensorNetworkRepository(PgPool client) {
+        this.client = client;
+    }
+
+    public static SensorNetworkRepository create(PgPool client) {
+        return new SensorNetworkRepository(client);
+    }
+
+
+    public Future<List<Unit>> loadAllUnits() {
+        return client.query("SELECT unit_id FROM maplog.unit")
+                .execute()
+                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
+                        .map(ROW_TO_UNIT).collect(Collectors.toList())
+                );
+    }
+
+    public Future<Unit> findUnitByIMEI(String imei) {
+        return client.query("SELECT unit_id, imei FROM maplog.unit LIMIT 1")
+                .execute()
+                .map(RowSet::iterator)
+                .map(iterator -> iterator.hasNext() ? ROW_TO_UNIT.apply(iterator.next()) : null)
+                .map(Optional::ofNullable)
+                .map(p -> p.orElseThrow(() -> new DataNotFoundException(imei)));
+    }
+
+}

+ 72 - 0
src/main/java/cz/senslog/telemetry/database/repository/TelemetryRepository.java

@@ -0,0 +1,72 @@
+package cz.senslog.telemetry.database.repository;
+
+import cz.senslog.telemetry.database.domain.ObsTelemetry;
+import io.vertx.core.Future;
+import io.vertx.pgclient.PgPool;
+import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.SqlResult;
+import io.vertx.sqlclient.Tuple;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.postgresql.util.PGobject;
+
+import java.sql.SQLException;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toList;
+
+public class TelemetryRepository {
+
+    private static final Logger logger = LogManager.getLogger(TelemetryRepository.class);
+
+    private static Function<Row, ObsTelemetry> MAPPER_TO_OBJECT = (row) ->
+            new ObsTelemetry();
+
+    private static Function<ObsTelemetry, Tuple> MAPPER_TO_TUPLE = (data) ->
+            Tuple.of(data.getTimestamp(), // 2019-06-10 10:05:36+00
+                    data.getUnitId(),
+                    data.getObservedValues().toString(),
+                    data.getLongitude(), data.getLatitude());
+
+    private final PgPool client;
+
+    private TelemetryRepository(PgPool client) {
+        this.client = client;
+    }
+
+    public static TelemetryRepository create(PgPool client) {
+        return new TelemetryRepository(client);
+    }
+
+
+    public Future<Integer> save(ObsTelemetry data) {
+        return client.preparedQuery("INSERT INTO maplog.obs_telemetry(time_stamp, unit_id, observed_values, the_geom) VALUES ($1, $2, $3::json, ST_SetSRID(ST_MakePoint($4, $5), 4326)) RETURNING (id)")
+                .execute(MAPPER_TO_TUPLE.apply(data))
+                .onComplete(res -> {
+                    if (res.succeeded()) {
+                        logger.info(res.result());
+                    } else {
+                        logger.error(res.cause());
+                    }
+                })
+                .map(rs -> rs.iterator().next().getInteger("id"))
+                .onFailure(logger::error);
+    }
+
+    public Future<Integer> saveAll(List<ObsTelemetry> data) {
+        List<Tuple> tuples = data.stream().map(MAPPER_TO_TUPLE).collect(toList());
+        return client.preparedQuery("INSERT INTO maplog.obs_telemetry(time_stamp, unit_id, observed_values, the_geom) VALUES ($1, $2,  $3::json, ST_SetSRID(ST_MakePoint($4, $5), 4326))")
+                .executeBatch(tuples)
+                .onComplete(res -> {
+                    if (res.succeeded()) {
+                        logger.info(res.result());
+                    } else {
+                        logger.error(res.cause());
+                    }
+                })
+                .map(SqlResult::rowCount);
+    }
+}

+ 24 - 0
src/main/java/cz/senslog/telemetry/domain/IOProperty.java

@@ -0,0 +1,24 @@
+package cz.senslog.telemetry.domain;
+
+public class IOProperty {
+
+    private final byte id;
+    private final long value;
+
+    public static IOProperty create(byte id, long value) {
+        return new IOProperty(id, value);
+    }
+
+    private IOProperty(byte id, long value) {
+        this.id = id;
+        this.value = value;
+    }
+
+    public byte getId() {
+        return id;
+    }
+
+    public long getValue() {
+        return value;
+    }
+}

+ 94 - 0
src/main/java/cz/senslog/telemetry/domain/TelemetryAVLRecord.java

@@ -0,0 +1,94 @@
+package cz.senslog.telemetry.domain;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TelemetryAVLRecord {
+
+    // add status of observation
+    // REJECT, NEW, UPDATE ...
+
+    private Instant timestamp;
+    private int priority;
+    private int longitude;
+    private int latitude;
+    private int altitude;
+    private int angle;
+    private int satellites;
+    private int speed;
+
+    private List<IOProperty> ioProperties;
+
+    public TelemetryAVLRecord() {
+        this.ioProperties = new ArrayList<>();
+    }
+
+    public List<IOProperty> getIoProperties() {
+        return ioProperties;
+    }
+
+    public Instant getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(Instant timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
+    public int getLongitude() {
+        return longitude;
+    }
+
+    public void setLongitude(int longitude) {
+        this.longitude = longitude;
+    }
+
+    public int getLatitude() {
+        return latitude;
+    }
+
+    public void setLatitude(int latitude) {
+        this.latitude = latitude;
+    }
+
+    public int getAltitude() {
+        return altitude;
+    }
+
+    public void setAltitude(int altitude) {
+        this.altitude = altitude;
+    }
+
+    public int getAngle() {
+        return angle;
+    }
+
+    public void setAngle(int angle) {
+        this.angle = angle;
+    }
+
+    public int getSatellites() {
+        return satellites;
+    }
+
+    public void setSatellites(int satellites) {
+        this.satellites = satellites;
+    }
+
+    public int getSpeed() {
+        return speed;
+    }
+
+    public void setSpeed(int speed) {
+        this.speed = speed;
+    }
+}

+ 23 - 0
src/main/java/cz/senslog/telemetry/protocol/CodecType.java

@@ -0,0 +1,23 @@
+package cz.senslog.telemetry.protocol;
+
+public enum CodecType {
+
+    CODEC_8      (0x08),
+    CODEC_16     (0x16)
+
+    ;
+    private final int codecId;
+
+    CodecType(int codecId) {
+        this.codecId = codecId;
+    }
+
+    public static CodecType getType(int codeId) {
+        for (CodecType value : values()) {
+            if (value.codecId == codeId) {
+                return value;
+            }
+        }
+        return null;
+    }
+}

+ 118 - 0
src/main/java/cz/senslog/telemetry/protocol/Fm4ex.java

@@ -0,0 +1,118 @@
+package cz.senslog.telemetry.protocol;
+
+import cz.senslog.telemetry.domain.IOProperty;
+import cz.senslog.telemetry.domain.TelemetryAVLRecord;
+import io.vertx.core.buffer.Buffer;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.time.Instant;
+
+public class Fm4ex {
+
+    private static final Logger logger = LogManager.getLogger(Fm4ex.class);
+
+
+    private static void bytesToBinPrint(byte[] bytes) {
+        System.out.print("0b");
+        for (byte b1 : bytes) {
+            String s1 = String.format("%8s", Integer.toBinaryString(b1 & 0xFF)).replace(' ', '0');
+            System.out.print(s1); // 10000001
+        }
+        System.out.println();
+    }
+
+    public static String parseIMEI(Buffer buffer) {
+        return buffer.getString(2, buffer.getShort(0) + 2);
+    }
+
+    public static TelemetryAVLRecord[] parseAVL(Buffer buffer) {
+     //   System.out.println(Arrays.toString(buffer.getBytes()));
+
+        int preamble = buffer.getInt(0);
+        if (preamble != 0) {
+
+        }
+        // check preamble is 0
+
+        int dataLen = buffer.getInt(4);
+        CodecType codecType = CodecType.getType(buffer.getByte(8));
+        // check codecType is CODEC_8
+
+        byte dataNm = buffer.getByte(9);
+        byte byteIndex = 10;
+
+        TelemetryAVLRecord[] records = new TelemetryAVLRecord[dataNm];
+        for (int recordIndex = 0; recordIndex < records.length; recordIndex++) {
+            records[recordIndex] = new TelemetryAVLRecord();
+            TelemetryAVLRecord tel = records[recordIndex];
+
+            tel.setTimestamp(Instant.ofEpochMilli(buffer.getLong(byteIndex)));
+            byteIndex+=8;
+
+            tel.setPriority(buffer.getByte(byteIndex));
+            byteIndex+=1;
+
+            tel.setLongitude(buffer.getInt(byteIndex));
+            byteIndex+=4;
+
+            tel.setLatitude(buffer.getInt(byteIndex));
+            byteIndex+=4;
+
+            tel.setAltitude(buffer.getShort(byteIndex));
+            byteIndex+=2;
+
+            tel.setAngle(buffer.getShort(byteIndex));
+            byteIndex+=2;
+
+            tel.setSatellites(buffer.getByte(byteIndex));
+            byteIndex+=1;
+
+            tel.setSpeed(buffer.getShort(32));
+            byteIndex+=2;
+
+            // IO
+            byte eventIOid = buffer.getByte(byteIndex++);
+            byte nmOfEventIDs = buffer.getByte(byteIndex++);
+
+            byte nmOf1BIOs = buffer.getByte(byteIndex++);
+            for (int count = 0; count < nmOf1BIOs; count++, byteIndex+=1) {
+                byte io1BId = buffer.getByte(byteIndex++);
+                byte io1BVal = buffer.getByte(byteIndex);
+                tel.getIoProperties().add(IOProperty.create(io1BId, io1BVal));
+            }
+
+            byte nmOf2BIOs = buffer.getByte(byteIndex++);
+            for (int count = 0; count < nmOf2BIOs; count++, byteIndex+=2) {
+                byte io2BId = buffer.getByte(byteIndex++);
+                short io2BVal = buffer.getShort(byteIndex);
+                tel.getIoProperties().add(IOProperty.create(io2BId, io2BVal));
+            }
+
+            byte nmOf4BIOs = buffer.getByte(byteIndex++);
+            for (int count = 0; count < nmOf4BIOs; count++, byteIndex+=4) {
+                byte io4BId = buffer.getByte(byteIndex++);
+                int io4BVal = buffer.getInt(byteIndex);
+                tel.getIoProperties().add(IOProperty.create(io4BId, io4BVal));
+            }
+
+            byte nmOf8BIOs = buffer.getByte(byteIndex++);
+            for (int count = 0; count < nmOf8BIOs; count++, byteIndex+=8) {
+                byte io8BId = buffer.getByte(byteIndex++);
+                long io8BVal = buffer.getLong(byteIndex);
+                tel.getIoProperties().add(IOProperty.create(io8BId, io8BVal));
+            }
+
+            // nmOfEventIDs == tel.getIoProperties().size();
+
+        }
+
+        byte nmTotalRecords = buffer.getByte(byteIndex++);
+       //  telRecords.length == nmTotalRecords == dataNm
+        int crc = buffer.getInt(byteIndex);
+        int crcNew = buffer.getInt(buffer.length()-4); // 51151 or 00 00 C7 CF
+        // check CRC
+
+        return records;
+    }
+}

+ 99 - 0
src/main/java/cz/senslog/telemetry/server/Fm4exSocketHandler.java

@@ -0,0 +1,99 @@
+package cz.senslog.telemetry.server;
+
+import cz.senslog.telemetry.database.domain.ObsTelemetry;
+import cz.senslog.telemetry.database.domain.Unit;
+import cz.senslog.telemetry.database.repository.SensorNetworkRepository;
+import cz.senslog.telemetry.database.repository.TelemetryRepository;
+import cz.senslog.telemetry.domain.IOProperty;
+import cz.senslog.telemetry.domain.TelemetryAVLRecord;
+import cz.senslog.telemetry.protocol.Fm4ex;
+import io.vertx.core.Future;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.json.JsonObject;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.time.OffsetDateTime;
+import java.util.*;
+import java.util.function.Function;
+
+import static java.time.ZoneOffset.UTC;
+
+public class Fm4exSocketHandler {
+
+    private static final Logger logger = LogManager.getLogger(Fm4exSocketHandler.class);
+
+    private static final LoopInvoker.Initializer<Buffer, Byte, SocketContext> invoker = LoopInvoker.<Buffer, Byte, SocketContext>create()
+            .registerHandler((b, c) -> c.handleIMEI(b))
+            .registerHandler((b, c) -> c.persistAVLData(b));
+    private final Map<String, LoopInvoker<Buffer, Byte, SocketContext>> sockets = new HashMap<>();
+
+    private final Function<String, LoopInvoker<Buffer, Byte, SocketContext>> newContext;
+
+    private Fm4exSocketHandler(TelemetryRepository telemetryRepo, SensorNetworkRepository sensorRepo) {
+        this.newContext = socId -> invoker.init(new SocketContext(socId, telemetryRepo, sensorRepo));
+    }
+
+    public static Fm4exSocketHandler create(TelemetryRepository telemetryRepo, SensorNetworkRepository sensorRepo) {
+        return new Fm4exSocketHandler(telemetryRepo, sensorRepo);
+    }
+
+    public Future<Buffer> process(String socketID, Buffer buffer) {
+        return sockets.computeIfAbsent(socketID, newContext).invoke(buffer)
+                .map(res -> Buffer.buffer(new byte[]{res}));
+    }
+
+    private static class SocketContext {
+
+        private static final Logger logger = LogManager.getLogger(SocketContext.class);
+
+        private static final Byte SUCCESS = (byte)0x01, ERROR = (byte)0x00;
+
+        private final TelemetryRepository telemetryRepo;
+        private final SensorNetworkRepository sensorRepo;
+
+        private final String socketId;
+
+        private Unit contextUnit;
+
+        private SocketContext(String socketId, TelemetryRepository telemetryRepo, SensorNetworkRepository sensorRepo) {
+            this.socketId = socketId;
+            this.telemetryRepo = telemetryRepo;
+            this.sensorRepo = sensorRepo;
+        }
+
+        public Future<Byte> handleIMEI(Buffer buffer) {
+            String imei = Fm4ex.parseIMEI(buffer);
+            return sensorRepo.findUnitByIMEI(imei).map(u -> {
+                contextUnit = u;
+                return SUCCESS;
+            });
+        }
+
+        public Future<Byte> persistAVLData(Buffer buffer) {
+            TelemetryAVLRecord[] avlRecords = Fm4ex.parseAVL(buffer);
+            if (avlRecords.length == 0) {
+                return Future.succeededFuture(ERROR);
+            }
+            List<ObsTelemetry> telemetries = new ArrayList<>(avlRecords.length);
+            for (TelemetryAVLRecord avl : avlRecords) {
+                ObsTelemetry obs = new ObsTelemetry();
+                telemetries.add(obs);
+
+                obs.setUnitId(contextUnit.getUnitId());
+                obs.setTimestamp(avl.getTimestamp().atZone(UTC).toOffsetDateTime());
+                obs.setLatitude(avl.getLatitude());
+                obs.setLongitude(avl.getLongitude());
+                obs.setAltitude(avl.getAltitude());
+                obs.setSpeed(avl.getSpeed());
+                obs.setAngle(avl.getAngle());
+                JsonObject observedValues = new JsonObject();
+                for (IOProperty io : avl.getIoProperties()) {
+                    observedValues.put(Byte.toString(io.getId()), io.getValue());
+                }
+                obs.setObservedValues(observedValues);
+            }
+            return telemetryRepo.saveAll(telemetries).map(res -> res > 0 ? SUCCESS : ERROR);
+        }
+    }
+}

+ 2 - 2
src/main/java/cz/senslog/telemetry/server/HttpVertxServer.java

@@ -1,7 +1,7 @@
 package cz.senslog.telemetry.server;
 
 import cz.senslog.telemetry.database.ConnectionPool;
-import cz.senslog.telemetry.database.TelemetryRepository;
+import cz.senslog.telemetry.database.repository.PostRepository;
 import io.vertx.core.AbstractVerticle;
 import io.vertx.core.Promise;
 import io.vertx.core.http.HttpMethod;
@@ -28,7 +28,7 @@ public final class HttpVertxServer extends AbstractVerticle {
                     logger.info("The OpenAPI specification was loaded successfully.");
 
                     PgPool pgPool = ConnectionPool.createWithVertx(vertx, config());
-                    TelemetryRepository repository = TelemetryRepository.create(pgPool);
+                    PostRepository repository = PostRepository.create(pgPool);
                     OpenAPIHandler apiHandler = OpenAPIHandler.create(repository);
 
                     routerBuilder.operation("infoOperation").handler(apiHandler::info);

+ 0 - 27
src/main/java/cz/senslog/telemetry/server/LoopCascadeInvoker.java

@@ -1,27 +0,0 @@
-package cz.senslog.telemetry.server;
-
-
-import java.util.List;
-import java.util.function.Function;
-
-public final class LoopCascadeInvoker<I, O> {
-
-    private final List<Function<I, O>> functions;
-    private int currentStart;
-
-    public static <I, O> LoopCascadeInvokerInitializer<I, O> create() {
-        return new LoopCascadeInvokerInitializer<>();
-    }
-
-    LoopCascadeInvoker(int start, List<Function<I, O>> functions) {
-        this.currentStart = start;
-        this.functions = functions;
-    }
-
-    public O invoke(I buffer) {
-        if (functions.isEmpty()) { return null; }
-        O res = functions.get(currentStart).apply(buffer);
-        currentStart = ++currentStart % functions.size();
-        return res;
-    }
-}

+ 0 - 28
src/main/java/cz/senslog/telemetry/server/LoopCascadeInvokerInitializer.java

@@ -1,28 +0,0 @@
-package cz.senslog.telemetry.server;
-
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-public class LoopCascadeInvokerInitializer<I, O> {
-
-    private final List<Function<I, O>> functions;
-
-    public LoopCascadeInvokerInitializer() {
-        this.functions = new ArrayList<>();
-    }
-
-    public LoopCascadeInvoker<I, O> init() {
-        return new LoopCascadeInvoker<>(0, functions);
-    }
-
-    public LoopCascadeInvoker<I, O> init(int start) {
-        return new LoopCascadeInvoker<>(start, functions);
-    }
-
-    public LoopCascadeInvokerInitializer<I, O> registerHandler(Function<I, O> fc) {
-        functions.add(fc);
-        return this;
-    }
-}

+ 74 - 0
src/main/java/cz/senslog/telemetry/server/LoopInvoker.java

@@ -0,0 +1,74 @@
+package cz.senslog.telemetry.server;
+
+import cz.senslog.telemetry.utils.Tuple;
+import io.vertx.core.Future;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+
+public class LoopInvoker<I, O, C> {
+
+    private final List<Tuple<AtomicInteger, BiFunction<I, C, Future<O>>>> functions;
+
+    private final AtomicInteger currentStart;
+
+    private final C telemetryContext;
+
+
+    public static <I, O, C> Initializer<I, O, C> create() {
+        return new Initializer<>();
+    }
+
+    LoopInvoker(int start, C telemetryContext, List<Tuple<AtomicInteger, BiFunction<I, C, Future<O>>>> functions) {
+        if (start >= functions.size()) {
+            throw new RuntimeException("Start position is out of range of functions.");
+        }
+        this.currentStart = new AtomicInteger(start);
+        this.telemetryContext = telemetryContext;
+        this.functions = functions;
+    }
+
+    public Future<O> invoke(I param) {
+        if (functions.isEmpty()) { return null; }
+        Tuple<AtomicInteger, BiFunction<I, C, Future<O>>> f = functions.get(currentStart.get());
+        Future<O> res = Future.failedFuture(new ServiceConfigurationError("No method found."));
+        if (f.getItem1().get() < 0) {
+            res = functions.get(currentStart.get()).getItem2().apply(param, telemetryContext);
+            currentStart.set(currentStart.incrementAndGet() % functions.size());
+        } else if (f.getItem1().get() > 0) {
+            res = functions.get(currentStart.get()).getItem2().apply(param, telemetryContext);
+            if (f.getItem1().decrementAndGet() == 0) {
+                functions.remove(currentStart.get());
+            } else {
+                currentStart.set(currentStart.incrementAndGet() % functions.size());
+            }
+        }
+        return res;
+    }
+
+    public static class Initializer<I, O, C> {
+
+        private final List<Tuple<AtomicInteger, BiFunction<I, C, Future<O>>>> functions;
+
+        public Initializer() {
+            this.functions = new ArrayList<>();
+        }
+
+        public LoopInvoker<I, O, C> init(C ctx) {
+            return new LoopInvoker<>(0, ctx, functions);
+        }
+
+        public Initializer<I, O, C> registerHandler(BiFunction<I, C, Future<O>> fc, int repeat) {
+            functions.add(Tuple.of(new AtomicInteger(repeat), fc));
+            return this;
+        }
+
+        public Initializer<I, O, C> registerHandler(BiFunction<I, C, Future<O>> fc) {
+            return registerHandler(fc, -1);
+        }
+    }
+
+}

+ 5 - 5
src/main/java/cz/senslog/telemetry/server/OpenAPIHandler.java

@@ -1,8 +1,8 @@
 package cz.senslog.telemetry.server;
 
 import cz.senslog.telemetry.app.Application;
-import cz.senslog.telemetry.database.TelemetryRepository;
-import cz.senslog.telemetry.domain.Post;
+import cz.senslog.telemetry.database.repository.PostRepository;
+import cz.senslog.telemetry.database.domain.Post;
 import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
 import io.vertx.ext.web.RoutingContext;
@@ -15,12 +15,12 @@ import java.util.stream.Collectors;
 public class OpenAPIHandler {
     private static final Logger logger = LogManager.getLogger(OpenAPIHandler.class);
 
-    private final TelemetryRepository repository;
-    private OpenAPIHandler(TelemetryRepository repository) {
+    private final PostRepository repository;
+    private OpenAPIHandler(PostRepository repository) {
         this.repository = repository;
     }
 
-    public static OpenAPIHandler create(TelemetryRepository repository) {
+    public static OpenAPIHandler create(PostRepository repository) {
         return new OpenAPIHandler(repository);
     }
 

+ 0 - 38
src/main/java/cz/senslog/telemetry/server/SocketHandler.java

@@ -1,38 +0,0 @@
-package cz.senslog.telemetry.server;
-
-import cz.senslog.telemetry.database.TelemetryRepository;
-import io.vertx.core.buffer.Buffer;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-public class SocketHandler {
-
-    private final TelemetryRepository repository;
-
-    private final Map<String, LoopCascadeInvoker<Buffer, Buffer>> socCounter = new HashMap<>();
-
-    private final LoopCascadeInvokerInitializer<Buffer, Buffer> invoker = LoopCascadeInvoker.<Buffer, Buffer>create()
-            .registerHandler(b -> Buffer.buffer(("first").getBytes()))
-            .registerHandler(b -> Buffer.buffer(("second").getBytes()));
-
-    private SocketHandler(TelemetryRepository repository) {
-        this.repository = repository;
-    }
-
-    public static SocketHandler create(TelemetryRepository repository) {
-        return new SocketHandler(repository);
-    }
-
-    private static final Logger logger = LogManager.getLogger(SocketHandler.class);
-
-    public Buffer process(String socketID, Buffer buffer) {
-        Buffer res = socCounter.computeIfAbsent(socketID, k -> invoker.init()).invoke(buffer);
-        logger.info("Received: " + Arrays.toString(buffer.getBytes()));
-
-        return res;
-    }
-}

+ 7 - 5
src/main/java/cz/senslog/telemetry/server/TCPVertxServer.java

@@ -1,7 +1,8 @@
 package cz.senslog.telemetry.server;
 
 import cz.senslog.telemetry.database.ConnectionPool;
-import cz.senslog.telemetry.database.TelemetryRepository;
+import cz.senslog.telemetry.database.repository.SensorNetworkRepository;
+import cz.senslog.telemetry.database.repository.TelemetryRepository;
 import io.vertx.core.AbstractVerticle;
 import io.vertx.core.Promise;
 import io.vertx.core.json.JsonObject;
@@ -22,10 +23,11 @@ public final class TCPVertxServer extends AbstractVerticle {
         NetServer server = vertx.createNetServer(serverOpt);
 
         PgPool pgPool = ConnectionPool.createWithVertx(vertx, config());
-        TelemetryRepository repository = TelemetryRepository.create(pgPool);
-        SocketHandler socHandler = SocketHandler.create(repository);
-        server.connectHandler(socket -> socket
-                    .handler(buffer -> socket.write(socHandler.process(socket.writeHandlerID(), buffer)))
+        TelemetryRepository telemetryRepo = TelemetryRepository.create(pgPool);
+        SensorNetworkRepository sensorRepo = SensorNetworkRepository.create(pgPool);
+        Fm4exSocketHandler socHandler = Fm4exSocketHandler.create(telemetryRepo, sensorRepo);
+        server.connectHandler(socket -> socket.handler(
+                buffer -> socHandler.process(socket.writeHandlerID(), buffer).onSuccess(socket::write))
                     .exceptionHandler(logger::error)
                     .closeHandler(v -> logger.info("The socket '{}' has been closed", socket.writeHandlerID()))
         );

+ 5 - 5
src/main/java/cz/senslog/telemetry/server/TestHandler.java

@@ -1,18 +1,18 @@
 package cz.senslog.telemetry.server;
 
-import cz.senslog.telemetry.database.TelemetryRepository;
-import cz.senslog.telemetry.domain.Post;
+import cz.senslog.telemetry.database.repository.PostRepository;
+import cz.senslog.telemetry.database.domain.Post;
 import io.vertx.core.json.Json;
 import io.vertx.ext.web.RoutingContext;
 
 
 public class TestHandler {
-    TelemetryRepository posts;
-    private TestHandler(TelemetryRepository posts) {
+    PostRepository posts;
+    private TestHandler(PostRepository posts) {
         this.posts = posts;
     }
 
-    public static TestHandler create(TelemetryRepository posts) {
+    public static TestHandler create(PostRepository posts) {
         return new TestHandler(posts);
     }
 

+ 65 - 0
src/main/java/cz/senslog/telemetry/utils/CRC16.java

@@ -0,0 +1,65 @@
+package cz.senslog.telemetry.utils;
+
+/******************************************************************************
+ *  Compilation:  javac CRC16.java
+ *  Execution:    java CRC16 s
+ *
+ *  Reads in a string s as a command-line argument, and prints out
+ *  its 16-bit Cyclic Redundancy Check (CRC16). Uses a lookup table.
+ *
+ *  Reference:  http://www.gelato.unsw.edu.au/lxr/source/lib/crc16.c
+ *
+ *  % java CRC16 123456789
+ *  CRC16 = bb3d
+ *
+ * Uses irreducible polynomial:  1 + x^2 + x^15 + x^16
+ *
+ *
+ ******************************************************************************/
+
+public class CRC16 {
+
+    private static final int[] table = {
+            0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
+            0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
+            0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
+            0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
+            0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
+            0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
+            0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
+            0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
+            0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
+            0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
+            0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
+            0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
+            0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
+            0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
+            0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
+            0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
+            0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
+            0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
+            0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
+            0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
+            0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
+            0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
+            0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
+            0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
+            0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
+            0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
+            0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
+            0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
+            0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
+            0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
+            0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
+            0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,
+    };
+
+    public static int get(byte[] bytes) {
+        int crc = 0x0000;
+        for (byte b : bytes) {
+            crc = (crc >>> 8) ^ table[(crc ^ b) & 0xff];
+        }
+        return crc;
+    }
+
+}

+ 24 - 0
src/main/java/cz/senslog/telemetry/utils/Tuple.java

@@ -0,0 +1,24 @@
+package cz.senslog.telemetry.utils;
+
+public final class Tuple<A, B> {
+
+    private final A item1;
+    private final B item2;
+
+    private Tuple(A item1, B item2) {
+        this.item1 = item1;
+        this.item2 = item2;
+    }
+
+    public static <A, B> Tuple<A,B> of(A item1, B item2) {
+        return new Tuple<>(item1, item2);
+    }
+
+    public A getItem1() {
+        return item1;
+    }
+
+    public B getItem2() {
+        return item2;
+    }
+}

+ 26 - 0
src/test/java/cz/senslog/telemetry/BinaryDataSet.java

@@ -0,0 +1,26 @@
+package cz.senslog.telemetry;
+
+
+import io.vertx.core.buffer.Buffer;
+
+public abstract class BinaryDataSet<D> {
+
+    private final Buffer buffer;
+
+    private final D object;
+
+    protected BinaryDataSet(Buffer b, D o) {
+        this.buffer = b;
+        this.object = o;
+    }
+
+    public Buffer getBuffer() {
+        return buffer;
+    }
+
+    public D getObject() {
+        return object;
+    }
+
+    public abstract void evaluate(D object);
+}

+ 114 - 0
src/test/java/cz/senslog/telemetry/DataSet.java

@@ -0,0 +1,114 @@
+package cz.senslog.telemetry;
+
+import cz.senslog.telemetry.database.domain.ObsTelemetry;
+import cz.senslog.telemetry.domain.IOProperty;
+import cz.senslog.telemetry.domain.TelemetryAVLRecord;
+import io.vertx.core.buffer.Buffer;
+
+import java.time.Instant;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DataSet {
+
+    public static void evaluate(TelemetryAVLRecord expected, ObsTelemetry actual) {
+        assertEquals(expected.getTimestamp(), actual.getTimestamp().toInstant());
+        assertEquals(expected.getLongitude(), actual.getLongitude());
+        assertEquals(expected.getLatitude(), actual.getLatitude());
+        assertEquals(expected.getAltitude(), actual.getAltitude());
+        assertEquals(expected.getSpeed(), actual.getSpeed());
+        assertEquals(expected.getAngle(), actual.getAngle());
+        for (IOProperty ioExp : expected.getIoProperties()) {
+            assertEquals(ioExp.getValue(), actual.getObservedValues().getValue(Byte.toString(ioExp.getId())));
+        }
+    }
+
+    private static void evaluate(TelemetryAVLRecord[] expected, TelemetryAVLRecord[] actual) {
+        assertEquals(expected.length, actual.length);
+        TelemetryAVLRecord ref = expected[0];
+        TelemetryAVLRecord eval = actual[0];
+
+        //     assertEquals(ref.getTimestamp(), eval.getTimestamp());
+        assertEquals(ref.getPriority(), eval.getPriority());
+        assertEquals(ref.getLatitude(), eval.getLatitude());
+        assertEquals(ref.getLongitude(), eval.getLongitude());
+        assertEquals(ref.getAltitude(), eval.getAltitude());
+        assertEquals(ref.getSatellites(), eval.getSatellites());
+        assertEquals(ref.getSpeed(), eval.getSpeed());
+
+        assertEquals(ref.getIoProperties().size(), eval.getIoProperties().size());
+
+        for (int i = 0; i < ref.getIoProperties().size(); i++) {
+            assertEquals(ref.getIoProperties().get(i).getId(), eval.getIoProperties().get(i).getId());
+            assertEquals(ref.getIoProperties().get(i).getValue(), eval.getIoProperties().get(i).getValue());
+        }
+    }
+
+    public static BinaryDataSet<String> IMEI_nm1() {
+        return new BinaryDataSet<>(Buffer.buffer(new byte[]{
+                0, 15, 51, 53, 54, 51, 48, 55, 48, 52, 50, 52, 52, 49, 48, 49, 51, 13, 10
+        }),
+                "356307042441013"
+        ) {
+            @Override
+            public void evaluate(String object) {
+                assertEquals(this.getObject(), object);
+            }
+        };
+    }
+
+    public static BinaryDataSet<TelemetryAVLRecord[]> AVLData_nm1() {
+        return new BinaryDataSet<>(Buffer.buffer(new byte[]{
+                0, 0, 0, 0, 0, 0, 0, 54, 8, 1, 0, 0, 1, 107, 64, 63, 63, 48, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 0, 0, 0, 0, 1, 5, 2, 21, 3, 1, 1, 1, 66, 94, 15, 1, 63, 0, 0, 96, 26, 1, 78, 0,
+                0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 63, 63, 13, 10
+        }), new TelemetryAVLRecord[]{
+                new TelemetryAVLRecord(){{
+                    setTimestamp(Instant.parse("2019-06-10T10:04:46Z"));
+                    setPriority(1);
+                    setLatitude(0);
+                    setLongitude(0);
+                    setAltitude(0);
+                    setSpeed(0);
+                    setSatellites(0);
+                    getIoProperties().add(IOProperty.create((byte)21, 3));
+                    getIoProperties().add(IOProperty.create((byte)1, 1));
+                    getIoProperties().add(IOProperty.create((byte)66, 24079));
+                    getIoProperties().add(IOProperty.create((byte)241, 24602));
+                    getIoProperties().add(IOProperty.create((byte)78, 0));
+                }}
+        }) {
+            @Override
+            public void evaluate(TelemetryAVLRecord[] object) {
+                DataSet.evaluate(this.getObject(), object);
+            }
+        };
+    }
+
+    public static BinaryDataSet<TelemetryAVLRecord[]> AVLData_nm2() {
+        return new BinaryDataSet<>(Buffer.buffer(new byte[]{
+                0, 0, 0, 0, 0, 0, 0, 40, 8, 1, 0, 0, 1, 107, 64, 63, 63, 63, 1,
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 21, 3, 1,
+                1, 1, 66, 94, 16, 0, 0, 1, 0, 0, 63, 42, 13, 10
+        }), new TelemetryAVLRecord[]{
+                new TelemetryAVLRecord(){{
+                    setTimestamp(Instant.parse("2019-06-10T10:05:36Z"));
+                    setPriority(1);
+                    setLatitude(0);
+                    setLongitude(0);
+                    setAltitude(0);
+                    setSpeed(0);
+                    setSatellites(0);
+                    getIoProperties().add(IOProperty.create((byte)21, 3));
+                    getIoProperties().add(IOProperty.create((byte)1, 1));
+                    getIoProperties().add(IOProperty.create((byte)66, 24079));
+                }}
+        }) {
+            @Override
+            public void evaluate(TelemetryAVLRecord[] object) {
+                DataSet.evaluate(this.getObject(), object);
+            }
+        };
+    }
+
+}

+ 84 - 0
src/test/java/cz/senslog/telemetry/database/repository/TelemetryRepositoryTest.java

@@ -0,0 +1,84 @@
+package cz.senslog.telemetry.database.repository;
+
+import cz.senslog.telemetry.BinaryDataSet;
+import cz.senslog.telemetry.DataSet;
+import cz.senslog.telemetry.app.PropertyConfig;
+import cz.senslog.telemetry.database.domain.ObsTelemetry;
+import cz.senslog.telemetry.database.domain.Unit;
+import io.vertx.core.Vertx;
+import io.vertx.core.json.JsonObject;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+import io.vertx.pgclient.PgPool;
+import io.vertx.sqlclient.Tuple;
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+
+import static cz.senslog.telemetry.database.ConnectionPool.createWithVertx;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(VertxExtension.class)
+class TelemetryRepositoryTest {
+
+    private static final long TEST_UNIT_ID = 100001;
+    private static final BinaryDataSet<String> IMEI_DATASET = DataSet.IMEI_nm1();
+    private static final Unit TEST_UNIT = Unit.of(TEST_UNIT_ID, IMEI_DATASET.getObject());
+
+    private PgPool pgPool;
+
+    @BeforeEach
+    void create_repository(Vertx vertx, VertxTestContext testContext) {
+        pgPool = createWithVertx(vertx, JsonObject.of("db", PropertyConfig.getInstance().db()));
+
+        // create sensor network
+        pgPool.preparedQuery("INSERT INTO maplog.unit_type(unit_type_id, type_name) VALUES($1, $2) RETURNING (unit_type_id)")
+                .execute(Tuple.of("T", "test"))
+                .map(rs -> rs.iterator().next().getString("unit_type_id"))
+                .onComplete(unit_type_id -> pgPool.preparedQuery("INSERT INTO  maplog.unit(unit_id, imei, unit_type_id) VALUES($1, $2, $3) RETURNING (unit_id)")
+                        .execute(Tuple.of(TEST_UNIT.getUnitId(), TEST_UNIT.getImei(), unit_type_id.result()))
+                        .map(rs -> rs.iterator().next().getLong("unit_id"))
+                        .onComplete(testContext.succeeding(unit_id -> testContext.verify(() -> {
+                            assertThat(unit_id).isEqualTo(TEST_UNIT.getUnitId());
+                            testContext.completeNow();
+                        })))
+                );
+
+    }
+
+    @Test
+    void save(Vertx vertx, VertxTestContext testContext) {
+        ObsTelemetry obs = new ObsTelemetry();
+        obs.setUnitId(TEST_UNIT_ID);
+        obs.setObservedValues(JsonObject.of());
+        obs.setLongitude(0);
+        obs.setLatitude(0);
+        obs.setSpeed(0);
+        obs.setAltitude(0);
+        obs.setAngle(0);
+        obs.setTimestamp(OffsetDateTime.now());
+
+        List<ObsTelemetry> data = List.of(obs);
+
+        TelemetryRepository repo = TelemetryRepository.create(pgPool);
+        repo.saveAll(data).onComplete(testContext.succeeding(res -> testContext.verify(() -> {
+            assertThat(res).isEqualTo(data.size());
+            testContext.completeNow();
+        })));
+    }
+
+    @AfterEach
+    void clean(Vertx vertx, VertxTestContext testContext) {
+        pgPool.preparedQuery("DELETE FROM maplog.unit WHERE unit_id = $1 RETURNING (unit_type_id)")
+                .execute(Tuple.of(TEST_UNIT_ID))
+                .map(rs -> rs.iterator().next().getString("unit_type_id"))
+                .onComplete(delUnit ->
+                        pgPool.preparedQuery("DELETE FROM maplog.unit_type WHERE unit_type_id = $1")
+                                .execute(Tuple.of(delUnit.result()))
+                                .onComplete(testContext.succeedingThenComplete())
+                );
+    }
+
+}

+ 113 - 0
src/test/java/cz/senslog/telemetry/protocol/Fm4exTest.java

@@ -0,0 +1,113 @@
+package cz.senslog.telemetry.protocol;
+
+import cz.senslog.telemetry.BinaryDataSet;
+import cz.senslog.telemetry.DataSet;
+import cz.senslog.telemetry.domain.TelemetryAVLRecord;
+import io.vertx.core.buffer.Buffer;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class Fm4exTest {
+
+    @Test
+    void test_IMEI_nm1() {
+        BinaryDataSet<String> dataset = DataSet.IMEI_nm1();
+
+        String imei = Fm4ex.parseIMEI(dataset.getBuffer());
+
+        dataset.evaluate(imei);
+    }
+
+    @Test
+    void test_AVLData_nm1() {
+        BinaryDataSet<TelemetryAVLRecord[]> dataset = DataSet.AVLData_nm1();
+
+        TelemetryAVLRecord[] telRecords = Fm4ex.parseAVL(dataset.getBuffer());
+
+        dataset.evaluate(telRecords);
+    }
+
+    @Test
+    void test_AVLData_nm2() {
+        BinaryDataSet<TelemetryAVLRecord[]> dataset = DataSet.AVLData_nm2();
+
+        TelemetryAVLRecord[] telRecords = Fm4ex.parseAVL(dataset.getBuffer());
+
+        dataset.evaluate(telRecords);
+    }
+
+    @Test // example 1
+    void parseExample1() {
+
+        Buffer buffer = Buffer.buffer(new byte[]{
+                0, 0, 0, 0, 0, 0, 0, 54, 8, 1, 0, 0, 1, 107, 64, 63, 63, 48, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 0, 0, 0, 0, 1, 5, 2, 21, 3, 1, 1, 1, 66, 94, 15, 1, 63, 0, 0, 96, 26, 1, 78, 0,
+                0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 63, 63, 13, 10
+        });
+
+        TelemetryAVLRecord[] telRecords = Fm4ex.parseAVL(buffer);
+        assertEquals(1, telRecords.length);
+        TelemetryAVLRecord tel = telRecords[0];
+
+    //    assertEquals(Instant.parse("2019-06-10T10:04:46Z"), tel.getTimestamp());
+        assertEquals(1, tel.getPriority());
+        assertEquals(0, tel.getLatitude());
+        assertEquals(0, tel.getLongitude());
+        assertEquals(0, tel.getAltitude());
+        assertEquals(0, tel.getSatellites());
+        assertEquals(0, tel.getSpeed());
+
+        assertEquals(5, tel.getIoProperties().size());
+
+        assertEquals(21, tel.getIoProperties().get(0).getId());
+        assertEquals(3, tel.getIoProperties().get(0).getValue());
+
+        assertEquals(1, tel.getIoProperties().get(1).getId());
+        assertEquals(1, tel.getIoProperties().get(1).getValue());
+
+        assertEquals(66, tel.getIoProperties().get(2).getId());
+        assertEquals(24079, tel.getIoProperties().get(2).getValue());
+
+   //     assertEquals(241, obs.getIoProperties().get(3).getId());
+        assertEquals(24602, tel.getIoProperties().get(3).getValue());
+
+        assertEquals(78, tel.getIoProperties().get(4).getId());
+        assertEquals(0, tel.getIoProperties().get(4).getValue());
+    }
+
+    @Test // example 1
+    void parseExample2() {
+
+        Buffer buffer = Buffer.buffer(new byte[]{
+                0, 0, 0, 0, 0, 0, 0, 40, 8, 1, 0, 0, 1, 107, 64, 63, 63, 63, 1,
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 21, 3, 1,
+                1, 1, 66, 94, 16, 0, 0, 1, 0, 0, 63, 42, 13, 10
+        });
+
+        TelemetryAVLRecord[] telRecords = Fm4ex.parseAVL(buffer);
+        assertEquals(1, telRecords.length);
+        TelemetryAVLRecord tel = telRecords[0];
+
+        //assertEquals(Instant.parse("2019-06-10T10:05:36Z"), obs.getTimestamp());
+        assertEquals(1, tel.getPriority());
+        assertEquals(0, tel.getLatitude());
+        assertEquals(0, tel.getLongitude());
+        assertEquals(0, tel.getAltitude());
+        assertEquals(0, tel.getSatellites());
+        assertEquals(0, tel.getSpeed());
+
+        assertEquals(3, tel.getIoProperties().size());
+
+        assertEquals(21, tel.getIoProperties().get(0).getId());
+        assertEquals(3, tel.getIoProperties().get(0).getValue());
+
+        assertEquals(1, tel.getIoProperties().get(1).getId());
+        assertEquals(1, tel.getIoProperties().get(1).getValue());
+
+        assertEquals(66, tel.getIoProperties().get(2).getId());
+        assertEquals(24080, tel.getIoProperties().get(2).getValue());
+
+    }
+
+}

+ 55 - 0
src/test/java/cz/senslog/telemetry/server/Fm4exSocketHandlerTest.java

@@ -0,0 +1,55 @@
+package cz.senslog.telemetry.server;
+
+import cz.senslog.telemetry.BinaryDataSet;
+import cz.senslog.telemetry.DataSet;
+import cz.senslog.telemetry.database.domain.ObsTelemetry;
+import cz.senslog.telemetry.database.domain.Unit;
+import cz.senslog.telemetry.database.repository.SensorNetworkRepository;
+import cz.senslog.telemetry.database.repository.TelemetryRepository;
+import cz.senslog.telemetry.domain.TelemetryAVLRecord;
+import io.vertx.core.Future;
+import io.vertx.core.buffer.Buffer;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class Fm4exSocketHandlerTest {
+
+    @Test
+    void process() {
+
+        String socketId = "test_socket_id";
+        long unitId = 100L;
+        BinaryDataSet<String> datasetIMEI = DataSet.IMEI_nm1();
+        BinaryDataSet<TelemetryAVLRecord[]> datasetAVLData = DataSet.AVLData_nm1();
+
+        TelemetryRepository telemetryRepo = mock(TelemetryRepository.class);
+        ArgumentCaptor<List<ObsTelemetry>> obsTelemetryCapture = ArgumentCaptor.forClass(List.class);
+        when(telemetryRepo.saveAll(obsTelemetryCapture.capture())).thenReturn(Future.succeededFuture(1));
+
+
+        SensorNetworkRepository sensorRepo = mock(SensorNetworkRepository.class);
+        when(sensorRepo.findUnitByIMEI(anyString()))
+                .thenReturn(Future.succeededFuture(Unit.of(unitId, datasetIMEI.getObject())));
+
+        Fm4exSocketHandler handler = Fm4exSocketHandler.create(telemetryRepo, sensorRepo);
+
+        handler.process(socketId, datasetIMEI.getBuffer()).onComplete(ctx ->
+                assertEquals(0x01, ctx.result().getByte(0))
+        );
+        handler.process(socketId, datasetAVLData.getBuffer()).onComplete(ctx ->
+                assertEquals(0x01, ctx.result().getByte(0))
+        );
+
+        List<ObsTelemetry> persistValues = obsTelemetryCapture.getValue();
+        assertEquals(1, persistValues.size());
+
+        assertEquals(unitId, persistValues.get(0).getUnitId());
+        DataSet.evaluate(datasetAVLData.getObject()[0], persistValues.get(0));
+    }
+}