├── docs ├── mitransNo285.pdf ├── gost33472-2015.pdf ├── gost54619-2011.pdf └── egts_sr_egtsplus_data.proto ├── cli ├── receiver │ ├── storage │ │ ├── store │ │ │ ├── postgresql │ │ │ │ ├── connector_test.go │ │ │ │ └── connector.go │ │ │ ├── mysql │ │ │ │ └── connector.go │ │ │ ├── nats │ │ │ │ └── connector.go │ │ │ ├── redis │ │ │ │ └── connector.go │ │ │ ├── rabbitmq │ │ │ │ └── connector.go │ │ │ └── tarantool_queue │ │ │ │ └── connector.go │ │ ├── default.go │ │ ├── packet.go │ │ └── store.go │ ├── integration_test │ │ ├── redis_test.go │ │ ├── mysql_test.go │ │ ├── postgresql_test.go │ │ └── integration_test.go │ ├── main.go │ ├── config │ │ ├── config.go │ │ └── config_test.go │ ├── main_test.go │ └── server │ │ ├── server_test.go │ │ └── server.go └── packet-gen │ └── main.go ├── libs └── egts │ ├── binary_data.go │ ├── crc_test.go │ ├── crc.go │ ├── egts_sr_abs_an_sensors_data_test.go │ ├── egts_sr_abs_an_sensors_data.go │ ├── egts_pt_response_test.go │ ├── egts_sr_result_code.go │ ├── egts_sr_pos_data_test.go │ ├── egts_sr_abs_cntr_data_test.go │ ├── egts_sr_state_data_test.go │ ├── egts_sr_ext_pos_data_test.go │ ├── egts_sr_liquid_level_sensor_test.go │ ├── service_data_records_test.go │ ├── egts_sr_counters_data_test.go │ ├── egts_sr_response.go │ ├── egts_sr_result_code_test.go │ ├── egts_sr_dispatcher_identity_test.go │ ├── egts_sr_egtsplus_data_test.go │ ├── egts_sr_abs_cntr_data.go │ ├── egts_sr_response_test.go │ ├── egts_sr_dispatcher_identity.go │ ├── egts_pt_response.go │ ├── egts_sr_auth_info_test.go │ ├── record_data_test.go │ ├── const_types.go │ ├── egts_sr_auth_info.go │ ├── egts_sr_ad_sensors_data_test.go │ ├── egts_sr_term_identity_test.go │ ├── errors.go │ ├── egts_sr_liquid_level_sensor.go │ ├── egts_sr_passengers_counters_test.go │ ├── egts_sr_state_data.go │ ├── egts_sr_module_data.go │ ├── egts_sr_ext_pos_data.go │ ├── record_data.go │ ├── egts_sr_passengers_counters.go │ ├── egts_sr_term_identity.go │ ├── service_data_records.go │ ├── egts_sr_module_data_test.go │ ├── egts_sr_counters_data.go │ ├── egts_sr_pos_data.go │ ├── egts_pkg.go │ └── egts_pkg_test.go ├── Dockerfile ├── .gitignore ├── Makefile ├── go.mod ├── docker-compose-test-env.yml ├── configs ├── receiver.yaml └── config.test.yaml ├── tools └── send_egts.py └── README.md /docs/mitransNo285.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuznetsovin/egts-protocol/HEAD/docs/mitransNo285.pdf -------------------------------------------------------------------------------- /docs/gost33472-2015.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuznetsovin/egts-protocol/HEAD/docs/gost33472-2015.pdf -------------------------------------------------------------------------------- /docs/gost54619-2011.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuznetsovin/egts-protocol/HEAD/docs/gost54619-2011.pdf -------------------------------------------------------------------------------- /cli/receiver/storage/store/postgresql/connector_test.go: -------------------------------------------------------------------------------- 1 | package postgresql 2 | 3 | func SetupTestDB() {} 4 | 5 | func TeardownTestDB() {} 6 | -------------------------------------------------------------------------------- /libs/egts/binary_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | // BinaryData интерфейс для работы с бинарными секциями 4 | type BinaryData interface { 5 | Decode([]byte) error 6 | Encode() ([]byte, error) 7 | Length() uint16 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.18 as egts-builder 2 | 3 | ENV GO111MODULE=on 4 | 5 | WORKDIR /app 6 | 7 | COPY . . 8 | 9 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 make 10 | 11 | FROM busybox 12 | 13 | COPY --from=egts-builder /app/bin /app/ 14 | COPY --from=egts-builder /app/configs/receiver.yaml /etc/egts-receviver/config.yaml 15 | 16 | ENTRYPOINT ["/app/receiver", "-c", "/etc/egts-receviver/config.yaml"] 17 | -------------------------------------------------------------------------------- /libs/egts/crc_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func Test_crc8(t *testing.T) { 9 | crc := crc8([]byte("123456789")) 10 | checkVal := byte(0xF7) 11 | 12 | assert.Equal(t, crc, checkVal) 13 | } 14 | 15 | func Test_crc16(t *testing.T) { 16 | crc := crc16([]byte("123456789")) 17 | checkVal := uint16(0x29b1) 18 | 19 | assert.Equal(t, crc, checkVal) 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Go 2 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 3 | *.o 4 | *.a 5 | *.so 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | 27 | .idea/ 28 | bin/ 29 | .vscode/ 30 | receiver/debug 31 | .DS_Store -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test 2 | TEST_CONFIG_PATH = $(shell pwd)/configs/config.test.yaml 3 | 4 | all: build_receiver build_packet_gen 5 | 6 | docker: 7 | docker build -t egts:latest . 8 | 9 | build_receiver: 10 | go build -o bin/receiver ./cli/receiver 11 | 12 | build_packet_gen: 13 | go build -o bin/packet_gen ./cli/packet-gen 14 | 15 | test: 16 | docker-compose -f docker-compose-test-env.yml up -d 17 | sleep 10 18 | TEST_CONFIG=$(TEST_CONFIG_PATH) go test ./... 19 | make clean 20 | 21 | clean: 22 | go clean -testcache 23 | docker-compose -f docker-compose-test-env.yml down 24 | -------------------------------------------------------------------------------- /cli/receiver/storage/default.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | log "github.com/sirupsen/logrus" 6 | ) 7 | 8 | type LogConnector struct{} 9 | 10 | func (c LogConnector) Init(cfg map[string]string) error { 11 | return nil 12 | } 13 | 14 | func (c LogConnector) Save(msg interface{ ToBytes() ([]byte, error) }) error { 15 | jsonPkg, err := json.MarshalIndent(msg, "", " ") 16 | if err != nil { 17 | return err 18 | } 19 | 20 | log.WithField("packet", string(jsonPkg)).Info("Export packet") 21 | return nil 22 | } 23 | 24 | func (c LogConnector) Close() error { 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /libs/egts/crc.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | func crc8(data []byte) byte { 4 | crc := byte(0xFF) 5 | for _, b := range data { 6 | crc ^= b 7 | 8 | for i := 0; i < 8; i++ { 9 | if crc&0x80 != 0 { 10 | crc = (crc << 1) ^ 0x31 11 | } else { 12 | crc = crc << 1 13 | } 14 | } 15 | } 16 | 17 | return crc 18 | } 19 | 20 | func crc16(data []byte) uint16 { 21 | crc := uint16(0xFFFF) 22 | for _, b := range data { 23 | crc ^= uint16(b) << 8 24 | 25 | for i := 0; i < 8; i++ { 26 | if crc&0x8000 != 0 { 27 | crc = (crc << 1) ^ 0x1021 28 | } else { 29 | crc = crc << 1 30 | } 31 | } 32 | } 33 | 34 | return crc 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kuznetsovin/egts-protocol 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/go-redis/redis/v8 v8.11.5 7 | github.com/go-sql-driver/mysql v1.6.0 8 | github.com/golang/protobuf v1.5.2 9 | github.com/lib/pq v1.1.1 10 | github.com/nats-io/nats-server/v2 v2.9.23 // indirect 11 | github.com/nats-io/nats.go v1.28.0 12 | github.com/sirupsen/logrus v1.8.1 13 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 14 | github.com/stretchr/testify v1.7.1 15 | github.com/tarantool/go-tarantool v0.0.0-20190330192518-aaa93c4bdc35 16 | google.golang.org/appengine v1.6.5 // indirect 17 | gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect 18 | gopkg.in/yaml.v2 v2.4.0 19 | ) 20 | -------------------------------------------------------------------------------- /cli/receiver/integration_test/redis_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/go-redis/redis/v8" 8 | ) 9 | 10 | type redisTest struct { 11 | conn *redis.Client 12 | subscriber *redis.PubSub 13 | } 14 | 15 | func (r *redisTest) receivedPoint() error { 16 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 17 | defer cancel() 18 | 19 | if _, err := r.subscriber.ReceiveMessage(ctx); err != nil { 20 | return err 21 | } 22 | 23 | return nil 24 | } 25 | 26 | func initTestRedis(conf map[string]string) redisTest { 27 | result := redisTest{} 28 | 29 | result.conn = redis.NewClient(&redis.Options{Addr: conf["server"]}) 30 | result.subscriber = result.conn.Subscribe(context.Background(), conf["queue"]) 31 | 32 | return result 33 | } 34 | -------------------------------------------------------------------------------- /docker-compose-test-env.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | postgres: 5 | image: postgres:12 6 | container_name: egts_pg_test 7 | environment: 8 | POSTGRES_USER: egts 9 | POSTGRES_PASSWORD: egts 10 | POSTGRES_DB: receiver 11 | ports: 12 | - "5432:5432" 13 | 14 | redis: 15 | image: redis:latest 16 | container_name: egts_redis_test 17 | ports: 18 | - "6379:6379" 19 | 20 | mysql: 21 | image: mysql:latest 22 | container_name: egts_mysql_test 23 | environment: 24 | MYSQL_ROOT_PASSWORD: egts 25 | MYSQL_USER: egts 26 | MYSQL_PASSWORD: egts 27 | MYSQL_DATABASE: receiver 28 | ports: 29 | - "3306:3306" 30 | 31 | # egts: 32 | # image: egts:latest 33 | # container_name: egts_receiver 34 | # ports: 35 | # - "6000:6000" 36 | 37 | # volumes: 38 | # - ./configs:/etc/egts-receviver/ 39 | -------------------------------------------------------------------------------- /configs/receiver.yaml: -------------------------------------------------------------------------------- 1 | host: "127.0.0.1" 2 | port: "6000" 3 | con_live_sec: 10 4 | log_level: "DEBUG" 5 | 6 | storage: 7 | postgresql: 8 | host: "localhost" 9 | port: "5432" 10 | user: "postgres" 11 | password: "postgres" 12 | database: "receiver" 13 | table: "points" 14 | redis: 15 | server: "redis:6379" 16 | queue: "egts" 17 | password: "" 18 | db: "0" 19 | mysql: 20 | uri: "" 21 | database: "receiver" 22 | table: "points" 23 | # rabbitmq: 24 | # host: "localhost" 25 | # port: "5672" 26 | # user: "guest" 27 | # password: "guest" 28 | # exchange: "receiver" 29 | # nats: 30 | # plugin: "nats.so" 31 | # servers: "nats://localhost:1222, nats://localhost:1223, nats://localhost:1224" 32 | # topic: "receiver" 33 | # tarantool_queue: 34 | # host: "localhost" 35 | # port: "5672" 36 | # user: "user" 37 | # password: "pass" 38 | # max_recons: 5 39 | # timeout: 1 40 | # reconnect: 1 41 | # queue: "points" 42 | -------------------------------------------------------------------------------- /configs/config.test.yaml: -------------------------------------------------------------------------------- 1 | host: "127.0.0.1" 2 | port: "7000" 3 | con_live_sec: 10 4 | log_level: "DEBUG" 5 | 6 | storage: 7 | postgresql: 8 | host: "localhost" 9 | port: "5432" 10 | user: "egts" 11 | password: "egts" 12 | database: "receiver" 13 | table: "points" 14 | sslmode: disable 15 | redis: 16 | server: "localhost:6379" 17 | queue: "egts" 18 | password: "" 19 | db: "0" 20 | mysql: 21 | uri: "egts:egts@/receiver" 22 | table: "points" 23 | # rabbitmq: 24 | # host: "localhost" 25 | # port: "5672" 26 | # user: "guest" 27 | # password: "guest" 28 | # exchange: "receiver" 29 | # nats: 30 | # plugin: "nats.so" 31 | # servers: "nats://localhost:1222, nats://localhost:1223, nats://localhost:1224" 32 | # topic: "receiver" 33 | # tarantool_queue: 34 | # host: "localhost" 35 | # port: "5672" 36 | # user: "user" 37 | # password: "pass" 38 | # max_recons: 5 39 | # timeout: 1 40 | # reconnect: 1 41 | # queue: "points" 42 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_abs_an_sensors_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestEgtsSrAbsAnSensData_Encode(t *testing.T) { 9 | a := SrAbsAnSensData{ 10 | SensorNumber: 0x98, 11 | Value: 0x123456, 12 | } 13 | data, err := a.Encode() 14 | if assert.NoError(t, err) { 15 | assert.False(t, data[0] != 0x98 || data[1] != 0x56 || data[2] != 0x34 || data[3] != 0x12) 16 | 17 | b := SrAbsAnSensData{} 18 | 19 | if err := b.Decode(data); assert.NoError(t, err) { 20 | assert.False(t, a.Value != b.Value || a.SensorNumber != b.SensorNumber) 21 | } 22 | } 23 | } 24 | func TestEgtsSrAbsAnSensData_Decode(t *testing.T) { 25 | data := []byte{0x98, 0x56, 0x34, 0x12} 26 | a := SrAbsAnSensData{} 27 | err := a.Decode(data) 28 | if assert.NoError(t, err) { 29 | assert.False(t, a.SensorNumber != 0x98 || a.Value != 0x123456) 30 | data2, err := a.Encode() 31 | if assert.NoError(t, err) { 32 | assert.Equal(t, data, data2) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tools/send_egts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from time import sleep 3 | import argparse 4 | 5 | import socket 6 | 7 | if __name__ == '__main__': 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("-s", "--host", help="Адрес сервера для приема ЕГТС", default='localhost', type=str) 10 | parser.add_argument("-p", "--port", help="Порт сервера для приема ЕГТС", default=6000, type=int) 11 | parser.add_argument("file", help="Тестовый файл с пакетами", type=str) 12 | 13 | args = parser.parse_args() 14 | 15 | TEST_ADDR = (args.host, args.port) 16 | 17 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | client.connect(TEST_ADDR) 19 | 20 | BUFF = 2048 21 | 22 | with open(args.file) as f: 23 | for rec in f.readlines(): 24 | print("send: {}".format(rec)) 25 | package = bytes.fromhex(rec[:-1]) 26 | client.send(package) 27 | 28 | rec_package = client.recv(BUFF) 29 | print("received: {}".format(rec_package.hex())) 30 | sleep(1) 31 | 32 | client.close() 33 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_abs_an_sensors_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | ) 7 | 8 | //SrAbsAnSensData структура подзаписи типа EGTS_SR_ABS_AN_SENS_DATA, которая применяется абонентским 9 | //терминалом для передачи данных о состоянии одного аналогового входа 10 | type SrAbsAnSensData struct { 11 | SensorNumber uint8 `json:"SensorNumber"` 12 | Value uint32 `json:"Value"` 13 | } 14 | 15 | //Decode разбирает байты в структуру подзаписи 16 | func (e *SrAbsAnSensData) Decode(content []byte) error { 17 | if len(content) < int(e.Length()) { 18 | return errors.New("Некорректный размер данных") 19 | } 20 | e.SensorNumber = uint8(content[0]) 21 | e.Value = uint32(binary.LittleEndian.Uint32(content) >> 8) 22 | return nil 23 | } 24 | 25 | //Encode преобразовывает подзапись в набор байт 26 | func (e *SrAbsAnSensData) Encode() ([]byte, error) { 27 | return []byte{ 28 | byte(e.SensorNumber), 29 | byte(e.Value), 30 | byte(e.Value >> 8), 31 | byte(e.Value >> 16), 32 | }, nil 33 | } 34 | 35 | //Length получает длинну закодированной подзаписи 36 | func (e *SrAbsAnSensData) Length() uint16 { 37 | return 4 38 | } 39 | -------------------------------------------------------------------------------- /cli/receiver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/kuznetsovin/egts-protocol/cli/receiver/config" 6 | "github.com/kuznetsovin/egts-protocol/cli/receiver/server" 7 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage" 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | func main() { 12 | cfgFilePath := "" 13 | flag.StringVar(&cfgFilePath, "c", "", "Конфигурационный файл") 14 | flag.Parse() 15 | 16 | if cfgFilePath == "" { 17 | log.Fatalf("Не задан путь до конфига") 18 | } 19 | 20 | cfg, err := config.New(cfgFilePath) 21 | if err != nil { 22 | log.Fatalf("Ошибка парсинга конфига: %v", err) 23 | } 24 | 25 | log.SetLevel(cfg.GetLogLevel()) 26 | 27 | storages := storage.NewRepository() 28 | if err := storages.LoadStorages(cfg.Store); err != nil { 29 | log.Errorf("ошибка загрузка хранилища: %v", err) 30 | 31 | // TODO: clear after test 32 | store := storage.LogConnector{} 33 | if err := store.Init(nil); err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | storages.AddStore(store) 38 | defer store.Close() 39 | } 40 | 41 | srv := server.New(cfg.GetListenAddress(), cfg.GetEmptyConnTTL(), storages) 42 | 43 | srv.Run() 44 | } 45 | -------------------------------------------------------------------------------- /cli/receiver/integration_test/mysql_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | _ "github.com/go-sql-driver/mysql" 8 | ) 9 | 10 | type mysqlTest struct { 11 | conn *sql.DB 12 | table string 13 | } 14 | 15 | func (pt *mysqlTest) pointCount() (int, error) { 16 | query := fmt.Sprintf("SELECT COUNT(*) FROM %s", pt.table) 17 | result := 0 18 | 19 | rows, err := pt.conn.Query(query) 20 | if err != nil { 21 | return result, err 22 | } 23 | 24 | for rows.Next() { 25 | if err = rows.Scan(&result); err != nil { 26 | return result, err 27 | } 28 | } 29 | 30 | return result, err 31 | } 32 | 33 | func initTestMysql(conf map[string]string) (mysqlTest, error) { 34 | var err error = nil 35 | 36 | result := mysqlTest{ 37 | conn: nil, 38 | } 39 | 40 | if result.conn, err = sql.Open("mysql", conf["uri"]); err != nil { 41 | return result, err 42 | } 43 | 44 | if err = result.conn.Ping(); err != nil { 45 | return result, err 46 | } 47 | 48 | result.table = conf["table"] 49 | 50 | creat_table_query := fmt.Sprintf("create table %s ( point json );", result.table) 51 | 52 | _, err = result.conn.Exec(creat_table_query) 53 | 54 | return result, err 55 | } 56 | -------------------------------------------------------------------------------- /libs/egts/egts_pt_response_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | egtsPkgResp = Package{ 10 | ProtocolVersion: 1, 11 | SecurityKeyID: 0, 12 | Prefix: "00", 13 | Route: "0", 14 | EncryptionAlg: "00", 15 | Compression: "0", 16 | Priority: "11", 17 | HeaderLength: 11, 18 | HeaderEncoding: 0, 19 | FrameDataLength: 3, 20 | PacketIdentifier: 137, 21 | PacketType: PtResponsePacket, 22 | HeaderCheckSum: 74, 23 | ServicesFrameData: &PtResponse{ 24 | ResponsePacketID: 14357, 25 | ProcessingResult: egtsPcOk, 26 | }, 27 | ServicesFrameDataCheckSum: 59443, 28 | } 29 | testEgtsPkgBytes = []byte{0x01, 0x00, 0x03, 0x0B, 0x00, 0x03, 0x00, 0x89, 0x00, 0x00, 0x4A, 0x15, 0x38, 0x00, 0x33, 0xE8} 30 | ) 31 | 32 | func TestEgtsPkgResp_Encode(t *testing.T) { 33 | 34 | posDataBytes, err := egtsPkgResp.Encode() 35 | if assert.NoError(t, err) { 36 | assert.Equal(t, posDataBytes, testEgtsPkgBytes) 37 | } 38 | } 39 | 40 | func TestEgtsPkgResp_Decode(t *testing.T) { 41 | egtsPkg := Package{} 42 | 43 | _, err := egtsPkg.Decode(testEgtsPkgBytes) 44 | if assert.NoError(t, err) { 45 | assert.Equal(t, egtsPkg, egtsPkgResp) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cli/receiver/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | /* 4 | Описание конфигурационного файла 5 | */ 6 | 7 | import ( 8 | log "github.com/sirupsen/logrus" 9 | "os" 10 | "time" 11 | 12 | "gopkg.in/yaml.v2" 13 | ) 14 | 15 | type Settings struct { 16 | Host string `yaml:"host"` 17 | Port string `yaml:"port"` 18 | ConnTTl int `yaml:"conn_ttl"` 19 | LogLevel string `yaml:"log_level"` 20 | Store map[string]map[string]string `yaml:"storage"` 21 | } 22 | 23 | func (s *Settings) GetEmptyConnTTL() time.Duration { 24 | return time.Duration(s.ConnTTl) * time.Second 25 | } 26 | func (s *Settings) GetListenAddress() string { 27 | return s.Host + ":" + s.Port 28 | } 29 | 30 | func (s *Settings) GetLogLevel() log.Level { 31 | var lvl log.Level 32 | 33 | switch s.LogLevel { 34 | case "DEBUG": 35 | lvl = log.DebugLevel 36 | break 37 | case "INFO": 38 | lvl = log.InfoLevel 39 | break 40 | case "WARN": 41 | lvl = log.WarnLevel 42 | break 43 | case "ERROR": 44 | lvl = log.ErrorLevel 45 | break 46 | default: 47 | lvl = log.InfoLevel 48 | } 49 | return lvl 50 | } 51 | 52 | func New(confPath string) (Settings, error) { 53 | c := Settings{} 54 | data, err := os.ReadFile(confPath) 55 | if err != nil { 56 | return c, err 57 | } 58 | err = yaml.Unmarshal(data, &c) 59 | return c, err 60 | } 61 | -------------------------------------------------------------------------------- /cli/receiver/integration_test/postgresql_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | _ "github.com/lib/pq" 8 | ) 9 | 10 | type postgresqlTest struct { 11 | conn *sql.DB 12 | table string 13 | } 14 | 15 | func (pt *postgresqlTest) pointCount() (int, error) { 16 | query := fmt.Sprintf("SELECT COUNT(*) FROM %s", pt.table) 17 | result := 0 18 | 19 | rows, err := pt.conn.Query(query) 20 | if err != nil { 21 | return result, err 22 | } 23 | 24 | for rows.Next() { 25 | if err = rows.Scan(&result); err != nil { 26 | return result, err 27 | } 28 | } 29 | 30 | return result, err 31 | } 32 | 33 | func initTestPostgresql(conf map[string]string) (postgresqlTest, error) { 34 | var err error = nil 35 | 36 | result := postgresqlTest{ 37 | conn: nil, 38 | } 39 | 40 | connStr := fmt.Sprintf("dbname=%s host=%s port=%s user=%s password=%s sslmode=%s", 41 | conf["database"], conf["host"], conf["port"], conf["user"], conf["password"], conf["sslmode"]) 42 | 43 | if result.conn, err = sql.Open("postgres", connStr); err != nil { 44 | return result, err 45 | } 46 | 47 | if err = result.conn.Ping(); err != nil { 48 | return result, err 49 | } 50 | 51 | result.table = conf["table"] 52 | 53 | creat_table_query := fmt.Sprintf("create table %s ( point jsonb );", result.table) 54 | 55 | _, err = result.conn.Exec(creat_table_query) 56 | 57 | return result, err 58 | } 59 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_result_code.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | //SrResultCode структура подзаписи типа EGTS_SR_RESULT_CODE, которая применяется телематической 9 | //платформой для информирования АС о результатах процедуры аутентификации АС. 10 | type SrResultCode struct { 11 | ResultCode uint8 `json:"RCD"` 12 | } 13 | 14 | //Decode разбирает байты в структуру подзаписи 15 | func (s *SrResultCode) Decode(content []byte) error { 16 | var ( 17 | err error 18 | ) 19 | buf := bytes.NewBuffer(content) 20 | 21 | if s.ResultCode, err = buf.ReadByte(); err != nil { 22 | return fmt.Errorf("Не удалось получить код результата: %v", err) 23 | } 24 | 25 | return err 26 | } 27 | 28 | //Encode преобразовывает подзапись в набор байт 29 | func (s *SrResultCode) Encode() ([]byte, error) { 30 | var ( 31 | result []byte 32 | err error 33 | ) 34 | buf := new(bytes.Buffer) 35 | 36 | if err = buf.WriteByte(s.ResultCode); err != nil { 37 | return result, fmt.Errorf("Не удалось записать код результата: %v", err) 38 | } 39 | 40 | result = buf.Bytes() 41 | return result, err 42 | } 43 | 44 | //Length получает длинну закодированной подзаписи 45 | func (s *SrResultCode) Length() uint16 { 46 | var result uint16 47 | 48 | if recBytes, err := s.Encode(); err != nil { 49 | result = uint16(0) 50 | } else { 51 | result = uint16(len(recBytes)) 52 | } 53 | 54 | return result 55 | } 56 | -------------------------------------------------------------------------------- /cli/receiver/storage/packet.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type NavRecord struct { 8 | Client uint32 `json:"client"` 9 | PacketID uint32 `json:"packet_id"` 10 | NavigationTimestamp int64 `json:"navigation_unix_time"` 11 | ReceivedTimestamp int64 `json:"received_unix_time"` 12 | Latitude float64 `json:"latitude"` 13 | Longitude float64 `json:"longitude"` 14 | Speed uint16 `json:"speed"` 15 | Pdop uint16 `json:"pdop"` 16 | Hdop uint16 `json:"hdop"` 17 | Vdop uint16 `json:"vdop"` 18 | Nsat uint8 `json:"nsat"` 19 | Ns uint16 `json:"ns"` 20 | Course uint8 `json:"course"` 21 | AnSensors []AnSensor `json:"an_sensors"` 22 | LiquidSensors []LiquidSensor `json:"liquid_sensors"` 23 | } 24 | 25 | func (eep *NavRecord) ToBytes() ([]byte, error) { 26 | return json.Marshal(eep) 27 | } 28 | 29 | type LiquidSensor struct { 30 | SensorNumber uint8 `json:"sensor_number"` 31 | ErrorFlag string `json:"error_flag"` 32 | ValueMm uint32 `json:"value_mm"` 33 | ValueL uint32 `json:"value_l"` 34 | } 35 | 36 | type AnSensor struct { 37 | SensorNumber uint8 `json:"sensor_number"` 38 | Value uint32 `json:"value"` 39 | } 40 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_pos_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var ( 10 | testEgtsSrPosDataBytes = []byte{0x55, 0x91, 0x02, 0x10, 0x6F, 0x1C, 0x05, 0x9E, 0x7A, 0xB5, 0x3C, 0x35, 11 | 0x01, 0xD0, 0x87, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00} 12 | testEgtsSrPosData = SrPosData{ 13 | NavigationTime: time.Date(2018, time.July, 6, 20, 8, 53, 0, time.UTC), 14 | Latitude: 55.55389399769574, 15 | Longitude: 37.43236696287812, 16 | ALTE: "0", 17 | LOHS: "0", 18 | LAHS: "0", 19 | MV: "0", 20 | BB: "0", 21 | CS: "0", 22 | FIX: "0", 23 | VLD: "1", 24 | DirectionHighestBit: 1, 25 | AltitudeSign: 0, 26 | Speed: 200, 27 | Direction: 172, 28 | Odometer: 1, 29 | DigitalInputs: 0, 30 | Source: 0, 31 | } 32 | ) 33 | 34 | func TestEgtsSrPosData_Encode(t *testing.T) { 35 | posDataBytes, err := testEgtsSrPosData.Encode() 36 | 37 | if assert.NoError(t, err) { 38 | assert.Equal(t, posDataBytes, testEgtsSrPosDataBytes) 39 | } 40 | } 41 | 42 | func TestEgtsSrPosData_Decode(t *testing.T) { 43 | posData := SrPosData{} 44 | 45 | if assert.NoError(t, posData.Decode(testEgtsSrPosDataBytes)) { 46 | assert.Equal(t, posData, testEgtsSrPosData) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_abs_cntr_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | srAbsCntrDataBytes = []byte{0x06, 0x75, 0x1D, 0x70} 10 | testEgtsSrAbsCntrData = SrAbsCntrData{ 11 | CounterNumber: 6, 12 | CounterValue: 7347573, 13 | } 14 | ) 15 | 16 | func TestEgtsSrAbsCntrData_Encode(t *testing.T) { 17 | posDataBytes, err := testEgtsSrAbsCntrData.Encode() 18 | if assert.NoError(t, err) { 19 | assert.Equal(t, posDataBytes, srAbsCntrDataBytes) 20 | } 21 | } 22 | 23 | func TestEgtsSrAbsCntrData_Decode(t *testing.T) { 24 | adSensData := SrAbsCntrData{} 25 | 26 | if err := adSensData.Decode(srAbsCntrDataBytes); assert.NoError(t, err) { 27 | assert.Equal(t, adSensData, testEgtsSrAbsCntrData) 28 | } 29 | 30 | } 31 | 32 | // проверяем что рекордсет работает правильно с данным типом подзаписи 33 | func TestEgtsSrAbsCntrDataRs(t *testing.T) { 34 | egtsSrAbsCntrDataRDBytes := append([]byte{0x19, 0x04, 0x00}, srAbsCntrDataBytes...) 35 | egtsSrAbsCntrDataRD := RecordDataSet{ 36 | RecordData{ 37 | SubrecordType: SrAbsCntrDataType, 38 | SubrecordLength: testEgtsSrAbsCntrData.Length(), 39 | SubrecordData: &testEgtsSrAbsCntrData, 40 | }, 41 | } 42 | testStruct := RecordDataSet{} 43 | 44 | testBytes, err := egtsSrAbsCntrDataRD.Encode() 45 | if assert.NoError(t, err) { 46 | assert.Equal(t, testBytes, egtsSrAbsCntrDataRDBytes) 47 | 48 | if err = testStruct.Decode(egtsSrAbsCntrDataRDBytes); assert.NoError(t, err) { 49 | assert.Equal(t, egtsSrAbsCntrDataRD, testStruct) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_state_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | testEgtsSrStateData = SrStateData{ 10 | State: 2, 11 | MainPowerSourceVoltage: 127, 12 | BackUpBatteryVoltage: 0, 13 | InternalBatteryVoltage: 41, 14 | NMS: "1", 15 | IBU: "0", 16 | BBU: "0", 17 | } 18 | testSrStateDataBytes = []byte{0x02, 0x7F, 0x00, 0x29, 0x04} 19 | ) 20 | 21 | func TestEgtsPkgSrStateData_Encode(t *testing.T) { 22 | 23 | pkgBytes, err := testEgtsSrStateData.Encode() 24 | if assert.NoError(t, err) { 25 | assert.Equal(t, pkgBytes, testSrStateDataBytes) 26 | } 27 | } 28 | 29 | func TestEgtsPkgSrStateData_Decode(t *testing.T) { 30 | stStateData := SrStateData{} 31 | 32 | if assert.NoError(t, stStateData.Decode(testSrStateDataBytes)) { 33 | assert.Equal(t, stStateData, testEgtsSrStateData) 34 | } 35 | } 36 | 37 | // проверяем что рекордсет работает правильно с данным типом подзаписи 38 | func TestEgtsSrStateDataRs(t *testing.T) { 39 | stateDataRDBytes := append([]byte{0x14, 0x05, 0x00}, testSrStateDataBytes...) 40 | stateDataRD := RecordDataSet{ 41 | RecordData{ 42 | SubrecordType: SrType20, 43 | SubrecordLength: 5, 44 | SubrecordData: &testEgtsSrStateData, 45 | }, 46 | } 47 | testStruct := RecordDataSet{} 48 | 49 | testBytes, err := stateDataRD.Encode() 50 | if assert.NoError(t, err) { 51 | assert.Equal(t, testBytes, stateDataRDBytes) 52 | 53 | if assert.NoError(t, testStruct.Decode(stateDataRDBytes)) { 54 | assert.Equal(t, stateDataRD, testStruct) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cli/receiver/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestConfigLoad(t *testing.T) { 11 | cfg := `host: "127.0.0.1" 12 | port: "5020" 13 | conn_ttl: 10 14 | log_level: "DEBUG" 15 | 16 | storage: 17 | rabbitmq: 18 | host: "localhost" 19 | port: "5672" 20 | user: "guest" 21 | password: "guest" 22 | exchange: "receiver" 23 | postgresql: 24 | host: "localhost" 25 | port: "5432" 26 | user: "postgres" 27 | password: "postgres" 28 | database: "receiver" 29 | table: "points" 30 | sslmode: "disable" 31 | ` 32 | 33 | file, err := ioutil.TempFile("/tmp", "config.toml") 34 | if !assert.NoError(t, err) { 35 | return 36 | } 37 | defer os.Remove(file.Name()) 38 | 39 | if _, err = file.WriteString(cfg); !assert.NoError(t, err) { 40 | return 41 | } 42 | 43 | conf, err := New(file.Name()) 44 | if assert.NoError(t, err) { 45 | assert.Equal(t, Settings{ 46 | Host: "127.0.0.1", 47 | Port: "5020", 48 | ConnTTl: 10, 49 | LogLevel: "DEBUG", 50 | Store: map[string]map[string]string{ 51 | "postgresql": { 52 | "host": "localhost", 53 | "port": "5432", 54 | "user": "postgres", 55 | "password": "postgres", 56 | "database": "receiver", 57 | "table": "points", 58 | "sslmode": "disable", 59 | }, 60 | "rabbitmq": { 61 | "exchange": "receiver", 62 | "host": "localhost", 63 | "password": "guest", 64 | "port": "5672", 65 | "user": "guest", 66 | }, 67 | }, 68 | }, 69 | conf, 70 | ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cli/receiver/storage/store/mysql/connector.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | /* 4 | Плагин для работы с MySQL. 5 | Плагин сохраняет пакет в jsonb поле point у заданной в настройках таблице. 6 | 7 | Раздел настроек, которые должны отвечають в конфиге для подключения хранилища: 8 | 9 | uri = "user:password@host:port/dbname" 10 | database = "receiver" 11 | table = "points" 12 | */ 13 | 14 | import ( 15 | "database/sql" 16 | "fmt" 17 | _ "github.com/go-sql-driver/mysql" 18 | ) 19 | 20 | type Connector struct { 21 | connection *sql.DB 22 | config map[string]string 23 | } 24 | 25 | func (c *Connector) Init(cfg map[string]string) error { 26 | var ( 27 | err error 28 | ) 29 | if cfg == nil { 30 | return fmt.Errorf("Не корректная ссылка на конфигурацию") 31 | } 32 | c.config = cfg 33 | 34 | if c.connection, err = sql.Open("mysql", c.config["uri"]); err != nil { 35 | return fmt.Errorf("Ошибка подключения к mysql: %v", err) 36 | } 37 | 38 | if err = c.connection.Ping(); err != nil { 39 | return fmt.Errorf("mysql недоступен: %v", err) 40 | } 41 | return err 42 | } 43 | 44 | func (c *Connector) Save(msg interface{ ToBytes() ([]byte, error) }) error { 45 | if msg == nil { 46 | return fmt.Errorf("Не корректная ссылка на пакет") 47 | } 48 | 49 | innerPkg, err := msg.ToBytes() 50 | if err != nil { 51 | return fmt.Errorf("Ошибка сериализации пакета: %v", err) 52 | } 53 | 54 | insertQuery := fmt.Sprintf("INSERT INTO %s (point) VALUES (?)", c.config["table"]) 55 | if _, err = c.connection.Exec(insertQuery, innerPkg); err != nil { 56 | return fmt.Errorf("Не удалось вставить запись в mysql: %v", err) 57 | } 58 | return nil 59 | } 60 | 61 | func (c *Connector) Close() error { 62 | return c.connection.Close() 63 | } 64 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_ext_pos_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | extPosDataBytes = []byte{0x0E, 0x32, 0x00, 0x00, 0x00, 0x0C} 10 | testEgtsSrExtPosData = SrExtPosData{ 11 | NavigationSystemFieldExists: "0", 12 | SatellitesFieldExists: "1", 13 | PdopFieldExists: "1", 14 | HdopFieldExists: "1", 15 | VdopFieldExists: "0", 16 | HorizontalDilutionOfPrecision: 50, 17 | PositionDilutionOfPrecision: 0, 18 | Satellites: 12, 19 | } 20 | ) 21 | 22 | func TestEgtsSrExtPosData_Encode(t *testing.T) { 23 | posDataBytes, err := testEgtsSrExtPosData.Encode() 24 | if assert.NoError(t, err) { 25 | assert.Equal(t, posDataBytes, extPosDataBytes) 26 | } 27 | } 28 | 29 | func TestEgtsSrExtPosData_Decode(t *testing.T) { 30 | extPosData := SrExtPosData{} 31 | if assert.NoError(t, extPosData.Decode(extPosDataBytes)) { 32 | assert.Equal(t, extPosData, testEgtsSrExtPosData) 33 | } 34 | } 35 | 36 | // проверяем что рекордсет работает правильно с данным типом подзаписи 37 | func TestEgtsSrExtPosDataRs(t *testing.T) { 38 | extPosDataRDBytes := append([]byte{0x11, 0x06, 0x00}, extPosDataBytes...) 39 | extPosDataRD := RecordDataSet{ 40 | RecordData{ 41 | SubrecordType: SrExtPosDataType, 42 | SubrecordLength: 6, 43 | SubrecordData: &testEgtsSrExtPosData, 44 | }, 45 | } 46 | testStruct := RecordDataSet{} 47 | 48 | testBytes, err := extPosDataRD.Encode() 49 | if assert.NoError(t, err) { 50 | assert.Equal(t, testBytes, extPosDataRDBytes) 51 | 52 | if assert.NoError(t, testStruct.Decode(extPosDataRDBytes)) { 53 | assert.Equal(t, extPosDataRD, testStruct) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_liquid_level_sensor_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | testSrLiquidLevelSensor = SrLiquidLevelSensor{ 10 | LiquidLevelSensorErrorFlag: "0", 11 | LiquidLevelSensorValueUnit: "00", 12 | RawDataFlag: "0", 13 | LiquidLevelSensorNumber: 3, 14 | ModuleAddress: 1, 15 | LiquidLevelSensorData: 0, 16 | } 17 | testSrLiquidLevelSensorBytes = []byte{0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00} 18 | ) 19 | 20 | func TestEgtsSrLiquidLevelSensor_Encode(t *testing.T) { 21 | pkgBytes, err := testSrLiquidLevelSensor.Encode() 22 | 23 | if assert.NoError(t, err) { 24 | assert.Equal(t, pkgBytes, testSrLiquidLevelSensorBytes) 25 | } 26 | } 27 | 28 | func TestEgtsSrLiquidLevelSensor_Decode(t *testing.T) { 29 | liquidLev := SrLiquidLevelSensor{} 30 | 31 | if assert.NoError(t, liquidLev.Decode(testSrLiquidLevelSensorBytes)) { 32 | assert.Equal(t, liquidLev, testSrLiquidLevelSensor) 33 | } 34 | } 35 | 36 | // проверяем что рекордсет работает правильно с данным типом подзаписи 37 | func TestEgtsSrLiquidLevelSensorRs(t *testing.T) { 38 | liquidLevelRDRDBytes := append([]byte{0x1B, 0x07, 0x00}, testSrLiquidLevelSensorBytes...) 39 | liquidLevelRD := RecordDataSet{ 40 | RecordData{ 41 | SubrecordType: SrLiquidLevelSensorType, 42 | SubrecordLength: 7, 43 | SubrecordData: &testSrLiquidLevelSensor, 44 | }, 45 | } 46 | testStruct := RecordDataSet{} 47 | 48 | testBytes, err := liquidLevelRD.Encode() 49 | if assert.NoError(t, err) { 50 | assert.Equal(t, testBytes, liquidLevelRDRDBytes) 51 | 52 | if assert.NoError(t, testStruct.Decode(liquidLevelRDRDBytes)) { 53 | assert.Equal(t, liquidLevelRD, testStruct) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /libs/egts/service_data_records_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestServiceDataRecord_Encode(t *testing.T) { 9 | testServiceDataRecord := ServiceDataSet{ 10 | ServiceDataRecord{ 11 | RecordLength: 0, 12 | RecordNumber: 97, 13 | SourceServiceOnDevice: "1", 14 | RecipientServiceOnDevice: "0", 15 | Group: "0", 16 | RecordProcessingPriority: "11", 17 | TimeFieldExists: "0", 18 | EventIDFieldExists: "0", 19 | ObjectIDFieldExists: "1", 20 | ObjectIdentifier: 133552, 21 | SourceServiceType: 2, 22 | RecipientServiceType: 2, 23 | }, 24 | } 25 | testServiceDataRecordBytes := []byte{0x00, 0x00, 0x61, 0x00, 0x99, 0xB0, 0x09, 0x02, 0x00, 0x02, 0x02} 26 | 27 | sdr, err := testServiceDataRecord.Encode() 28 | if assert.NoError(t, err) { 29 | assert.Equal(t, sdr, testServiceDataRecordBytes) 30 | } 31 | } 32 | 33 | func TestServiceDataRecord_Decode(t *testing.T) { 34 | sdr := ServiceDataSet{} 35 | testServiceDataRecord := ServiceDataSet{ 36 | ServiceDataRecord{ 37 | RecordLength: 24, 38 | RecordNumber: 97, 39 | SourceServiceOnDevice: "1", 40 | RecipientServiceOnDevice: "0", 41 | Group: "0", 42 | RecordProcessingPriority: "11", 43 | TimeFieldExists: "0", 44 | EventIDFieldExists: "0", 45 | ObjectIDFieldExists: "1", 46 | ObjectIdentifier: 133552, 47 | SourceServiceType: 2, 48 | RecipientServiceType: 2, 49 | }, 50 | } 51 | testServiceDataRecordBytes := []byte{0x18, 0x00, 0x61, 0x00, 0x99, 0xB0, 0x09, 0x02, 0x00, 0x02, 0x02} 52 | if assert.NoError(t, sdr.Decode(testServiceDataRecordBytes)) { 53 | assert.Equal(t, sdr, testServiceDataRecord) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cli/receiver/storage/store/nats/connector.go: -------------------------------------------------------------------------------- 1 | package nats 2 | 3 | /* 4 | Плагин для работы с NATS. 5 | Плагин отправляет пакет в топик NATS messaging system. 6 | 7 | Раздел настроек, которые должны отвечають в конфиге для подключения хранилища: 8 | 9 | servers = "nats://localhost:1222, nats://localhost:1223, nats://localhost:1224" 10 | topic = "receiver" 11 | */ 12 | 13 | import ( 14 | "fmt" 15 | natsLib "github.com/nats-io/nats.go" 16 | ) 17 | 18 | type Connector struct { 19 | connection *natsLib.Conn 20 | config map[string]string 21 | } 22 | 23 | func (c *Connector) Init(cfg map[string]string) error { 24 | var ( 25 | err error 26 | ) 27 | if cfg == nil { 28 | return fmt.Errorf("Не корректная ссылка на конфигурацию") 29 | } 30 | c.config = cfg 31 | 32 | var options = make([]natsLib.Option, 3) 33 | 34 | options = append(options, natsLib.Name(fmt.Sprintf("EGTS handler, topic: %s", c.config["topic"]))) 35 | 36 | if user, uOk := c.config["user"]; uOk { 37 | if password, pOk := c.config["password"]; pOk { 38 | options = append(options, natsLib.UserInfo(user, password)) 39 | } 40 | } 41 | 42 | if c.connection, err = natsLib.Connect(c.config["servers"], options...); err != nil { 43 | return fmt.Errorf("Ошибка подключения к nats шине: %v", err) 44 | } 45 | return err 46 | } 47 | 48 | func (c *Connector) Save(msg interface{ ToBytes() ([]byte, error) }) error { 49 | if msg == nil { 50 | return fmt.Errorf("Не корректная ссылка на пакет") 51 | } 52 | 53 | innerPkg, err := msg.ToBytes() 54 | if err != nil { 55 | return fmt.Errorf("Ошибка сериализации пакета: %v", err) 56 | } 57 | 58 | if err = c.connection.Publish(c.config["topic"], innerPkg); err != nil { 59 | return fmt.Errorf("Не удалось отправить сообщение в топик: %v", err) 60 | } 61 | return nil 62 | } 63 | 64 | func (c *Connector) Close() error { 65 | c.connection.Close() 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /cli/receiver/storage/store/redis/connector.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | /* 4 | Плагин для работы с Redis. 5 | Плагин отправляет пакет в redis очередь. 6 | 7 | Раздел настроек, которые должны отвечають в конфиге для подключения хранилища: 8 | 9 | server = "localhost:6379" 10 | queue = "egts" 11 | password = "" 12 | db = 0 13 | */ 14 | 15 | import ( 16 | "context" 17 | "fmt" 18 | "github.com/go-redis/redis/v8" 19 | "strconv" 20 | ) 21 | 22 | type Connector struct { 23 | conn *redis.Client 24 | queue string 25 | config map[string]string 26 | } 27 | 28 | func (c *Connector) Init(cfg map[string]string) error { 29 | var ( 30 | err error 31 | ) 32 | if cfg == nil { 33 | return fmt.Errorf("не корректная ссылка на конфигурацию") 34 | } 35 | c.config = cfg 36 | 37 | addr, ok := c.config["server"] 38 | if !ok { 39 | return fmt.Errorf("не задан адрес redis сервера") 40 | } 41 | 42 | configDb, _ := c.config["db"] 43 | if !ok { 44 | configDb = "0" 45 | } 46 | 47 | db, err := strconv.Atoi(configDb) 48 | if err != nil { 49 | return fmt.Errorf("не корретное имя redis бд: %v", err) 50 | } 51 | 52 | c.conn = redis.NewClient(&redis.Options{ 53 | Addr: addr, 54 | Password: c.config["password"], 55 | DB: db, 56 | }) 57 | 58 | c.queue, ok = c.config["queue"] 59 | if !ok { 60 | return fmt.Errorf("не корретное имя redis очереди") 61 | } 62 | return err 63 | } 64 | 65 | func (c *Connector) Save(msg interface{ ToBytes() ([]byte, error) }) error { 66 | if msg == nil { 67 | return fmt.Errorf("Не корректная ссылка на пакет") 68 | } 69 | 70 | innerPkg, err := msg.ToBytes() 71 | if err != nil { 72 | return fmt.Errorf("Ошибка сериализации пакета: %v", err) 73 | } 74 | 75 | if err := c.conn.Publish(context.Background(), c.queue, innerPkg).Err(); err != nil { 76 | return fmt.Errorf("Ошибка отправки пакета в redis: %v", err) 77 | } 78 | 79 | return nil 80 | } 81 | 82 | func (c *Connector) Close() error { 83 | return c.conn.Close() 84 | } 85 | -------------------------------------------------------------------------------- /cli/receiver/storage/store/postgresql/connector.go: -------------------------------------------------------------------------------- 1 | package postgresql 2 | 3 | /* 4 | Плагин для работы с PostgreSQL. 5 | Плагин сохраняет пакет в jsonb поле point у заданной в настройках таблице. 6 | 7 | Раздел настроек, которые должны отвечають в конфиге для подключения хранилища: 8 | 9 | host = "localhost" 10 | port = "5432" 11 | user = "postgres" 12 | password = "postgres" 13 | database = "receiver" 14 | table = "points" 15 | sslmode = "disable" 16 | */ 17 | 18 | import ( 19 | "database/sql" 20 | "fmt" 21 | _ "github.com/lib/pq" 22 | ) 23 | 24 | type Connector struct { 25 | connection *sql.DB 26 | config map[string]string 27 | } 28 | 29 | func (c *Connector) Init(cfg map[string]string) error { 30 | var ( 31 | err error 32 | ) 33 | if cfg == nil { 34 | return fmt.Errorf("Не корректная ссылка на конфигурацию") 35 | } 36 | c.config = cfg 37 | connStr := fmt.Sprintf("dbname=%s host=%s port=%s user=%s password=%s sslmode=%s", 38 | c.config["database"], c.config["host"], c.config["port"], c.config["user"], c.config["password"], c.config["sslmode"]) 39 | if c.connection, err = sql.Open("postgres", connStr); err != nil { 40 | return fmt.Errorf("Ошибка подключения к postgresql: %v", err) 41 | } 42 | 43 | if err = c.connection.Ping(); err != nil { 44 | return fmt.Errorf("postgresql недоступен: %v", err) 45 | } 46 | return err 47 | } 48 | 49 | func (c *Connector) Save(msg interface{ ToBytes() ([]byte, error) }) error { 50 | if msg == nil { 51 | return fmt.Errorf("Не корректная ссылка на пакет") 52 | } 53 | 54 | innerPkg, err := msg.ToBytes() 55 | if err != nil { 56 | return fmt.Errorf("Ошибка сериализации пакета: %v", err) 57 | } 58 | 59 | insertQuery := fmt.Sprintf("INSERT INTO %s (point) VALUES ($1)", c.config["table"]) 60 | if _, err = c.connection.Exec(insertQuery, innerPkg); err != nil { 61 | return fmt.Errorf("Не удалось вставить запись: %v", err) 62 | } 63 | return nil 64 | } 65 | 66 | func (c *Connector) Close() error { 67 | return c.connection.Close() 68 | } 69 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_counters_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | testEgtsSrCountersData = SrCountersData{ 10 | CounterFieldExists1: "0", 11 | CounterFieldExists2: "0", 12 | CounterFieldExists3: "0", 13 | CounterFieldExists4: "0", 14 | CounterFieldExists5: "0", 15 | CounterFieldExists6: "0", 16 | CounterFieldExists7: "1", 17 | CounterFieldExists8: "1", 18 | Counter1: 0, 19 | Counter2: 0, 20 | Counter3: 0, 21 | Counter4: 0, 22 | Counter5: 0, 23 | Counter6: 0, 24 | Counter7: 0, 25 | Counter8: 3, 26 | } 27 | testSrCountersDataBytes = []byte{0xC0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00} 28 | ) 29 | 30 | func TestEgtsSrCountersData_Encode(t *testing.T) { 31 | countersBytes, err := testEgtsSrCountersData.Encode() 32 | if assert.NoError(t, err) { 33 | assert.Equal(t, countersBytes, testSrCountersDataBytes) 34 | } 35 | } 36 | 37 | func TestEgtsSrCountersData_Decode(t *testing.T) { 38 | countersData := SrCountersData{} 39 | 40 | if assert.NoError(t, countersData.Decode(testSrCountersDataBytes)) { 41 | assert.Equal(t, countersData, testEgtsSrCountersData) 42 | } 43 | } 44 | 45 | // проверяем что рекордсет работает правильно с данным типом подзаписи 46 | func TestEgtsSrCountersDataRs(t *testing.T) { 47 | countersDataRDBytes := append([]byte{0x13, 0x07, 0x00}, testSrCountersDataBytes...) 48 | countersDataRD := RecordDataSet{ 49 | RecordData{ 50 | SubrecordType: SrCountersDataType, 51 | SubrecordLength: testEgtsSrCountersData.Length(), 52 | SubrecordData: &testEgtsSrCountersData, 53 | }, 54 | } 55 | testStruct := RecordDataSet{} 56 | 57 | testBytes, err := countersDataRD.Encode() 58 | if assert.NoError(t, err) { 59 | assert.Equal(t, testBytes, countersDataRDBytes) 60 | 61 | if assert.NoError(t, testStruct.Decode(countersDataRDBytes)) { 62 | assert.Equal(t, countersDataRD, testStruct) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_response.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | //SrResponse структура подзаписи типа EGTS_SR_RESPONSE, которая применяется для подтверждения 10 | //приема результатов обработки поддержки услуг 11 | type SrResponse struct { 12 | ConfirmedRecordNumber uint16 `json:"CRN"` 13 | RecordStatus uint8 `json:"RST"` 14 | } 15 | 16 | //Decode разбирает байты в структуру подзаписи 17 | func (s *SrResponse) Decode(content []byte) error { 18 | var ( 19 | err error 20 | ) 21 | buf := bytes.NewBuffer(content) 22 | 23 | tmpIntBuf := make([]byte, 2) 24 | if _, err = buf.Read(tmpIntBuf); err != nil { 25 | return fmt.Errorf("Не удалось получить номер подтверждаемой записи: %v", err) 26 | } 27 | s.ConfirmedRecordNumber = binary.LittleEndian.Uint16(tmpIntBuf) 28 | 29 | if s.RecordStatus, err = buf.ReadByte(); err != nil { 30 | return fmt.Errorf("Не удалось получить статус обработки записи: %v", err) 31 | } 32 | 33 | sfd := ServiceDataSet{} 34 | if err = sfd.Decode(buf.Bytes()); err != nil { 35 | return err 36 | } 37 | return err 38 | } 39 | 40 | //Encode преобразовывает подзапись в набор байт 41 | func (s *SrResponse) Encode() ([]byte, error) { 42 | var ( 43 | result []byte 44 | err error 45 | ) 46 | buf := new(bytes.Buffer) 47 | 48 | if err = binary.Write(buf, binary.LittleEndian, s.ConfirmedRecordNumber); err != nil { 49 | return result, fmt.Errorf("Не удалось записать номер подтверждаемой записи: %v", err) 50 | } 51 | 52 | if err = buf.WriteByte(s.RecordStatus); err != nil { 53 | return result, fmt.Errorf("Не удалось записать статус обработки записи: %v", err) 54 | } 55 | 56 | result = buf.Bytes() 57 | return result, err 58 | } 59 | 60 | //Length получает длинну закодированной подзаписи 61 | func (s *SrResponse) Length() uint16 { 62 | var result uint16 63 | 64 | if recBytes, err := s.Encode(); err != nil { 65 | result = uint16(0) 66 | } else { 67 | result = uint16(len(recBytes)) 68 | } 69 | 70 | return result 71 | } 72 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_result_code_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | egtsPkgSrResCode = Package{ 10 | ProtocolVersion: 1, 11 | SecurityKeyID: 0, 12 | Prefix: "00", 13 | Route: "0", 14 | EncryptionAlg: "00", 15 | Compression: "0", 16 | Priority: "00", 17 | HeaderLength: 11, 18 | HeaderEncoding: 0, 19 | FrameDataLength: 11, 20 | PacketIdentifier: 14357, 21 | PacketType: PtAppdataPacket, 22 | HeaderCheckSum: 17, 23 | ServicesFrameData: &ServiceDataSet{ 24 | ServiceDataRecord{ 25 | RecordLength: 4, 26 | RecordNumber: 14357, 27 | SourceServiceOnDevice: "0", 28 | RecipientServiceOnDevice: "0", 29 | Group: "1", 30 | RecordProcessingPriority: "00", 31 | TimeFieldExists: "0", 32 | EventIDFieldExists: "0", 33 | ObjectIDFieldExists: "0", 34 | SourceServiceType: AuthService, 35 | RecipientServiceType: AuthService, 36 | RecordDataSet: RecordDataSet{ 37 | RecordData{ 38 | SubrecordType: SrResultCodeType, 39 | SubrecordLength: 1, 40 | SubrecordData: &SrResultCode{ 41 | ResultCode: egtsPcOk, 42 | }, 43 | }, 44 | }, 45 | }, 46 | }, 47 | ServicesFrameDataCheckSum: 48188, 48 | } 49 | 50 | testEgtsPkgSrResCodeBytes = []byte{0x01, 0x00, 0x00, 0x0B, 0x00, 0x0B, 0x00, 0x15, 0x38, 0x01, 0x11, 0x04, 0x00, 51 | 0x15, 0x38, 0x20, 0x01, 0x01, 0x09, 0x01, 0x00, 0x00, 0x3C, 0xBC} 52 | ) 53 | 54 | func TestEgtsPkgSrResCode_Encode(t *testing.T) { 55 | pkgBytes, err := egtsPkgSrResCode.Encode() 56 | 57 | if assert.NoError(t, err) { 58 | assert.Equal(t, pkgBytes, testEgtsPkgSrResCodeBytes) 59 | } 60 | } 61 | 62 | func TestEgtsPkgSrResCode_Decode(t *testing.T) { 63 | egtsPkg := Package{} 64 | 65 | _, err := egtsPkg.Decode(testEgtsPkgSrResCodeBytes) 66 | if assert.NoError(t, err) { 67 | assert.Equal(t, egtsPkg, egtsPkgSrResCode) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_dispatcher_identity_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | srDispatcherIdentityPkgBytes = []byte{0x01, 0x00, 0x00, 0x0b, 0x00, 0x0f, 0x00, 0x01, 0x00, 10 | 0x01, 0x06, 0x08, 0x00, 0x00, 0x00, 0x98, 0x01, 0x01, 0x05, 0x05, 0x00, 0x00, 0x47, 0x00, 11 | 0x00, 0x00, 0x51, 0x9d} 12 | 13 | testDispatcherIdentityPkg = Package{ 14 | ProtocolVersion: 1, 15 | SecurityKeyID: 0, 16 | Prefix: "00", 17 | Route: "0", 18 | EncryptionAlg: "00", 19 | Compression: "0", 20 | Priority: "00", 21 | HeaderLength: 11, 22 | HeaderEncoding: 0, 23 | FrameDataLength: 15, 24 | PacketIdentifier: 1, 25 | PacketType: PtAppdataPacket, 26 | HeaderCheckSum: 6, 27 | ServicesFrameData: &ServiceDataSet{ 28 | { 29 | RecordLength: 0x08, 30 | SourceServiceOnDevice: "1", 31 | RecipientServiceOnDevice: "0", 32 | Group: "0", 33 | RecordProcessingPriority: "11", 34 | TimeFieldExists: "0", 35 | EventIDFieldExists: "0", 36 | ObjectIDFieldExists: "0", 37 | SourceServiceType: 0x01, 38 | RecipientServiceType: 0x01, 39 | RecordDataSet: RecordDataSet{ 40 | { 41 | SubrecordType: 0x05, 42 | SubrecordLength: 0x05, 43 | SubrecordData: &SrDispatcherIdentity{ 44 | DispatcherType: 0, 45 | DispatcherID: 71, 46 | }, 47 | }, 48 | }, 49 | }, 50 | }, 51 | ServicesFrameDataCheckSum: 40273, 52 | } 53 | ) 54 | 55 | func TestEgtsSrDispatcherIdentity_Encode(t *testing.T) { 56 | authInfoPkg, err := testAuthInfoPkg.Encode() 57 | if assert.NoError(t, err) { 58 | assert.Equal(t, authInfoPkg, srAuthInfoPkgBytes) 59 | } 60 | } 61 | 62 | func TestEgtsSrDispatcherIdentity_Decode(t *testing.T) { 63 | authPkg := Package{} 64 | 65 | if _, err := authPkg.Decode(srDispatcherIdentityPkgBytes); assert.NoError(t, err) { 66 | assert.Equal(t, authPkg, testDispatcherIdentityPkg) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_egtsplus_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | srEgtsPlusBytes = []byte{0x08, 0xFB, 0xD4, 0x03, 0x15, 0x3B, 0x46, 0x5F, 0x5C, 0x25, 0x00, 0x00, 0x00, 0x00, 10 | 0x82, 0x01, 0x04, 0x08, 0x01, 0x30, 0x4F, 0x8A, 0x01, 0x02, 0x08, 0x01} 11 | 12 | rn = uint32(60027) 13 | ts = uint32(1549747771) 14 | sf = uint32(0) 15 | sn = uint32(1) 16 | t = int32(-40) 17 | scltde = SensCanLogTmpDataExt{ 18 | SensNum: &sn, 19 | } 20 | scld = SensCanLogData{ 21 | SensNum: &sn, 22 | EngineTemperature: &t, 23 | } 24 | testEgtsPlusData = StorageRecord{ 25 | RecordNumber: &rn, 26 | TimeStamp: &ts, 27 | StatusFlags: &sf, 28 | SensCanLogData: []*SensCanLogData{&scld}, 29 | SensCanLogTmpDataExt: []*SensCanLogTmpDataExt{&scltde}, 30 | } 31 | ) 32 | 33 | func TestStorageRecord_Encode(t *testing.T) { 34 | egtsPlusBytes, err := testEgtsPlusData.Encode() 35 | if assert.NoError(t, err) { 36 | assert.Equal(t, egtsPlusBytes, srEgtsPlusBytes) 37 | } 38 | } 39 | 40 | func TestStorageRecord_Decode(t *testing.T) { 41 | egtsPlus := StorageRecord{} 42 | 43 | if assert.NoError(t, egtsPlus.Decode(srEgtsPlusBytes)) { 44 | assert.Equal(t, egtsPlus, testEgtsPlusData) 45 | } 46 | } 47 | 48 | // проверяем что рекордсет работает правильно с данным типом подзаписи 49 | func TestStorageRecordRs(t *testing.T) { 50 | egtsPlusRDBytes := append([]byte{0x0F, 0x1A, 0x00}, srEgtsPlusBytes...) 51 | egtsPlusDataRD := RecordDataSet{ 52 | RecordData{ 53 | SubrecordType: SrEgtsPlusDataType, 54 | SubrecordLength: testEgtsPlusData.Length(), 55 | SubrecordData: &testEgtsPlusData, 56 | }, 57 | } 58 | testStruct := RecordDataSet{} 59 | 60 | testBytes, err := egtsPlusDataRD.Encode() 61 | if assert.NoError(t, err) { 62 | assert.Equal(t, testBytes, egtsPlusRDBytes) 63 | 64 | if assert.NoError(t, testStruct.Decode(egtsPlusRDBytes)) { 65 | assert.Equal(t, egtsPlusDataRD, testStruct) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_abs_cntr_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | //SrAbsCntrData структура подзаписи типа EGTS_SR_ABS_CNTR_DATA, которая применяется 10 | //абонентским терминалом для передачи на аппаратно-программный комплекс данных о 11 | //состоянии одного счетного входа 12 | type SrAbsCntrData struct { 13 | CounterNumber uint8 `json:"CN"` 14 | CounterValue uint32 `json:"CNV"` 15 | } 16 | 17 | // Decode разбирает байты в структуры секции 18 | func (e *SrAbsCntrData) Decode(content []byte) error { 19 | var ( 20 | err error 21 | ) 22 | buf := bytes.NewReader(content) 23 | 24 | if e.CounterNumber, err = buf.ReadByte(); err != nil { 25 | return fmt.Errorf("Не удалось получить номер счетного входа: %v", err) 26 | } 27 | 28 | tmpBuf := make([]byte, 3) 29 | if _, err = buf.Read(tmpBuf); err != nil { 30 | return fmt.Errorf("Не удалось получить значение показаний счетного входа: %v", err) 31 | } 32 | 33 | counterVal := append(tmpBuf, 0x00) 34 | e.CounterValue = binary.LittleEndian.Uint32(counterVal) 35 | 36 | return err 37 | } 38 | 39 | // Encode преобразовывает подзапись в набор байт 40 | func (e *SrAbsCntrData) Encode() ([]byte, error) { 41 | var ( 42 | err error 43 | result []byte 44 | ) 45 | buf := new(bytes.Buffer) 46 | 47 | if err = buf.WriteByte(e.CounterNumber); err != nil { 48 | return result, fmt.Errorf("Не удалось записать номер счетного входа: %v", err) 49 | } 50 | 51 | counterVal := make([]byte, 4) 52 | binary.LittleEndian.PutUint32(counterVal, e.CounterValue) 53 | if _, err = buf.Write(counterVal[:3]); err != nil { 54 | return result, fmt.Errorf("Не удалось записать значение показаний счетного входа: %v", err) 55 | } 56 | 57 | result = buf.Bytes() 58 | return result, err 59 | } 60 | 61 | //Length получает длинну закодированной подзаписи 62 | func (e *SrAbsCntrData) Length() uint16 { 63 | var result uint16 64 | 65 | if recBytes, err := e.Encode(); err != nil { 66 | result = uint16(0) 67 | } else { 68 | result = uint16(len(recBytes)) 69 | } 70 | 71 | return result 72 | } 73 | -------------------------------------------------------------------------------- /cli/receiver/storage/store/rabbitmq/connector.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | /* 4 | Плагин для работы с RabbitMQ через amqp. 5 | 6 | Раздел настроек, которые должны отвечать в конфиге для подключения хранилища: 7 | 8 | host = "localhost" 9 | port = "5672" 10 | user = "guest" 11 | password = "guest" 12 | exchange = "receiver" 13 | exchange_type = "topic" 14 | */ 15 | 16 | import ( 17 | "fmt" 18 | "github.com/streadway/amqp" 19 | ) 20 | 21 | type Connector struct { 22 | connection *amqp.Connection 23 | channel *amqp.Channel 24 | config map[string]string 25 | } 26 | 27 | func (c *Connector) Init(cfg map[string]string) error { 28 | var ( 29 | err error 30 | ) 31 | if cfg == nil { 32 | return fmt.Errorf("Не корректная ссылка на конфигурацию") 33 | } 34 | 35 | c.config = cfg 36 | conStr := fmt.Sprintf("amqp://%s:%s@%s:%s/", c.config["user"], c.config["password"], c.config["host"], c.config["port"]) 37 | if c.connection, err = amqp.Dial(conStr); err != nil { 38 | return fmt.Errorf("Ошибка установки соединеия RabbitMQ: %v", err) 39 | } 40 | 41 | if c.channel, err = c.connection.Channel(); err != nil { 42 | return fmt.Errorf("Ошибка открытия канала RabbitMQ: %v", err) 43 | } 44 | 45 | return err 46 | } 47 | 48 | func (c *Connector) Save(msg interface{ ToBytes() ([]byte, error) }) error { 49 | if msg == nil { 50 | return fmt.Errorf("Не корректная ссылка на пакет") 51 | } 52 | 53 | innerPkg, err := msg.ToBytes() 54 | if err != nil { 55 | return fmt.Errorf("Ошибка сериализации пакета: %v", err) 56 | } 57 | 58 | if err = c.channel.Publish( 59 | c.config["exchange"], 60 | c.config["key"], 61 | false, 62 | false, 63 | amqp.Publishing{ 64 | ContentType: "text/plain", 65 | Body: innerPkg, 66 | }); err != nil { 67 | return fmt.Errorf("Ошибка отправки сырого пакета в RabbitMQ: %v", err) 68 | } 69 | return nil 70 | } 71 | 72 | func (c *Connector) Close() error { 73 | var err error 74 | if c != nil { 75 | if c.channel != nil { 76 | if err = c.channel.Close(); err != nil { 77 | return err 78 | } 79 | } 80 | if c.connection != nil { 81 | if err = c.connection.Close(); err != nil { 82 | return err 83 | } 84 | } 85 | } 86 | return err 87 | } 88 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_response_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | egtsPkgSrResp = Package{ 10 | ProtocolVersion: 1, 11 | SecurityKeyID: 0, 12 | Prefix: "00", 13 | Route: "0", 14 | EncryptionAlg: "00", 15 | Compression: "0", 16 | Priority: "00", 17 | HeaderLength: 11, 18 | HeaderEncoding: 0, 19 | FrameDataLength: 16, 20 | PacketIdentifier: 134, 21 | PacketType: PtResponsePacket, 22 | HeaderCheckSum: 24, 23 | ServicesFrameData: &PtResponse{ 24 | ResponsePacketID: 134, 25 | ProcessingResult: 0, 26 | SDR: &ServiceDataSet{ 27 | ServiceDataRecord{ 28 | RecordLength: 6, 29 | RecordNumber: 95, 30 | SourceServiceOnDevice: "0", 31 | RecipientServiceOnDevice: "0", 32 | Group: "1", 33 | RecordProcessingPriority: "00", 34 | TimeFieldExists: "0", 35 | EventIDFieldExists: "0", 36 | ObjectIDFieldExists: "0", 37 | SourceServiceType: AuthService, 38 | RecipientServiceType: AuthService, 39 | RecordDataSet: RecordDataSet{ 40 | RecordData{ 41 | SubrecordType: SrRecordResponseType, 42 | SubrecordLength: 3, 43 | SubrecordData: &SrResponse{ 44 | ConfirmedRecordNumber: 95, 45 | RecordStatus: egtsPcOk, 46 | }, 47 | }, 48 | }, 49 | }, 50 | }, 51 | }, 52 | ServicesFrameDataCheckSum: 29459, 53 | } 54 | testEgtsPkgSrRespBytes = []byte{0x01, 0x00, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x86, 0x00, 0x00, 0x18, 0x86, 0x00, 0x00, 55 | 0x06, 0x00, 0x5F, 0x00, 0x20, 0x01, 0x01, 0x00, 0x03, 0x00, 0x5F, 0x00, 0x00, 0x13, 0x73} 56 | ) 57 | 58 | func TestEgtsPkgSrResp_Encode(t *testing.T) { 59 | pkgBytes, err := egtsPkgSrResp.Encode() 60 | 61 | if assert.NoError(t, err) { 62 | assert.Equal(t, pkgBytes, testEgtsPkgSrRespBytes) 63 | } 64 | } 65 | 66 | func TestEgtsPkgSrResp_Decode(t *testing.T) { 67 | egtsPkg := Package{} 68 | 69 | _, err := egtsPkg.Decode(testEgtsPkgSrRespBytes) 70 | if assert.NoError(t, err) { 71 | assert.Equal(t, egtsPkg, egtsPkgSrResp) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_dispatcher_identity.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | // SrDispatcherIdentity структура подзаписи типа EGTS_SR_DISPATCHER_IDENTITY, которая используется 10 | //только авторизуемой ТП при запросе авторизации на авторизующей ТП и содержит учетные данные 11 | //авторизуемой АСН 12 | type SrDispatcherIdentity struct { 13 | DispatcherType uint8 `json:"DT"` 14 | DispatcherID uint32 `json:"DID"` 15 | Description string `json:"DSCR"` 16 | } 17 | 18 | // Decode разбирает байты в структуру подзаписи 19 | func (d *SrDispatcherIdentity) Decode(content []byte) error { 20 | var err error 21 | 22 | buf := bytes.NewBuffer(content) 23 | 24 | if d.DispatcherType, err = buf.ReadByte(); err != nil { 25 | return fmt.Errorf("Не удалось получить тип диспетчера: %v", err) 26 | } 27 | 28 | tmpIntBuf := make([]byte, 4) 29 | if _, err = buf.Read(tmpIntBuf); err != nil { 30 | return fmt.Errorf("Не удалось получить уникальный идентификатор диспетчера: %v", err) 31 | } 32 | d.DispatcherID = binary.LittleEndian.Uint32(tmpIntBuf) 33 | 34 | d.Description = buf.String() 35 | 36 | return err 37 | } 38 | 39 | // Encode преобразовывает подзапись в набор байт 40 | func (d *SrDispatcherIdentity) Encode() ([]byte, error) { 41 | var ( 42 | result []byte 43 | err error 44 | ) 45 | 46 | buf := new(bytes.Buffer) 47 | 48 | if err = buf.WriteByte(d.DispatcherType); err != nil { 49 | return result, fmt.Errorf("Не удалось записать тип диспетчера: %v", err) 50 | } 51 | 52 | if err = binary.Write(buf, binary.LittleEndian, d.DispatcherID); err != nil { 53 | return result, fmt.Errorf("Не удалось записать уникальный идентификатор диспетчера: %v", err) 54 | } 55 | 56 | if _, err = buf.WriteString(d.Description); err != nil { 57 | return result, fmt.Errorf("Не удалось записать уникальный краткое описание: %v", err) 58 | } 59 | 60 | return buf.Bytes(), err 61 | } 62 | 63 | //Length получает длинну закодированной подзаписи 64 | func (d *SrDispatcherIdentity) Length() uint16 { 65 | var result uint16 66 | 67 | if recBytes, err := d.Encode(); err != nil { 68 | result = uint16(0) 69 | } else { 70 | result = uint16(len(recBytes)) 71 | } 72 | 73 | return result 74 | } 75 | -------------------------------------------------------------------------------- /libs/egts/egts_pt_response.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | //PtResponse структура подзаписи типа EGTS_PT_RESPONSE 10 | type PtResponse struct { 11 | ResponsePacketID uint16 `json:"RPID"` 12 | ProcessingResult uint8 `json:"PR"` 13 | SDR BinaryData `json:"SDR"` 14 | } 15 | 16 | // Decode разбирает байты в структуру подзаписи 17 | func (s *PtResponse) Decode(content []byte) error { 18 | var ( 19 | err error 20 | ) 21 | buf := bytes.NewBuffer(content) 22 | 23 | tmpIntBuf := make([]byte, 2) 24 | if _, err = buf.Read(tmpIntBuf); err != nil { 25 | return fmt.Errorf("Не удалось получить идентификатор пакета из ответа: %v", err) 26 | } 27 | s.ResponsePacketID = binary.LittleEndian.Uint16(tmpIntBuf) 28 | 29 | if s.ProcessingResult, err = buf.ReadByte(); err != nil { 30 | return fmt.Errorf("Не удалось получить код обработки: %v", err) 31 | } 32 | 33 | // если имеется о сервисном уровне, так как она необязательна 34 | if buf.Len() > 0 { 35 | s.SDR = &ServiceDataSet{} 36 | if err = s.SDR.Decode(buf.Bytes()); err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return err 42 | } 43 | 44 | // Encode преобразовывает подзапись в набор байт 45 | func (s *PtResponse) Encode() ([]byte, error) { 46 | var ( 47 | result []byte 48 | sdrBytes []byte 49 | err error 50 | ) 51 | buf := new(bytes.Buffer) 52 | 53 | if err = binary.Write(buf, binary.LittleEndian, s.ResponsePacketID); err != nil { 54 | return result, fmt.Errorf("Не удалось записать индентификатор пакета в ответ: %v", err) 55 | } 56 | 57 | if err = buf.WriteByte(s.ProcessingResult); err != nil { 58 | return result, fmt.Errorf("Не удалось записать результат обработки в пакет: %v", err) 59 | } 60 | 61 | if s.SDR != nil { 62 | if sdrBytes, err = s.SDR.Encode(); err != nil { 63 | return result, err 64 | } 65 | buf.Write(sdrBytes) 66 | } 67 | 68 | result = buf.Bytes() 69 | return result, err 70 | } 71 | 72 | //Length получает длинну закодированной подзаписи 73 | func (s *PtResponse) Length() uint16 { 74 | var result uint16 75 | 76 | if recBytes, err := s.Encode(); err != nil { 77 | result = uint16(0) 78 | } else { 79 | result = uint16(len(recBytes)) 80 | } 81 | 82 | return result 83 | } 84 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_auth_info_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | srAuthInfoPkgBytes = []byte{0x01, 0x00, 0x01, 0x0B, 0x00, 0x33, 0x00, 0x01, 0x00, 0x01, 0xEA, 0x28, 0x00, 0x00, 10 | 0x00, 0x0C, 0x44, 0x98, 0x11, 0x11, 0x01, 0x01, 0x07, 0x25, 0x00, 0x38, 0x30, 0x30, 0x00, 0x45, 0x46, 0x32, 0x38, 11 | 0x34, 0x45, 0x37, 0x41, 0x45, 0x33, 0x35, 0x31, 0x44, 0x36, 0x44, 0x46, 0x39, 0x32, 0x43, 0x45, 0x33, 0x32, 0x33, 12 | 0x44, 0x37, 0x34, 0x41, 0x44, 0x32, 0x45, 0x42, 0x33, 0x00, 0x92, 0x61, 13 | } 14 | 15 | testAuthInfoPkg = Package{ 16 | ProtocolVersion: 1, 17 | SecurityKeyID: 0, 18 | Prefix: "00", 19 | Route: "0", 20 | EncryptionAlg: "00", 21 | Compression: "0", 22 | Priority: "01", 23 | HeaderLength: 11, 24 | HeaderEncoding: 0, 25 | FrameDataLength: 51, 26 | PacketIdentifier: 1, 27 | PacketType: PtAppdataPacket, 28 | HeaderCheckSum: 234, 29 | ServicesFrameData: &ServiceDataSet{ 30 | ServiceDataRecord{ 31 | RecordLength: 40, 32 | RecordNumber: 0, 33 | SourceServiceOnDevice: "0", 34 | RecipientServiceOnDevice: "0", 35 | Group: "0", 36 | RecordProcessingPriority: "01", 37 | TimeFieldExists: "1", 38 | EventIDFieldExists: "0", 39 | ObjectIDFieldExists: "0", 40 | Time: 286365764, 41 | SourceServiceType: AuthService, 42 | RecipientServiceType: AuthService, 43 | RecordDataSet: RecordDataSet{ 44 | RecordData{ 45 | SubrecordType: SrAuthInfoType, 46 | SubrecordLength: 37, 47 | SubrecordData: &SrAuthInfo{ 48 | UserName: "800", 49 | UserPassword: "EF284E7AE351D6DF92CE323D74AD2EB3", 50 | }, 51 | }, 52 | }, 53 | }, 54 | }, 55 | ServicesFrameDataCheckSum: 24978, 56 | } 57 | ) 58 | 59 | func TestEgtsSrAuthInfo_Encode(t *testing.T) { 60 | authInfoPkg, err := testAuthInfoPkg.Encode() 61 | if assert.NoError(t, err) { 62 | assert.Equal(t, authInfoPkg, srAuthInfoPkgBytes) 63 | } 64 | } 65 | 66 | func TestEgtsSrAuthInfo_Decode(t *testing.T) { 67 | authPkg := Package{} 68 | 69 | if _, err := authPkg.Decode(srAuthInfoPkgBytes); assert.NoError(t, err) { 70 | assert.Equal(t, authPkg, testAuthInfoPkg) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cli/receiver/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kuznetsovin/egts-protocol/cli/receiver/server" 5 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage" 6 | "github.com/stretchr/testify/assert" 7 | "net" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestServer(t *testing.T) { 13 | test_addr := "localhost:5020" 14 | storages := storage.NewRepository() 15 | store := storage.LogConnector{} 16 | defer store.Close() 17 | if err := store.Init(nil); !assert.NoError(t, err) { 18 | return 19 | } 20 | storages.AddStore(store) 21 | 22 | go func() { 23 | srv := server.New(test_addr, time.Duration(2*time.Second), storages) 24 | srv.Run() 25 | }() 26 | time.Sleep(time.Second) 27 | 28 | message := []byte{0x01, 0x00, 0x00, 0x0B, 0x00, 0xB1, 0x00, 0xE8, 0x04, 0x01, 0x4E, 0xA6, 0x00, 0xA1, 0x0A, 0x81, 0x34, 0xF6, 0xE9, 0x01, 29 | 0x02, 0x02, 0x10, 0x1A, 0x00, 0x4F, 0x5F, 0xE5, 0x10, 0x00, 0xBE, 0xCD, 0x9E, 0x80, 0x7F, 0x8B, 0x35, 0x93, 0x9B, 0x80, 0x2F, 0xF9, 0x80, 30 | 0x02, 0x01, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x11, 0x06, 0x00, 0x0E, 0x46, 0x00, 0x00, 0x00, 0x0C, 0x12, 0x1C, 0x00, 0x01, 0x0F, 0xFF, 31 | 0x01, 0x44, 0x6D, 0x00, 0xB8, 0x00, 0x00, 0x0B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x14, 0x05, 0x00, 0x02, 0xFF, 0x00, 0x29, 0x04, 0x1B, 0x07, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x07, 0x00, 33 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x07, 0x00, 0x03, 0x01, 0x00, 0x5A, 0x08, 0x00, 0x00, 0x1B, 0x07, 0x00, 0x04, 0x02, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x19, 0x04, 0x00, 0x64, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x65, 0x00, 0x00, 0x00, 0x19, 0x04, 0x00, 0x66, 0x01, 35 | 0x00, 0x00, 0x19, 0x04, 0x00, 0x67, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x68, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x69, 0x4F, 0x9A, 0x22, 36 | 0x19, 0x04, 0x00, 0x6E, 0x77, 0x2A, 0x04, 0x41, 0xF6} 37 | response := []byte{0x1, 0x0, 0x0, 0xb, 0x0, 0x10, 0x0, 0xe9, 0x4, 0x0, 0xa1, 0xe8, 0x4, 0x0, 0x6, 0x0, 0x1, 0x0, 0x20, 0x2, 0x2, 0x0, 0x3, 38 | 0x0, 0xa1, 0xa, 0x0, 0x5e, 0xb6} 39 | 40 | conn, err := net.Dial("tcp", test_addr) 41 | if assert.NoError(t, err) { 42 | _ = conn.SetWriteDeadline(time.Now().Add(2 * time.Second)) 43 | _, _ = conn.Write(message) 44 | 45 | buf := make([]byte, 29) 46 | _, _ = conn.Read(buf) 47 | 48 | assert.Equal(t, response, buf) 49 | 50 | } 51 | defer conn.Close() 52 | } 53 | -------------------------------------------------------------------------------- /libs/egts/record_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var ( 10 | testRecordDataBytes = []byte{0x10, 0x15, 0x00, 0xD5, 0x3F, 0x01, 0x10, 0x6F, 0x1C, 0x05, 0x9E, 0x7A, 0xB5, 11 | 0x3C, 0x35, 0x01, 0xD0, 0x87, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00} 12 | ) 13 | 14 | func TestRecordDataSet_Encode(t *testing.T) { 15 | testRecordDataSet := RecordDataSet{ 16 | RecordData{ 17 | SubrecordData: &SrPosData{ 18 | NavigationTime: time.Date(2018, time.July, 5, 20, 8, 53, 0, time.UTC), 19 | Latitude: 55.55389399769574, 20 | Longitude: 37.43236696287812, 21 | ALTE: "0", 22 | LOHS: "0", 23 | LAHS: "0", 24 | MV: "0", 25 | BB: "0", 26 | CS: "0", 27 | FIX: "0", 28 | VLD: "1", 29 | DirectionHighestBit: 1, 30 | AltitudeSign: 0, 31 | Speed: 200, 32 | Direction: 172, 33 | Odometer: 1, 34 | DigitalInputs: 0, 35 | Source: 0, 36 | }, 37 | }, 38 | } 39 | 40 | rdBytes, err := testRecordDataSet.Encode() 41 | if assert.NoError(t, err) { 42 | assert.Equal(t, rdBytes, testRecordDataBytes) 43 | } 44 | } 45 | 46 | func TestRecordDataSet_Decode(t *testing.T) { 47 | rds := RecordDataSet{} 48 | testRecordDataSet := RecordDataSet{ 49 | RecordData{ 50 | SubrecordType: SrPosDataType, 51 | SubrecordLength: 21, 52 | SubrecordData: &SrPosData{ 53 | NavigationTime: time.Date(2018, time.July, 5, 20, 8, 53, 0, time.UTC), 54 | Latitude: 55.55389399769574, 55 | Longitude: 37.43236696287812, 56 | ALTE: "0", 57 | LOHS: "0", 58 | LAHS: "0", 59 | MV: "0", 60 | BB: "0", 61 | CS: "0", 62 | FIX: "0", 63 | VLD: "1", 64 | DirectionHighestBit: 1, 65 | AltitudeSign: 0, 66 | Speed: 200, 67 | Direction: 172, 68 | Odometer: 1, 69 | DigitalInputs: 0, 70 | Source: 0, 71 | }, 72 | }, 73 | } 74 | if assert.NoError(t, rds.Decode(testRecordDataBytes)) { 75 | assert.Equal(t, rds, testRecordDataSet) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /cli/receiver/storage/store/tarantool_queue/connector.go: -------------------------------------------------------------------------------- 1 | package tarantool_queue 2 | 3 | /* 4 | Плагин для работы с Tarantool queue. 5 | 6 | Раздел настроек, которые должны отвечають в конфиге для подключения хранилища: 7 | 8 | host = "localhost" 9 | port = "5672" 10 | user = "user" 11 | password = "pass" 12 | max_recons = 5 13 | timeout = 1 14 | reconnect = 1 15 | queue = "points" 16 | */ 17 | 18 | import ( 19 | "fmt" 20 | "github.com/tarantool/go-tarantool" 21 | "github.com/tarantool/go-tarantool/queue" 22 | "strconv" 23 | "time" 24 | ) 25 | 26 | type Connector struct { 27 | connection *tarantool.Connection 28 | queue queue.Queue 29 | config map[string]string 30 | } 31 | 32 | func (c *Connector) Init(cfg map[string]string) error { 33 | if cfg == nil { 34 | return fmt.Errorf("Не корректная ссылка на конфигурацию") 35 | } 36 | 37 | c.config = cfg 38 | conStr := fmt.Sprintf("%s:%s", c.config["host"], c.config["port"]) 39 | 40 | maxRecons, err := strconv.Atoi(c.config["max_recons"]) 41 | if err != nil { 42 | return fmt.Errorf("Не удалось получить MaxReconnects: %v", err) 43 | } 44 | timeout, err := strconv.Atoi(c.config["timeout"]) 45 | if err != nil { 46 | return fmt.Errorf("Не удалось получить timeout: %v", err) 47 | } 48 | reconnect, err := strconv.Atoi(c.config["reconnect"]) 49 | if err != nil { 50 | return fmt.Errorf("Не удалось получить reconnect: %v", err) 51 | } 52 | opts := tarantool.Opts{ 53 | Timeout: time.Duration(timeout) * time.Second, 54 | Reconnect: time.Duration(reconnect) * time.Second, 55 | MaxReconnects: uint(maxRecons), 56 | User: c.config["user"], 57 | Pass: c.config["password"], 58 | } 59 | 60 | c.connection, err = tarantool.Connect(conStr, opts) 61 | if err != nil { 62 | return fmt.Errorf("Не удалось подключиться к tarantool: %v", err) 63 | } 64 | c.queue = queue.New(c.connection, c.config["queue"]) 65 | 66 | return err 67 | } 68 | 69 | func (c *Connector) Save(msg interface{ ToBytes() ([]byte, error) }) error { 70 | if msg == nil { 71 | return fmt.Errorf("Не корректная ссылка на пакет") 72 | } 73 | 74 | innerPkg, err := msg.ToBytes() 75 | if err != nil { 76 | return fmt.Errorf("Ошибка сериализации пакета: %v", err) 77 | } 78 | 79 | _, err = c.queue.Put(innerPkg) 80 | if err != nil { 81 | return fmt.Errorf("Не удалось отправить сообщение: %v", err) 82 | } 83 | return nil 84 | } 85 | 86 | func (c *Connector) Close() error { 87 | return c.connection.Close() 88 | } 89 | -------------------------------------------------------------------------------- /libs/egts/const_types.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | //SrRecordResponseType код типа подзаписи EGTS_SR_RECORD_RESPONSE 4 | const SrRecordResponseType = 0 5 | 6 | //SrTermIdentityType код типа подзаписи EGTS_SR_TERM_IDENTITY 7 | const SrTermIdentityType = 1 8 | 9 | // SrModuleDataType код типа подзаписи EGTS_SR_MODULE_DATA 10 | const SrModuleDataType = 2 11 | 12 | //SrResultCodeType код типа подзаписи EGTS_SR_RESULT_CODE 13 | const SrResultCodeType = 9 14 | 15 | //SrAuthInfoType код типа подзаписи EGTS_SR_AUTH_INFO 16 | const SrAuthInfoType = 7 17 | 18 | //SrEgtsPlusDataType код типа подзаписи EGTS_SR_EGTS_PLUS_DATA 19 | const SrEgtsPlusDataType = 15 20 | 21 | //SrPosDataType код типа подзаписи EGTS_SR_POS_DATA 22 | const SrPosDataType = 16 23 | 24 | //SrExtPosDataType код типа подзаписи EGTS_SR_EXT_POS_DATA 25 | const SrExtPosDataType = 17 26 | 27 | //SrAdSensorsDataType код типа подзаписи EGTS_SR_AD_SENSORS_DATA 28 | const SrAdSensorsDataType = 18 29 | 30 | //SrCountersDataType код типа подзаписи EGTS_SR_COUNTERS_DATA 31 | const SrCountersDataType = 19 32 | 33 | //SrType20 в зависимости от длины может содержать секцию EGTS_SR_STATE_DATA (если длина 5 байт) или EGTS_SR_ACCEL_DATA 34 | const SrType20 = 20 35 | 36 | //SrStateDataType код типа подзаписи EGTS_SR_STATE_DATA 37 | const SrStateDataType = 21 38 | 39 | //SrLoopinDataType код типа подзаписи EGTS_SR_TERM_IDENTITY_TYPE 40 | const SrLoopinDataType = 22 41 | 42 | //SrAbsDigSensDataType код типа подзаписи EGTS_SR_ABS_DIG_SENS_DATA 43 | const SrAbsDigSensDataType = 23 44 | 45 | //SrAbsAnSensDataType код типа подзаписи EGTS_SR_ABS_AN_SENS_DATA 46 | const SrAbsAnSensDataType = 24 47 | 48 | //SrAbsCntrDataType код типа подзаписи EGTS_SR_ABS_CNTR_DATA 49 | const SrAbsCntrDataType = 25 50 | 51 | //SrAbsLoopinDataType код типа подзаписи EGTS_SR_ABS_LOOPIN_DATA 52 | const SrAbsLoopinDataType = 26 53 | 54 | //SrLiquidLevelSensorType код код типа подзаписи EGTS_SR_LIQUID_LEVEL_SENSOR 55 | const SrLiquidLevelSensorType = 27 56 | 57 | //SrPassengersCountersType код типа подзаписи EGTS_SR_PASSENGERS_COUNTERS 58 | const SrPassengersCountersType = 28 59 | 60 | //PtAppdataPacket код типа пакета PT_APP_DATA 61 | const PtAppdataPacket = 1 62 | 63 | //PtResponsePacket код типа пакета PT_RESPONSE 64 | const PtResponsePacket = 0 65 | 66 | //AuthService тип сервиса AUTH_SERVICE 67 | const AuthService = 1 68 | 69 | //TeledataService тип сервиса TELEDATA_SERVICE 70 | const TeledataService = 2 71 | 72 | //SrDispatcherIdentityType код типа подзаписи EGTS_SR_DISPATCHER_IDENTITY 73 | const SrDispatcherIdentityType = 5 74 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_auth_info.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | //SrAuthInfo структура подзаписи типа EGTS_SR_AUTH_INFO, которая предназначена для передачи на 10 | //телематическую платформу аутентификационных данных АС с использованием ранее переданных 11 | //со стороны платформы параметров для осуществления шифрования данных. 12 | type SrAuthInfo struct { 13 | UserName string `json:"UNM"` 14 | UserPassword string `json:"UPSW"` 15 | ServerSequence string `json:"SS"` 16 | } 17 | 18 | // Decode разбирает байты в структуру подзаписи 19 | func (e *SrAuthInfo) Decode(content []byte) error { 20 | var ( 21 | err error 22 | tmpStr string 23 | ) 24 | //разделитель строковых полей из ГОСТ 54619 - 2011 секции EGTS_SR_AUTH_INFO 25 | sep := byte(0x00) 26 | 27 | buf := bytes.NewBuffer(content) 28 | tmpStr, err = buf.ReadString(sep) 29 | if err != nil { 30 | return fmt.Errorf("Не удалось считать имя пользователя sr_auth_info: %v", err) 31 | } 32 | e.UserName = strings.TrimSuffix(tmpStr, string(sep)) 33 | 34 | tmpStr, err = buf.ReadString(sep) 35 | if err != nil { 36 | return fmt.Errorf("Не удалось считать пароль sr_auth_info: %v", err) 37 | } 38 | e.UserPassword = strings.TrimSuffix(tmpStr, string(sep)) 39 | 40 | if buf.Len() > 0 { 41 | tmpStr, err = buf.ReadString(sep) 42 | if err != nil { 43 | return fmt.Errorf("Не удалось считать SS из sr_auth_info: %v", err) 44 | } 45 | e.ServerSequence = strings.TrimSuffix(tmpStr, string(sep)) 46 | } 47 | 48 | return err 49 | } 50 | 51 | // Encode преобразовывает подзапись в набор байт 52 | func (e *SrAuthInfo) Encode() ([]byte, error) { 53 | var ( 54 | err error 55 | result []byte 56 | ) 57 | //разделитель строковых полей из ГОСТ 54619 - 2011 секции EGTS_SR_AUTH_INFO 58 | sep := byte(0x00) 59 | 60 | result = append(result, []byte(e.UserName)...) 61 | result = append(result, sep) 62 | 63 | result = append(result, []byte(e.UserPassword)...) 64 | result = append(result, sep) 65 | 66 | // необязательное поле, наличие зависит от используемого алгоритма шифрования 67 | // специальная серверная последовательность байт, передаваемая в подзаписи EGTS_SR_AUTH_PARAMS 68 | if e.ServerSequence != "" { 69 | result = append(result, []byte(e.ServerSequence)...) 70 | result = append(result, sep) 71 | } 72 | 73 | return result, err 74 | } 75 | 76 | //Length получает длинну закодированной подзаписи 77 | func (e *SrAuthInfo) Length() uint16 { 78 | var result uint16 79 | 80 | if recBytes, err := e.Encode(); err != nil { 81 | result = uint16(0) 82 | } else { 83 | result = uint16(len(recBytes)) 84 | } 85 | 86 | return result 87 | } 88 | -------------------------------------------------------------------------------- /cli/receiver/storage/store.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage/store/mysql" 7 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage/store/nats" 8 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage/store/postgresql" 9 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage/store/rabbitmq" 10 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage/store/redis" 11 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage/store/tarantool_queue" 12 | ) 13 | 14 | var InvalidStorage = errors.New("storage not found") 15 | var UnknownStorage = errors.New("storage isn't support yet") 16 | 17 | type Store interface { 18 | Connector 19 | Saver 20 | } 21 | 22 | //Saver интерфейс для подключения внешних хранилищ 23 | type Saver interface { 24 | // Save сохранение в хранилище 25 | Save(interface{ ToBytes() ([]byte, error) }) error 26 | } 27 | 28 | //Connector интерфейс для подключения внешних хранилищ 29 | type Connector interface { 30 | // Init установка соединения с хранилищем 31 | Init(map[string]string) error 32 | 33 | // Close закрытие соединения с хранилищем 34 | Close() error 35 | } 36 | 37 | //Repository набор выходных хранилищ 38 | type Repository struct { 39 | storages []Saver 40 | } 41 | 42 | //AddStore добавляет хранилище для сохранения данных 43 | func (r *Repository) AddStore(s Saver) { 44 | r.storages = append(r.storages, s) 45 | } 46 | 47 | //Save сохраняет данные во все установленные хранилища 48 | func (r *Repository) Save(m interface{ ToBytes() ([]byte, error) }) error { 49 | for _, store := range r.storages { 50 | if err := store.Save(m); err != nil { 51 | return err 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | //LoadStorages загружает хранилища из структуры конфига 58 | func (r *Repository) LoadStorages(storages map[string]map[string]string) error { 59 | if len(storages) == 0 { 60 | return InvalidStorage 61 | } 62 | 63 | var db Store 64 | for store, params := range storages { 65 | switch store { 66 | case "rabbitmq": 67 | db = &rabbitmq.Connector{} 68 | case "postgresql": 69 | db = &postgresql.Connector{} 70 | case "nats": 71 | db = &nats.Connector{} 72 | case "tarantool_queue": 73 | db = &tarantool_queue.Connector{} 74 | case "redis": 75 | db = &redis.Connector{} 76 | case "mysql": 77 | db = &mysql.Connector{} 78 | default: 79 | return UnknownStorage 80 | } 81 | 82 | if err := db.Init(params); err != nil { 83 | return err 84 | } 85 | 86 | r.AddStore(db) 87 | } 88 | return nil 89 | } 90 | 91 | //NewRepository создает пустой репозиторий 92 | func NewRepository() *Repository { 93 | return &Repository{} 94 | } 95 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_ad_sensors_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | srAdSensorsDataBytes = []byte{0x01, 0x0F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 11 | testEgtsSrAdSensorsData = SrAdSensorsData{ 12 | DigitalInputsOctetExists1: "1", 13 | DigitalInputsOctetExists2: "0", 14 | DigitalInputsOctetExists3: "0", 15 | DigitalInputsOctetExists4: "0", 16 | DigitalInputsOctetExists5: "0", 17 | DigitalInputsOctetExists6: "0", 18 | DigitalInputsOctetExists7: "0", 19 | DigitalInputsOctetExists8: "0", 20 | DigitalOutputs: 15, 21 | AnalogSensorFieldExists1: "1", 22 | AnalogSensorFieldExists2: "1", 23 | AnalogSensorFieldExists3: "1", 24 | AnalogSensorFieldExists4: "1", 25 | AnalogSensorFieldExists5: "1", 26 | AnalogSensorFieldExists6: "1", 27 | AnalogSensorFieldExists7: "1", 28 | AnalogSensorFieldExists8: "1", 29 | AdditionalDigitalInputsOctet1: 0, 30 | AnalogSensor1: 0, 31 | AnalogSensor2: 0, 32 | AnalogSensor3: 0, 33 | AnalogSensor4: 0, 34 | AnalogSensor5: 0, 35 | AnalogSensor6: 0, 36 | AnalogSensor7: 0, 37 | AnalogSensor8: 0, 38 | } 39 | ) 40 | 41 | func TestEgtsSrAdSensorsData_Encode(t *testing.T) { 42 | sensDataBytes, err := testEgtsSrAdSensorsData.Encode() 43 | if assert.NoError(t, err) { 44 | assert.Equal(t, sensDataBytes, srAdSensorsDataBytes) 45 | } 46 | } 47 | 48 | func TestEgtsSrAdSensorsData_Decode(t *testing.T) { 49 | adSensData := SrAdSensorsData{} 50 | 51 | if assert.NoError(t, adSensData.Decode(srAdSensorsDataBytes)) { 52 | assert.Equal(t, adSensData, testEgtsSrAdSensorsData) 53 | } 54 | } 55 | 56 | // проверяем что рекордсет работает правильно с данным типом подзаписи 57 | func TestEgtsSrAdSensorsDataaRs(t *testing.T) { 58 | adSensDataRDBytes := append([]byte{0x12, 0x1C, 0x00}, srAdSensorsDataBytes...) 59 | adSensDataRD := RecordDataSet{ 60 | RecordData{ 61 | SubrecordType: SrAdSensorsDataType, 62 | SubrecordLength: testEgtsSrAdSensorsData.Length(), 63 | SubrecordData: &testEgtsSrAdSensorsData, 64 | }, 65 | } 66 | testStruct := RecordDataSet{} 67 | 68 | testBytes, err := adSensDataRD.Encode() 69 | if assert.NoError(t, err) { 70 | assert.Equal(t, testBytes, adSensDataRDBytes) 71 | 72 | if assert.NoError(t, testStruct.Decode(adSensDataRDBytes)) { 73 | assert.Equal(t, adSensDataRD, testStruct) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /cli/receiver/server/server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage" 5 | "github.com/stretchr/testify/assert" 6 | "net" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestServer(t *testing.T) { 12 | srvAddr := "127.0.0.1:5020" 13 | message := []byte{0x01, 0x00, 0x00, 0x0B, 0x00, 0xB1, 0x00, 0xE8, 0x04, 0x01, 0x4E, 0xA6, 0x00, 0xA1, 0x0A, 0x81, 0x34, 0xF6, 0xE9, 0x01, 14 | 0x02, 0x02, 0x10, 0x1A, 0x00, 0x4F, 0x5F, 0xE5, 0x10, 0x00, 0xBE, 0xCD, 0x9E, 0x80, 0x7F, 0x8B, 0x35, 0x93, 0x9B, 0x80, 0x2F, 0xF9, 0x80, 15 | 0x02, 0x01, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x11, 0x06, 0x00, 0x0E, 0x46, 0x00, 0x00, 0x00, 0x0C, 0x12, 0x1C, 0x00, 0x01, 0x0F, 0xFF, 16 | 0x01, 0x44, 0x6D, 0x00, 0xB8, 0x00, 0x00, 0x0B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x14, 0x05, 0x00, 0x02, 0xFF, 0x00, 0x29, 0x04, 0x1B, 0x07, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x07, 0x00, 18 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x07, 0x00, 0x03, 0x01, 0x00, 0x5A, 0x08, 0x00, 0x00, 0x1B, 0x07, 0x00, 0x04, 0x02, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x19, 0x04, 0x00, 0x64, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x65, 0x00, 0x00, 0x00, 0x19, 0x04, 0x00, 0x66, 0x01, 20 | 0x00, 0x00, 0x19, 0x04, 0x00, 0x67, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x68, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x69, 0x4F, 0x9A, 0x22, 21 | 0x19, 0x04, 0x00, 0x6E, 0x77, 0x2A, 0x04, 0x41, 0xF6} 22 | response := []byte{0x1, 0x0, 0x0, 0xb, 0x0, 0x10, 0x0, 0xe9, 0x4, 0x0, 0xa1, 0xe8, 0x4, 0x0, 0x6, 0x0, 0x1, 0x0, 0x20, 0x2, 0x2, 0x0, 0x3, 23 | 0x0, 0xa1, 0xa, 0x0, 0x5e, 0xb6} 24 | 25 | s := &TestConnector{} 26 | _ = s.Init(nil) 27 | 28 | r := storage.NewRepository() 29 | r.AddStore(s) 30 | 31 | srv := New(srvAddr, 3*time.Second, r) 32 | defer srv.Stop() 33 | 34 | // запускаем сервер 35 | go func() { 36 | srv.Run() 37 | }() 38 | 39 | time.Sleep(500 * time.Microsecond) 40 | conn, err := net.Dial("tcp", srvAddr) 41 | if assert.NoError(t, err) { 42 | _ = conn.SetWriteDeadline(time.Now().Add(2 * time.Second)) 43 | _, _ = conn.Write(message) 44 | 45 | buf := make([]byte, 29) 46 | _, _ = conn.Read(buf) 47 | 48 | assert.Equal(t, response, buf) 49 | 50 | assert.Equal(t, len(s.ch), 1) 51 | } 52 | defer conn.Close() 53 | } 54 | 55 | type TestConnector struct { 56 | ch chan []byte 57 | } 58 | 59 | func (t *TestConnector) Init(c map[string]string) error { 60 | t.ch = make(chan []byte, 2) 61 | return nil 62 | } 63 | 64 | func (t *TestConnector) Save(p interface{ ToBytes() ([]byte, error) }) error { 65 | v, err := p.ToBytes() 66 | t.ch <- v 67 | 68 | return err 69 | } 70 | 71 | func (t *TestConnector) Close() error { return nil } 72 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_term_identity_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | testEgtsSrTermIdentityBin = []byte{0xB0, 0x09, 0x02, 0x00, 0x10} 10 | testEgtsSrTermIdentity = SrTermIdentity{ 11 | TerminalIdentifier: 133552, 12 | MNE: "0", 13 | BSE: "0", 14 | NIDE: "0", 15 | SSRA: "1", 16 | LNGCE: "0", 17 | IMSIE: "0", 18 | IMEIE: "0", 19 | HDIDE: "0", 20 | } 21 | testEgtsSrTermIdentityPkgBin = []byte{0x01, 0x00, 0x03, 0x0B, 0x00, 0x13, 0x00, 0x86, 0x00, 0x01, 0xB6, 0x08, 0x00, 22 | 0x5F, 0x00, 0x99, 0x02, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x05, 0x00, 0xB0, 0x09, 0x02, 0x00, 0x10, 0x0D, 0xCE} 23 | testEgtsSrTermIdentityPkg = Package{ 24 | ProtocolVersion: 1, 25 | SecurityKeyID: 0, 26 | Prefix: "00", 27 | Route: "0", 28 | EncryptionAlg: "00", 29 | Compression: "0", 30 | Priority: "11", 31 | HeaderLength: 11, 32 | HeaderEncoding: 0, 33 | FrameDataLength: 19, 34 | PacketIdentifier: 134, 35 | PacketType: PtAppdataPacket, 36 | HeaderCheckSum: 182, 37 | ServicesFrameData: &ServiceDataSet{ 38 | ServiceDataRecord{ 39 | RecordLength: 8, 40 | RecordNumber: 95, 41 | SourceServiceOnDevice: "1", 42 | RecipientServiceOnDevice: "0", 43 | Group: "0", 44 | RecordProcessingPriority: "11", 45 | TimeFieldExists: "0", 46 | EventIDFieldExists: "0", 47 | ObjectIDFieldExists: "1", 48 | ObjectIdentifier: 2, 49 | SourceServiceType: AuthService, 50 | RecipientServiceType: AuthService, 51 | RecordDataSet: RecordDataSet{ 52 | RecordData{ 53 | SubrecordType: SrTermIdentityType, 54 | SubrecordLength: 5, 55 | SubrecordData: &testEgtsSrTermIdentity, 56 | }, 57 | }, 58 | }, 59 | }, 60 | ServicesFrameDataCheckSum: 52749, 61 | } 62 | ) 63 | 64 | func TestEgtsSrTermIdentity_Encode(t *testing.T) { 65 | sti, err := testEgtsSrTermIdentity.Encode() 66 | 67 | if assert.NoError(t, err) { 68 | assert.Equal(t, sti, testEgtsSrTermIdentityBin) 69 | } 70 | } 71 | 72 | func TestEgtsSrTermIdentity_Decode(t *testing.T) { 73 | srTermIdent := SrTermIdentity{} 74 | 75 | if assert.NoError(t, srTermIdent.Decode(testEgtsSrTermIdentityBin)) { 76 | assert.Equal(t, srTermIdent, testEgtsSrTermIdentity) 77 | } 78 | } 79 | 80 | func TestEgtsSrTermIdentityPkg_Encode(t *testing.T) { 81 | pkg, err := testEgtsSrTermIdentityPkg.Encode() 82 | 83 | if assert.NoError(t, err) { 84 | assert.Equal(t, pkg, testEgtsSrTermIdentityPkgBin) 85 | } 86 | } 87 | 88 | func TestEgtsSrTermIdentityPkg_Decode(t *testing.T) { 89 | srTermIdentPkg := Package{} 90 | 91 | _, err := srTermIdentPkg.Decode(testEgtsSrTermIdentityPkgBin) 92 | if assert.NoError(t, err) { 93 | assert.Equal(t, srTermIdentPkg, testEgtsSrTermIdentityPkg) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /libs/egts/errors.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | //код сообщения, что пакет успешно обработано 4 | const egtsPcOk = uint8(0) 5 | 6 | //код сообщения, что пакет в процессе обработки (результат обработки ещё не известен) 7 | const egtsPcInProgress = uint8(1) 8 | 9 | //неподдерживаемый протокол 10 | const egtsPcUnsProtocol = uint8(128) 11 | 12 | //ошибка декодирования 13 | const egtsPcDecryptError = uint8(129) 14 | 15 | //обработка запрещена 16 | const egtsPcProcDenied = uint8(130) 17 | 18 | //неверный формат заголовка 19 | const egtsPcIncHeaderform = uint8(131) 20 | 21 | //неверный формат данных 22 | const egtsPcIncDataform = uint8(132) 23 | 24 | //EgtsPcUnsType неподдерживаемый тип 25 | const egtsPcUnsType = uint8(133) 26 | 27 | //неверное количество параметров 28 | const egtsPcNotenParams = uint8(134) 29 | 30 | //попытка повторной обработки 31 | const egtsPcDblProc = uint8(135) 32 | 33 | //обработка данных от источника запрещена 34 | const egtsPcProcSrcDenied = uint8(136) 35 | 36 | //ошибка контрольной суммы заголовка 37 | const egtsPcHeaderCrcError = uint8(137) 38 | 39 | //ошибка контрольной суммы данных 40 | const egtsPcDatacrcError = uint8(138) 41 | 42 | //некорректная длина данных 43 | const egtsPcInvdatalen = uint8(139) 44 | 45 | //маршрут не найден 46 | const egtsPcRouteNfound = uint8(140) 47 | 48 | //маршрут закрыт 49 | const egtsPcRouteClosed = uint8(141) 50 | 51 | //маршрутизация запрещена 52 | const egtsPcRouteDenied = uint8(142) 53 | 54 | //неверный адрес 55 | const egtsPcInvaddr = uint8(143) 56 | 57 | //превышено количество ретрансляции данных 58 | const egtsPcTtlexpired = uint8(144) 59 | 60 | //нет подтверждения 61 | const egtsPcNoAck = uint8(145) 62 | 63 | //объект не найден 64 | const egtsPcObjNfound = uint8(146) 65 | 66 | //событие не найдено 67 | const egtsPcEvntNfound = uint8(147) 68 | 69 | //сервис не найден 70 | const egtsPcSrvcNfound = uint8(148) 71 | 72 | //сервис запрещён 73 | const egtsPcSrvcDenied = uint8(149) 74 | 75 | //неизвестный тип сервиса 76 | const egtsPcSrvcUnkn = uint8(150) 77 | 78 | //авторизация запрещена 79 | const egtsPcAuthPenied = uint8(151) 80 | 81 | //объект уже существует 82 | const egtsPcAlreadyExists = uint8(152) 83 | 84 | //идентификатор не найден 85 | const egtsPcIDNfound = uint8(153) 86 | 87 | //неправильная дата и время 88 | const egtsPcIncDatetime = uint8(154) 89 | 90 | //ошибка ввода/вывода 91 | const egtsPcIoError = uint8(155) 92 | 93 | //недостаточно ресурсов 94 | const egtsPcNoResAvail = uint8(156) 95 | 96 | //внутренний сбой модуля 97 | const egtsPcModuleFault = uint8(157) 98 | 99 | //сбой в работе цепи питания модуля 100 | const egtsPcModulePwrFlt = uint8(158) 101 | 102 | //сбой в работе микроконтроллера модуля 103 | const egtsPcModuleProcFlt = uint8(159) 104 | 105 | //сбой в работе программы модуля 106 | const egtsPcModuleSwFlt = uint8(160) 107 | 108 | //сбой в работе внутреннего ПО модуля 109 | const egtsPcModuleFwFlt = uint8(161) 110 | 111 | //сбой в работе блока ввода/вывода модуля 112 | const egtsPcModuleIoFlt = uint8(162) 113 | 114 | //сбой в работе внутренней памяти модуля 115 | const egtsPcModuleMemFlt = uint8(163) 116 | 117 | //тест не пройден 118 | const egtsPcTestFailed = uint8(164) 119 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_liquid_level_sensor.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | //SrLiquidLevelSensor структура подзаписи типа EGTS_SR_LIQUID_LEVEL_SENSOR, которая применяется 11 | //абонентским терминалом для передачи на аппаратно-программный комплекс данных о показаниях ДУЖ 12 | type SrLiquidLevelSensor struct { 13 | LiquidLevelSensorErrorFlag string `json:"LLSEF"` 14 | LiquidLevelSensorValueUnit string `json:"LLSVU"` 15 | RawDataFlag string `json:"RDF"` 16 | LiquidLevelSensorNumber uint8 `json:"LLSN"` 17 | ModuleAddress uint16 `json:"MADDR"` 18 | LiquidLevelSensorData uint32 `json:"LLSD"` 19 | } 20 | 21 | // Decode разбирает байты в структуру подзаписи 22 | func (e *SrLiquidLevelSensor) Decode(content []byte) error { 23 | var ( 24 | err error 25 | flags byte 26 | sensNum uint64 27 | ) 28 | buf := bytes.NewReader(content) 29 | 30 | if flags, err = buf.ReadByte(); err != nil { 31 | return fmt.Errorf("Не удалось получить байт флагов liquid_level: %v", err) 32 | } 33 | flagBits := fmt.Sprintf("%08b", flags) 34 | 35 | e.LiquidLevelSensorErrorFlag = flagBits[1:2] 36 | e.LiquidLevelSensorValueUnit = flagBits[2:4] 37 | e.RawDataFlag = flagBits[4:5] 38 | 39 | if sensNum, err = strconv.ParseUint(flagBits[5:], 2, 8); err != nil { 40 | return fmt.Errorf("Не удалось получить номер датчика ДУЖ: %v", err) 41 | } 42 | e.LiquidLevelSensorNumber = uint8(sensNum) 43 | 44 | bytesTmpBuf := make([]byte, 2) 45 | if _, err = buf.Read(bytesTmpBuf); err != nil { 46 | return fmt.Errorf("Не удалось получить адрес модуля ДУЖ: %v", err) 47 | } 48 | e.ModuleAddress = binary.LittleEndian.Uint16(bytesTmpBuf) 49 | 50 | bytesTmpBuf = make([]byte, 4) 51 | if _, err = buf.Read(bytesTmpBuf); err != nil { 52 | return fmt.Errorf("Не удалось получить показания ДУЖ: %v", err) 53 | } 54 | e.LiquidLevelSensorData = binary.LittleEndian.Uint32(bytesTmpBuf) 55 | 56 | return err 57 | } 58 | 59 | // Encode преобразовывает подзапись в набор байт 60 | func (e *SrLiquidLevelSensor) Encode() ([]byte, error) { 61 | var ( 62 | err error 63 | flags uint64 64 | result []byte 65 | ) 66 | buf := new(bytes.Buffer) 67 | 68 | flagsBits := "0" + e.LiquidLevelSensorErrorFlag + e.LiquidLevelSensorValueUnit + 69 | e.RawDataFlag + fmt.Sprintf("%03b", e.LiquidLevelSensorNumber) 70 | if flags, err = strconv.ParseUint(flagsBits, 2, 8); err != nil { 71 | return result, fmt.Errorf("Не удалось сгенерировать байт флагов ext_pos_data: %v", err) 72 | } 73 | 74 | if err = buf.WriteByte(uint8(flags)); err != nil { 75 | return result, fmt.Errorf("Не удалось записать байт флагов ext_pos_data: %v", err) 76 | } 77 | 78 | if err = binary.Write(buf, binary.LittleEndian, e.ModuleAddress); err != nil { 79 | return result, fmt.Errorf("Не удалось записать адрес модуля ДУЖ: %v", err) 80 | } 81 | 82 | if err = binary.Write(buf, binary.LittleEndian, e.LiquidLevelSensorData); err != nil { 83 | return result, fmt.Errorf("Не удалось записать показания ДУЖ: %v", err) 84 | } 85 | 86 | result = buf.Bytes() 87 | 88 | return result, err 89 | } 90 | 91 | //Length получает длинну закодированной подзаписи 92 | func (e *SrLiquidLevelSensor) Length() uint16 { 93 | var result uint16 94 | 95 | if recBytes, err := e.Encode(); err != nil { 96 | result = uint16(0) 97 | } else { 98 | result = uint16(len(recBytes)) 99 | } 100 | 101 | return result 102 | } 103 | -------------------------------------------------------------------------------- /cli/receiver/integration_test/integration_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/kuznetsovin/egts-protocol/cli/receiver/config" 10 | "github.com/kuznetsovin/egts-protocol/cli/receiver/server" 11 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestIntegration(t *testing.T) { 16 | 17 | test_conf := os.Getenv("TEST_CONFIG") 18 | assert.NotEmpty(t, test_conf) 19 | 20 | conf, err := config.New(test_conf) 21 | if !assert.NoError(t, err) { 22 | return 23 | } 24 | 25 | pg_conf, ok := conf.Store["postgresql"] 26 | if !assert.True(t, ok) { 27 | return 28 | } 29 | 30 | pg_store, err := initTestPostgresql(pg_conf) 31 | if !assert.NoError(t, err) { 32 | return 33 | } 34 | 35 | redis_conf, ok := conf.Store["redis"] 36 | if !assert.True(t, ok) { 37 | return 38 | } 39 | 40 | redis_store := initTestRedis(redis_conf) 41 | 42 | mysql_conf, ok := conf.Store["mysql"] 43 | if !assert.True(t, ok) { 44 | return 45 | } 46 | 47 | mysql_store, err := initTestMysql(mysql_conf) 48 | if !assert.NoError(t, err) { 49 | return 50 | } 51 | 52 | storages := storage.NewRepository() 53 | err = storages.LoadStorages(conf.Store) 54 | if !assert.NoError(t, err) { 55 | return 56 | } 57 | 58 | go func() { 59 | srv := server.New(conf.GetListenAddress(), conf.GetEmptyConnTTL(), storages) 60 | srv.Run() 61 | }() 62 | time.Sleep(time.Second) 63 | 64 | message := []byte{0x01, 0x00, 0x00, 0x0B, 0x00, 0xB1, 0x00, 0xE8, 0x04, 0x01, 0x4E, 0xA6, 0x00, 0xA1, 0x0A, 0x81, 0x34, 0xF6, 0xE9, 0x01, 65 | 0x02, 0x02, 0x10, 0x1A, 0x00, 0x4F, 0x5F, 0xE5, 0x10, 0x00, 0xBE, 0xCD, 0x9E, 0x80, 0x7F, 0x8B, 0x35, 0x93, 0x9B, 0x80, 0x2F, 0xF9, 0x80, 66 | 0x02, 0x01, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x11, 0x06, 0x00, 0x0E, 0x46, 0x00, 0x00, 0x00, 0x0C, 0x12, 0x1C, 0x00, 0x01, 0x0F, 0xFF, 67 | 0x01, 0x44, 0x6D, 0x00, 0xB8, 0x00, 0x00, 0x0B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x14, 0x05, 0x00, 0x02, 0xFF, 0x00, 0x29, 0x04, 0x1B, 0x07, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x07, 0x00, 69 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x07, 0x00, 0x03, 0x01, 0x00, 0x5A, 0x08, 0x00, 0x00, 0x1B, 0x07, 0x00, 0x04, 0x02, 0x00, 70 | 0x00, 0x00, 0x00, 0x00, 0x19, 0x04, 0x00, 0x64, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x65, 0x00, 0x00, 0x00, 0x19, 0x04, 0x00, 0x66, 0x01, 71 | 0x00, 0x00, 0x19, 0x04, 0x00, 0x67, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x68, 0x77, 0x2A, 0x04, 0x19, 0x04, 0x00, 0x69, 0x4F, 0x9A, 0x22, 72 | 0x19, 0x04, 0x00, 0x6E, 0x77, 0x2A, 0x04, 0x41, 0xF6} 73 | response := []byte{0x1, 0x0, 0x0, 0xb, 0x0, 0x10, 0x0, 0xe9, 0x4, 0x0, 0xa1, 0xe8, 0x4, 0x0, 0x6, 0x0, 0x1, 0x0, 0x20, 0x2, 0x2, 0x0, 0x3, 74 | 0x0, 0xa1, 0xa, 0x0, 0x5e, 0xb6} 75 | 76 | conn, err := net.Dial("tcp", conf.GetListenAddress()) 77 | if assert.NoError(t, err) { 78 | _ = conn.SetWriteDeadline(time.Now().Add(2 * time.Second)) 79 | _, _ = conn.Write(message) 80 | 81 | buf := make([]byte, 29) 82 | _, _ = conn.Read(buf) 83 | 84 | assert.Equal(t, response, buf) 85 | 86 | } 87 | defer conn.Close() 88 | 89 | if res, err := pg_store.pointCount(); assert.NoError(t, err) { 90 | assert.Equal(t, 1, res) 91 | } 92 | 93 | if err = redis_store.receivedPoint(); !assert.NoError(t, err) { 94 | return 95 | } 96 | 97 | if res, err := mysql_store.pointCount(); assert.NoError(t, err) { 98 | assert.Equal(t, 1, res) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_passengers_counters_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | var ( 11 | srPassengersCountersBytes = []byte{0x00, 0x15, 0x14, 0x92, 0x10, 0x00, 0x00, 0x07, 0x03, 0x0A, 0x0F} 12 | testEgtsSrPassengersCounters = SrPassengersCountersData{ 13 | RawDataFlag: "0", 14 | DoorsPresented: "00010101", 15 | DoorsReleased: "00010100", 16 | ModuleAddress: 4242, 17 | PassengersCountersData: []PassengersCounter{ 18 | { 19 | DoorNo: 1, 20 | In: 0, 21 | Out: 0, 22 | }, 23 | { 24 | DoorNo: 3, 25 | In: 7, 26 | Out: 3, 27 | }, 28 | { 29 | DoorNo: 5, 30 | In: 10, 31 | Out: 15, 32 | }, 33 | }, 34 | PassengersCountersRawData: nil, 35 | } 36 | 37 | srPassengersCountersRawBytes = []byte{0x01, 0x15, 0x14, 0x92, 0x10, 0x00, 0x00, 0x07, 0x03, 0x0A, 0x0F} 38 | testEgtsSrPassengersCountersRaw = SrPassengersCountersData{ 39 | RawDataFlag: "1", 40 | DoorsPresented: "00010101", 41 | DoorsReleased: "00010100", 42 | ModuleAddress: 4242, 43 | PassengersCountersData: nil, 44 | PassengersCountersRawData: []byte{0x00, 0x00, 0x07, 0x03, 0x0A, 0x0F}, 45 | } 46 | ) 47 | 48 | func TestEgtsSrPassengersCounters_Encode(t *testing.T) { 49 | passengersCountersDataBytes, err := testEgtsSrPassengersCounters.Encode() 50 | if err != nil { 51 | t.Errorf("Ошибка кодирования: %v\n", err) 52 | } 53 | 54 | if !bytes.Equal(passengersCountersDataBytes, srPassengersCountersBytes) { 55 | t.Errorf("Байтовые строки не совпадают: %v != %v ", passengersCountersDataBytes, srPassengersCountersBytes) 56 | } 57 | } 58 | 59 | func TestEgtsSrPassengersCounters_EncodeRawCounters(t *testing.T) { 60 | passengersCountersDataBytes, err := testEgtsSrPassengersCountersRaw.Encode() 61 | if err != nil { 62 | t.Errorf("Ошибка кодирования: %v\n", err) 63 | } 64 | 65 | if !bytes.Equal(passengersCountersDataBytes, srPassengersCountersRawBytes) { 66 | t.Errorf("Байтовые строки не совпадают: %v != %v ", passengersCountersDataBytes, srPassengersCountersRawBytes) 67 | } 68 | } 69 | 70 | func TestEgtsSrPassengersCounters_Decode(t *testing.T) { 71 | passengersCountersData := SrPassengersCountersData{} 72 | 73 | if err := passengersCountersData.Decode(srPassengersCountersBytes); err != nil { 74 | t.Errorf("Ошибка декодирования: %v\n", err) 75 | } 76 | 77 | if diff := cmp.Diff(passengersCountersData, testEgtsSrPassengersCounters); diff != "" { 78 | t.Errorf("Записи не совпадают: (-нужно +сейчас)\n%s", diff) 79 | } 80 | } 81 | 82 | func TestEgtsSrPassengersCounters_DecodeRawCounters(t *testing.T) { 83 | passengersCountersData := SrPassengersCountersData{} 84 | 85 | if err := passengersCountersData.Decode(srPassengersCountersRawBytes); err != nil { 86 | t.Errorf("Ошибка декодирования: %v\n", err) 87 | } 88 | 89 | if diff := cmp.Diff(passengersCountersData, testEgtsSrPassengersCountersRaw); diff != "" { 90 | t.Errorf("Записи не совпадают: (-нужно +сейчас)\n%s", diff) 91 | } 92 | } 93 | 94 | func TestEgtsSrPassengersCounters_Length(t *testing.T) { 95 | if diff := cmp.Diff(uint16(11), testEgtsSrPassengersCounters.Length()); diff != "" { 96 | t.Errorf("Записи не совпадают: (-нужно +сейчас)\n%s", diff) 97 | } 98 | } 99 | 100 | func TestEgtsSrPassengersCounters_LengthRawCounters(t *testing.T) { 101 | if diff := cmp.Diff(uint16(11), testEgtsSrPassengersCountersRaw.Length()); diff != "" { 102 | t.Errorf("Записи не совпадают: (-нужно +сейчас)\n%s", diff) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_state_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strconv" 7 | ) 8 | 9 | //SrStateData структура подзаписи типа EGTS_SR_STATE_DATA, которая используется для передачи на 10 | //аппаратно-программный комплекс информации о состоянии абонентского терминала (текущий режим работы, 11 | //напряжение основного и резервного источников питания и т.д.) 12 | type SrStateData struct { 13 | State uint8 `json:"ST"` 14 | MainPowerSourceVoltage uint8 `json:"MPSV"` 15 | BackUpBatteryVoltage uint8 `json:"BBV"` 16 | InternalBatteryVoltage uint8 `json:"IBV"` 17 | NMS string `json:"NMS"` 18 | IBU string `json:"IBU"` 19 | BBU string `json:"BBU"` 20 | } 21 | 22 | //Decode разбирает байты в структуру подзаписи 23 | func (e *SrStateData) Decode(content []byte) error { 24 | var ( 25 | err error 26 | flags byte 27 | ) 28 | 29 | buf := bytes.NewReader(content) 30 | if e.State, err = buf.ReadByte(); err != nil { 31 | return fmt.Errorf("Не удалось получить текущий режим работы: %v", err) 32 | } 33 | 34 | if e.MainPowerSourceVoltage, err = buf.ReadByte(); err != nil { 35 | return fmt.Errorf("Не удалось получить значение напряжения основного источника питания: %v", err) 36 | } 37 | 38 | if e.BackUpBatteryVoltage, err = buf.ReadByte(); err != nil { 39 | return fmt.Errorf("Не удалось получить значение напряжения резервной батареи: %v", err) 40 | } 41 | 42 | if e.InternalBatteryVoltage, err = buf.ReadByte(); err != nil { 43 | return fmt.Errorf("Не удалось получить значение напряжения внутренней батареи: %v", err) 44 | } 45 | 46 | if flags, err = buf.ReadByte(); err != nil { 47 | return fmt.Errorf("Не удалось получить байт флагов state_data: %v", err) 48 | } 49 | flagBits := fmt.Sprintf("%08b", flags) 50 | e.NMS = flagBits[5:6] 51 | e.IBU = flagBits[6:7] 52 | e.BBU = flagBits[7:] 53 | 54 | return err 55 | } 56 | 57 | //Encode преобразовывает подзапись в набор байт 58 | func (e *SrStateData) Encode() ([]byte, error) { 59 | var ( 60 | err error 61 | flags uint64 62 | result []byte 63 | ) 64 | buf := new(bytes.Buffer) 65 | 66 | if err = buf.WriteByte(e.State); err != nil { 67 | return result, fmt.Errorf("Не удалось записать текущий режим работы: %v", err) 68 | } 69 | 70 | if err = buf.WriteByte(e.MainPowerSourceVoltage); err != nil { 71 | return result, fmt.Errorf("Не удалось записать значение напряжения основного источника питания: %v", err) 72 | } 73 | 74 | if err = buf.WriteByte(e.BackUpBatteryVoltage); err != nil { 75 | return result, fmt.Errorf("Не удалось записать значение напряжения резервной батареи: %v", err) 76 | } 77 | 78 | if err = buf.WriteByte(e.InternalBatteryVoltage); err != nil { 79 | return result, fmt.Errorf("Не удалось записать значение напряжения внутренней батареи: %v", err) 80 | } 81 | 82 | if flags, err = strconv.ParseUint("00000"+e.NMS+e.IBU+e.BBU, 2, 8); err != nil { 83 | return result, fmt.Errorf("Не удалось сгенерировать байт флагов state_data: %v", err) 84 | } 85 | 86 | if err = buf.WriteByte(uint8(flags)); err != nil { 87 | return result, fmt.Errorf("Не удалось записать байт флагов state_data: %v", err) 88 | } 89 | 90 | result = buf.Bytes() 91 | 92 | return result, err 93 | } 94 | 95 | //Length получает длинну закодированной подзаписи 96 | func (e *SrStateData) Length() uint16 { 97 | var result uint16 98 | 99 | if recBytes, err := e.Encode(); err != nil { 100 | result = uint16(0) 101 | } else { 102 | result = uint16(len(recBytes)) 103 | } 104 | 105 | return result 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EGTS receiver 2 | 3 | EGTS receiver server realization writen on Go. 4 | 5 | Library for implementation EGTS protocol that parsing binary packag based on 6 | [GOST R 54619 - 2011](./docs`/gost54619-2011.pdf) and 7 | [Order No. 285 of the Ministry of Transport of Russia dated July 31, 2012](./docs/mitransNo285.pdf). 8 | Describe fields you can find in these documents. 9 | 10 | More information you can read in [article](https://www.swe-notes.ru/post/protocol-egts/) (Russian). 11 | 12 | Server save all navigation data from ```EGTS_SR_POS_DATA``` section. If packet have several records with 13 | ```EGTS_SR_POS_DATA``` section, it saves all of them. 14 | 15 | Storage for data realized as plugins. Any plugin must have ```[store]``` section in configure file. 16 | Plugin interface will be described below. 17 | 18 | If configure file has't section for a plugin (```[store]```), then packet will be print to stdout. 19 | 20 | ## Install 21 | 22 | ```bash 23 | git clone https://github.com/kuznetsovin/egts-protocol 24 | cd egts-protocol 25 | make 26 | ``` 27 | 28 | ## Run 29 | 30 | ```bash 31 | ./bin/receiver -c config.yaml 32 | ``` 33 | 34 | ```config.yaml``` - configure file 35 | 36 | ## Docker 37 | 38 | Build image 39 | 40 | ```bash 41 | make docker 42 | ``` 43 | 44 | Start container: 45 | 46 | ```bash 47 | docker run --name egts-receiver egts:latest 48 | ``` 49 | 50 | Start container with custom port and config: 51 | ```bash 52 | docker run --name egts-receiver -v ./configs:/etc/egts-receiver -p 6000:6000 egts:latest 53 | ``` 54 | 55 | Example docker-compose: 56 | 57 | ```yaml 58 | version: '3' 59 | 60 | services: 61 | redis: 62 | image: redis:latest 63 | container_name: egts_redis 64 | 65 | egts: 66 | image: egts:latest 67 | container_name: egts_receiver 68 | ports: 69 | - "6000:6000" 70 | 71 | volumes: 72 | - ./configs:/etc/egts-receviver/ 73 | ``` 74 | 75 | ## Config format 76 | 77 | ```yaml 78 | host: "127.0.0.1" 79 | port: "6000" 80 | con_live_sec: 10 81 | log_level: "DEBUG" 82 | 83 | storage: 84 | ``` 85 | 86 | Parameters description: 87 | 88 | - *host* - bind address 89 | - *port* - bind port 90 | - *conn_ttl* - if server not received data longer time in the parameter, then the connection is closed. 91 | - *log_level* - logging level 92 | - *storage* - section with storage configs. (see [example](./configs/receiver.yaml)) 93 | 94 | ## Usage only Golang EGTS library 95 | 96 | Example for encoding packet: 97 | 98 | ```go 99 | package main 100 | 101 | import ( 102 | "github.com/kuznetsovin/egts-protocol/libs/egts" 103 | "log" 104 | ) 105 | 106 | func main() { 107 | pkg := egts.Package{ 108 | ProtocolVersion: 1, 109 | SecurityKeyID: 0, 110 | Prefix: "00", 111 | Route: "0", 112 | EncryptionAlg: "00", 113 | Compression: "0", 114 | Priority: "11", 115 | HeaderLength: 11, 116 | HeaderEncoding: 0, 117 | FrameDataLength: 3, 118 | PacketIdentifier: 137, 119 | PacketType: egts.PtResponsePacket, 120 | HeaderCheckSum: 74, 121 | ServicesFrameData: &egts.PtResponse{ 122 | ResponsePacketID: 14357, 123 | ProcessingResult: 0, 124 | }, 125 | } 126 | 127 | rawPkg, err := pkg.Encode() 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | 132 | log.Println("Bytes packet: ", rawPkg) 133 | } 134 | 135 | ``` 136 | 137 | Example for decoding packet: 138 | 139 | ```go 140 | package main 141 | 142 | import ( 143 | "github.com/kuznetsovin/egts-protocol/libs/egts" 144 | "log" 145 | ) 146 | 147 | func main() { 148 | pkg := []byte{0x01, 0x00, 0x03, 0x0B, 0x00, 0x03, 0x00, 0x89, 0x00, 0x00, 0x4A, 0x15, 0x38, 0x00, 0x33, 0xE8} 149 | result := egts.Package{} 150 | 151 | state, err := result.Decode(pkg) 152 | if err != nil { 153 | log.Fatal(err) 154 | } 155 | 156 | log.Println("State: ", state) 157 | log.Println("Package: ", result) 158 | } 159 | ``` 160 | 161 | # Store plugins 162 | 163 | That create a new plugin you must implement ```Connector``` interface: 164 | 165 | ```go 166 | type Connector interface { 167 | // setup store connection 168 | Init(map[string]string) error 169 | 170 | // save to store method 171 | Save(interface{ ToBytes() ([]byte, error) }) error 172 | 173 | // close connection with store 174 | Close() error 175 | } 176 | ``` 177 | 178 | All plugins available in [store folder](/cli/receiver/storage/store). 179 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_module_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | const ( 10 | Delimiter = uint8(0) 11 | textSectionSize = 10 12 | ) 13 | 14 | // SrModuleData структура подзапись EGTS_AUTH_SERVICE типа EGTS_SR_MODULE_DATA 15 | type SrModuleData struct { 16 | ModuleType int8 `json:"MT"` 17 | VendorID uint32 `json:"VID"` 18 | FirmwareVersion uint16 `json:"FWV"` 19 | SoftwareVersion uint16 `json:"SWV"` 20 | Modification byte `json:"MD"` 21 | State byte `json:"ST"` 22 | SerialNumber string `json:"SRN"` 23 | _ byte `json:"-"` 24 | Description string `json:"DSCR"` 25 | _ byte `json:"-"` 26 | } 27 | 28 | // Decode разбирает байты в структуру подзаписи 29 | //nolint:funlen 30 | func (e *SrModuleData) Decode(content []byte) error { 31 | var err error 32 | buf := bytes.NewReader(content) 33 | 34 | moduleType, err := buf.ReadByte() 35 | if err != nil { 36 | return fmt.Errorf("не удалось получить тип модуля") 37 | } 38 | e.ModuleType = int8(moduleType) 39 | 40 | tmpBuf := make([]byte, 4) 41 | if _, err = buf.Read(tmpBuf); err != nil { 42 | return fmt.Errorf("не удалось получить ID вендора") 43 | } 44 | e.VendorID = binary.LittleEndian.Uint32(tmpBuf) 45 | 46 | tmpBuf = make([]byte, 2) 47 | if _, err = buf.Read(tmpBuf); err != nil { 48 | return fmt.Errorf("не удалось получить версию прошивки") 49 | } 50 | e.FirmwareVersion = binary.LittleEndian.Uint16(tmpBuf) 51 | 52 | tmpBuf = make([]byte, 2) 53 | if _, err = buf.Read(tmpBuf); err != nil { 54 | return fmt.Errorf("не удалось получить версию ПО") 55 | } 56 | e.SoftwareVersion = binary.LittleEndian.Uint16(tmpBuf) 57 | 58 | e.Modification, err = buf.ReadByte() 59 | if err != nil { 60 | return fmt.Errorf("не удалось получить модификацию") 61 | } 62 | 63 | e.State, err = buf.ReadByte() 64 | if err != nil { 65 | return fmt.Errorf("не удалось получить состояние") 66 | } 67 | 68 | serialNumber := make([]byte, 0, textSectionSize) 69 | for { 70 | b, err := buf.ReadByte() 71 | if err != nil { 72 | return fmt.Errorf("не удалось получить серийный номер") 73 | } 74 | if b == Delimiter { 75 | break 76 | } 77 | serialNumber = append(serialNumber, b) 78 | } 79 | e.SerialNumber = string(serialNumber) 80 | 81 | description := make([]byte, 0, textSectionSize) 82 | for { 83 | b, err := buf.ReadByte() 84 | if err != nil { 85 | return fmt.Errorf("не удалось получить описание") 86 | } 87 | if b == Delimiter { 88 | break 89 | } 90 | description = append(description, b) 91 | } 92 | e.Description = string(description) 93 | 94 | return err 95 | } 96 | 97 | // Encode преобразовывает подзапись в набор байт 98 | func (e *SrModuleData) Encode() ([]byte, error) { 99 | var ( 100 | result []byte 101 | err error 102 | ) 103 | buf := new(bytes.Buffer) 104 | 105 | if err = binary.Write(buf, binary.LittleEndian, e.ModuleType); err != nil { 106 | return result, fmt.Errorf("не удалось записать тип модуля") 107 | } 108 | if err = binary.Write(buf, binary.LittleEndian, e.VendorID); err != nil { 109 | return result, fmt.Errorf("не удалось записать ID вендора") 110 | } 111 | if err = binary.Write(buf, binary.LittleEndian, e.FirmwareVersion); err != nil { 112 | return result, fmt.Errorf("не удалось записать версию прошивки") 113 | } 114 | if err = binary.Write(buf, binary.LittleEndian, e.SoftwareVersion); err != nil { 115 | return result, fmt.Errorf("не удалось записать версию ПО") 116 | } 117 | if err = binary.Write(buf, binary.LittleEndian, e.Modification); err != nil { 118 | return result, fmt.Errorf("не удалось записать модификацию") 119 | } 120 | if err = binary.Write(buf, binary.LittleEndian, e.State); err != nil { 121 | return result, fmt.Errorf("не удалось записать состояние") 122 | } 123 | if err = binary.Write(buf, binary.LittleEndian, []byte(e.SerialNumber)); err != nil { 124 | return result, fmt.Errorf("не удалось записать серийный номер") 125 | } 126 | if err = binary.Write(buf, binary.LittleEndian, Delimiter); err != nil { 127 | return result, fmt.Errorf("не удалось записать раделитель") 128 | } 129 | if err = binary.Write(buf, binary.LittleEndian, []byte(e.Description)); err != nil { 130 | return result, fmt.Errorf("не удалось записать описание") 131 | } 132 | if err = binary.Write(buf, binary.LittleEndian, Delimiter); err != nil { 133 | return result, fmt.Errorf("не удалось записать разделитель") 134 | } 135 | 136 | return buf.Bytes(), err 137 | } 138 | 139 | // Length получает длину закодированной подзаписи 140 | func (e *SrModuleData) Length() uint16 { 141 | var result uint16 142 | 143 | if recBytes, err := e.Encode(); err == nil { 144 | result = uint16(len(recBytes)) 145 | } 146 | 147 | return result 148 | } 149 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_ext_pos_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | //SrExtPosData структура подзаписи типа EGTS_SR_EXT_POS_DATA, которая используется абонентским 11 | //терминалом при передаче дополнительных данных определения местоположения 12 | type SrExtPosData struct { 13 | NavigationSystemFieldExists string `json:"NSFE"` 14 | SatellitesFieldExists string `json:"SFE"` 15 | PdopFieldExists string `json:"PFE"` 16 | HdopFieldExists string `json:"HFE"` 17 | VdopFieldExists string `json:"VFE"` 18 | VerticalDilutionOfPrecision uint16 `json:"VDOP"` 19 | HorizontalDilutionOfPrecision uint16 `json:"HDOP"` 20 | PositionDilutionOfPrecision uint16 `json:"PDOP"` 21 | Satellites uint8 `json:"SAT"` 22 | NavigationSystem uint16 `json:"NS"` 23 | } 24 | 25 | //Decode разбирает байты в структуру подзаписи 26 | func (e *SrExtPosData) Decode(content []byte) error { 27 | var ( 28 | err error 29 | flags byte 30 | ) 31 | tmpBuf := make([]byte, 2) 32 | buf := bytes.NewReader(content) 33 | 34 | //байт флагов 35 | if flags, err = buf.ReadByte(); err != nil { 36 | return fmt.Errorf("Не удалось получить байт флагов ext_pos_data: %v", err) 37 | } 38 | flagBits := fmt.Sprintf("%08b", flags) 39 | 40 | e.NavigationSystemFieldExists = flagBits[3:4] 41 | e.SatellitesFieldExists = flagBits[4:5] 42 | e.PdopFieldExists = flagBits[5:6] 43 | e.HdopFieldExists = flagBits[6:7] 44 | e.VdopFieldExists = flagBits[7:] 45 | 46 | if e.VdopFieldExists == "1" { 47 | if _, err = buf.Read(tmpBuf); err != nil { 48 | return fmt.Errorf("Не удалось получить снижение точности в вертикальной плоскости: %v", err) 49 | } 50 | e.VerticalDilutionOfPrecision = binary.LittleEndian.Uint16(tmpBuf) 51 | } 52 | 53 | if e.HdopFieldExists == "1" { 54 | if _, err = buf.Read(tmpBuf); err != nil { 55 | return fmt.Errorf("Не удалось получить снижение точности в горизонтальной плоскости: %v", err) 56 | } 57 | e.HorizontalDilutionOfPrecision = binary.LittleEndian.Uint16(tmpBuf) 58 | } 59 | 60 | if e.PdopFieldExists == "1" { 61 | if _, err = buf.Read(tmpBuf); err != nil { 62 | return fmt.Errorf("Не удалось получить снижение точности по местоположению: %v", err) 63 | } 64 | e.PositionDilutionOfPrecision = binary.LittleEndian.Uint16(tmpBuf) 65 | } 66 | 67 | if e.SatellitesFieldExists == "1" { 68 | if e.Satellites, err = buf.ReadByte(); err != nil { 69 | return fmt.Errorf("Не удалось получить количество видимых спутников: %v", err) 70 | } 71 | } 72 | 73 | if e.NavigationSystemFieldExists == "1" { 74 | if _, err = buf.Read(tmpBuf); err != nil { 75 | return fmt.Errorf("Не удалось получить битовые флаги спутниковых систем: %v", err) 76 | } 77 | e.NavigationSystem = binary.LittleEndian.Uint16(tmpBuf) 78 | } 79 | 80 | return err 81 | } 82 | 83 | //Encode преобразовывает подзапись в набор байт 84 | func (e *SrExtPosData) Encode() ([]byte, error) { 85 | var ( 86 | err error 87 | flags uint64 88 | result []byte 89 | ) 90 | 91 | buf := new(bytes.Buffer) 92 | 93 | //байт флагов 94 | flagsBits := "000" + e.NavigationSystemFieldExists + e.SatellitesFieldExists + 95 | e.PdopFieldExists + e.HdopFieldExists + e.VdopFieldExists 96 | if flags, err = strconv.ParseUint(flagsBits, 2, 8); err != nil { 97 | return result, fmt.Errorf("Не удалось сгенерировать байт флагов ext_pos_data: %v", err) 98 | } 99 | 100 | if err = buf.WriteByte(uint8(flags)); err != nil { 101 | return result, fmt.Errorf("Не удалось записать байт флагов ext_pos_data: %v", err) 102 | } 103 | 104 | if e.VdopFieldExists == "1" { 105 | if err = binary.Write(buf, binary.LittleEndian, e.VerticalDilutionOfPrecision); err != nil { 106 | return result, fmt.Errorf("Не удалось записать снижение точности в вертикальной плоскости: %v", err) 107 | } 108 | } 109 | 110 | if e.HdopFieldExists == "1" { 111 | if err = binary.Write(buf, binary.LittleEndian, e.HorizontalDilutionOfPrecision); err != nil { 112 | return result, fmt.Errorf("Не удалось записать снижение точности в горизонтальной плоскости: %v", err) 113 | } 114 | } 115 | 116 | if e.PdopFieldExists == "1" { 117 | if err = binary.Write(buf, binary.LittleEndian, e.PositionDilutionOfPrecision); err != nil { 118 | return result, fmt.Errorf("Не удалось записать снижение точности по местоположению: %v", err) 119 | } 120 | } 121 | 122 | if e.SatellitesFieldExists == "1" { 123 | if err = buf.WriteByte(e.Satellites); err != nil { 124 | return result, fmt.Errorf("Не удалось записать количество видимых спутников: %v", err) 125 | } 126 | } 127 | 128 | if e.NavigationSystemFieldExists == "1" { 129 | if err = binary.Write(buf, binary.LittleEndian, e.NavigationSystem); err != nil { 130 | return result, fmt.Errorf("Не удалось записать битовые флаги спутниковых систем: %v", err) 131 | } 132 | } 133 | 134 | result = buf.Bytes() 135 | return result, err 136 | } 137 | 138 | //Length получает длинну закодированной подзаписи 139 | func (e *SrExtPosData) Length() uint16 { 140 | var result uint16 141 | 142 | if recBytes, err := e.Encode(); err != nil { 143 | result = uint16(0) 144 | } else { 145 | result = uint16(len(recBytes)) 146 | } 147 | 148 | return result 149 | } 150 | -------------------------------------------------------------------------------- /libs/egts/record_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // RecordData структура секции подзапси у записи ServiceDataRecord 12 | type RecordData struct { 13 | SubrecordType byte `json:"SRT"` 14 | SubrecordLength uint16 `json:"SRL"` 15 | SubrecordData BinaryData `json:"SRD"` 16 | } 17 | 18 | // RecordDataSet описывает массив с подзаписями протокола ЕГТС 19 | type RecordDataSet []RecordData 20 | 21 | // Decode разбирает байты в структуру подзаписи 22 | func (rds *RecordDataSet) Decode(recDS []byte) error { 23 | var ( 24 | err error 25 | ) 26 | buf := bytes.NewBuffer(recDS) 27 | for buf.Len() > 0 { 28 | rd := RecordData{} 29 | if rd.SubrecordType, err = buf.ReadByte(); err != nil { 30 | return fmt.Errorf("Не удалось получить тип записи subrecord data: %v", err) 31 | } 32 | 33 | tmpIntBuf := make([]byte, 2) 34 | if _, err = buf.Read(tmpIntBuf); err != nil { 35 | return fmt.Errorf("Не удалось получить длину записи subrecord data: %v", err) 36 | } 37 | rd.SubrecordLength = binary.LittleEndian.Uint16(tmpIntBuf) 38 | 39 | subRecordBytes := buf.Next(int(rd.SubrecordLength)) 40 | 41 | switch rd.SubrecordType { 42 | case SrPosDataType: 43 | rd.SubrecordData = &SrPosData{} 44 | case SrTermIdentityType: 45 | rd.SubrecordData = &SrTermIdentity{} 46 | case SrModuleDataType: 47 | rd.SubrecordData = &SrModuleData{} 48 | case SrRecordResponseType: 49 | rd.SubrecordData = &SrResponse{} 50 | case SrResultCodeType: 51 | rd.SubrecordData = &SrResultCode{} 52 | case SrExtPosDataType: 53 | rd.SubrecordData = &SrExtPosData{} 54 | case SrAdSensorsDataType: 55 | rd.SubrecordData = &SrAdSensorsData{} 56 | case SrType20: 57 | // признак косвенный в спецификациях его нет 58 | if rd.SubrecordLength == uint16(5) { 59 | rd.SubrecordData = &SrStateData{} 60 | } else { 61 | // TODO: добавить секцию EGTS_SR_ACCEL_DATA 62 | return fmt.Errorf("Не реализованная секция EGTS_SR_ACCEL_DATA: %d. Длина: %d. Содержимое: %X", rd.SubrecordType, rd.SubrecordLength, subRecordBytes) 63 | } 64 | case SrStateDataType: 65 | rd.SubrecordData = &SrStateData{} 66 | case SrLiquidLevelSensorType: 67 | rd.SubrecordData = &SrLiquidLevelSensor{} 68 | case SrAbsCntrDataType: 69 | rd.SubrecordData = &SrAbsCntrData{} 70 | case SrAuthInfoType: 71 | rd.SubrecordData = &SrAuthInfo{} 72 | case SrCountersDataType: 73 | rd.SubrecordData = &SrCountersData{} 74 | case SrEgtsPlusDataType: 75 | rd.SubrecordData = &StorageRecord{} 76 | case SrAbsAnSensDataType: 77 | rd.SubrecordData = &SrAbsAnSensData{} 78 | case SrDispatcherIdentityType: 79 | rd.SubrecordData = &SrDispatcherIdentity{} 80 | case SrPassengersCountersType: 81 | rd.SubrecordData = &SrPassengersCountersData{} 82 | default: 83 | log.Infof("Не известный тип подзаписи: %d. Длина: %d. Содержимое: %X", rd.SubrecordType, rd.SubrecordLength, subRecordBytes) 84 | continue 85 | } 86 | 87 | if err = rd.SubrecordData.Decode(subRecordBytes); err != nil { 88 | return err 89 | } 90 | 91 | *rds = append(*rds, rd) 92 | } 93 | 94 | return err 95 | } 96 | 97 | // Encode преобразовывает подзапись в набор байт 98 | func (rds *RecordDataSet) Encode() ([]byte, error) { 99 | var ( 100 | result []byte 101 | err error 102 | ) 103 | buf := new(bytes.Buffer) 104 | 105 | for _, rd := range *rds { 106 | if rd.SubrecordType == 0 { 107 | switch rd.SubrecordData.(type) { 108 | case *SrPosData: 109 | rd.SubrecordType = SrPosDataType 110 | case *SrTermIdentity: 111 | rd.SubrecordType = SrTermIdentityType 112 | case *SrResponse: 113 | rd.SubrecordType = SrRecordResponseType 114 | case *SrResultCode: 115 | rd.SubrecordType = SrResultCodeType 116 | case *SrExtPosData: 117 | rd.SubrecordType = SrExtPosDataType 118 | case *SrAdSensorsData: 119 | rd.SubrecordType = SrAdSensorsDataType 120 | case *SrStateData: 121 | rd.SubrecordType = SrStateDataType 122 | case *SrLiquidLevelSensor: 123 | rd.SubrecordType = SrLiquidLevelSensorType 124 | case *SrAbsCntrData: 125 | rd.SubrecordType = SrAbsCntrDataType 126 | case *SrAuthInfo: 127 | rd.SubrecordType = SrAuthInfoType 128 | case *SrCountersData: 129 | rd.SubrecordType = SrCountersDataType 130 | case *StorageRecord: 131 | rd.SubrecordType = SrEgtsPlusDataType 132 | case *SrAbsAnSensData: 133 | rd.SubrecordType = SrAbsAnSensDataType 134 | default: 135 | return result, fmt.Errorf("не известен код для данного типа подзаписи") 136 | } 137 | } 138 | 139 | if err := binary.Write(buf, binary.LittleEndian, rd.SubrecordType); err != nil { 140 | return result, err 141 | } 142 | 143 | if rd.SubrecordLength == 0 { 144 | rd.SubrecordLength = rd.SubrecordData.Length() 145 | } 146 | if err := binary.Write(buf, binary.LittleEndian, rd.SubrecordLength); err != nil { 147 | return result, err 148 | } 149 | 150 | srd, err := rd.SubrecordData.Encode() 151 | if err != nil { 152 | return result, err 153 | } 154 | buf.Write(srd) 155 | } 156 | 157 | result = buf.Bytes() 158 | 159 | return result, err 160 | } 161 | 162 | // Length получает длину массива записей 163 | func (rds *RecordDataSet) Length() uint16 { 164 | var result uint16 165 | 166 | if recBytes, err := rds.Encode(); err != nil { 167 | result = uint16(0) 168 | } else { 169 | result = uint16(len(recBytes)) 170 | } 171 | 172 | return result 173 | } 174 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_passengers_counters.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | //SrPassengersCountersData структура подзаписи типа EGTS_SR_PASSENGERS_COUNTERS, 11 | //которая применяется абонентским терминалом для передачи на аппаратно-программный 12 | //комплекс данных о показаниях счетчиков пассажиропотока 13 | type SrPassengersCountersData struct { 14 | RawDataFlag string `json:"RawDataFlag"` 15 | DoorsPresented string `json:"DoorsPresented"` 16 | DoorsReleased string `json:"DoorsReleased"` 17 | ModuleAddress uint16 `json:"ModuleAddress"` 18 | PassengersCountersData []PassengersCounter `json:"PassengersCountersData"` 19 | PassengersCountersRawData []byte `json:"PassengersCountersRawData"` 20 | } 21 | 22 | //Decode разбирает байты в структуру подзаписи 23 | func (e *SrPassengersCountersData) Decode(content []byte) error { 24 | var ( 25 | err error 26 | byteBuf uint8 27 | ) 28 | maBuf := make([]byte, 2) 29 | buf := bytes.NewReader(content) 30 | 31 | if byteBuf, err = buf.ReadByte(); err != nil { 32 | return fmt.Errorf("Не удалось получить байт флагов EGTS_SR_PASSENGERS_COUNTERS: %v", err) 33 | } 34 | e.RawDataFlag = fmt.Sprintf("%08b", byteBuf)[7:] 35 | 36 | if byteBuf, err = buf.ReadByte(); err != nil { 37 | return fmt.Errorf("Не удалось получить наличие счетчиков на дверях: %v", err) 38 | } 39 | e.DoorsPresented = fmt.Sprintf("%08b", byteBuf) 40 | 41 | if byteBuf, err = buf.ReadByte(); err != nil { 42 | return fmt.Errorf("Не удалось получить двери, которые открывались и закрывались: %v", err) 43 | } 44 | e.DoorsReleased = fmt.Sprintf("%08b", byteBuf) 45 | 46 | if _, err = buf.Read(maBuf); err != nil { 47 | return fmt.Errorf("Не удалось получить адрес модуля: %v", err) 48 | } 49 | e.ModuleAddress = binary.LittleEndian.Uint16(maBuf) 50 | 51 | if e.RawDataFlag == "0" { 52 | var in, out uint8 53 | for i := 1; i < 9; i++ { 54 | if e.DoorsPresented[8-i:9-i] == "0" { 55 | continue 56 | } 57 | 58 | if in, err = buf.ReadByte(); err != nil { 59 | return fmt.Errorf("Не удалось получить количество вошедших пассажиров через дверь #%d: %v", i, err) 60 | } 61 | 62 | if out, err = buf.ReadByte(); err != nil { 63 | return fmt.Errorf("Не удалось получить количество вышедших пассажиров через дверь #%d: %v", i, err) 64 | } 65 | 66 | e.PassengersCountersData = append(e.PassengersCountersData, PassengersCounter{ 67 | DoorNo: uint8(i), 68 | In: in, 69 | Out: out, 70 | }) 71 | } 72 | } else { 73 | pcdRawBuf := make([]byte, buf.Len()) 74 | if _, err = buf.Read(pcdRawBuf); err != nil { 75 | return fmt.Errorf("Не удалось получить данные счетчиков пассажиропотока в необработанном виде: %v", err) 76 | } 77 | e.PassengersCountersRawData = pcdRawBuf 78 | } 79 | 80 | return err 81 | } 82 | 83 | //Encode преобразовывает подзапись в набор байт 84 | func (e *SrPassengersCountersData) Encode() ([]byte, error) { 85 | var ( 86 | err error 87 | flags uint64 88 | dpr uint64 89 | drl uint64 90 | result []byte 91 | ) 92 | maddrBuf := make([]byte, 2) 93 | buf := new(bytes.Buffer) 94 | 95 | if flags, err = strconv.ParseUint(e.RawDataFlag, 2, 8); err != nil { 96 | return result, fmt.Errorf("Не удалось сгенерировать байт флагов EGTS_SR_PASSENGERS_COUNTERS: %v", err) 97 | } 98 | if err = buf.WriteByte(uint8(flags)); err != nil { 99 | return result, fmt.Errorf("Не удалось записать байт флагов EGTS_SR_PASSENGERS_COUNTERS: %v", err) 100 | } 101 | 102 | if dpr, err = strconv.ParseUint(e.DoorsPresented, 2, 8); err != nil { 103 | return result, fmt.Errorf("Не удалось закодировать поле Doors Presented для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 104 | } 105 | if err = buf.WriteByte(uint8(dpr)); err != nil { 106 | return result, fmt.Errorf("Не удалось записать поле Doors Presented для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 107 | } 108 | 109 | if drl, err = strconv.ParseUint(e.DoorsReleased, 2, 8); err != nil { 110 | return result, fmt.Errorf("Не удалось закодировать поле Doors Released для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 111 | } 112 | if err = buf.WriteByte(uint8(drl)); err != nil { 113 | return result, fmt.Errorf("Не удалось записать поле Doors Released для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 114 | } 115 | 116 | binary.LittleEndian.PutUint16(maddrBuf, e.ModuleAddress) 117 | if _, err = buf.Write(maddrBuf); err != nil { 118 | return result, fmt.Errorf("Не удалось записать поле Module Address для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 119 | } 120 | 121 | if e.RawDataFlag == "0" { 122 | for _, counter := range e.PassengersCountersData { 123 | encodedCounter, err := counter.encode() 124 | if err != nil { 125 | return result, fmt.Errorf("Не удалось закодировать поле Passengers Counters Data для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 126 | } 127 | if _, err = buf.Write(encodedCounter); err != nil { 128 | return result, fmt.Errorf("Не удалось записать поле Passengers Counters Data для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 129 | } 130 | } 131 | } else { 132 | if _, err = buf.Write(e.PassengersCountersRawData); err != nil { 133 | return result, fmt.Errorf("Не удалось записать поле Passengers Counters Data (raw) для EGTS_SR_PASSENGERS_COUNTERS: %v", err) 134 | } 135 | } 136 | 137 | return buf.Bytes(), err 138 | } 139 | 140 | //Length получает длинну закодированной подзаписи 141 | func (e *SrPassengersCountersData) Length() uint16 { 142 | encoded, err := e.Encode() 143 | 144 | if err != nil { 145 | return 0 146 | } 147 | 148 | return uint16(len(encoded)) 149 | } 150 | 151 | type PassengersCounter struct { 152 | DoorNo uint8 `json:"DoorNo"` 153 | In uint8 `json:"In"` 154 | Out uint8 `json:"Out"` 155 | } 156 | 157 | //Encode преобразовывает структуру в набор байт 158 | func (c *PassengersCounter) encode() ([]byte, error) { 159 | if c.DoorNo == 0 { 160 | return []byte{}, fmt.Errorf("Попытка закодировать неинициализированную структуру PassengersCounter") 161 | } 162 | 163 | return []byte{c.In, c.Out}, nil 164 | } 165 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_term_identity.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | //SrTermIdentity структура подзаписи типа EGTS_SR_TERM_IDENTITY, которая используется АС при запросе 11 | //авторизации на телематическую платформу и содержит учетные данные АС. 12 | type SrTermIdentity struct { 13 | TerminalIdentifier uint32 `json:"TID"` 14 | MNE string `json:"MNE"` 15 | BSE string `json:"BSE"` 16 | NIDE string `json:"NIDE"` 17 | SSRA string `json:"SSRA"` 18 | LNGCE string `json:"LNGCE"` 19 | IMSIE string `json:"IMSIE"` 20 | IMEIE string `json:"IMEIE"` 21 | HDIDE string `json:"HDIDE"` 22 | HomeDispatcherIdentifier uint16 `json:"HDID"` 23 | IMEI string `json:"IMEI"` 24 | IMSI string `json:"IMSI"` 25 | LanguageCode string `json:"LNGC"` 26 | NetworkIdentifier []byte `json:"NID"` 27 | BufferSize uint16 `json:"BS"` 28 | MobileNumber string `json:"MSISDN"` 29 | } 30 | 31 | //Decode разбирает байты в структуру подзаписи 32 | func (e *SrTermIdentity) Decode(content []byte) error { 33 | var ( 34 | err error 35 | flags byte 36 | ) 37 | buf := bytes.NewReader(content) 38 | 39 | tmpBuf := make([]byte, 4) 40 | if _, err = buf.Read(tmpBuf); err != nil { 41 | return fmt.Errorf("Не удалось получить идентификатор терминал при авторизации") 42 | } 43 | e.TerminalIdentifier = binary.LittleEndian.Uint32(tmpBuf) 44 | 45 | if flags, err = buf.ReadByte(); err != nil { 46 | return fmt.Errorf("Не удалось считать байт флагов term identify: %v", err) 47 | } 48 | flagBits := fmt.Sprintf("%08b", flags) 49 | e.MNE = flagBits[:1] 50 | e.BSE = flagBits[1:2] 51 | e.NIDE = flagBits[2:3] 52 | e.SSRA = flagBits[3:4] 53 | e.LNGCE = flagBits[4:5] 54 | e.IMSIE = flagBits[5:6] 55 | e.IMEIE = flagBits[6:7] 56 | e.HDIDE = flagBits[7:] 57 | 58 | if e.HDIDE == "1" { 59 | tmpBuf = make([]byte, 2) 60 | if _, err = buf.Read(tmpBuf); err != nil { 61 | return fmt.Errorf("Не удалось получить идентификатор «домашней» телематической платформы при авторизации") 62 | } 63 | e.HomeDispatcherIdentifier = binary.LittleEndian.Uint16(tmpBuf) 64 | 65 | } 66 | 67 | if e.IMEIE == "1" { 68 | tmpBuf = make([]byte, 15) 69 | if _, err = buf.Read(tmpBuf); err != nil { 70 | return fmt.Errorf("Не удалось получить IMEI при авторизации") 71 | } 72 | e.IMEI = string(tmpBuf) 73 | } 74 | 75 | if e.IMSIE == "1" { 76 | tmpBuf = make([]byte, 16) 77 | if _, err = buf.Read(tmpBuf); err != nil { 78 | return fmt.Errorf("Не удалось получить IMSI при авторизации") 79 | } 80 | e.IMSI = string(tmpBuf) 81 | } 82 | 83 | if e.LNGCE == "1" { 84 | tmpBuf = make([]byte, 3) 85 | if _, err = buf.Read(tmpBuf); err != nil { 86 | return fmt.Errorf("Не удалось получить код языка при авторизации") 87 | } 88 | e.LanguageCode = string(tmpBuf) 89 | } 90 | 91 | if e.NIDE == "1" { 92 | e.NetworkIdentifier = make([]byte, 3) 93 | if _, err = buf.Read(e.NetworkIdentifier); err != nil { 94 | return fmt.Errorf("Не удалось получить код идентификатор сети оператора при авторизации") 95 | } 96 | } 97 | 98 | if e.BSE == "1" { 99 | tmpBuf = make([]byte, 2) 100 | if _, err = buf.Read(tmpBuf); err != nil { 101 | return fmt.Errorf("Не удалось получить максимальный размер буфера при авторизации") 102 | } 103 | e.BufferSize = binary.LittleEndian.Uint16(tmpBuf) 104 | } 105 | 106 | if e.MNE == "1" { 107 | tmpBuf = make([]byte, 15) 108 | if _, err = buf.Read(tmpBuf); err != nil { 109 | return fmt.Errorf("Не удалось получить телефонный номер мобильного абонента") 110 | } 111 | e.MobileNumber = string(tmpBuf) 112 | } 113 | 114 | return err 115 | } 116 | 117 | //Encode преобразовывает подзапись в набор байт 118 | func (e *SrTermIdentity) Encode() ([]byte, error) { 119 | var ( 120 | result []byte 121 | flags uint64 122 | err error 123 | ) 124 | buf := new(bytes.Buffer) 125 | 126 | if err = binary.Write(buf, binary.LittleEndian, e.TerminalIdentifier); err != nil { 127 | return result, fmt.Errorf("Не удалось записать идентификатор терминал при авторизации") 128 | } 129 | 130 | flags, err = strconv.ParseUint(e.MNE+e.BSE+e.NIDE+e.SSRA+e.LNGCE+e.IMSIE+e.IMEIE+e.HDIDE, 2, 8) 131 | if err = buf.WriteByte(uint8(flags)); err != nil { 132 | return result, fmt.Errorf("Не удалось записать байт флагов term identify: %v", err) 133 | } 134 | 135 | if e.HDIDE == "1" { 136 | if err = binary.Write(buf, binary.LittleEndian, e.HomeDispatcherIdentifier); err != nil { 137 | return result, fmt.Errorf("Не удалось записать идентификатор «домашней» телематической платформы при авторизации") 138 | } 139 | } 140 | 141 | if e.IMEIE == "1" { 142 | if _, err = buf.Write([]byte(e.IMEI)); err != nil { 143 | return result, fmt.Errorf("Не удалось записать IMEI при авторизации") 144 | } 145 | } 146 | 147 | if e.IMSIE == "1" { 148 | if _, err = buf.Write([]byte(e.IMSI)); err != nil { 149 | return result, fmt.Errorf("Не удалось записать IMSI при авторизации") 150 | } 151 | } 152 | 153 | if e.LNGCE == "1" { 154 | if _, err = buf.Write([]byte(e.LanguageCode)); err != nil { 155 | return result, fmt.Errorf("Не удалось записать IMSI при авторизации") 156 | } 157 | } 158 | 159 | if e.NIDE == "1" { 160 | if _, err = buf.Write(e.NetworkIdentifier); err != nil { 161 | return result, fmt.Errorf("Не удалось записать код идентификатор сети оператора при авторизации") 162 | } 163 | } 164 | 165 | if e.BSE == "1" { 166 | if err = binary.Write(buf, binary.LittleEndian, e.BufferSize); err != nil { 167 | return result, fmt.Errorf("Не удалось записать максимальный размер буфера при авторизации") 168 | } 169 | } 170 | 171 | if e.MNE == "1" { 172 | if _, err = buf.Write([]byte(e.MobileNumber)); err != nil { 173 | return result, fmt.Errorf("Не удалось записать телефонный номер мобильного абонента") 174 | } 175 | } 176 | 177 | result = buf.Bytes() 178 | return result, err 179 | } 180 | 181 | //Length получает длинну закодированной подзаписи 182 | func (e *SrTermIdentity) Length() uint16 { 183 | var result uint16 184 | 185 | if recBytes, err := e.Encode(); err != nil { 186 | result = uint16(0) 187 | } else { 188 | result = uint16(len(recBytes)) 189 | } 190 | 191 | return result 192 | } 193 | -------------------------------------------------------------------------------- /cli/packet-gen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/kuznetsovin/egts-protocol/libs/egts" 7 | "net" 8 | "os" 9 | "time" 10 | ) 11 | 12 | /* 13 | EGTS packet generator. 14 | 15 | Util create egts packet from setting parameters. 16 | 17 | Usage: 18 | -pid int 19 | Packet identifier (require) 20 | -oid int 21 | Client identifier (require) 22 | -time string 23 | Timestamp in RFC 3339 format (require) 24 | -lat float 25 | Latitude 26 | -liquid int 27 | Liquid level for first sensor 28 | -lon float 29 | Longitude 30 | -server string 31 | Egts server address in format : (default "localhost:5555") 32 | -timeout int 33 | Ack waiting time in seconds, Default: 5 34 | 35 | Example 36 | 37 | ``` 38 | ./packet-gen --pid 1 --oid 12 --time 2021-12-16T09:12:00Z --lat 45 --lon 60.344 --server localhost:5555 39 | ``` 40 | 41 | Created by Igor Kuznetsov 42 | */ 43 | 44 | func main() { 45 | 46 | pid := 0 47 | oid := 0 48 | ts := "" 49 | liqLvl := 0 50 | lat := 0.0 51 | lon := 0.0 52 | server := "" 53 | ackTimeout := 0 54 | 55 | flag.IntVar(&pid, "pid", 0, "Packet identifier (require)") 56 | flag.IntVar(&oid, "oid", 0, "Client identifier (require)") 57 | flag.StringVar(&ts, "time", "", "Timestamp in RFC 3339 format (require)") 58 | flag.IntVar(&liqLvl, "liquid", 0, "Liquid level for first sensor") 59 | flag.Float64Var(&lat, "lat", 0, "Latitude") 60 | flag.Float64Var(&lon, "lon", 0, "Longitude") 61 | flag.IntVar(&ackTimeout, "timeout", 0, "Ack waiting time in seconds, Default: 5") 62 | flag.StringVar(&server, "server", "localhost:5555", "Egts server address in format :") 63 | 64 | flag.Parse() 65 | 66 | if pid == 0 { 67 | fmt.Println("Packet identifier is require. See to help (-h)") 68 | os.Exit(1) 69 | } 70 | 71 | if oid == 0 { 72 | fmt.Println("Client identifier is require. See to help (-h)") 73 | os.Exit(1) 74 | } 75 | 76 | if ts == "" { 77 | fmt.Println("Timestamp is require. See to help (-h)") 78 | os.Exit(1) 79 | } 80 | timestamp, err := time.Parse(time.RFC3339, ts) 81 | if err != nil { 82 | fmt.Println("Parsing timestamp failed: ", timestamp) 83 | os.Exit(1) 84 | } 85 | 86 | egtsSendPacket := egts.Package{ 87 | ProtocolVersion: 1, 88 | SecurityKeyID: 0, 89 | Prefix: "00", 90 | Route: "0", 91 | EncryptionAlg: "00", 92 | Compression: "0", 93 | Priority: "10", 94 | HeaderLength: 11, 95 | HeaderEncoding: 0, 96 | PacketIdentifier: uint16(pid), 97 | PacketType: 1, 98 | ServicesFrameData: &egts.ServiceDataSet{ 99 | egts.ServiceDataRecord{ 100 | RecordNumber: 1, 101 | SourceServiceOnDevice: "0", 102 | RecipientServiceOnDevice: "0", 103 | Group: "0", 104 | RecordProcessingPriority: "10", 105 | TimeFieldExists: "0", 106 | EventIDFieldExists: "1", 107 | ObjectIDFieldExists: "1", 108 | EventIdentifier: 3436, 109 | ObjectIdentifier: uint32(oid), 110 | SourceServiceType: 2, 111 | RecipientServiceType: 2, 112 | RecordDataSet: egts.RecordDataSet{ 113 | egts.RecordData{ 114 | SubrecordType: 16, 115 | SubrecordData: &egts.SrPosData{ 116 | NavigationTime: time.Date(2021, time.February, 20, 0, 30, 40, 0, time.UTC), 117 | Latitude: lat, 118 | Longitude: lon, 119 | ALTE: "1", 120 | LOHS: "0", 121 | LAHS: "0", 122 | MV: "1", 123 | BB: "1", 124 | CS: "0", 125 | FIX: "1", 126 | VLD: "1", 127 | DirectionHighestBit: 0, 128 | AltitudeSign: 0, 129 | Speed: 34, 130 | Direction: 172, 131 | Odometer: 191, 132 | DigitalInputs: 144, 133 | Source: 0, 134 | Altitude: 30, 135 | }, 136 | }, 137 | egts.RecordData{ 138 | SubrecordType: 27, 139 | SubrecordData: &egts.SrLiquidLevelSensor{ 140 | LiquidLevelSensorErrorFlag: "1", 141 | LiquidLevelSensorValueUnit: "00", 142 | RawDataFlag: "0", 143 | LiquidLevelSensorNumber: 1, 144 | ModuleAddress: uint16(1), 145 | LiquidLevelSensorData: uint32(liqLvl), 146 | }, 147 | }, 148 | }, 149 | }, 150 | }, 151 | } 152 | 153 | sendBytes, err := egtsSendPacket.Encode() 154 | if err != nil { 155 | fmt.Println("Encode message failed: ", err) 156 | os.Exit(1) 157 | } 158 | 159 | tcpAddr, err := net.ResolveTCPAddr("tcp", server) 160 | if err != nil { 161 | fmt.Println("ResolveTCPAddr failed:", err) 162 | os.Exit(1) 163 | } 164 | 165 | conn, err := net.DialTCP("tcp", nil, tcpAddr) 166 | if err != nil { 167 | fmt.Println("Dial failed:", err) 168 | os.Exit(1) 169 | } 170 | 171 | _, err = conn.Write(sendBytes) 172 | if err != nil { 173 | fmt.Println("Write to server failed:", err) 174 | os.Exit(1) 175 | } 176 | 177 | ackBuf := make([]byte, 1024) 178 | 179 | _ = conn.SetReadDeadline(time.Now().Add(time.Duration(ackTimeout) * time.Second)) 180 | ackLen, err := conn.Read(ackBuf) 181 | if err != nil { 182 | fmt.Println("Read from server failed:", err) 183 | os.Exit(1) 184 | } 185 | 186 | ackPacket := egts.Package{} 187 | _, err = ackPacket.Decode(ackBuf[:ackLen]) 188 | if err != nil { 189 | fmt.Println("Parse ack packet failed:", err) 190 | os.Exit(1) 191 | } 192 | 193 | ack, ok := ackPacket.ServicesFrameData.(*egts.PtResponse) 194 | if !ok { 195 | fmt.Println("Received packet is not egts ack") 196 | os.Exit(1) 197 | } 198 | 199 | if ack.ResponsePacketID != egtsSendPacket.PacketIdentifier { 200 | fmt.Printf("Incorrect response packet id: %d (actual) != %d (expected)", 201 | ack.ResponsePacketID, egtsSendPacket.PacketIdentifier) 202 | os.Exit(1) 203 | } 204 | 205 | if ack.ProcessingResult != 0 { 206 | fmt.Printf("Incorrect processing result: %d (actual) != 0 (expected)", ack.ProcessingResult) 207 | os.Exit(1) 208 | } 209 | 210 | for _, rec := range *ack.SDR.(*egts.ServiceDataSet) { 211 | for _, subRec := range rec.RecordDataSet { 212 | if subRec.SubrecordType == egts.SrRecordResponseType { 213 | if response, ok := subRec.SubrecordData.(*egts.SrResponse); ok { 214 | if response.RecordStatus != 0 { 215 | fmt.Printf("Incorrect record status: %d (actual) != 0 (expected)", response.RecordStatus) 216 | os.Exit(1) 217 | } 218 | } 219 | } 220 | } 221 | } 222 | 223 | fmt.Println("Packet sent and processed correct") 224 | os.Exit(1) 225 | } 226 | -------------------------------------------------------------------------------- /libs/egts/service_data_records.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | // Преобразуем время навигации к формату, который требует стандарт: количество секунд с 00:00:00 01.01.2010 UTC 12 | var timeOffset = time.Date(2010, time.January, 1, 0, 0, 0, 0, time.UTC) 13 | 14 | // ServiceDataRecord запись содержащая мониторинговую информацию 15 | type ServiceDataRecord struct { 16 | RecordLength uint16 `json:"RL"` 17 | RecordNumber uint16 `json:"RN"` 18 | SourceServiceOnDevice string `json:"SSOD"` 19 | RecipientServiceOnDevice string `json:"RSOD"` 20 | Group string `json:"GRP"` 21 | RecordProcessingPriority string `json:"RPP"` 22 | TimeFieldExists string `json:"TMFE"` 23 | EventIDFieldExists string `json:"EVFE"` 24 | ObjectIDFieldExists string `json:"OBFE"` 25 | ObjectIdentifier uint32 `json:"OID"` 26 | EventIdentifier uint32 `json:"EVID"` 27 | Time time.Time `json:"TM"` 28 | SourceServiceType byte `json:"SST"` 29 | RecipientServiceType byte `json:"RST"` 30 | RecordDataSet `json:"RD"` 31 | } 32 | 33 | // ServiceDataSet набор последовательных записей с информаций 34 | type ServiceDataSet []ServiceDataRecord 35 | 36 | // Decode разбирает байты в структуру подзаписи 37 | //nolint:funlen 38 | func (s *ServiceDataSet) Decode(serviceDS []byte) error { 39 | var ( 40 | err error 41 | flags byte 42 | ) 43 | buf := bytes.NewReader(serviceDS) 44 | 45 | for buf.Len() > 0 { 46 | sdr := ServiceDataRecord{} 47 | tmpIntBuf := make([]byte, 2) 48 | if _, err = buf.Read(tmpIntBuf); err != nil { 49 | return fmt.Errorf("не удалось получить длину записи SDR: %v", err) 50 | } 51 | sdr.RecordLength = binary.LittleEndian.Uint16(tmpIntBuf) 52 | 53 | if _, err = buf.Read(tmpIntBuf); err != nil { 54 | return fmt.Errorf("не удалось получить номер записи SDR: %v", err) 55 | } 56 | sdr.RecordNumber = binary.LittleEndian.Uint16(tmpIntBuf) 57 | 58 | if flags, err = buf.ReadByte(); err != nil { 59 | return fmt.Errorf("не удалось считать байт флагов SDR: %v", err) 60 | } 61 | flagBits := fmt.Sprintf("%08b", flags) 62 | sdr.SourceServiceOnDevice = flagBits[:1] 63 | sdr.RecipientServiceOnDevice = flagBits[1:2] 64 | sdr.Group = flagBits[2:3] 65 | sdr.RecordProcessingPriority = flagBits[3:5] 66 | sdr.TimeFieldExists = flagBits[5:6] 67 | sdr.EventIDFieldExists = flagBits[6:7] 68 | sdr.ObjectIDFieldExists = flagBits[7:] 69 | 70 | if sdr.ObjectIDFieldExists == "1" { 71 | oid := make([]byte, 4) 72 | if _, err := buf.Read(oid); err != nil { 73 | return fmt.Errorf("не удалось получить идентификатор объекта SDR: %v", err) 74 | } 75 | sdr.ObjectIdentifier = binary.LittleEndian.Uint32(oid) 76 | } 77 | 78 | if sdr.EventIDFieldExists == "1" { 79 | event := make([]byte, 4) 80 | if _, err := buf.Read(event); err != nil { 81 | return fmt.Errorf("не удалось получить идентификатор события SDR: %v", err) 82 | } 83 | sdr.EventIdentifier = binary.LittleEndian.Uint32(event) 84 | } 85 | 86 | // Преобразуем время навигации к формату, который требует стандарт: количество секунд с 00:00:00 01.01.2010 UTC 87 | if sdr.TimeFieldExists == "1" { 88 | tm := make([]byte, 4) 89 | if _, err := buf.Read(tm); err != nil { 90 | return fmt.Errorf("не удалось получить время формирования записи на стороне отправителя SDR: %v", err) 91 | } 92 | preFieldVal := binary.LittleEndian.Uint32(tm) 93 | sdr.Time = timeOffset.Add(time.Duration(preFieldVal) * time.Second) 94 | } 95 | 96 | if sdr.SourceServiceType, err = buf.ReadByte(); err != nil { 97 | return fmt.Errorf("не удалось считать идентификатор тип сервиса-отправителя SDR: %v", err) 98 | } 99 | 100 | if sdr.RecipientServiceType, err = buf.ReadByte(); err != nil { 101 | return fmt.Errorf("не удалось считать идентификатор тип сервиса-получателя SDR: %v", err) 102 | } 103 | 104 | if buf.Len() != 0 { 105 | rds := RecordDataSet{} 106 | rdsBytes := make([]byte, sdr.RecordLength) 107 | if _, err = buf.Read(rdsBytes); err != nil { 108 | return err 109 | } 110 | 111 | if err = rds.Decode(rdsBytes); err != nil { 112 | return err 113 | } 114 | sdr.RecordDataSet = rds 115 | } 116 | 117 | *s = append(*s, sdr) 118 | } 119 | return err 120 | } 121 | 122 | // Encode кодирование структуры в байты 123 | func (s *ServiceDataSet) Encode() ([]byte, error) { 124 | var ( 125 | result []byte 126 | flags uint64 127 | ) 128 | 129 | buf := new(bytes.Buffer) 130 | 131 | for _, sdr := range *s { 132 | rd, err := sdr.RecordDataSet.Encode() 133 | if err != nil { 134 | return result, err 135 | } 136 | 137 | if sdr.RecordLength == 0 { 138 | sdr.RecordLength = uint16(len(rd)) 139 | } 140 | if err = binary.Write(buf, binary.LittleEndian, sdr.RecordLength); err != nil { 141 | return result, fmt.Errorf("не удалось записать длину записи SDR: %v", err) 142 | } 143 | 144 | if err = binary.Write(buf, binary.LittleEndian, sdr.RecordNumber); err != nil { 145 | return result, fmt.Errorf("не удалось записать номер записи SDR: %v", err) 146 | } 147 | 148 | // составной байт 149 | flagsBits := sdr.SourceServiceOnDevice + sdr.RecipientServiceOnDevice + sdr.Group + sdr.RecordProcessingPriority + 150 | sdr.TimeFieldExists + sdr.EventIDFieldExists + sdr.ObjectIDFieldExists 151 | if flags, err = strconv.ParseUint(flagsBits, 2, 8); err != nil { 152 | return result, fmt.Errorf("не удалось сгенерировать байт флагов SDR: %v", err) 153 | } 154 | if err = buf.WriteByte(uint8(flags)); err != nil { 155 | return result, fmt.Errorf("не удалось записать флаги SDR: %v", err) 156 | } 157 | 158 | if sdr.ObjectIDFieldExists == "1" { 159 | if err = binary.Write(buf, binary.LittleEndian, sdr.ObjectIdentifier); err != nil { 160 | return result, fmt.Errorf("не удалось записать идентификатор объекта SDR: %v", err) 161 | } 162 | } 163 | 164 | if sdr.EventIDFieldExists == "1" { 165 | if err = binary.Write(buf, binary.LittleEndian, sdr.EventIdentifier); err != nil { 166 | return result, fmt.Errorf("не удалось записать идентификатор события SDR: %v", err) 167 | } 168 | } 169 | 170 | if sdr.TimeFieldExists == "1" { 171 | tm := uint32(sdr.Time.Unix() - timeOffset.Unix()) 172 | if err := binary.Write(buf, binary.LittleEndian, tm); err != nil { 173 | return result, fmt.Errorf("не удалось записать время формирования записи на стороне отправителя SDR: %v", err) 174 | } 175 | } 176 | 177 | if err := buf.WriteByte(sdr.SourceServiceType); err != nil { 178 | return result, fmt.Errorf("не удалось записать идентификатор тип сервиса-отправителя SDR: %v", err) 179 | } 180 | 181 | if err := buf.WriteByte(sdr.RecipientServiceType); err != nil { 182 | return result, fmt.Errorf("не удалось записать идентификатор тип сервиса-получателя SDR: %v", err) 183 | } 184 | 185 | buf.Write(rd) 186 | } 187 | 188 | result = buf.Bytes() 189 | 190 | return result, nil 191 | } 192 | 193 | // Length получает длину массива записей 194 | func (s *ServiceDataSet) Length() uint16 { 195 | var result uint16 196 | 197 | if recBytes, err := s.Encode(); err != nil { 198 | result = uint16(0) 199 | } else { 200 | result = uint16(len(recBytes)) 201 | } 202 | 203 | return result 204 | } 205 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_module_data_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | var ( 11 | testSrModuleData = SrModuleData{ 12 | ModuleType: 1, 13 | VendorID: 0, 14 | FirmwareVersion: 257, 15 | SoftwareVersion: 259, 16 | Modification: 1, 17 | State: 1, 18 | SerialNumber: "TB1011010000001022023", 19 | Description: "rev170122.A", 20 | } 21 | //nolint:lll 22 | testSrModuleDataBytes = []byte{0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x54, 0x42, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x32, 0x30, 0x32, 0x33, 0x0, 0x72, 0x65, 0x76, 0x31, 0x37, 0x30, 0x31, 0x32, 0x32, 0x2e, 0x41, 0x0} 23 | ) 24 | 25 | // проверяем, что рекордсет работает правильно с данным типом подзаписи 26 | //nolint:dupl 27 | func TestEgtsSrModuleDataRs(t *testing.T) { 28 | stateDataRDBytes := append([]byte{0x02, 0x2d, 0x00}, testSrModuleDataBytes...) 29 | stateDataRD := RecordDataSet{ 30 | RecordData{ 31 | SubrecordType: SrModuleDataType, 32 | SubrecordLength: uint16(len(testSrModuleDataBytes)), 33 | SubrecordData: &testSrModuleData, 34 | }, 35 | } 36 | testStruct := RecordDataSet{} 37 | 38 | testBytes, err := stateDataRD.Encode() 39 | if assert.NoError(t, err) { 40 | assert.Equal(t, testBytes, stateDataRDBytes) 41 | 42 | if assert.NoError(t, testStruct.Decode(stateDataRDBytes)) { 43 | assert.Equal(t, stateDataRD, testStruct) 44 | } 45 | } 46 | } 47 | 48 | //nolint:funlen 49 | func TestSrModuleData_Decode(t *testing.T) { 50 | type fields struct { 51 | ModuleType int8 52 | VendorID uint32 53 | FirmwareVersion uint16 54 | SoftwareVersion uint16 55 | Modification byte 56 | State byte 57 | SerialNumber string 58 | _ byte 59 | Description string 60 | _ byte 61 | } 62 | type args struct { 63 | content []byte 64 | } 65 | tests := []struct { 66 | name string 67 | fields fields 68 | args args 69 | wantErr bool 70 | }{ 71 | { 72 | name: "Success", 73 | fields: fields(testSrModuleData), 74 | args: args{content: testSrModuleDataBytes}, 75 | wantErr: false, 76 | }, 77 | { 78 | name: "Error - ModuleType", 79 | fields: fields(testSrModuleData), 80 | args: args{content: []byte{}}, 81 | wantErr: true, 82 | }, 83 | { 84 | name: "Error - VendorID", 85 | fields: fields(testSrModuleData), 86 | args: args{content: []byte{0x01}}, 87 | wantErr: true, 88 | }, 89 | { 90 | name: "Error - FirmwareVersion", 91 | fields: fields(testSrModuleData), 92 | args: args{content: []byte{0x01, 0x0, 0x0, 0x0, 0x0}}, 93 | wantErr: true, 94 | }, 95 | { 96 | name: "Error - SoftwareVersion", 97 | fields: fields(testSrModuleData), 98 | args: args{content: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1}}, 99 | wantErr: true, 100 | }, 101 | { 102 | name: "Error - Modification", 103 | fields: fields(testSrModuleData), 104 | args: args{content: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x3, 0x1}}, 105 | wantErr: true, 106 | }, 107 | { 108 | name: "Error - State", 109 | fields: fields(testSrModuleData), 110 | args: args{content: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x3, 0x1, 0x1}}, 111 | wantErr: true, 112 | }, 113 | { 114 | name: "Error - SerialNumber", 115 | fields: fields(testSrModuleData), 116 | args: args{content: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1}}, 117 | wantErr: true, 118 | }, 119 | { 120 | name: "Error - Description", 121 | fields: fields(testSrModuleData), 122 | //nolint:lll 123 | args: args{content: []byte{0x01, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x3, 0x1, 0x1, 0x1, 0x54, 0x42, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x32, 0x30, 0x32, 0x33, 0x00}}, 124 | wantErr: true, 125 | }, 126 | } 127 | for _, tt := range tests { 128 | t.Run(tt.name, func(t *testing.T) { 129 | e := &SrModuleData{ 130 | ModuleType: tt.fields.ModuleType, 131 | VendorID: tt.fields.VendorID, 132 | FirmwareVersion: tt.fields.FirmwareVersion, 133 | SoftwareVersion: tt.fields.SoftwareVersion, 134 | Modification: tt.fields.Modification, 135 | State: tt.fields.State, 136 | SerialNumber: tt.fields.SerialNumber, 137 | Description: tt.fields.Description, 138 | } 139 | if err := e.Decode(tt.args.content); (err != nil) != tt.wantErr { 140 | t.Errorf("SrModuleData.Decode() error = %v, wantErr %v", err, tt.wantErr) 141 | } 142 | }) 143 | } 144 | } 145 | 146 | func TestSrModuleData_Encode(t *testing.T) { 147 | type fields struct { 148 | ModuleType int8 149 | VendorID uint32 150 | FirmwareVersion uint16 151 | SoftwareVersion uint16 152 | Modification byte 153 | State byte 154 | SerialNumber string 155 | _ byte 156 | Description string 157 | _ byte 158 | } 159 | tests := []struct { 160 | name string 161 | fields fields 162 | want []byte 163 | wantErr bool 164 | }{ 165 | { 166 | name: "Success", 167 | fields: fields(testSrModuleData), 168 | want: testSrModuleDataBytes, 169 | wantErr: false, 170 | }, 171 | } 172 | for _, tt := range tests { 173 | t.Run(tt.name, func(t *testing.T) { 174 | e := &SrModuleData{ 175 | ModuleType: tt.fields.ModuleType, 176 | VendorID: tt.fields.VendorID, 177 | FirmwareVersion: tt.fields.FirmwareVersion, 178 | SoftwareVersion: tt.fields.SoftwareVersion, 179 | Modification: tt.fields.Modification, 180 | State: tt.fields.State, 181 | SerialNumber: tt.fields.SerialNumber, 182 | Description: tt.fields.Description, 183 | } 184 | got, err := e.Encode() 185 | if (err != nil) != tt.wantErr { 186 | t.Errorf("SrModuleData.Encode() error = %v, wantErr %v", err, tt.wantErr) 187 | return 188 | } 189 | if !reflect.DeepEqual(got, tt.want) { 190 | t.Errorf("SrModuleData.Encode() = %v, want %v", got, tt.want) 191 | } 192 | }) 193 | } 194 | } 195 | 196 | func TestSrModuleData_Length(t *testing.T) { 197 | type fields struct { 198 | ModuleType int8 199 | VendorID uint32 200 | FirmwareVersion uint16 201 | SoftwareVersion uint16 202 | Modification byte 203 | State byte 204 | SerialNumber string 205 | _ byte 206 | Description string 207 | _ byte 208 | } 209 | tests := []struct { 210 | name string 211 | fields fields 212 | want uint16 213 | }{ 214 | { 215 | name: "Success", 216 | fields: fields(testSrModuleData), 217 | want: 45, 218 | }, 219 | } 220 | for _, tt := range tests { 221 | t.Run(tt.name, func(t *testing.T) { 222 | e := &SrModuleData{ 223 | ModuleType: tt.fields.ModuleType, 224 | VendorID: tt.fields.VendorID, 225 | FirmwareVersion: tt.fields.FirmwareVersion, 226 | SoftwareVersion: tt.fields.SoftwareVersion, 227 | Modification: tt.fields.Modification, 228 | State: tt.fields.State, 229 | SerialNumber: tt.fields.SerialNumber, 230 | Description: tt.fields.Description, 231 | } 232 | if got := e.Length(); got != tt.want { 233 | t.Errorf("SrModuleData.Length() = %v, want %v", got, tt.want) 234 | } 235 | }) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_counters_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | //SrCountersData структура подзаписи типа EGTS_SR_COUNTERS_DATA, которая используется аппаратно-программным 11 | //комплексом для передачи на абонентский терминал данных о значении счетных входов 12 | type SrCountersData struct { 13 | CounterFieldExists1 string `json:"CFE1"` 14 | CounterFieldExists2 string `json:"CFE2"` 15 | CounterFieldExists3 string `json:"CFE3"` 16 | CounterFieldExists4 string `json:"CFE4"` 17 | CounterFieldExists5 string `json:"CFE5"` 18 | CounterFieldExists6 string `json:"CFE6"` 19 | CounterFieldExists7 string `json:"CFE7"` 20 | CounterFieldExists8 string `json:"CFE8"` 21 | Counter1 uint32 `json:"CN1"` 22 | Counter2 uint32 `json:"CN2"` 23 | Counter3 uint32 `json:"CN3"` 24 | Counter4 uint32 `json:"CN4"` 25 | Counter5 uint32 `json:"CN5"` 26 | Counter6 uint32 `json:"CN6"` 27 | Counter7 uint32 `json:"CN7"` 28 | Counter8 uint32 `json:"CN8"` 29 | } 30 | 31 | //Decode разбирает байты в структуру подзаписи 32 | func (c *SrCountersData) Decode(content []byte) error { 33 | var ( 34 | err error 35 | flags byte 36 | counterVal []byte 37 | ) 38 | buf := bytes.NewReader(content) 39 | 40 | if flags, err = buf.ReadByte(); err != nil { 41 | return fmt.Errorf("Не удалось получить байт цифровых выходов sr_counters_data: %v", err) 42 | } 43 | flagBits := fmt.Sprintf("%08b", flags) 44 | 45 | c.CounterFieldExists8 = flagBits[:1] 46 | c.CounterFieldExists7 = flagBits[1:2] 47 | c.CounterFieldExists6 = flagBits[2:3] 48 | c.CounterFieldExists5 = flagBits[3:4] 49 | c.CounterFieldExists4 = flagBits[4:5] 50 | c.CounterFieldExists3 = flagBits[5:6] 51 | c.CounterFieldExists2 = flagBits[6:7] 52 | c.CounterFieldExists1 = flagBits[7:] 53 | 54 | tmpBuf := make([]byte, 3) 55 | if c.CounterFieldExists1 == "1" { 56 | if _, err = buf.Read(tmpBuf); err != nil { 57 | return fmt.Errorf("Не удалось получить показания CN1: %v", err) 58 | } 59 | counterVal = append(tmpBuf, 0x00) 60 | c.Counter1 = binary.LittleEndian.Uint32(counterVal) 61 | } 62 | 63 | if c.CounterFieldExists2 == "1" { 64 | if _, err = buf.Read(tmpBuf); err != nil { 65 | return fmt.Errorf("Не удалось получить показания CN2: %v", err) 66 | } 67 | counterVal = append(tmpBuf, 0x00) 68 | c.Counter1 = binary.LittleEndian.Uint32(counterVal) 69 | } 70 | 71 | if c.CounterFieldExists3 == "1" { 72 | if _, err = buf.Read(tmpBuf); err != nil { 73 | return fmt.Errorf("Не удалось получить показания CN3: %v", err) 74 | } 75 | counterVal = append(tmpBuf, 0x00) 76 | c.Counter3 = binary.LittleEndian.Uint32(counterVal) 77 | } 78 | 79 | if c.CounterFieldExists4 == "1" { 80 | if _, err = buf.Read(tmpBuf); err != nil { 81 | return fmt.Errorf("Не удалось получить показания CN4: %v", err) 82 | } 83 | counterVal = append(tmpBuf, 0x00) 84 | c.Counter4 = binary.LittleEndian.Uint32(counterVal) 85 | } 86 | 87 | if c.CounterFieldExists5 == "1" { 88 | if _, err = buf.Read(tmpBuf); err != nil { 89 | return fmt.Errorf("Не удалось получить показания CN5: %v", err) 90 | } 91 | counterVal = append(tmpBuf, 0x00) 92 | c.Counter5 = binary.LittleEndian.Uint32(counterVal) 93 | } 94 | 95 | if c.CounterFieldExists6 == "1" { 96 | if _, err = buf.Read(tmpBuf); err != nil { 97 | return fmt.Errorf("Не удалось получить показания CN6: %v", err) 98 | } 99 | counterVal = append(tmpBuf, 0x00) 100 | c.Counter6 = binary.LittleEndian.Uint32(counterVal) 101 | } 102 | 103 | if c.CounterFieldExists7 == "1" { 104 | if _, err = buf.Read(tmpBuf); err != nil { 105 | return fmt.Errorf("Не удалось получить показания CN7: %v", err) 106 | } 107 | counterVal = append(tmpBuf, 0x00) 108 | c.Counter7 = binary.LittleEndian.Uint32(counterVal) 109 | } 110 | 111 | if c.CounterFieldExists8 == "1" { 112 | if _, err = buf.Read(tmpBuf); err != nil { 113 | return fmt.Errorf("Не удалось получить показания CN8: %v", err) 114 | } 115 | counterVal = append(tmpBuf, 0x00) 116 | c.Counter8 = binary.LittleEndian.Uint32(counterVal) 117 | } 118 | return err 119 | } 120 | 121 | //Encode преобразовывает подзапись в набор байт 122 | func (c *SrCountersData) Encode() ([]byte, error) { 123 | var ( 124 | err error 125 | flags uint64 126 | result []byte 127 | ) 128 | buf := new(bytes.Buffer) 129 | flagsBits := c.CounterFieldExists8 + 130 | c.CounterFieldExists7 + 131 | c.CounterFieldExists6 + 132 | c.CounterFieldExists5 + 133 | c.CounterFieldExists4 + 134 | c.CounterFieldExists3 + 135 | c.CounterFieldExists2 + 136 | c.CounterFieldExists1 137 | 138 | if flags, err = strconv.ParseUint(flagsBits, 2, 8); err != nil { 139 | return result, fmt.Errorf("Не удалось сгенерировать байт байт аналоговых выходов counters_data: %v", err) 140 | } 141 | if err = buf.WriteByte(uint8(flags)); err != nil { 142 | return result, fmt.Errorf("Не удалось записать байт байт аналоговых выходов counters_data: %v", err) 143 | } 144 | 145 | sensVal := make([]byte, 4) 146 | if c.CounterFieldExists1 == "1" { 147 | binary.LittleEndian.PutUint32(sensVal, c.Counter1) 148 | if _, err = buf.Write(sensVal[:3]); err != nil { 149 | return result, fmt.Errorf("Не удалось запистаь показания CN1: %v", err) 150 | } 151 | } 152 | 153 | if c.CounterFieldExists2 == "1" { 154 | binary.LittleEndian.PutUint32(sensVal, c.Counter2) 155 | if _, err = buf.Write(sensVal[:3]); err != nil { 156 | return result, fmt.Errorf("Не удалось запистаь показания CN2: %v", err) 157 | } 158 | } 159 | 160 | if c.CounterFieldExists3 == "1" { 161 | binary.LittleEndian.PutUint32(sensVal, c.Counter3) 162 | if _, err = buf.Write(sensVal[:3]); err != nil { 163 | return result, fmt.Errorf("Не удалось запистаь показания CN3: %v", err) 164 | } 165 | } 166 | 167 | if c.CounterFieldExists4 == "1" { 168 | binary.LittleEndian.PutUint32(sensVal, c.Counter4) 169 | if _, err = buf.Write(sensVal[:3]); err != nil { 170 | return result, fmt.Errorf("Не удалось запистаь показания CN4: %v", err) 171 | } 172 | } 173 | 174 | if c.CounterFieldExists5 == "1" { 175 | binary.LittleEndian.PutUint32(sensVal, c.Counter5) 176 | if _, err = buf.Write(sensVal[:3]); err != nil { 177 | return result, fmt.Errorf("Не удалось запистаь показания CN5: %v", err) 178 | } 179 | } 180 | 181 | if c.CounterFieldExists6 == "1" { 182 | binary.LittleEndian.PutUint32(sensVal, c.Counter6) 183 | if _, err = buf.Write(sensVal[:3]); err != nil { 184 | return result, fmt.Errorf("Не удалось запистаь показания CN6: %v", err) 185 | } 186 | } 187 | 188 | if c.CounterFieldExists7 == "1" { 189 | binary.LittleEndian.PutUint32(sensVal, c.Counter7) 190 | if _, err = buf.Write(sensVal[:3]); err != nil { 191 | return result, fmt.Errorf("Не удалось запистаь показания CN7: %v", err) 192 | } 193 | } 194 | 195 | if c.CounterFieldExists8 == "1" { 196 | binary.LittleEndian.PutUint32(sensVal, c.Counter8) 197 | if _, err = buf.Write(sensVal[:3]); err != nil { 198 | return result, fmt.Errorf("Не удалось запистаь показания CN8: %v", err) 199 | } 200 | } 201 | 202 | result = buf.Bytes() 203 | 204 | return result, err 205 | } 206 | 207 | //Length получает длинну закодированной подзаписи 208 | func (c *SrCountersData) Length() uint16 { 209 | var result uint16 210 | 211 | if recBytes, err := c.Encode(); err != nil { 212 | result = uint16(0) 213 | } else { 214 | result = uint16(len(recBytes)) 215 | } 216 | 217 | return result 218 | } 219 | -------------------------------------------------------------------------------- /libs/egts/egts_sr_pos_data.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | //SrPosData структура подзаписи типа EGTS_SR_POS_DATA, которая используется абонентским 12 | //терминалом при передаче основных данных определения местоположения 13 | type SrPosData struct { 14 | NavigationTime time.Time `json:"NTM"` 15 | Latitude float64 `json:"LAT"` 16 | Longitude float64 `json:"LONG"` 17 | ALTE string `json:"ALTE"` 18 | LOHS string `json:"LOHS"` 19 | LAHS string `json:"LAHS"` 20 | MV string `json:"MV"` 21 | BB string `json:"BB"` 22 | CS string `json:"CS"` 23 | FIX string `json:"FIX"` 24 | VLD string `json:"VLD"` 25 | DirectionHighestBit uint8 `json:"DIRH"` 26 | AltitudeSign uint8 `json:"ALTS"` 27 | Speed uint16 `json:"SPD"` 28 | Direction byte `json:"DIR"` 29 | Odometer uint32 `json:"ODM"` 30 | DigitalInputs byte `json:"DIN"` 31 | Source byte `json:"SRC"` 32 | Altitude uint32 `json:"ALT"` 33 | SourceData int16 `json:"SRCD"` 34 | } 35 | 36 | //Decode разбирает байты в структуру подзаписи 37 | func (e *SrPosData) Decode(content []byte) error { 38 | var ( 39 | err error 40 | flags byte 41 | speed uint64 42 | ) 43 | buf := bytes.NewReader(content) 44 | 45 | // Преобразуем время навигации к формату, который требует стандарт: количество секунд с 00:00:00 01.01.2010 UTC 46 | startDate := time.Date(2010, time.January, 1, 0, 0, 0, 0, time.UTC) 47 | tmpUint32Buf := make([]byte, 4) 48 | if _, err = buf.Read(tmpUint32Buf); err != nil { 49 | return fmt.Errorf("Не удалось получить время навигации: %v", err) 50 | } 51 | preFieldVal := binary.LittleEndian.Uint32(tmpUint32Buf) 52 | e.NavigationTime = startDate.Add(time.Duration(preFieldVal) * time.Second) 53 | 54 | // В протоколе значение хранится в виде: широта по модулю, градусы/90*0xFFFFFFFF и взята целая часть 55 | if _, err = buf.Read(tmpUint32Buf); err != nil { 56 | return fmt.Errorf("Не удалось получить широту: %v", err) 57 | } 58 | 59 | preFieldVal = binary.LittleEndian.Uint32(tmpUint32Buf) 60 | e.Latitude = float64(float64(preFieldVal) * 90 / 0xFFFFFFFF) 61 | 62 | // В протоколе значение хранится в виде: долгота по модулю, градусы/180*0xFFFFFFFF и взята целая часть 63 | if _, err = buf.Read(tmpUint32Buf); err != nil { 64 | return fmt.Errorf("Не удалось получить время долгату: %v", err) 65 | } 66 | preFieldVal = binary.LittleEndian.Uint32(tmpUint32Buf) 67 | e.Longitude = float64(float64(preFieldVal) * 180 / 0xFFFFFFFF) 68 | 69 | //байт флагов 70 | if flags, err = buf.ReadByte(); err != nil { 71 | return fmt.Errorf("Не удалось получить байт флагов pos_data: %v", err) 72 | } 73 | flagBits := fmt.Sprintf("%08b", flags) 74 | e.ALTE = flagBits[:1] 75 | e.LOHS = flagBits[1:2] 76 | e.LAHS = flagBits[2:3] 77 | e.MV = flagBits[3:4] 78 | e.BB = flagBits[4:5] 79 | e.CS = flagBits[5:6] 80 | e.FIX = flagBits[6:7] 81 | e.VLD = flagBits[7:] 82 | 83 | // скорость 84 | tmpUint16Buf := make([]byte, 2) 85 | if _, err = buf.Read(tmpUint16Buf); err != nil { 86 | return fmt.Errorf("Не удалось получить скорость: %v", err) 87 | } 88 | spd := binary.LittleEndian.Uint16(tmpUint16Buf) 89 | e.DirectionHighestBit = uint8(spd >> 15 & 0x1) 90 | e.AltitudeSign = uint8(spd >> 14 & 0x1) 91 | 92 | speedBits := fmt.Sprintf("%016b", spd) 93 | if speed, err = strconv.ParseUint(speedBits[2:], 2, 16); err != nil { 94 | return fmt.Errorf("Не удалось расшифровать скорость из битов: %v", err) 95 | } 96 | 97 | // т.к. скорость с дискретностью 0,1 км 98 | e.Speed = uint16(speed) / 10 99 | 100 | if e.Direction, err = buf.ReadByte(); err != nil { 101 | return fmt.Errorf("Не удалось получить направление движения: %v", err) 102 | } 103 | e.Direction |= e.DirectionHighestBit << 7 104 | 105 | bytesTmpBuf := make([]byte, 3) 106 | if _, err = buf.Read(bytesTmpBuf); err != nil { 107 | return fmt.Errorf("Не удалось получить пройденное расстояние (пробег) в км: %v", err) 108 | } 109 | bytesTmpBuf = append(bytesTmpBuf, 0x00) 110 | e.Odometer = binary.LittleEndian.Uint32(bytesTmpBuf) 111 | 112 | if e.DigitalInputs, err = buf.ReadByte(); err != nil { 113 | return fmt.Errorf("Не удалось получить битовые флаги, определяют состояние основных дискретных входов: %v", err) 114 | } 115 | 116 | if e.Source, err = buf.ReadByte(); err != nil { 117 | return fmt.Errorf("Не удалось получить источник (событие), инициировавший посылку: %v", err) 118 | } 119 | 120 | if e.ALTE == "1" { 121 | bytesTmpBuf = []byte{0, 0, 0, 0} 122 | if _, err = buf.Read(bytesTmpBuf); err != nil { 123 | return fmt.Errorf("Не удалось получить высоту над уровнем моря: %v", err) 124 | } 125 | e.Altitude = binary.LittleEndian.Uint32(bytesTmpBuf) 126 | } 127 | 128 | //TODO: разобраться с разбором SourceData 129 | return err 130 | } 131 | 132 | //Encode преобразовывает подзапись в набор байт 133 | func (e *SrPosData) Encode() ([]byte, error) { 134 | var ( 135 | err error 136 | flags uint64 137 | result []byte 138 | ) 139 | 140 | buf := new(bytes.Buffer) 141 | // Преобразуем время навигации к формату, который требует стандарт: количество секунд с 00:00:00 01.01.2010 UTC 142 | startDate := time.Date(2010, time.January, 1, 0, 0, 0, 0, time.UTC) 143 | if err = binary.Write(buf, binary.LittleEndian, uint32(e.NavigationTime.Sub(startDate).Seconds())); err != nil { 144 | return result, fmt.Errorf("Не удалось записать время навигации: %v", err) 145 | } 146 | 147 | // В протоколе значение хранится в виде: широта по модулю, градусы/90*0xFFFFFFFF и взята целая часть 148 | if err = binary.Write(buf, binary.LittleEndian, uint32(e.Latitude/90*0xFFFFFFFF)); err != nil { 149 | return result, fmt.Errorf("Не удалось записать широту: %v", err) 150 | } 151 | 152 | // В протоколе значение хранится в виде: долгота по модулю, градусы/180*0xFFFFFFFF и взята целая часть 153 | if err = binary.Write(buf, binary.LittleEndian, uint32(e.Longitude/180*0xFFFFFFFF)); err != nil { 154 | return result, fmt.Errorf("Не удалось записать долготу: %v", err) 155 | } 156 | 157 | //байт флагов 158 | flags, err = strconv.ParseUint(e.ALTE+e.LOHS+e.LAHS+e.MV+e.BB+e.CS+e.FIX+e.VLD, 2, 8) 159 | if err != nil { 160 | return result, fmt.Errorf("Не удалось сгенерировать байт флагов pos_data: %v", err) 161 | } 162 | 163 | if err = buf.WriteByte(uint8(flags)); err != nil { 164 | return result, fmt.Errorf("Не удалось записать флаги: %v", err) 165 | } 166 | 167 | // скорость 168 | speed := e.Speed*10 | uint16(e.DirectionHighestBit)<<15 // 15 бит 169 | speed = speed | uint16(e.AltitudeSign)<<14 //14 бит 170 | spd := make([]byte, 2) 171 | binary.LittleEndian.PutUint16(spd, speed) 172 | if _, err = buf.Write(spd); err != nil { 173 | return result, fmt.Errorf("Не удалось записать скорость: %v", err) 174 | } 175 | 176 | dir := e.Direction &^ (e.DirectionHighestBit << 7) 177 | if err = binary.Write(buf, binary.LittleEndian, dir); err != nil { 178 | return result, fmt.Errorf("Не удалось записать направление движения: %v", err) 179 | } 180 | 181 | bytesTmpBuf := make([]byte, 4) 182 | binary.LittleEndian.PutUint32(bytesTmpBuf, e.Odometer) 183 | if _, err = buf.Write(bytesTmpBuf[:3]); err != nil { 184 | return result, fmt.Errorf("Не удалось запсиать пройденное расстояние (пробег) в км: %v", err) 185 | } 186 | 187 | if err = binary.Write(buf, binary.LittleEndian, e.DigitalInputs); err != nil { 188 | return result, fmt.Errorf("Не удалось записать битовые флаги, определяют состояние основных дискретных входов: %v", err) 189 | } 190 | 191 | if err = binary.Write(buf, binary.LittleEndian, e.Source); err != nil { 192 | return result, fmt.Errorf("Не удалось записать источник (событие), инициировавший посылку: %v", err) 193 | } 194 | 195 | if e.ALTE == "1" { 196 | bytesTmpBuf = []byte{0, 0, 0, 0} 197 | binary.LittleEndian.PutUint32(bytesTmpBuf, e.Altitude) 198 | if _, err = buf.Write(bytesTmpBuf[:3]); err != nil { 199 | return result, fmt.Errorf("Не удалось записать высоту над уровнем моря: %v", err) 200 | } 201 | } 202 | 203 | //TODO: разобраться с записью SourceData 204 | result = buf.Bytes() 205 | return result, nil 206 | } 207 | 208 | //Length получает длинну закодированной подзаписи 209 | func (e *SrPosData) Length() uint16 { 210 | var result uint16 211 | 212 | if recBytes, err := e.Encode(); err != nil { 213 | result = uint16(0) 214 | } else { 215 | result = uint16(len(recBytes)) 216 | } 217 | 218 | return result 219 | } 220 | -------------------------------------------------------------------------------- /libs/egts/egts_pkg.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "fmt" 8 | "strconv" 9 | ) 10 | 11 | const DEFAULT_HEADER_LEN = 11 12 | 13 | var errSecretKey = fmt.Errorf("package is encrypted but secret key is nil") 14 | 15 | // Package структура для описания пакета ЕГТС 16 | type Package struct { 17 | ProtocolVersion byte `json:"PRV"` 18 | SecurityKeyID byte `json:"SKID"` 19 | Prefix string `json:"PRF"` 20 | Route string `json:"RTE"` 21 | EncryptionAlg string `json:"ENA"` 22 | Compression string `json:"CMP"` 23 | Priority string `json:"PR"` 24 | HeaderLength byte `json:"HL"` 25 | HeaderEncoding byte `json:"HE"` 26 | FrameDataLength uint16 `json:"FDL"` 27 | PacketIdentifier uint16 `json:"PID"` 28 | PacketType byte `json:"PT"` 29 | PeerAddress uint16 `json:"PRA"` 30 | RecipientAddress uint16 `json:"RCA"` 31 | TimeToLive byte `json:"TTL"` 32 | HeaderCheckSum byte `json:"HCS"` 33 | ServicesFrameData BinaryData `json:"SFRD"` 34 | ServicesFrameDataCheckSum uint16 `json:"SFRCS"` 35 | } 36 | 37 | type SecretKey interface { 38 | Decode([]byte) ([]byte, error) 39 | Encode(data []byte) ([]byte, error) 40 | } 41 | 42 | type Options struct { 43 | Secret SecretKey 44 | } 45 | 46 | // Decode разбирает набор байт в структуру пакета 47 | func (p *Package) Decode(content []byte, opt ...func(*Options)) (uint8, error) { 48 | options := &Options{} 49 | for _, o := range opt { 50 | o(options) 51 | } 52 | 53 | secretKey := options.Secret 54 | 55 | var ( 56 | err error 57 | flags byte 58 | ) 59 | buf := bytes.NewReader(content) 60 | if p.ProtocolVersion, err = buf.ReadByte(); err != nil { 61 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить версию протокола: %v", err) 62 | } 63 | 64 | if p.SecurityKeyID, err = buf.ReadByte(); err != nil { 65 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить идентификатор ключа: %v", err) 66 | } 67 | 68 | //разбираем флаги 69 | if flags, err = buf.ReadByte(); err != nil { 70 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось флаги: %v", err) 71 | } 72 | flagBits := fmt.Sprintf("%08b", flags) 73 | p.Prefix = flagBits[:2] // flags << 7, flags << 6 74 | p.Route = flagBits[2:3] // flags << 5 75 | p.EncryptionAlg = flagBits[3:5] // flags << 4, flags << 3 76 | p.Compression = flagBits[5:6] // flags << 2 77 | p.Priority = flagBits[6:] // flags << 1, flags << 0 78 | 79 | isEncrypted := p.EncryptionAlg != "00" 80 | 81 | if p.HeaderLength, err = buf.ReadByte(); err != nil { 82 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить длину заголовка: %v", err) 83 | } 84 | 85 | if p.HeaderEncoding, err = buf.ReadByte(); err != nil { 86 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить метод кодирования: %v", err) 87 | } 88 | 89 | tmpIntBuf := make([]byte, 2) 90 | if _, err = buf.Read(tmpIntBuf); err != nil { 91 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить длину секции данных: %v", err) 92 | } 93 | p.FrameDataLength = binary.LittleEndian.Uint16(tmpIntBuf) 94 | 95 | if _, err = buf.Read(tmpIntBuf); err != nil { 96 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить идентификатор пакета: %v", err) 97 | } 98 | p.PacketIdentifier = binary.LittleEndian.Uint16(tmpIntBuf) 99 | 100 | if p.PacketType, err = buf.ReadByte(); err != nil { 101 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить тип пакета: %v", err) 102 | } 103 | 104 | if p.Route == "1" { 105 | if _, err = buf.Read(tmpIntBuf); err != nil { 106 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить адрес апк отправителя: %v", err) 107 | } 108 | p.PeerAddress = binary.LittleEndian.Uint16(tmpIntBuf) 109 | 110 | if _, err = buf.Read(tmpIntBuf); err != nil { 111 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить адрес апк получателя: %v", err) 112 | } 113 | p.RecipientAddress = binary.LittleEndian.Uint16(tmpIntBuf) 114 | 115 | if p.TimeToLive, err = buf.ReadByte(); err != nil { 116 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить TTL пакета: %v", err) 117 | } 118 | } 119 | 120 | if p.HeaderCheckSum, err = buf.ReadByte(); err != nil { 121 | return egtsPcIncHeaderform, fmt.Errorf("Не удалось получить crc заголовка: %v", err) 122 | } 123 | 124 | if p.HeaderCheckSum != crc8(content[:p.HeaderLength-1]) { 125 | return egtsPcHeaderCrcError, fmt.Errorf("Не верная сумма заголовка пакета") 126 | } 127 | 128 | dataFrameBytes := make([]byte, p.FrameDataLength) 129 | if _, err = buf.Read(dataFrameBytes); err != nil { 130 | return egtsPcIncDataform, fmt.Errorf("Не считать тело пакета: %v", err) 131 | } 132 | switch p.PacketType { 133 | case PtAppdataPacket: 134 | p.ServicesFrameData = &ServiceDataSet{} 135 | break 136 | case PtResponsePacket: 137 | p.ServicesFrameData = &PtResponse{} 138 | break 139 | default: 140 | return egtsPcUnsType, fmt.Errorf("Неизвестный тип пакета: %d", p.PacketType) 141 | } 142 | 143 | if isEncrypted { 144 | if secretKey == nil { 145 | return egtsPcDecryptError, errSecretKey 146 | } 147 | dataFrameBytes, err = secretKey.Decode(dataFrameBytes) 148 | if err != nil { 149 | return egtsPcDecryptError, err 150 | } 151 | } 152 | 153 | if err = p.ServicesFrameData.Decode(dataFrameBytes); err != nil { 154 | return egtsPcDecryptError, err 155 | } 156 | 157 | crcBytes := make([]byte, 2) 158 | if _, err = buf.Read(crcBytes); err != nil { 159 | return egtsPcDecryptError, fmt.Errorf("Не удалось считать crc16 пакета: %v", err) 160 | } 161 | p.ServicesFrameDataCheckSum = binary.LittleEndian.Uint16(crcBytes) 162 | 163 | if p.ServicesFrameDataCheckSum != crc16(content[p.HeaderLength:uint16(p.HeaderLength)+p.FrameDataLength]) { 164 | return egtsPcHeaderCrcError, fmt.Errorf("Не верная сумма тела пакета") 165 | } 166 | return egtsPcOk, err 167 | } 168 | 169 | // Encode кодирует струткуру в байтовую строку 170 | func (p *Package) Encode(opt ...func(*Options)) ([]byte, error) { 171 | var ( 172 | result []byte 173 | err error 174 | flags uint64 175 | ) 176 | 177 | options := &Options{} 178 | for _, o := range opt { 179 | o(options) 180 | } 181 | 182 | secretKey := options.Secret 183 | 184 | buf := new(bytes.Buffer) 185 | 186 | if err = buf.WriteByte(p.ProtocolVersion); err != nil { 187 | return result, fmt.Errorf("Не удалось записать версию протокола: %v", err) 188 | } 189 | if err = buf.WriteByte(p.SecurityKeyID); err != nil { 190 | return result, fmt.Errorf("Не удалось записать идентификатор ключа: %v", err) 191 | } 192 | 193 | //собираем флаги 194 | flagsBits := p.Prefix + p.Route + p.EncryptionAlg + p.Compression + p.Priority 195 | if flags, err = strconv.ParseUint(flagsBits, 2, 8); err != nil { 196 | return result, fmt.Errorf("Не удалось сгенерировать байт флагов: %v", err) 197 | } 198 | 199 | if err = buf.WriteByte(uint8(flags)); err != nil { 200 | return result, fmt.Errorf("Не удалось записать флаги: %v", err) 201 | } 202 | 203 | if p.HeaderLength == 0 { 204 | p.HeaderLength = DEFAULT_HEADER_LEN 205 | if p.Route == "1" { 206 | p.HeaderLength += 5 207 | } 208 | } 209 | 210 | if err = buf.WriteByte(p.HeaderLength); err != nil { 211 | return result, fmt.Errorf("Не удалось записать длину заголовка: %v", err) 212 | } 213 | 214 | if err = buf.WriteByte(p.HeaderEncoding); err != nil { 215 | return result, fmt.Errorf("Не удалось записать метод кодирования: %v", err) 216 | } 217 | 218 | var sfrd []byte 219 | if p.ServicesFrameData != nil { 220 | sfrd, err = p.ServicesFrameData.Encode() 221 | if err != nil { 222 | return result, err 223 | } 224 | 225 | if p.EncryptionAlg != "00" { 226 | if secretKey == nil { 227 | return result, errSecretKey 228 | } 229 | sfrd, err = secretKey.Encode(sfrd) 230 | if err != nil { 231 | return result, err 232 | } 233 | } 234 | } 235 | p.FrameDataLength = uint16(len(sfrd)) 236 | if err = binary.Write(buf, binary.LittleEndian, p.FrameDataLength); err != nil { 237 | return result, fmt.Errorf("Не удалось записать длину секции данных: %v", err) 238 | } 239 | 240 | if err = binary.Write(buf, binary.LittleEndian, p.PacketIdentifier); err != nil { 241 | return result, fmt.Errorf("Не удалось записать идентификатор пакета: %v", err) 242 | } 243 | 244 | if err = buf.WriteByte(p.PacketType); err != nil { 245 | return result, fmt.Errorf("Не удалось записать идентификатор пакета: %v", err) 246 | } 247 | 248 | if p.Route == "1" { 249 | if err = binary.Write(buf, binary.LittleEndian, p.PeerAddress); err != nil { 250 | return result, fmt.Errorf("Не удалось записать адрес апк отправителя: %v", err) 251 | } 252 | 253 | if err = binary.Write(buf, binary.LittleEndian, p.RecipientAddress); err != nil { 254 | return result, fmt.Errorf("Не удалось записать адрес апк получателя: %v", err) 255 | } 256 | 257 | if err = buf.WriteByte(p.TimeToLive); err != nil { 258 | return result, fmt.Errorf("Не удалось записать TTL пакета: %v", err) 259 | } 260 | } 261 | 262 | buf.WriteByte(crc8(buf.Bytes())) 263 | 264 | if p.FrameDataLength > 0 { 265 | buf.Write(sfrd) 266 | if err := binary.Write(buf, binary.LittleEndian, crc16(sfrd)); err != nil { 267 | return result, fmt.Errorf("Не удалось записать crc16 пакета: %v", err) 268 | } 269 | } 270 | 271 | result = buf.Bytes() 272 | return result, err 273 | } 274 | 275 | //ToBytes переводит пакет в json 276 | func (p *Package) ToBytes() ([]byte, error) { 277 | return json.Marshal(p) 278 | } 279 | -------------------------------------------------------------------------------- /docs/egts_sr_egtsplus_data.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | package nv; 3 | 4 | message storage_record 5 | { 6 | enum reason 7 | { 8 | DEVICE_RESET = 1; 9 | PROFILE_CHANGE = 2; 10 | IGNITION_ON = 3; 11 | IGNITION_OFF = 4; 12 | TRIP_BEGIN = 5; 13 | TRIP_END = 6; 14 | STOP = 7; 15 | MOVE = 8; 16 | TOWING_BEGIN = 9; 17 | TOWING_END = 10; 18 | TIMER_MOVE = 11; 19 | TIMER_STOP = 12; 20 | ANGLE = 13; 21 | DISTANCE = 14; 22 | SOS_BUTTON = 15; 23 | SERVICE_BUTTON = 16; 24 | TAMPER = 17; 25 | ANTENNA_SWITCH = 18; 26 | GSM_LOST = 19; 27 | GSM_RECONNECT = 20; 28 | GNSS_LOST = 21; 29 | GNSS_REAQUISITION = 22; 30 | JAMMER_GSM = 23; 31 | JAMMER_GNSS = 24; 32 | OVER_SPEED = 25; 33 | OVER_RPM = 26; 34 | OVER_TEMPERATURE = 27; 35 | DANGEROUS_DRIVING = 28; 36 | ACCIDENT = 29; 37 | OVERTHROW = 30; 38 | ON_BATTERY = 31; 39 | BATTERY_DISCHARGE = 32; 40 | RADIO_TAG_REG = 33; 41 | RADIO_TAG_UNREG = 34; 42 | MOVE_WITHOUT_RADIO_TAG = 35; 43 | ECU_ERROR_CODE = 36; 44 | EXTERNAL_REQUEST = 37; 45 | DEVICE_TEST = 38; 46 | OTHER_REASON = 99; 47 | } 48 | 49 | // -------------------------------------------------------- 50 | 51 | // Номер записи 52 | required uint32 record_number = 1; 53 | 54 | // Время отметки 55 | required fixed32 time_stamp = 2; 56 | 57 | // Причина формирования отметки (см. enum reason) 58 | repeated reason record_reason = 3; 59 | 60 | // Статусные флаги 61 | required fixed32 status_flags = 4; 62 | 63 | 64 | // -------------------------------------------------------- 65 | 66 | 67 | // Датчик ускорения 68 | repeated nv.sens_accelerometer_data sens_accelerometer_data = 18; 69 | 70 | // Аналоговый вход 71 | repeated nv.sens_ain_ain_value sens_ain_ain_value = 9; 72 | 73 | // Буферизованные данные 74 | repeated nv.sens_buffer_data sens_buffer_data = 20; 75 | 76 | // Кнопка 77 | repeated nv.sens_button_press_counter sens_button_press_counter = 14; 78 | 79 | // CANLog 80 | repeated nv.sens_can_log_data sens_can_log_data = 16; 81 | 82 | // Расширение CANLog 83 | repeated nv.sens_can_log_tmp_data_ext sens_can_log_tmp_data_ext = 17; 84 | 85 | // Счётчик 86 | repeated nv.sens_counter_count sens_counter_count = 11; 87 | 88 | // Датчик аварии 89 | repeated nv.sens_crash_data sens_crash_data = 24; 90 | 91 | // Цифровые входы 92 | repeated nv.sens_dins_flags sens_dins_flags = 10; 93 | 94 | // Цифровые выходы 95 | repeated nv.sens_douts_flags sens_douts_flags = 19; 96 | 97 | // Частотомер 98 | repeated nv.sens_fmeter_frequency sens_fmeter_frequency = 12; 99 | 100 | // Датчик топлива 101 | repeated nv.sens_fuel_level sens_fuel_level = 8; 102 | 103 | // Информация о базовых станциях GSM 104 | repeated nv.sens_gsm_cell_monotoring_cell_monitoring sens_gsm_cell_monotoring_cell_monitoring = 13; 105 | 106 | // Навигационные данные 107 | repeated nv.sens_nd_nav_data sens_nd_nav_data = 7; 108 | 109 | // Статистика хранилище навигационных данных 110 | repeated nv.sens_stor_info sens_stor_info = 21; 111 | 112 | // Датчик температуры 113 | repeated nv.sens_termo_data sens_termo_data = 15; 114 | 115 | // Тестовые данные ЭРА ГЛОНАСС 116 | repeated nv.sens_test_era_tests sens_test_era_tests = 23; 117 | 118 | // Статистика трэкера 119 | repeated nv.sens_tracker_info sens_tracker_info = 22; 120 | } 121 | 122 | // Датчик ускорения 123 | message sens_accelerometer_data 124 | { 125 | optional uint32 sens_num = 1; // Номер датчика 126 | 127 | optional bytes buf = 2; // Буфер с данными 128 | optional uint32 atm = 3; // UTC время формирования первой отметки в буфере 129 | optional uint32 frequency = 4; // Частота следования отметок в ГЦ деленная на 10 130 | optional uint32 range = 5; // Диапазон измерений акселерометра умноженный на 10 131 | optional uint32 format = 6; // Тип кодирования отметок (0 - 8-ми битовые значения со знаком, 1 - 16-ти битовые значения со знаком, 2 - 32-х битовые значения со знаком, 3 - в формате ASN1 PER ) 132 | optional uint32 zlib = 7; // Битовый флаг использования архиватора данных 133 | } 134 | 135 | // Аналоговый вход 136 | message sens_ain_ain_value 137 | { 138 | optional uint32 sens_num = 1; // Номер датчика 139 | 140 | optional uint32 mv = 2; // Значение датчика в милливольтах 141 | } 142 | 143 | // Буферизованные данные 144 | message sens_buffer_data 145 | { 146 | optional uint32 sens_num = 1; // Номер датчика 147 | 148 | optional bytes data = 2; // Данные 149 | optional bool is_packed = 3; // Признак того, что данные запакованы 150 | } 151 | 152 | // Кнопка 153 | message sens_button_press_counter 154 | { 155 | optional uint32 sens_num = 1; // Номер датчика 156 | 157 | optional bool state = 2; // Состояние кнопки (нажата или нет) 158 | } 159 | 160 | 161 | // CANLog 162 | message sens_can_log_data 163 | { 164 | optional uint32 sens_num = 1; // Номер датчика 165 | 166 | optional uint32 flag_security_state = 2; // Флаги работоспособности и безопасностности устройства 167 | optional uint32 flag_alarm = 3; // Флаги контроллеров аварии 168 | optional uint32 engine_time_all = 4; // Полное время работы двигателя, ч*100. Пример: 1ч 15мин = 125. 169 | optional uint32 engine_turn_speed = 5; // Скорость вращения двигателя, rpm 170 | optional sint32 engine_temperature = 6; // Температура двигателя, Ц. 171 | optional uint32 fuel_consumption_all = 7; // Полный расход топлива, л. 172 | optional uint32 fuel_level = 8; // bit0..bit14 - Уровень топлива. bit15- Единицы измерения. 1 - %, 0 - л. 173 | optional uint32 track_all = 9; // Полный пробег транспортного средства, км*100. 174 | optional uint32 speed = 10; // Скорость, км/ч. 175 | optional uint32 pressure_axis_1 = 11; // Давление на ось 1, кг*10 176 | optional uint32 pressure_axis_2 = 12; // Давление на ось 2, кг*10 177 | optional uint32 pressure_axis_3 = 13; // Давление на ось 3, кг*10 178 | optional uint32 pressure_axis_4 = 14; // Давление на ось 4, кг*10 179 | optional uint32 pressure_axis_5 = 15; // Давление на ось 5, кг*10 180 | } 181 | 182 | // Расширение CANLog 183 | message sens_can_log_tmp_data_ext 184 | { 185 | optional uint32 sens_num = 1; // Номер датчика 186 | 187 | optional uint32 flags_high = 2; // Флаги механизмов 188 | optional uint32 flags_low = 3; // Флаги механизмов 189 | } 190 | 191 | // Счётчик 192 | message sens_counter_count 193 | { 194 | optional uint32 sens_num = 1; // Номер датчика 195 | 196 | optional uint32 value = 2; // Значение счетчика 197 | } 198 | 199 | // Датчик аварии 200 | message sens_crash_data 201 | { 202 | optional uint32 sens_num = 1; // Номер датчика 203 | 204 | optional uint32 energy = 2; // Сила удара 205 | optional bool is_overturned = 3; // Признак переворота 206 | } 207 | 208 | // Цифровые входы 209 | message sens_dins_flags 210 | { 211 | optional uint32 sens_num = 1; // Номер датчика 212 | 213 | optional uint32 device = 2; // Входы прибора (каждый бит соответствует своему входу) 214 | optional uint32 external = 3; // Внешние входы (каждый бит соответствует своему входу) 215 | } 216 | 217 | // Цифровые выходы 218 | message sens_douts_flags 219 | { 220 | optional uint32 sens_num = 1; // Номер датчика 221 | 222 | optional uint32 device = 2; // Выходы прибора (каждый бит соответствует своему выходу) 223 | optional uint32 external = 3; // Внешние выходы (каждый бит соответствует своему выходу) 224 | } 225 | 226 | // Частотомер 227 | message sens_fmeter_frequency 228 | { 229 | optional uint32 sens_num = 1; // Номер датчика 230 | 231 | optional uint32 value = 2; // Значение частотомера 232 | } 233 | 234 | // Датчик топлива 235 | message sens_fuel_level 236 | { 237 | optional uint32 sens_num = 1; // Номер датчика 238 | 239 | optional float value = 2; // Значение датчика 240 | optional uint32 parrots = 3; // Значение датчика в условных единицах 241 | optional uint32 unit = 4; // Единица измерения для поля value (1 - литры, 2 - миллилитры) 242 | optional uint32 t = 5; // Температура датчика 243 | optional uint32 status = 6; // Статус датчика 244 | } 245 | 246 | // Информация о базовых станциях GSM 247 | message sens_gsm_cell_monotoring_cell_monitoring 248 | { 249 | optional uint32 sens_num = 1; // Номер датчика 250 | 251 | optional bytes lac = 4; // Код локальной зоны 252 | optional bytes cid = 5; // Идентификатор соты 253 | optional bytes rssi = 6; // Уровень принимаемого по данному каналу радиосигнала на входе в приёмник телефона 254 | optional bytes time_adv = 7; // Параметр компенсации времени прохождения сигнала от телефона до БС 255 | optional uint32 mcc = 2; // Код страны 256 | optional uint32 mnc = 3; // Код сотовой сети 257 | } 258 | 259 | // Навигационные данные 260 | message sens_nd_nav_data 261 | { 262 | optional uint32 sens_num = 1; // Номер датчика 263 | 264 | optional sfixed32 longitude = 2; // Долгота (в формате МНД ЭРА ГЛОНАСС) 265 | optional sfixed32 latitude = 3; // Широта (в формате МНД ЭРА ГЛОНАСС) 266 | optional uint32 altitude = 4; // Высота, м 267 | optional uint32 speed = 5; // Скорость, км/ч 268 | optional uint32 course = 6; // Курс (угол) 269 | optional uint32 sat_count = 7; // Количество спутников 270 | optional uint32 pdop = 8; // Погрешность позиционирования 271 | optional uint32 track = 9; // Трэк, м 272 | optional uint32 odometer = 10; // Одометр, м 273 | } 274 | 275 | // Статистика хранилище навигационных данных 276 | message sens_stor_info 277 | { 278 | optional uint32 sens_num = 1; // Номер датчика 279 | 280 | optional uint32 id_max = 2; // Идентификатор самой новой отметки 281 | optional uint32 id_min = 3; // Идентификатор самой старой отметки 282 | optional uint32 tm_oldest = 4; // Время создания самой старой отметки 283 | optional uint32 tm_oldest_unack = 5; // Время создания самой старой отметки, которая не подтверждена 284 | optional uint32 cnt_unack = 6; // Количество неподтвержденных отметок 285 | optional uint32 cnt_unack_losted = 7; // Количество потерянных неподтвержденных отметок 286 | } 287 | 288 | // Датчик температуры 289 | message sens_termo_data 290 | { 291 | optional uint32 sens_num = 1; // Номер датчика 292 | 293 | optional uint32 status = 2; // Статус датчика (0 - исправен, 1 - нет ответа, 2 - неисправность) 294 | optional sint32 temperature = 3; // Температура 295 | } 296 | 297 | // Тестовые данные ЭРА ГЛОНАСС 298 | message sens_test_era_tests 299 | { 300 | optional uint32 sens_num = 1; // Номер датчика 301 | 302 | optional bool mic_con_failure = 2; // Некорректное подключение микрофона 303 | optional bool mic_failure = 3; // Неработоспособность микрофона 304 | optional bool ignition_failure = 4; // Неисправность при определении состояния линии зажигания 305 | optional bool uim_failure = 5; // Неисправность БИП 306 | optional bool bat_failure = 6; // Неисправность резервной батареи 307 | optional bool bat_volt_low = 7; // Разряд резервной батарее ниже допустимого уровня 308 | optional bool crash_sens_failure = 8; // Неисправность датчика автоматической идентификации события ДТП 309 | optional bool raim_problem = 9; // Отсутствие целостности (достоверности) определяемых приемником ГНСС навигационно-временных параметров (функция RAIM) 310 | optional bool gnss_antenna_failure = 10; // Неработоспособность приемника ГНСС 311 | optional bool events_memory_overflow = 11; // Переполнение внутренней памяти событий 312 | } 313 | 314 | // Статистика трэкера 315 | message sens_tracker_info 316 | { 317 | optional uint32 sens_num = 1; // Номер датчика 318 | 319 | optional uint32 cnt_ack = 2; // Количество подтвержденных отметок 320 | optional uint32 cnt_ack_realtime = 3; // Количество подтвержденных отметок в качестве realtime 321 | optional uint32 cnt_noack = 4; // Количество отметок, которых не получилось отправить 322 | optional uint32 cnt_connect = 5; // Количество соединений с сервером 323 | } 324 | 325 | -------------------------------------------------------------------------------- /libs/egts/egts_pkg_test.go: -------------------------------------------------------------------------------- 1 | package egts 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | var ( 10 | egtsPkgPosDataBytes = []byte{0x01, 0x00, 0x03, 0x0B, 0x00, 0x23, 0x00, 0x8A, 0x00, 0x01, 0x49, 0x18, 0x00, 0x61, 11 | 0x00, 0x99, 0xB0, 0x09, 0x02, 0x00, 0x02, 0x02, 0x10, 0x15, 0x00, 0xD5, 0x3F, 0x01, 0x10, 0x6F, 0x1C, 0x05, 0x9E, 12 | 0x7A, 0xB5, 0x3C, 0x35, 0x01, 0xD0, 0x87, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0xCC, 0x27} 13 | ) 14 | 15 | func TestEgtsPackagePosData_Encode(t *testing.T) { 16 | egtsPkgPosData := Package{ 17 | ProtocolVersion: 1, 18 | SecurityKeyID: 0, 19 | Prefix: "00", 20 | Route: "0", 21 | EncryptionAlg: "00", 22 | Compression: "0", 23 | Priority: "11", 24 | HeaderEncoding: 0, 25 | FrameDataLength: 35, 26 | PacketIdentifier: 138, 27 | PacketType: 1, 28 | HeaderCheckSum: 73, 29 | ServicesFrameData: &ServiceDataSet{ 30 | ServiceDataRecord{ 31 | RecordNumber: 97, 32 | SourceServiceOnDevice: "1", 33 | RecipientServiceOnDevice: "0", 34 | Group: "0", 35 | RecordProcessingPriority: "11", 36 | TimeFieldExists: "0", 37 | EventIDFieldExists: "0", 38 | ObjectIDFieldExists: "1", 39 | ObjectIdentifier: 133552, 40 | SourceServiceType: 2, 41 | RecipientServiceType: 2, 42 | RecordDataSet: RecordDataSet{ 43 | RecordData{ 44 | SubrecordData: &SrPosData{ 45 | NavigationTime: time.Date(2018, time.July, 5, 20, 8, 53, 0, time.UTC), 46 | Latitude: 55.55389399769574, 47 | Longitude: 37.43236696287812, 48 | ALTE: "0", 49 | LOHS: "0", 50 | LAHS: "0", 51 | MV: "0", 52 | BB: "0", 53 | CS: "0", 54 | FIX: "0", 55 | VLD: "1", 56 | DirectionHighestBit: 1, 57 | AltitudeSign: 0, 58 | Speed: 200, 59 | Direction: 172, 60 | Odometer: 1, 61 | DigitalInputs: 0, 62 | Source: 0, 63 | }, 64 | }, 65 | }, 66 | }, 67 | }, 68 | } 69 | 70 | posDataBytes, err := egtsPkgPosData.Encode() 71 | if assert.NoError(t, err) { 72 | assert.Equal(t, posDataBytes, egtsPkgPosDataBytes) 73 | } 74 | } 75 | 76 | func TestEgtsPackagePosData_Decode(t *testing.T) { 77 | egtsPkg := Package{} 78 | egtsPkgPosData := Package{ 79 | ProtocolVersion: 1, 80 | SecurityKeyID: 0, 81 | Prefix: "00", 82 | Route: "0", 83 | EncryptionAlg: "00", 84 | Compression: "0", 85 | Priority: "11", 86 | HeaderLength: 11, 87 | HeaderEncoding: 0, 88 | FrameDataLength: 35, 89 | PacketIdentifier: 138, 90 | PacketType: 1, 91 | HeaderCheckSum: 73, 92 | ServicesFrameData: &ServiceDataSet{ 93 | ServiceDataRecord{ 94 | RecordLength: 24, 95 | RecordNumber: 97, 96 | SourceServiceOnDevice: "1", 97 | RecipientServiceOnDevice: "0", 98 | Group: "0", 99 | RecordProcessingPriority: "11", 100 | TimeFieldExists: "0", 101 | EventIDFieldExists: "0", 102 | ObjectIDFieldExists: "1", 103 | ObjectIdentifier: 133552, 104 | SourceServiceType: 2, 105 | RecipientServiceType: 2, 106 | RecordDataSet: RecordDataSet{ 107 | RecordData{ 108 | SubrecordType: 16, 109 | SubrecordLength: 21, 110 | SubrecordData: &SrPosData{ 111 | NavigationTime: time.Date(2018, time.July, 5, 20, 8, 53, 0, time.UTC), 112 | Latitude: 55.55389399769574, 113 | Longitude: 37.43236696287812, 114 | ALTE: "0", 115 | LOHS: "0", 116 | LAHS: "0", 117 | MV: "0", 118 | BB: "0", 119 | CS: "0", 120 | FIX: "0", 121 | VLD: "1", 122 | DirectionHighestBit: 1, 123 | AltitudeSign: 0, 124 | Speed: 200, 125 | Direction: 172, 126 | Odometer: 1, 127 | DigitalInputs: 0, 128 | Source: 0, 129 | }, 130 | }, 131 | }, 132 | }, 133 | }, 134 | ServicesFrameDataCheckSum: 10188, 135 | } 136 | 137 | if _, err := egtsPkg.Decode(egtsPkgPosDataBytes); assert.NoError(t, err) { 138 | assert.Equal(t, egtsPkg, egtsPkgPosData) 139 | } 140 | } 141 | 142 | func TestFullCycleCoding(t *testing.T) { 143 | egtsPkg := Package{} 144 | egtsPkgPosData := Package{ 145 | ProtocolVersion: 1, 146 | SecurityKeyID: 0, 147 | Prefix: "00", 148 | Route: "0", 149 | EncryptionAlg: "00", 150 | Compression: "0", 151 | Priority: "11", 152 | HeaderLength: 11, 153 | HeaderEncoding: 0, 154 | FrameDataLength: 35, 155 | PacketIdentifier: 138, 156 | PacketType: 1, 157 | HeaderCheckSum: 73, 158 | ServicesFrameData: &ServiceDataSet{ 159 | ServiceDataRecord{ 160 | RecordLength: 24, 161 | RecordNumber: 97, 162 | SourceServiceOnDevice: "1", 163 | RecipientServiceOnDevice: "0", 164 | Group: "0", 165 | RecordProcessingPriority: "11", 166 | TimeFieldExists: "0", 167 | EventIDFieldExists: "0", 168 | ObjectIDFieldExists: "1", 169 | ObjectIdentifier: 133552, 170 | SourceServiceType: 2, 171 | RecipientServiceType: 2, 172 | RecordDataSet: RecordDataSet{ 173 | RecordData{ 174 | SubrecordType: 16, 175 | SubrecordLength: 21, 176 | SubrecordData: &SrPosData{ 177 | NavigationTime: time.Date(2018, time.July, 5, 20, 8, 53, 0, time.UTC), 178 | Latitude: 55.55389399769574, 179 | Longitude: 37.43236696287812, 180 | ALTE: "0", 181 | LOHS: "0", 182 | LAHS: "0", 183 | MV: "0", 184 | BB: "0", 185 | CS: "0", 186 | FIX: "0", 187 | VLD: "1", 188 | DirectionHighestBit: 1, 189 | AltitudeSign: 0, 190 | Speed: 200, 191 | Direction: 172, 192 | Odometer: 1, 193 | DigitalInputs: 0, 194 | Source: 0, 195 | }, 196 | }, 197 | }, 198 | }, 199 | }, 200 | ServicesFrameDataCheckSum: 10188, 201 | } 202 | 203 | _, err := egtsPkg.Decode(egtsPkgPosDataBytes) 204 | if assert.NoError(t, err) { 205 | assert.Equal(t, egtsPkg, egtsPkgPosData) 206 | 207 | posDataBytes, err := egtsPkg.Encode() 208 | if assert.NoError(t, err) { 209 | assert.Equal(t, posDataBytes, egtsPkgPosDataBytes) 210 | } 211 | } 212 | } 213 | 214 | func TestRebuildCycleCoding(t *testing.T) { 215 | egtsPkg := Package{ 216 | ProtocolVersion: 1, 217 | SecurityKeyID: 0, 218 | Prefix: "00", 219 | Route: "0", 220 | EncryptionAlg: "00", 221 | Compression: "0", 222 | Priority: "10", 223 | HeaderLength: 11, 224 | HeaderEncoding: 0, 225 | FrameDataLength: 48, 226 | PacketIdentifier: 4608, 227 | PacketType: 1, 228 | HeaderCheckSum: 0x1b, 229 | ServicesFrameData: &ServiceDataSet{ 230 | ServiceDataRecord{ 231 | RecordLength: 37, 232 | RecordNumber: 134, 233 | SourceServiceOnDevice: "0", 234 | RecipientServiceOnDevice: "0", 235 | Group: "0", 236 | RecordProcessingPriority: "10", 237 | TimeFieldExists: "0", 238 | EventIDFieldExists: "1", 239 | ObjectIDFieldExists: "0", 240 | EventIdentifier: 3436, 241 | SourceServiceType: 2, 242 | RecipientServiceType: 2, 243 | RecordDataSet: RecordDataSet{ 244 | RecordData{ 245 | SubrecordType: 16, 246 | SubrecordLength: 24, 247 | SubrecordData: &SrPosData{ 248 | NavigationTime: time.Date(2021, time.February, 20, 0, 30, 40, 0, time.UTC), 249 | Latitude: 46.9429406935682, 250 | Longitude: 142.732571163851, 251 | ALTE: "1", 252 | LOHS: "0", 253 | LAHS: "0", 254 | MV: "1", 255 | BB: "1", 256 | CS: "0", 257 | FIX: "1", 258 | VLD: "1", 259 | DirectionHighestBit: 0, 260 | AltitudeSign: 0, 261 | Speed: 34, 262 | Direction: 172, 263 | Odometer: 191, 264 | DigitalInputs: 144, 265 | Source: 0, 266 | Altitude: 30, 267 | }, 268 | }, 269 | RecordData{ 270 | SubrecordType: 27, 271 | SubrecordLength: 7, 272 | SubrecordData: &SrLiquidLevelSensor{ 273 | LiquidLevelSensorErrorFlag: "1", 274 | LiquidLevelSensorValueUnit: "00", 275 | RawDataFlag: "0", 276 | LiquidLevelSensorNumber: 1, 277 | ModuleAddress: uint16(1), 278 | LiquidLevelSensorData: uint32(0), 279 | }, 280 | }, 281 | }, 282 | }, 283 | }, 284 | } 285 | p := []byte{0x01, 0x00, 0x02, 0x0b, 0x00, 0x30, 0x00, 0x00, 0x12, 0x01, 0x84, 0x25, 0x00, 0x86, 0x00, 286 | 0x12, 0x6c, 0x0d, 0x00, 0x00, 0x02, 0x02, 0x10, 0x18, 0x00, 0x30, 0x1d, 0xf3, 0x14, 0x65, 0xce, 287 | 0x86, 0x85, 0xde, 0x57, 0xff, 0xca, 0x9b, 0x54, 0x01, 0xac, 0xbf, 0x00, 0x00, 0x90, 0x00, 0x1e, 288 | 0x00, 0x00, 0x1b, 0x07, 0x00, 0x41, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xe8} 289 | 290 | encodePkg, err := egtsPkg.Encode() 291 | if assert.NoError(t, err) { 292 | assert.Equal(t, encodePkg, p) 293 | 294 | dp := Package{} 295 | _, err = dp.Decode(encodePkg) 296 | assert.NoError(t, err) 297 | } 298 | } 299 | 300 | func TestRebuildOID(t *testing.T) { 301 | egtsPkg := Package{ 302 | ProtocolVersion: 1, 303 | SecurityKeyID: 0, 304 | Prefix: "00", 305 | Route: "0", 306 | EncryptionAlg: "00", 307 | Compression: "0", 308 | Priority: "10", 309 | HeaderLength: 11, 310 | HeaderEncoding: 0, 311 | FrameDataLength: 48, 312 | PacketIdentifier: 4608, 313 | PacketType: 1, 314 | HeaderCheckSum: 0x1b, 315 | ServicesFrameData: &ServiceDataSet{ 316 | ServiceDataRecord{ 317 | RecordLength: 37, 318 | RecordNumber: 134, 319 | SourceServiceOnDevice: "0", 320 | RecipientServiceOnDevice: "0", 321 | Group: "0", 322 | RecordProcessingPriority: "10", 323 | TimeFieldExists: "0", 324 | EventIDFieldExists: "1", 325 | ObjectIDFieldExists: "1", 326 | EventIdentifier: 3436, 327 | ObjectIdentifier: 326009033, 328 | SourceServiceType: 2, 329 | RecipientServiceType: 2, 330 | RecordDataSet: RecordDataSet{ 331 | RecordData{ 332 | SubrecordType: 16, 333 | SubrecordLength: 24, 334 | SubrecordData: &SrPosData{ 335 | NavigationTime: time.Date(2021, time.February, 20, 0, 30, 40, 0, time.UTC), 336 | Latitude: 46.9429406935682, 337 | Longitude: 142.732571163851, 338 | ALTE: "1", 339 | LOHS: "0", 340 | LAHS: "0", 341 | MV: "1", 342 | BB: "1", 343 | CS: "0", 344 | FIX: "1", 345 | VLD: "1", 346 | DirectionHighestBit: 0, 347 | AltitudeSign: 0, 348 | Speed: 34, 349 | Direction: 172, 350 | Odometer: 191, 351 | DigitalInputs: 144, 352 | Source: 0, 353 | Altitude: 30, 354 | }, 355 | }, 356 | RecordData{ 357 | SubrecordType: 27, 358 | SubrecordLength: 7, 359 | SubrecordData: &SrLiquidLevelSensor{ 360 | LiquidLevelSensorErrorFlag: "1", 361 | LiquidLevelSensorValueUnit: "00", 362 | RawDataFlag: "0", 363 | LiquidLevelSensorNumber: 1, 364 | ModuleAddress: uint16(1), 365 | LiquidLevelSensorData: uint32(0), 366 | }, 367 | }, 368 | }, 369 | }, 370 | }, 371 | } 372 | 373 | encodePkg, err := egtsPkg.Encode() 374 | if assert.NoError(t, err) { 375 | p := Package{} 376 | 377 | _, err = p.Decode(encodePkg) 378 | assert.NoError(t, err) 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /cli/receiver/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/kuznetsovin/egts-protocol/cli/receiver/storage" 6 | "github.com/kuznetsovin/egts-protocol/libs/egts" 7 | log "github.com/sirupsen/logrus" 8 | "io" 9 | "net" 10 | "time" 11 | ) 12 | 13 | const ( 14 | egtsPcOk = 0 15 | headerLen = 10 16 | ) 17 | 18 | type Server struct { 19 | addr string 20 | ttl time.Duration 21 | store *storage.Repository 22 | l net.Listener 23 | } 24 | 25 | func (s *Server) Run() { 26 | var err error 27 | 28 | s.l, err = net.Listen("tcp", s.addr) 29 | if err != nil { 30 | log.Fatalf("Не удалось открыть соединение: %v", err) 31 | } 32 | defer s.l.Close() 33 | 34 | log.Infof("Запущен сервер %s...", s.addr) 35 | for { 36 | conn, err := s.l.Accept() 37 | if err != nil { 38 | log.WithField("err", err).Errorf("Ошибка соединения") 39 | } else { 40 | go s.handleConn(conn) 41 | } 42 | } 43 | } 44 | 45 | func (s *Server) Stop() error { 46 | if s.l != nil { 47 | return s.l.Close() 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (s *Server) handleConn(conn net.Conn) { 54 | defer conn.Close() 55 | 56 | var ( 57 | isPkgSave bool 58 | srResultCodePkg []byte 59 | serviceType uint8 60 | srResponsesRecord egts.RecordDataSet 61 | recvPacket []byte 62 | client uint32 63 | ) 64 | 65 | if s.store == nil { 66 | log.Error("Не корректная ссылка на объект хранилища") 67 | return 68 | } 69 | log.WithField("ip", conn.RemoteAddr()).Info("Установлено соединение") 70 | 71 | for { 72 | Received: 73 | serviceType = 0 74 | srResponsesRecord = nil 75 | srResultCodePkg = nil 76 | recvPacket = nil 77 | 78 | connTimer := time.NewTimer(s.ttl) 79 | 80 | // считываем заголовок пакета 81 | headerBuf := make([]byte, headerLen) 82 | _, err := io.ReadFull(conn, headerBuf) 83 | 84 | switch err { 85 | case nil: 86 | connTimer.Reset(s.ttl) 87 | 88 | // если пакет не егтс формата закрываем соединение 89 | if headerBuf[0] != 0x01 { 90 | log.WithField("ip", conn.RemoteAddr()).Warn("Пакет не соответствует формату ЕГТС. Закрыто соединение") 91 | return 92 | } 93 | 94 | // вычисляем длину пакета, равную длине заголовка (HL) + длина тела (FDL) + CRC пакета 2 байта если есть FDL из приказа минтранса №285 95 | bodyLen := binary.LittleEndian.Uint16(headerBuf[5:7]) 96 | pkgLen := uint16(headerBuf[3]) 97 | if bodyLen > 0 { 98 | pkgLen += bodyLen + 2 99 | } 100 | // получаем концовку ЕГТС пакета 101 | buf := make([]byte, pkgLen-headerLen) 102 | if _, err := io.ReadFull(conn, buf); err != nil { 103 | log.WithField("err", err).Error("Ошибка при получении тела пакета") 104 | return 105 | } 106 | 107 | // формируем полный пакет 108 | recvPacket = append(headerBuf, buf...) 109 | case io.EOF: 110 | <-connTimer.C 111 | log.WithField("ip", conn.RemoteAddr()).Warnf("Соединение закрыто по таймауту") 112 | return 113 | default: 114 | log.WithField("err", err).Error("Ошибка при получении") 115 | return 116 | } 117 | 118 | log.WithField("packet", recvPacket).Debug("Принят пакет") 119 | pkg := egts.Package{} 120 | receivedTimestamp := time.Now().UTC().Unix() 121 | resultCode, err := pkg.Decode(recvPacket) 122 | if err != nil { 123 | log.WithField("err", err).Error("Ошибка расшифровки пакета") 124 | 125 | resp, err := createPtResponse(pkg.PacketIdentifier, resultCode, serviceType, nil) 126 | if err != nil { 127 | log.WithField("err", err).Error("Ошибка сборки ответа EGTS_PT_RESPONSE с ошибкой") 128 | goto Received 129 | } 130 | _, _ = conn.Write(resp) 131 | 132 | goto Received 133 | } 134 | 135 | switch pkg.PacketType { 136 | case egts.PtAppdataPacket: 137 | log.Debug("Тип пакета EGTS_PT_APPDATA") 138 | 139 | for _, rec := range *pkg.ServicesFrameData.(*egts.ServiceDataSet) { 140 | exportPacket := storage.NavRecord{ 141 | PacketID: uint32(pkg.PacketIdentifier), 142 | } 143 | 144 | isPkgSave = false 145 | packetIDBytes := make([]byte, 4) 146 | 147 | srResponsesRecord = append(srResponsesRecord, egts.RecordData{ 148 | SubrecordType: egts.SrRecordResponseType, 149 | SubrecordLength: 3, 150 | SubrecordData: &egts.SrResponse{ 151 | ConfirmedRecordNumber: rec.RecordNumber, 152 | RecordStatus: egtsPcOk, 153 | }, 154 | }) 155 | serviceType = rec.SourceServiceType 156 | log.Info("Тип сервиса ", serviceType) 157 | 158 | // если в секции с данными есть oid то обновляем его 159 | if rec.ObjectIDFieldExists == "1" { 160 | client = rec.ObjectIdentifier 161 | } 162 | 163 | for _, subRec := range rec.RecordDataSet { 164 | switch subRecData := subRec.SubrecordData.(type) { 165 | case *egts.SrTermIdentity: 166 | log.Debug("Разбор подзаписи EGTS_SR_TERM_IDENTITY") 167 | 168 | // на случай если секция с данными не содержит oid 169 | client = subRecData.TerminalIdentifier 170 | 171 | if srResultCodePkg, err = createSrResultCode(pkg.PacketIdentifier, egtsPcOk); err != nil { 172 | log.Errorf("Ошибка сборки EGTS_SR_RESULT_CODE: %v", err) 173 | } 174 | case *egts.SrAuthInfo: 175 | log.Debug("Разбор подзаписи EGTS_SR_AUTH_INFO") 176 | if srResultCodePkg, err = createSrResultCode(pkg.PacketIdentifier, egtsPcOk); err != nil { 177 | log.Errorf("Ошибка сборки EGTS_SR_RESULT_CODE: %v", err) 178 | } 179 | case *egts.SrResponse: 180 | log.Debugf("Разбор подзаписи EGTS_SR_RESPONSE") 181 | goto Received 182 | case *egts.SrPosData: 183 | log.Debugf("Разбор подзаписи EGTS_SR_POS_DATA") 184 | isPkgSave = true 185 | 186 | exportPacket.NavigationTimestamp = subRecData.NavigationTime.Unix() 187 | exportPacket.ReceivedTimestamp = receivedTimestamp 188 | exportPacket.Latitude = subRecData.Latitude 189 | exportPacket.Longitude = subRecData.Longitude 190 | exportPacket.Speed = subRecData.Speed 191 | exportPacket.Course = subRecData.Direction 192 | case *egts.SrExtPosData: 193 | log.Debug("Разбор подзаписи EGTS_SR_EXT_POS_DATA") 194 | exportPacket.Nsat = subRecData.Satellites 195 | exportPacket.Pdop = subRecData.PositionDilutionOfPrecision 196 | exportPacket.Hdop = subRecData.HorizontalDilutionOfPrecision 197 | exportPacket.Vdop = subRecData.VerticalDilutionOfPrecision 198 | exportPacket.Ns = subRecData.NavigationSystem 199 | 200 | case *egts.SrAdSensorsData: 201 | log.Debug("Разбор подзаписи EGTS_SR_AD_SENSORS_DATA") 202 | if subRecData.AnalogSensorFieldExists1 == "1" { 203 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 1, Value: subRecData.AnalogSensor1}) 204 | } 205 | 206 | if subRecData.AnalogSensorFieldExists2 == "1" { 207 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 2, Value: subRecData.AnalogSensor2}) 208 | } 209 | 210 | if subRecData.AnalogSensorFieldExists3 == "1" { 211 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 3, Value: subRecData.AnalogSensor3}) 212 | } 213 | if subRecData.AnalogSensorFieldExists4 == "1" { 214 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 4, Value: subRecData.AnalogSensor4}) 215 | } 216 | if subRecData.AnalogSensorFieldExists5 == "1" { 217 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 5, Value: subRecData.AnalogSensor5}) 218 | } 219 | if subRecData.AnalogSensorFieldExists6 == "1" { 220 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 6, Value: subRecData.AnalogSensor6}) 221 | } 222 | if subRecData.AnalogSensorFieldExists7 == "1" { 223 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 7, Value: subRecData.AnalogSensor7}) 224 | } 225 | if subRecData.AnalogSensorFieldExists8 == "1" { 226 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: 8, Value: subRecData.AnalogSensor8}) 227 | } 228 | case *egts.SrAbsAnSensData: 229 | log.Debug("Разбор подзаписи EGTS_SR_ABS_AN_SENS_DATA") 230 | exportPacket.AnSensors = append(exportPacket.AnSensors, storage.AnSensor{SensorNumber: subRecData.SensorNumber, Value: subRecData.Value}) 231 | 232 | case *egts.SrAbsCntrData: 233 | log.Debug("Разбор подзаписи EGTS_SR_ABS_CNTR_DATA") 234 | 235 | switch subRecData.CounterNumber { 236 | case 110: 237 | // Три младших байта номера передаваемой записи (идет вместе с каждой POS_DATA). 238 | binary.BigEndian.PutUint32(packetIDBytes, subRecData.CounterValue) 239 | exportPacket.PacketID = subRecData.CounterValue 240 | case 111: 241 | // один старший байт номера передаваемой записи (идет вместе с каждой POS_DATA). 242 | tmpBuf := make([]byte, 4) 243 | binary.BigEndian.PutUint32(tmpBuf, subRecData.CounterValue) 244 | 245 | if len(packetIDBytes) == 4 { 246 | packetIDBytes[3] = tmpBuf[3] 247 | } else { 248 | packetIDBytes = tmpBuf 249 | } 250 | 251 | exportPacket.PacketID = binary.LittleEndian.Uint32(packetIDBytes) 252 | } 253 | case *egts.SrLiquidLevelSensor: 254 | log.Debug("Разбор подзаписи EGTS_SR_LIQUID_LEVEL_SENSOR") 255 | sensorData := storage.LiquidSensor{ 256 | SensorNumber: subRecData.LiquidLevelSensorNumber, 257 | ErrorFlag: subRecData.LiquidLevelSensorErrorFlag, 258 | } 259 | 260 | switch subRecData.LiquidLevelSensorValueUnit { 261 | case "00", "01": 262 | sensorData.ValueMm = subRecData.LiquidLevelSensorData 263 | case "10": 264 | sensorData.ValueL = subRecData.LiquidLevelSensorData * 10 265 | } 266 | 267 | exportPacket.LiquidSensors = append(exportPacket.LiquidSensors, sensorData) 268 | } 269 | } 270 | 271 | exportPacket.Client = client 272 | if isPkgSave { 273 | if err := s.store.Save(&exportPacket); err != nil { 274 | log.WithField("err", err).Error("Ошибка сохранения телеметрии") 275 | } 276 | } 277 | } 278 | 279 | resp, err := createPtResponse(pkg.PacketIdentifier, resultCode, serviceType, srResponsesRecord) 280 | if err != nil { 281 | log.WithField("err", err).Error("Ошибка сборки ответа") 282 | goto Received 283 | } 284 | _, _ = conn.Write(resp) 285 | 286 | log.WithField("packet", resp).Debug("Отправлен пакет EGTS_PT_RESPONSE") 287 | 288 | if len(srResultCodePkg) > 0 { 289 | _, _ = conn.Write(srResultCodePkg) 290 | log.WithField("packet", resp).Debug("Отправлен пакет EGTS_SR_RESULT_CODE") 291 | } 292 | case egts.PtResponsePacket: 293 | log.Debug("Тип пакета EGTS_PT_RESPONSE") 294 | } 295 | 296 | } 297 | } 298 | 299 | func New(srvAddress string, ttl time.Duration, s *storage.Repository) Server { 300 | return Server{ 301 | addr: srvAddress, 302 | ttl: ttl, 303 | store: s, 304 | } 305 | } 306 | 307 | func createPtResponse(pid uint16, resultCode, serviceType uint8, srResponses egts.RecordDataSet) ([]byte, error) { 308 | respSection := egts.PtResponse{ 309 | ResponsePacketID: pid, 310 | ProcessingResult: resultCode, 311 | } 312 | 313 | if srResponses != nil { 314 | respSection.SDR = &egts.ServiceDataSet{ 315 | egts.ServiceDataRecord{ 316 | RecordLength: srResponses.Length(), 317 | RecordNumber: 1, 318 | SourceServiceOnDevice: "0", 319 | RecipientServiceOnDevice: "0", 320 | Group: "1", 321 | RecordProcessingPriority: "00", 322 | TimeFieldExists: "0", 323 | EventIDFieldExists: "0", 324 | ObjectIDFieldExists: "0", 325 | SourceServiceType: serviceType, 326 | RecipientServiceType: serviceType, 327 | RecordDataSet: srResponses, 328 | }, 329 | } 330 | } 331 | 332 | respPkg := egts.Package{ 333 | ProtocolVersion: 1, 334 | SecurityKeyID: 0, 335 | Prefix: "00", 336 | Route: "0", 337 | EncryptionAlg: "00", 338 | Compression: "0", 339 | Priority: "00", 340 | HeaderLength: 11, 341 | HeaderEncoding: 0, 342 | FrameDataLength: respSection.Length(), 343 | PacketIdentifier: pid + 1, 344 | PacketType: egts.PtResponsePacket, 345 | ServicesFrameData: &respSection, 346 | } 347 | 348 | return respPkg.Encode() 349 | } 350 | 351 | func createSrResultCode(pid uint16, resultCode uint8) ([]byte, error) { 352 | rds := egts.RecordDataSet{ 353 | egts.RecordData{ 354 | SubrecordType: egts.SrResultCodeType, 355 | SubrecordLength: uint16(1), 356 | SubrecordData: &egts.SrResultCode{ 357 | ResultCode: resultCode, 358 | }, 359 | }, 360 | } 361 | 362 | sfd := egts.ServiceDataSet{ 363 | egts.ServiceDataRecord{ 364 | RecordLength: rds.Length(), 365 | RecordNumber: 1, 366 | SourceServiceOnDevice: "0", 367 | RecipientServiceOnDevice: "0", 368 | Group: "1", 369 | RecordProcessingPriority: "00", 370 | TimeFieldExists: "0", 371 | EventIDFieldExists: "0", 372 | ObjectIDFieldExists: "0", 373 | SourceServiceType: egts.AuthService, 374 | RecipientServiceType: egts.AuthService, 375 | RecordDataSet: rds, 376 | }, 377 | } 378 | 379 | respPkg := egts.Package{ 380 | ProtocolVersion: 1, 381 | SecurityKeyID: 0, 382 | Prefix: "00", 383 | Route: "0", 384 | EncryptionAlg: "00", 385 | Compression: "0", 386 | Priority: "00", 387 | HeaderLength: 11, 388 | HeaderEncoding: 0, 389 | FrameDataLength: sfd.Length(), 390 | PacketIdentifier: pid + 1, 391 | PacketType: egts.PtResponsePacket, 392 | ServicesFrameData: &sfd, 393 | } 394 | 395 | return respPkg.Encode() 396 | } 397 | --------------------------------------------------------------------------------