Шлюз для отримання даних СКТ Глобус через websocket

Сервіс, який служить для конвертації старого протоколу (PC клієнт СКТ Глобус) в API для доступу до даних про переміщення транспортного засобу через Websocket.

Протоколом є посилання даних у форматі json. Дані від сервера можуть бути як відповідями на запит клієнта, так і асинхронними посилками, викликаними приходом даних від пристроїв при зміні стану (положення, датчиків і т.д.)

Будь-яке надсилання даних має вигляд json об’єкта. Тип запиту визначається значенням поля name.

Запити від клієнта до сервера

Авторизація “auth”

Після встановлення з’єднання з сервером за протоколом websocket клієнт повинен авторизуватися. Для цього є один з варіантів запиту:

1.Авторизація за допомогою імені користувача та пароля

Cхема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["auth"] },
        "login": { "type": "string" },
        "password": { "type": "string" }
    }
    "required": ["name", "login", "password"]
}

Приклад:

{
    "name": "auth",
    "login": "ім'я користувача",
    "password": "пароль"
}

2. Авторизація за допомогою ідентифікатора сесії

Цей варіант потрібен, коли вже є ідентифікатор сесії, отриманий для даного логіну та пароля за допомогою api авторизації

Схема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["auth"] },
        "sid": { 
            "type": "string",
            "description": "id сессії, отриманої шляхом авторизації через api"
        }
    },
    "required": ["name", "sid"]
}

Приклад:

{
    "name": "auth",
    "sid": "880d1335818669842d086600d97f7ee9b7c69497ddfc3ba9ed8ad42457ced967"
}

Вілповідь:

У відповідь сервер повинен надіслати повідомлення з таким самим типом auth і полем successful типу boolean, яке означатиме вдалу (true) або невдалу (false) спробу авторизації.

Схема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["auth"] },
        "successful": { "type": "boolean" },
        "sid": {
            "type": "string",
            "description": "id сесії для використання в запитах до REST API"
        }
    },
    "required": ["name", "successful"]
}

Приклад:

{
    "name": "auth",
    "successful": true
}

Після невдалої спроби авторизації, сервер закриває з’єднання з клієнтом. Після успішної – клієнт може надсилати інші запити серверу, і навіть сервер може надсилати дані клієнту.

Запит списку пристроїв “devicesList”
Після успішної авторизації клієнт може запросити список пристроїв із сервера.

Схема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["devicesList"] }
    },
    "required": ["name"]
}

Приклад:

{
    "name": "devicesList"
}

Відповідь зі списком пристроїв

