Сервер принимал данные и терял их: как мы патчили EGTS-декодер Traccar, чтобы датчики доезжали до базы

22 мин чтения
Схема пути EGTS-пакета через декодер Traccar: до патча сенсорные субзаписи без координат разбирались не полностью и запись выбрасывалась целиком, после двух правок датчики раскладываются в атрибуты и позиция сохраняется с подставленной последней известной точкой.
Схему можно потянуть вбок.

Жалоба «сервис не видит датчики» почти всегда заканчивается разговором про прибор: не та прошивка, не тот вход, не подключён провод. У нас прибор был ни при чём. Трекер исправно слал напряжение бортсети, температуры, уровень топлива и состояние дверей. Сервер эти пакеты принимал, подтверждал приём и выбрасывал целиком, потому что в них не было координат.

Ниже разбор двух правок в EgtsProtocolDecoder из Traccar 6.5. Обе крошечные, обе вставки, и вторая важнее первой настолько, что без неё первая вообще ничего не меняет. Мы проверили это на проде ровно тем способом, каким проверять не хотелось: наложили первый хунк, посмотрели в базу и не увидели там ничего нового.

Цифра, вокруг которой строится вся история: за неделю наблюдения по одному трекеру в базе оказалось 42 547 записей, из них 16 641 навигационных и 24 898 сенсорных. Три пятых потока, это ровно те пакеты, которые до патча не доживали до таблицы позиций.

как устроен наш GPS-мониторинг

Коротко о главном

  • Traccar сохранял позицию из EGTS только при валидном GPS-фиксе. Датчики приходят отдельными пакетами, в которых координат нет вовсе, поэтому весь пакет отбрасывался вместе с показаниями. Данные приходили, сервер их принимал и терял.
  • Типы субзаписей декодер знал: константы MSG_ABS_AN_SENS_DATA и MSG_ABS_DIG_SENS_DATA в исходнике объявлены. Ветки разбора для них не было.
  • Патчей два. Первый раскладывает аналоговые и дискретные субзаписи в атрибуты adc<N> и din<N>. Второй меняет условие сохранения на «валидный фикс либо непустые атрибуты». Без второго первый бесполезен, и это первая грабля, на которой люди решают, что прибор не шлёт датчики.
  • Дело в сервере, а не в приборе, показало одно сравнение: сырые EGTS-фреймы из лога против содержимого таблицы позиций за тот же интервал. Не «сервис против приложения производителя», а «поток против того, что сервер из него сохранил».
  • Протокол описывает, как передать значение датчика, но не что оно означает. Наша расшифровка каналов StarLine получена сопоставлением сырых фреймов с состоянием машины и на другого производителя не переносится.
  • Побочный эффект честно неприятный: подставленная координата в базе неотличима от настоящей, а вместе с ней копируется скорость и признак движения. Пометить такие записи атрибутом synthetic=true стоило бы одной строки, и мы этого не сделали.

Что именно терялось: сенсорный пакет без координат

EGTS передаёт телематику службой SERVICE_TELEDATA, и внутри одного пакета лежит набор субзаписей разных типов. Координаты, скорость и время едут в навигационной субзаписи 0x16. Датчики едут отдельно: аналоговые в 0x18, дискретные в 0x17, уровень жидкости в 0x1b. Ключевой факт, из которого растёт вся проблема: сенсорные субзаписи приходят своими пакетами, и навигационной части в них может не быть вообще.

Дальше две неприятности накладываются друг на друга.

Первая: декодер типы 0x17 и 0x18 знал, но не разбирал. Константы MSG_ABS_AN_SENS_DATA и MSG_ABS_DIG_SENS_DATA в файле объявлены, а ветки, которая бы вытащила из них значения, в цикле разбора субзаписей нет. Байты просто перескакивались через buf.readerIndex(end).

Вторая, и она главная: даже если бы датчики разбирались, позиция всё равно не попала бы в базу. Условие сохранения выглядело так:

if (serviceType == SERVICE_TELEDATA && position.getValid()) {

Признак valid выставляется при разборе навигационной субзаписи. В сенсорном пакете её нет, значит getValid() возвращает false, значит позиция со всеми атрибутами выбрасывается целиком. Тихо. Без строки в логе, без предупреждения, без счётчика.

Именно отсюда растёт распространённая жалоба «сервис не показывает датчики». Владелец видит в приложении производителя уровень топлива и напряжение, а в стороннем сервисе пусто, и вывод напрашивается сам: прибор отдаёт данные только в своё облако. В нашем случае вывод был неверным. Прибор отдавал всё, что от него ждали, по открытому протоколу, на сторонний сервер.

какие приборы и протоколы мы поддерживаем

Как поняли, что дело в сервере, а не в приборе

Скажем честно: не искали. Патч родился из другого расследования, разбора «фризов», когда трекер числился на связи, слал пакеты, а позиции в базу не писались. Разбирая тот же сырой поток, мы заметили, что трекер шлёт больше, чем лежит в базе: субзаписи 0x17 и 0x18 в логе есть, а в атрибутах позиций их нет ни разу.

разбор фриза, из которого вырос этот патч

Приём, который стоит забрать из этой истории отдельно от EGTS, формулируется так: сравнивать надо не «что показывает сервис» с «что показывает приложение производителя», а сырой поток с тем, что сервер из него сохранил.

Разница принципиальная. Сравнение двух интерфейсов не даёт ответа на главный вопрос, потому что оба конца цепочки скрыты. Приложение производителя берёт данные из своего облака по своему протоколу, ваш сервис берёт их из своей базы, и расхождение объясняется любой из десятка причин: прибор не шлёт, оператор не доставил, сервер не принял, сервер принял и не разобрал, разобрал и не сохранил, сохранил и не показал. Расхождение видно, а место разрыва нет.

Сравнение «фрейм в логе против строки в базе» отвечает ровно на один вопрос, но отвечает точно: дошло ли до сервера то, чего нет в базе. Если байты в логе есть, а атрибута в таблице нет, обсуждать прошивку и провода больше не нужно, разрыв внутри вашего сервера. Мы включили подробное логирование, взяли окно в несколько часов и разложили обе стороны по времени. Субзаписи в потоке присутствовали, соответствующих атрибутов в таблице позиций за тот же интервал не было.

Хунк 1: разбор аналоговых и дискретных субзаписей

Первая правка живёт в цикле разбора субзаписей, прямо перед buf.readerIndex(end). Это добавление двух веток в существующую цепочку else if:

} else if (type == MSG_ABS_AN_SENS_DATA) {
    int sensorNumber = buf.readUnsignedByte();
    int sensorValue = buf.readUnsignedShortLE();
    position.set("adc" + sensorNumber, sensorValue);
} else if (type == MSG_ABS_DIG_SENS_DATA) {
    int digital = buf.readUnsignedByte();
    position.set("din" + (digital >> 4), digital & 0x0F);
}

Форматы простые, и вся хитрость в порядке байтов и в полубайтах.

Аналоговая субзапись 0x18: номер датчика один байт, значение два байта младшим вперёд, дальше флаг один байт. Флаг мы не читаем: readerIndex(end) в конце итерации перескочит остаток субзаписи сам, так что дочитывать хвост вручную не нужно.

Дискретная субзапись 0x17: два байта, из которых значащий только первый. В нём старший полубайт это номер входа, а младший его состояние, ноль или единица. Отсюда digital >> 4 для номера и digital & 0x0F для значения, и отсюда же диапазон имён din0 ... din15. Второй байт мы не читаем, readerIndex(end) перескочит его сам.

Уточнение про длину появилось не сразу. Сначала мы описали субзапись как однобайтную, потому что читаем из неё ровно один байт, а на разбор длина не влияет. Проверили захватом: tcpdump на порту 5162, три минуты, 144 субзаписи типа 23. Поле SRL во всех равно 2, второй байт во всех 144 случаях нулевой.

Тот же захват показал, чем субзапись не является. В приказе Минтранса №285 поля названы DSN (номер входа) и DSST (состояние), и по описанию это читается как байт на поле. Так не сходится: в проводе идут байты вроде 0x91 и 0xe1, номер входа при побайтном чтении вышел бы 145 и 225. Работает только разбивка по полубайтам: 0x91 это din9 = 1, 0xe1 это din14 = 1, 0xf0 это din15 = 0. Сверили с базой посекундно, шестнадцать каналов из шестнадцати совпали.

Вывод шире одного прибора: сверяйте разбор с проводом, а не с описанием протокола. Стандарт задаёт рамку, а как в неё уложился конкретный производитель, видно только в захвате.

В одном пакете приходят сразу все шестнадцать субзаписей, по одной на канал, в порядке 5…15, затем 0…4. Аналоговых в том же захвате было 45 штук с SRL равным 4, что описанию выше соответствует.

Названия атрибутов adc<N> и din<N> мы выбрали по традиции самого Traccar: другие протоколы в проекте называют аналоговые и дискретные входы так же, и всё, что в системе уже умеет работать с этими именами, начинает работать без дополнительных правок.

Хунк 2: условие сохранения, ради которого всё и затевалось

Вторая правка одна строка плюс небольшой блок внутри. Было:

if (serviceType == SERVICE_TELEDATA && position.getValid()) {

Стало:

if (serviceType == SERVICE_TELEDATA && (position.getValid() || !position.getAttributes().isEmpty())) {
    ...
    if (deviceSession != null) {
        if (!position.getValid()) {
            getLastLocation(position, null);
        }
        positions.add(position);
    }
}

Логика читается прямо: сохраняем позицию, если в ней есть валидный фикс или хоть какие-то атрибуты. Если фикса нет, getLastLocation подставляет последнюю известную точку устройства, и запись становится пригодной для хранения в таблице, где широта и долгота обязательны.

Без этого хунка первый бесполезен. Датчики разбирались бы, аккуратно складывались в атрибуты позиции, и позиция целиком уходила бы в мусор на следующей же строке, потому что координат в ней нет. Мы это наблюдали на проде: с наложенным первым хунком и без второго adc и din в базу не попадали вообще.

Это же первая позиция в списке граблей ниже, и она объясняет, почему проблема живёт так долго. Человек находит в декодере пропущенные типы субзаписей, дописывает разбор, пересобирает, смотрит в базу и видит ровно то же самое, что видел до правки. Естественный вывод: значит, прибор их всё-таки не шлёт. Вывод неверный, а проверить его без второй правки невозможно.

Что стало доезжать до базы

После обеих правок в атрибутах позиций появились три группы значений.

adc0 ... adcN, сырые значения аналоговых входов. У нашего прибора это напряжение бортсети в милливольтах, температуры и уровень топлива в процентах.

din0 ... din15, состояния дискретных входов: зажигание, двери, охрана.

liquid, штатный канал уровня жидкости. Здесь важный нюанс, который мы сначала описали неверно и поправили после проверки по коду: субзапись 0x1b LIQUID_LEVEL Traccar разбирает сам, штатно, наш патч для этого не нужен. Но приезжала она в тех самых сенсорных пакетах, которые выбрасывались целиком, поэтому в базу не попадала. Первый хунк её не касается, второй возвращает её к жизни.

Для чужого прибора liquid предпочтительнее сырого adc: это выделенный топливный канал стандарта, он работает для любого EGTS-трекера со штатным датчиком уровня и не требует ручной разметки каналов. Сырые adc нужны там, где производитель штатным каналом не пользуется.

Соотношение записей в базе за неделю наблюдения по одному трекеру:

Тип записи Количество Доля
Всего записей 42 547 100 %
Навигационных (есть число спутников) 16 641 около 39 %
Сенсорных (координата подставлена) 24 898 около 61 %

Три пятых потока, это ровно те пакеты, которые до патча выбрасывались целиком. Отдельного замера «было и стало в сутки» мы не делали, поэтому других чисел здесь не будет: соотношение внутри одной недели измерено, а сравнение с досостоянием пришлось бы восстанавливать по памяти.

как мы считаем расход и ловим слив топлива

Смысл каналов протоколом не определён

Вот место, где заканчивается инженерия и начинается археология. EGTS описывает, как передать значение датчика: номер, два байта, флаг. Он не описывает, что этот датчик означает. У одного производителя din5 это дверь, у другого что угодно другое, и никакого справочника соответствий в стандарте нет.

Размапливали без специальной поездки и без стенда. Выгрузили сырые фреймы за два окна реальных поездок и свели их по времени с состоянием машины из облака производителя: заведён или заглушен, открыта дверь или нет, снят ли с охраны. Дальше сопоставление глазами, канал за каналом.

Проверка сошлась на топливе, и сошлась убедительно. 31 августа канал adc5 скакнул с 63 % до 100 %, это примерно плюс 18,5 литра. Ровно в ту же секунду сработал канал двери. И на то же время у владельца была записана вручную заправка на 18,79 литра. Три независимых источника, датчик, дискретный вход и человеческая запись, показали одно и то же событие.

Наша расшифровка каналов StarLine выглядит так:

Канал Что означает На чём опознали
adc0 напряжение бортсети, милливольты стоянка около 12,6 В, заведён 14,1-14,3 В от генератора, провал до 11,8 В в момент старта
adc1 температура салона медленный дрейф, не связан с зажиганием
adc2 температура двигателя холодный старт около 30°, прогрев до 88°
adc5 уровень топлива, проценты рост 66 → 100 % внутри интервала открытой двери, совпал с записанной заправкой 18,79 л
adc3 не константа два значения, 200 и 55; за неделю три переключения, все в одно утро
adc6 единственная настоящая константа всегда 1
din0, din14 охрана переключаются при постановке и снятии
din8 зажигание держится всю поездку, синхронно с ростом напряжения до 14 В
din10 двигатель запущен включается на 3-5 секунд позже зажигания, гаснет вместе с ним
din5 дверь одиночные срабатывания на стоянке
din9 ручник, инвертированный 0 поднят, 1 опущен; значим только при включённом зажигании
din11, din12 смысл не установлен гипотеза «педаль и стопы» проверку не прошла

Эта таблица не переносится на другого производителя. Она получена на конкретном приборе StarLine и годится ровно для него. Соседний прибор другой марки на том же протоколе может разложить те же самые входы в другом порядке, и никакого способа узнать это заранее из протокола нет. Единственный рабочий метод остаётся тем же: сопоставить сырые каналы с реальными событиями, которые вы можете независимо подтвердить.

Три строки этой таблицы стоят отдельного разговора, потому что каждая показывает свой способ ошибиться.

din9 — ловушка залипания. Мы сначала записали его в шум: по недельной выгрузке он почти всегда равен единице, что для сигнала выглядит бессмысленно. На деле это ручник, причём инвертированный (0 — поднят, 1 — опущен), и значим он только при включённом зажигании: без питания канал залипает в единице. Машина стоит большую часть недели, отсюда и «почти всегда 1». Канал, который выглядит константой, стоит проверить на подмножестве, где он вообще может меняться, прежде чем объявлять его мусором.

din11 и din12 — гипотеза, не прошедшая проверку. Они ритмично мигают в движении, и мы предположили педаль с стоп-сигналами: правдоподобно и приятно. Отрицательный тест это убил — за весь период с поднятым ручником и работающим двигателем ноль единиц из 52 замеров. Красивое объяснение оказалось неверным, и мы оставили честное «смысл не установлен».

adc3 — не константа, хотя выглядела ею. В первом разборе канал держал одно значение, и мы записали его в конфигурационные. На неделе данных нашлось второе значение, 55 вместо 200, и ровно три переключения — все в одно утро, в пределах сорока минут. Остальные шесть суток канал не шевелился вовсе.

Именно поэтому пример показателен. Речь не о том, что мы смотрели невнимательно: на окне, не захватившем то самое утро, канал добросовестно выглядит константой — потому что константой в этом окне и является. Дело не в длине наблюдения: единственное событие можно пропустить окном любой длины, если оно легло рядом. «Не менялся ни разу» и «не менялся в тот отрезок, который я посмотрел» — разные утверждения, и второе выдаёт себя за первое тем охотнее, чем реже происходит событие. Единственная настоящая константа здесь adc6.

Общий вывод из всех трёх: канал, который ритмично меняется, провоцирует додумать ему смысл, а канал, который не меняется, провоцирует списать его в мусор. Обе ошибки лечатся одинаково — проверкой на большем объёме данных и отрицательным тестом. Ошибиться в трактовке дискретного входа легко, а обнаружить ошибку потом трудно: вести себя канал будет правдоподобно в любом случае.

Побочный эффект, о котором надо сказать честно

getLastLocation копирует в сенсорную позицию последнюю известную точку и ставит признак валидности. Значит, в базе «настоящий фикс» и «подставленная координата» по этому полю больше не различаются. Мы это знали и приняли осознанно.

Укусило позже и с неожиданной стороны. Копируются не только координаты: вместе с ними в запись приходит скорость и производный от неё признак движения. У трекера, потерявшего спутники, они замерзают на последнем известном значении. Анти-угонное правило «машина едет, а GPS пропал» чуть не начало давать ложные тревоги на неподвижной машине, шесть с половиной часов подряд, потому что последний фикс перед провалом поймал дрожание в 2 км/ч. Не сработало по случайности: в том конкретном эпизоде последний фикс оказался с нулевой скоростью.

почему трекер показывает движение при пропавшем GPS

Что сделали бы иначе. Пометили бы подставленные позиции отдельным атрибутом, synthetic=true, прямо в патче, в той же строке, где вызывается getLastLocation. Это стоило бы ровно одной строки кода и сэкономило бы весь последующий разбор с ложными тревогами.

Сейчас навигационную запись от сенсорной отличают по наличию числа спутников: у всех 16 641 навигационных записей недели оно есть, у всех 24 898 сенсорных его нет ни разу. Признак работает, но он найден постфактум, а не заложен. Разница существенная: заложенный признак гарантирован кодом, а найденный держится на наблюдении, которое верно для одного прибора и одной прошивки.

Ещё одна деталь из той же выгрузки, полезная всем, кто строит логику на флаге валидности. За неделю в базе ноль записей с невалидным фиксом. Трекер про плохой приём не сообщает вовсе, он просто молчит о координатах. Полагаться на флаг валидности как на ответ «есть ли сейчас GPS» нельзя, надёжнее смотреть на свежесть координат. Оговорка обязательная: это наблюдение про наш трекер на EGTS, за другие протоколы мы не ручаемся.

Как собрать образ и выкатить

Форка у нас нет, jar в git не лежит, потому что это артефакт в десятки мегабайт. В репозитории хранятся сами патчи, Dockerfile и рецепт. Порядок такой.

1. Клонируем нужный тег неглубоко:

git clone --branch v6.5 --depth 1 https://github.com/traccar/traccar.git

2. Накладываем оба патча. Для EGTS обязательно с --recount, иначе git apply откажется, см. грабли:

git apply --recount ../patches/egts-sensors.patch

3. Собираем в одноразовом контейнере. JDK на хост ставить не нужно:

docker run --rm -v "$(pwd)":/work -w /work eclipse-temurin:17-jdk \
  bash -c "./gradlew assemble --no-daemon"

Занимает около четырёх минут, на выходе target/tracker-server.jar.

4. Кладём свой jar поверх официального образа. Весь Dockerfile из двух строк:

FROM traccar/traccar:6.5
COPY tracker-server.jar /opt/traccar/tracker-server.jar

Смысл именно в этом: зависимости, JRE и раскладку каталогов из официального образа мы не трогаем, подменяется единственный файл. Собранный образ нигде не публикуется, он переносится на сервер через docker save | gzip и docker load. Откат тривиальный: вернуть в compose официальный образ и пересоздать контейнер. Данные при этом не трогаются, база остаётся на месте.

Про обновления честно. Патч к конкретной версии не приколочен, но и не бесплатен. При переходе на новую версию клонируется её тег, патч накладывается заново, и если контекст разошёлся, те же строки вносятся руками. Места в файле уникальные, найти их нетрудно, но это ручная работа на каждом обновлении, и её надо закладывать в план. Тот, кто не готов повторять эту процедуру, должен либо жить без датчиков, либо дождаться, пока разбор появится в апстриме.

Шесть граблей для того, кто повторит

1. Разобрать субзаписи мало. Позиция без координат всё равно будет выброшена условием сохранения. Это самая дорогая ошибка в списке, потому что она выглядит как отрицательный результат эксперимента: правку внесли, ничего не изменилось, значит гипотеза неверна. На самом деле неверна не гипотеза, а место, где остановились.

2. git apply ругается на EGTS-патч. Заголовок @@ в нём формальный, счётчики строк не сходятся. Накладывать нужно с --recount либо через patch -p1 --fuzz=3. Ошибка выглядит как «patch does not apply» и провоцирует искать несовместимость версий там, где её нет.

3. Смысл каналов не стандартизован. Повторим ещё раз, потому что это ловушка не для сборки, а для интеграции: din5 в нашей таблице означает дверь только на нашем приборе. Переносить чужую расшифровку на свой парк нельзя, размапливать нужно сопоставлением с событиями, которые вы можете проверить независимо.

4. Датчики приходят редко и отдельными пакетами. Уровень топлива и температуры могут идти раз в десятки, а то и сотни позиций, и тонут в потоке частых adc и din. Запрос вида «последние N строк» их просто не увидит: в выборку попадут только частые каналы. Читать надо окном по времени (у нас трое суток), идя от новых записей к старым и беря первое встреченное значение каждого канала. Мы на этом обожглись: датчик «пропадал» с экрана и алёрт не срабатывал, хотя данные в базе лежали.

Уточнение к предыдущему пункту, тоже оплаченное собственной ошибкой: окно и сортировку строить по времени прихода пакета, а не по времени GPS-фикса. У прибора с потерянными спутниками фикс замирает, и при сортировке по нему свежие показания перемешиваются со старыми. «Самым свежим» напряжением легко окажется пришедшее часом раньше.

5. Даунгрейд Traccar ниже 6.6 на существующей базе ломает авторизацию. Формат хеша пароля сменился, и LoginService старой версии падает на любой попытке входа, у всех пользователей сразу. Мы наступили на это ровно во время расследования фризов, когда откатывались на старую версию в диагностических целях, и уронили сайт для всех. Схема базы при этом выглядит совместимой, никакой миграции «вниз» не требуется, так что предупреждения не приходит ниоткуда. Под 6.5 у нас поэтому отдельная база.

6. Устройство опознаётся по TCP-сессии, а не по содержимому пакета. Вопрос про безопасность возникает у каждого, кто дочитал до места «сохраняем позицию без координат»: не означает ли это, что чужие данные могут попасть в чужое устройство? Нет. Привязка пакета к устройству делается по сессии соединения, установленной при авторизации трекера, и содержимое субзаписей на неё не влияет. Патч меняет только то, что делать с уже привязанной позицией, и не трогает механизм привязки.

Чего мы не утверждаем

Что так ведут себя все версии Traccar. Мы правили 6.5 и работали с ней. Проверять поведение на других ветках специально не стали.

Что расшифровка каналов универсальна. Она получена на одном приборе StarLine. У другого производителя номера входов будут другими, и никакого признака «эта таблица вам не подходит» не появится, каналы просто будут показывать неправдоподобные вещи не там, где вы ждёте.

Что мы читаем CAN-шину или тахограф. Не читаем. Всё, о чём здесь речь, это аналоговые и дискретные входы самого трекера плюс штатный канал уровня жидкости.

Что датчик уровня по RS-485 у терминалов Неоматики (ADM) или Навтелекома («Сигнал») заработает так же. Теоретически эти приборы уровень передают, но наш разбор ищет каналы с именами adc, din и liquid, а как их называют эти производители, мы не знаем: живого прибора у нас не было.

Что мы измерили эффект патча в абсолютных величинах. Мы знаем соотношение внутри недели после патча. Замера «столько записей в сутки было и столько стало» не делали, и восстанавливать его задним числом не будем.

Частые вопросы

Нужен ли патч, если у прибора штатный датчик уровня топлива? Первый хунк не нужен, второй нужен. Субзапись 0x1b LIQUID_LEVEL Traccar разбирает сам в атрибут liquid, но приезжает она в тех же сенсорных пакетах без координат, которые отбрасываются условием сохранения. То есть значение декодируется корректно и не сохраняется. Второй хунк это чинит.

Не появятся ли из-за патча лишние точки на карте? Появятся, и это надо учитывать при построении треков. Каждая сенсорная запись получает подставленную координату последней известной точки, поэтому в таблице позиций растёт число записей с одинаковыми координатами. На карте они лежат друг на друге, но в отчётах и выборках их видно. Отделять их лучше явным атрибутом, а если патч уже выкачен без него, то по наличию числа спутников.

Можно ли по флагу валидности понять, есть ли у трекера приём спутников? После патча нельзя, до патча тоже было ненадёжно. У нашего прибора за неделю не оказалось ни одной записи с невалидным фиксом: он не сообщает о плохом приёме, он просто молчит о координатах. Смотреть надо на свежесть самого фикса. как отличить пропажу GPS от глушилки

Почему не отправить патч в апстрим? Разбор субзаписей туда просится, и это правильный путь. Изменение условия сохранения обсуждать сложнее: оно меняет контракт таблицы позиций для всех пользователей протокола, и в апстриме его разумно делать с явной пометкой синтетических записей, а не тихой подстановкой координат. Мы у себя эту пометку не сделали и считаем это своей ошибкой.

Сколько времени занимает вся процедура с нуля? Клон тега и наложение патчей минуты, сборка в контейнере около четырёх минут, сборка образа и перенос через docker save зависят от канала. Основное время уходит не на это, а на разметку каналов: понять, что означают adc и din конкретного прибора, за один вечер обычно не получается, нужны данные хотя бы за пару реальных поездок.

Вывод

Главное здесь не про EGTS и даже не про Traccar. Формулировка «сервис не поддерживает датчики этого прибора» описывает наблюдаемый результат, а не причину, и в неё умещаются шесть разных мест разрыва: от прошивки трекера до отрисовки в интерфейсе. Пока эти места не разделены, любой вывод про прибор преждевременен.

Разделяются они одним сравнением: сырой поток против того, что сервер из него сохранил. У нас байты в логе были, а атрибутов в базе не было, и на этом разговор про прошивку закончился, не начавшись.

Вторая мысль про цену пропущенной строки. Мы починили потерю данных и в той же правке создали новую неоднозначность, потому что не пометили подставленные записи. Строка кода, которую не написали за две секунды, стоила отдельного расследования про ложные анти-угонные тревоги. Если будете повторять патч, начните с неё.

Практический следующий шаг, если у вас свой Traccar: возьмите час логов протокола и сверьте, какие субзаписи в них приходят, с тем, какие атрибуты за тот же час появились в таблице позиций. Расхождение либо будет, либо нет, но узнаете вы это точно, а не по косвенным признакам.

Материал подготовлен на данных сервиса мониторинга «Точка GPS»: патчи и рецепт сборки из рабочего репозитория, выгрузка боевой базы Traccar 6.5 за 30 августа - 6 сентября 2026 года по одному устройству и разбор исходников декодера.

другие технические разборы сервиса

TraccarEGTSGPS-мониторингдатчикиStarLineDockerпатч

Читайте также