Схема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["devicesList"] },
        "devices": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "regId": {
                        "type": "integer",
                        "description": "Унікальний номер пристрою у системі"
                    },
                    "name": {
                        "type": "string",
                        "description": "Довільна назва пристрою"
                    },
                    "lastKnownTime": {
                        "type": "string",
                        "description": "Час останнього отримання даних у форматі ISO 8601 ( https://ru.wikipedia.org/wiki/ISO_8601 )"
                    },
                    "latitude": {
                        "type": "number"
                    },
                    "longitude: {
                        "type": "number"
                    },
                    "speed": {
                        "type": "number",
                        "description": "Середня швидкість на останній ділянці у метрах в секунду"
                    },
                    "group" {
                        "type": "string",
                        "description": "Група, до якої належить цей пристрій. Якщо цього поля немає - то пристрій у кореневій групі"
                    },
                    "reportPeriods" {
                        "type": "array",
                        "items": {
                            "type": "integer"
                        },
                        "minItems": 2,
                        "maxItems": 2,
                        "description": "Періоди передачі при увімкненому та вимкненому запалюванні"
                    },
                    "ignition" {
                        "type": "object",
                        "properties": {
                            "on": {
                                "type": "boolean",
                                "description": "Чи увімкнено запалювання"
                            },
                            "used": {
                                "type": "boolean",
                                "description": "Чи використовується датчик запалювання на цьому пристрої"
                            }
                        },
                        "required": [ "on", "used" ]
                    },
                    "gpsAntennaStatus": {
                        "type": "object",
                        "properties": {
                            "disconnected": {
                                "type": "boolean",
                                "description": "Антена вимкнена"
                            },
                            "shortCircuit": {
                                "type": "boolean",
                                "description": "Антена замкнута"
                            }
                        },
                        "required": [ "disconnected", "shortCircuit" ],
                        "description": "Стан GPS антени (якщо цього поля немає - то антена в порядку)"
                    },
                    "sensorsValues": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "num": {
                                    "type": "integer",
                                    "description": "Номер датчика"
                                },
                                "name": {
                                    "type": "string",
                                    "description": "Назва датчика для відображення користувачу"
                                },
                                "rawValue": {
                                    "type": "number",
                                    "description": "Початкове значення датчика до застосування перерахування на сервері (для налагоджувального режиму)"
                                },
                                "scaledValue": {
                                    "type": "number",
                                    "description": "Значення датчика уже в потрібних величинах"
                                },
                                "displayValue": {
                                    "type": "string",
                                    "description": "Відформатоване для показу користувачу значення (з потрібною кількістю знаків після коми, одиницями вимірювання і т.д."
                                },
                                "flags" {
                                    "type": "object",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "isFuelLevel": {
                                                "type": "boolean",
                                                "description": "Чи є ДРП"
                                            },
                                            "isFuelMeter": {
                                                "type": "boolean",
                                                "description": "Чи є витратоміром палива"
                                            }
                                        },
                                        "required": [ "isFuelLevel", "isFuelMeter" ]
                                    }
                                }
                            },
                            "required": [ "num", "name", "flags" ]
                        }
                    }
                },
                "required": ["regId", "name", "lastKnownTime", "reportPeriods", "ignition"]
            }
        }
    },
    "required": ["name", "devices"]
}

Приклад:

{
    "devices": [
        {
            "ignition": {
                "on": false,
                "used": true
            },
            "name": "\"Мерседес - Бенц АР 7926 СВ\"",
            "latitude": 47.41034666666667,
            "reportPeriods": [60, 600],
            "longitude": 34.820725,
            "regId": 52857,
            "sensorsValues": [{
                "name": "\"БортСеть\"",
                "rawValue": 404.0,
                "flags": {
                    "isFuelLevel": false,
                    "isFuelMeter": false
                },
                "num": 1,
                "displayValue": "\"13,03 В\"",
                "scaledValue": 13.032257856000001
            }],
            "lastKnownTime": "2016-05-10T07:54:06Z",
            "speed": 0.0
        },
        {
            "ignition": {
                "on": false,
                "used": true
            },
            "name": "\"KAMAZ 4308\"",
            "latitude": 47.852666666666664,
            "reportPeriods": [30, 300],
            "longitude": 35.23113166666667,
            "regId": 8908,
            "sensorsValues": [
                {
                    "name": "\"Термодатчик 1\"",
                    "rawValue": 230.0,
                    "flags": {
                        "isFuelLevel": false,
                        "isFuelMeter": false
                    },
                    "num": 0,
                    "displayValue": "\"20,9 ℃\"",
                    "scaledValue": 20.872131147540983
                },
                {
                    "name": "\"Термодатчик 2\"",
                    "rawValue": 230.0,
                    "flags": {
                        "isFuelLevel": false,
                        "isFuelMeter": false
                    },
                    "num": 7,
                    "displayValue": "\"20,9 ℃\"",
                    "scaledValue": 20.872131147540983
                },
                {
                    "name": "\"Рівень палива\"",
                    "rawValue": 876.0,
                    "flags": {
                        "isFuelLevel": true,
                        "isFuelMeter": false
                    },
                    "num": 27,
                    "displayValue": "\"101,36 л\"",
                    "scaledValue": 101.36363636363637
                }
            ],
            "lastKnownTime": "2016-05-13T13:27:45Z",
            "speed": 0.0
        },
        {
            "ignition": {
                "on": false,
                "used": true
            },
            "name": "\"ГазельАР01-37СМ\"",
            "latitude": 47.262431666666664,
            "reportPeriods": [30, 600],
            "longitude": 35.70977,
            "regId": 14041,
            "sensorsValues": [],
            "lastKnownTime": "2016-05-13T13:24:13Z",
            "speed": 0.0
        },
        {
            "ignition": {
                "on": true,
                "used": false
            },
            "name": "\"Газель-пропан\"",
            "latitude": 48.20518333333333,
            "reportPeriods": [30, 600],
            "longitude": 34.943553333333334,
            "regId": 9533,
            "sensorsValues": [],
            "lastKnownTime": "2016-05-13T13:30:12Z",
            "speed": 0.23
        },
        {
            "ignition": {
                "on": false,
                "used": true
            },
            "name": "\"Газель-дизель АР8462СА\"",
            "latitude": 47.85151166666667,
            "reportPeriods": [30, 600],
            "longitude": 35.236088333333335,
            "regId": 9421,
            "sensorsValues": [{
                "name": "\"Рівень палива\"",
                "rawValue": 627.0,
                "flags": {
                    "isFuelLevel": true,
                    "isFuelMeter": false
                },
                "num": 27,
                "displayValue": "\"6,32 л\"",
                "scaledValue": 6.3157894736842195
            }],
            "lastKnownTime": "2016-05-13T13:22:29Z",
            "speed": 0.0
        },
        {
            "ignition": {
                "on": false,
                "used": true
            },
            "name": "\"Газель - Метан АР 6451 ВО\"",
            "latitude": 47.85129666666667,
            "reportPeriods": [60, 600],
            "longitude": 35.209718333333335,
            "regId": 52858,
            "sensorsValues": [{
                "name": "\"БортСеть\"",
                "rawValue": 401.0,
                "flags": {
                    "isFuelLevel": false,
                    "isFuelMeter": false
                },
                "num": 1,
                "displayValue": "\"12,94 В\"",
                "scaledValue": 12.935483664000001
            }],
            "lastKnownTime": "2016-05-13T13:23:03Z",
            "speed": 0.0
        }],
    "name": "devicesList"
}

Дані від сервера до клієнта
Крім описаних вище відповідей на запити, сервер також може передавати дані у міру отримання від пристроїв. Так як отримання цих даних має асинхронну природу – вони можуть почати приходити відразу ж після успішної авторизації, до отримання списку пристроїв. Також слід врахувати, що дані від пристрою з новітньою міткою часу можуть надійти до отримання списку пристроїв з даними з старішою міткою часу. Тобто. дані у списку пристроїв будуть старішими за ті дані від пристрою, які прийшли раніше.

Положення пристрою “location”

Передаються при отриманні сервером нових даних від пристрою.

Схема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["location"] }
        "regId": {
            "type": "integer",
            "description": "Номер пристрою для якого передаються дані"
        },
        "time": {
            "type": "string",
            "description": "Час фіксації координат у форматі ISO 8601"
        },
        "latitide": { "type": "number" },
        "longitude": { "type": "number" },
        "speed": {
            "type": "number",
            "description": "Середня швидкість у метрах в секунду на ділянці від минулої точки маршруту"
        }
    }
    "required": [ "name", "regId", "time", "latitude", "longitude", "speed" ]
}

Значення датчиків “sensorValue”

Передаються після надсилання пристрою як окреме повідомлення. Відносяться до попереднього отриманого положення пристрою з тим же номером (тобто записані в той же час, що й попередні дані про положення).

Схема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["sensorValue"] },
        "regId": {
            "type": "integer",
            "description": "Номер пристрою для якого передаються дані"
        },
        "num": {
            "type": "integer",
            "description": "Номер датчика. Такий же, як і у значеннях датчика в devicesList"
        },
        "rawValue": { "type": "number" },
        "scaledValue": { "type": "number" },
        "displayValue": { "type": "string" },
    }
    "required": [ "name", "regId", "num", "rawValue", "scaledValue", "displayValue" ]
}

Значення полів – ті ж, що й у даних про дачники devicesList, правда назва датчика не передається.

У випадку, дані про датчиках нічого не винні приходити безпосередньо після даних про становище, з-поміж них можуть прийти й інші дані, від іншого устрою тощо. Але вони все одно однозначно зв’язуються з попередніми даними про положення пристрою з таким самим regId.

Дані кожного датчика не повинні бути присутніми після кожної посилки location. Дані можуть бути не від усіх датчиків або взагалі відсутні, якщо з якихось причин для даної точки фіксації координат вони не були виміряні.

Зміна стану запалювання “ignitionChange”

Передаються після даних про положення пристрою як окреме повідомлення. Передаються за зміни стану запалення на транспортному засобі.

Можуть передаватися навіть якщо поле “used” у значеннях датчика у списку пристроїв було позначене false. У цьому випадку – можна відображати це значення клієнту для інформації, проте діяти як би датчика не було. Або ігнорувати ці повідомлення.

Схема:

{
    "type": "object",
    "properties": {
        "name" { "type": "string", "enum": ["ignitionChange"] },
        "regId" { "type": "integer" },
        "on" {
            "type": "boolean",
            "description": "Увімкнено чи вимкнено запалювання"
        }
    },
    "required": [ "name", "regId", "on" ]
}

Посилання повідомлення про зміну стану – це подія, що сталася у час, зазначений у попередній посилці location для цього транспортного засобу, тобто. з цього моменту значення значення запалювання стало таким, як зазначено в полі on. До цього моменту воно було в попередньому стані. Тобто. клієнт може вибрати показувати, наприклад стан “стоп, запалення вимкнено”, навіть якщо в попередній посилці середня швидкість була не 0, так як це середня швидкість за попередній інтервал, де воно було включено.

Також, у деяких випадках, подія про зміну стану запалення може прийти, коли запалення вже й так перебуває у тому ж стані.

Помилка “error”

У разі виникнення будь-яких помилок при обробці команд від клієнта, або повідомлень від сервера – клієнту передається повідомлення з описом помилки.

Якщо помилка вважається досить серйозною – з’єднання може бути закрите сервером. В інших випадках – якщо з’єднання не було закрито – можна продовжити обмін даними із сервером.

Схема:

{
    "type": "object",
    "properties": {
        "name": { "type": "string", "enum": ["error"] },
        "causedBy": { 
            "type": "string",
            "description": "Ім'я запиту, який визвав цю помилку (якщо він відомий)" 
        }
        "cause": {
            "type": "string",
            "description": "Опис помилки в довільному форматі"
        }
    },
    "required": ["name"]
}

Перевірка з’єднання (“ping” – “pong”)

На даний момент для з’єднань по вебсокету встановлено тайм-аут в 300 секунд. При типовому використанні цього буде достатньо навіть у разі, коли всі машини клієнта стоять і передають з періодом 300 секунд (стандартний період при стоянці). Для якихось рідкісних випадків і, при необхідності, перевірці з’єднання на “живість” – можна використовувати парний запит – відповідь із команд ping та pong. Друга – відповідь на першу.

Тобто, клієнт може налаштувати відправлення запиту ping через, наприклад, 250 секунд, протягом яких не було передачі з боку сервера. У відповідь сервер надішле команду pong. Можна такі запити надсилати і частіше, наприклад для перевірки зв’язку мобільним клієнтом, в умовах проблемного мобільного інтернету.

Також клієнт повинен бути готовий сам відповісти на команду ping відповіддю pong. У поточній реалізації сервер не відправляє таких запитів до клієнта, проте така можливість має бути передбачена клієнтом.

Ping:

{
    "type": "object"
    "properties": {
        "name": { "type": "string", "enum": ["ping"] }
    },
    "required": ["name"]
}

Pong:

{
    "type": "object"
    "properties": {
        "name": { "type": "string", "enum": ["pong"] }
    },
    "required": ["name"]
}