Skip to content

commandus/pkt2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pkt2

История изменений

2020/02/26 добавлена утилита pkt2js

2018/03/06 добавлены дампер pkt2dumpfcm и обработчик handler-fcm

2018/01/10 добавлены пример записываемых данных в PostgreSQL, опция --bind для repeator

2017/10/03 добавлено описание опций сжатия tcpreceiver

2017/08/16 добавлен раздел "Чтение данных из бинарных и текстовых файлов"

2017/06/13 исправлен раздел "Описание пакетов"

2017/04/25 pkt2dumppq

2017/01/18 Пример описания

2017/01/11 Черновик

Репозитории

  • pkt2 репозиторий pkt2
  • collarie-0.1 Сервис ошейников(ретранслирует TCP пакеты в pkt2)
  • thermo-php Веб интерфейс для термодатчиков на PHP
  • data.pl, serv_upd.pl Веб интерфейс для термодатчиков на Perl (в письме от Ивана)
  • huffcode-test Утилиты для проверки сжатия, тестовые

Назначение

Разбор приходящих TCP/IP пакетов по формальному описанию.

Описание пакетов

Каждому пакету необходимо его описание (протокол).

Протокол содержит:

  • описание входящих данных(входящего пакета), опционально- адреса источника данных и адреса назначения.
  • описание извлекаемых(выходных) данных, опционально- формат для представления в виде строки.

Программы загружают описания пакетов при старте.

Описания (протокол) записываются на языке Protobuf(https://developers.google.com/protocol-buffers/), предназначенном для описания сериализуемых одноименной библиотекой данных как опции языка:

  • option(pkt2.output) входящие данные (поля, их смещения и размеры, опционально- адреса, откуда приходят пакеты и на какие интерфейсы)
  • option(pkt2.variable) выходные данные (описание того, как извлекаются из полей пакета данных в выходные переменные, опционально- как они переводятся в читаемый текст)

Номера опций - в приложении 1.

в файле с расширением .proto в папке proto. Здесь используется возможность расширения языка Protobuf "опциями".

Одному пакету данных должно быть сопоставлено одно сообщение (message), записанное в файле .proto в кодировке UTF-8.

Например, файл proto/example/example1.proto начинается с:

1 syntax = "proto3";
2 package example1;
3 option cc_enable_arenas = true;
4 import "pkt2.proto";    // обязательно включить файл с описанием опций
5 /// Temperature
6 message TemperaturePkt {
...
Строка Пояснения
1 Версия языка должна быть proto3, а не proto2
2 Имя пакета. Примеры имен: iridium, globalstar, gprs
3 Необязательная опция компилятора protoc, разрешающая генерировать код с использованием специального аллокатора памяти(*)
4 Подключаемый файл(каждый отдельно)
5 Комментарии: однострочные //, многострочные /* */
6 Описание сообщения (message), в одном файле модет быть несколько

*- эта опция задает режим генерации кода компилятора protoc, и так как в программах используется динамическая работа с сообщениями, минуя кодогенератор protoc, данная опция не влияет на производительность и поэтому может быть опущена.

Каждое сообщение (message) относится к пакету. Полное имя сообщения в примере: example1.TemperaturePkt.

Включаемые файлы

Программы по умолчанию начинают поиск включаемых файлов в каталоге proto.

В этом каталоге включаемые файлы могут располагаться иерархично в подкаталогах.

Например, каталог proto может хранить файлы в дереве подкаталогов, как в примере ниже:

     proto
     |
     +-- pkt2.proto
     |
     +-- example
     |   |
     |   +-- example1.proto
     |
     +-- google
     |   |
     |   +-- protobuf
     |       |
     |       +-- descriptor.proto
     |
     +-- iridium  
         |
         +-- animals.proto
         |
         +-- gps16.proto
         |
         +-- ie_ioheader.proto
         |
         +-- ie_location.proto
         |
         +-- packet8.proto
         |
         +-- time5.proto

И, чтобы включить файл packet8.proto, расположенный в подкаталоге iridium, нужно указать путь, используя разделитель "/":

import "pkt2.proto";
import "iridium/packet8.proto";

Имя и численный идентификатор пакета

Пакет в пределах границ каталога proto определяется по имени пакета и имени сообщения(message), в котором он определен. Имя сообщения должно быть уникальным в пределах пакета.

Пример описания пакета (фрагмент):

	option(pkt2.packet) = {
    	id: 5001 
        name: "temperature"
        short_name: "Температура"
        full_name: "DEVICE TEMP"
        set: "device = message.device; unix_time = message.time; value = (message.degrees_c / 2) / 1.22);"

Поля(fields)

В сообщении(message) опция pkt2.packet содержит поля(fields) пакета.

Каждое поле- это непрерывная область памяти. Для поля указывается смещение и размер в байтах.

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

Важно, чтобы по смещению и размеру полей можно было определить размер пакета. Если пакет содержит в конце неиспользуемые(пустые) данные, нужно задать поле, чтобы прошоамма могла определить размер пакета.

Пример описания пакета (фрагмент):

	option(pkt2.packet) = {
    	id: 5001 
        name: "temperature"
        short_name: "Температура"
        full_name: "DEVICE TEMP"
        set: "device = message.device; unix_time = message.time; value = (message.degrees_c / 2) / 1.22);"
        source: {
            proto: PROTO_TCP
            address: "84.237.104.57"
            port: 50052 // 0- any port
        }
        fields: [
        {
            name: "device"
            type: INPUT_UINT
            size: 1
            offset: 0
        },
        {
            name: "unix_time"
            type: INPUT_UINT
            size: 4
            offset: 1
            endian: ENDIAN_BIG_ENDIAN
        },
        {
            name: "value"
            type: INPUT_UINT
            size: 2
            offset: 5
            endian: ENDIAN_BIG_ENDIAN
        },
        {
            name: "tag"
            type: INPUT_UINT
            size: 1
            offset: 7
            tag: 255
        }
        ]
    };

Для полей, занимающих более 1 байта, нужно указывать порядок байт в слове как в примере: endian: ENDIAN_BIG_ENDIAN.

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

В пакете некоторые поля имеют постоянные значения, позволяющие по ним определять, что это за пакет. Это могут быть магические числа в заголовках, признаки конца сообщегний или фрагментов, константы и другие признаки.

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

В следующем примере:

message IE_IOHeader
{
    // input packet description
    option(pkt2.packet) = { 
        name: "ioheader"
        short_name: "Iridium IO header"
        full_name: "Iridium message IO header"
        fields: [
        {
            name: "ie_id"
            type: INPUT_UINT
            size: 1
            offset: 0
            tag: 1			      ///< tag 1- I/O header
        },
        {
            name: "ie_size"
            type: INPUT_UINT
            size: 2
            offset: 1
            endian: ENDIAN_BIG_ENDIAN
            tag: 28               ///< tag, 28 bytes long
        },
        {
  • поле ie_id помечено тегом 1, так как это признак I/O заголовка;
  • поле ie_size помечено тегом 28, так как I/O заголовок имеет фиксированый размер тела, равный 28 байт.

Эти поля могут считаться признаком того, что пакет с тегами 1 и 28 - это заголовок IOHeader, и их следует отметить признаком tag со значениями 1 и 28 соответсвенно.

Помечать опцией tag необязательно, но желательно. Если в сообщении нет ни одного tag, пвкет будет определяться только по длине пакета.

Значение тега указывается не как последовательность байт, а как число(максимум 64 бит), и, так как поле ie_size двухбайтное, то для него нужно указывать порядок байт опцией endian:

endian: ENDIAN_BIG_ENDIAN

Или можно было бы задать два однобайтных поля с тегами 0 и 28 без указания порядка байт опцией endian. Однако это не слишком хорошо, если значение ie_size использовалось бы где-то, тогда пришлось бы все равно вводить еще одно двухбайтно поле под размер.

Обращение к полям из Javascipt

Из выражений на языке Javascript значения полей доступны как свойства объекта field.<имя поля>

Свойства объекта имеют тип, задаваемый опцией type по имени:

Имя типа Тип Тип Javascipt, значение
INPUT_NONE Нет boolean, false
INPUT_MESSAGE Сообщение Вложенный объект
INPUT_DOUBLE double Number
INPUT_INT int unsigned int
INPUT_UINT uint unsigned int
INPUT_BYTES array array
INPUT_CHAR Сhar String
INPUT_STRING String String

В примере поле is_size

name: "ie_size"
type: INPUT_UINT
size: 2
offset: 1
endian: ENDIAN_BIG_ENDIAN

имеет тип INPUT_UINT, поэтому следующее Javascript выражение:

field.ie_size

вернет целое число 28 (тип целого).

Опция endian задает порядок байт в числах и может быть равно:

Имя типа Преобразование
ENDIAN_NO_MATTER Нет
ENDIAN_LITTLE_ENDIAN Да
ENDIAN_BIG_ENDIAN Да

По умолчанию опция endian- ENDIAN_NO_MATTER (не изменять порядок байт в слове).

Опция endian не применяется к нечисловым типам, как массивы, сообщегия и строки, а также к числам, помещающимся в одном байте.

Выходные данные(variables)

Поля указывают только на области памяти, откуда могут быть извлечены данные.

Приложение указывает, какие данные ему нужны для дальнейшей обработки в опции pkt2.variable.

В примере:

double degrees_c = 3 [(pkt2.variable) = {
        name: "degrees_c"
        short_name: "Температура"
        full_name: "Температура"
        measure_unit: "C"
        get: "1.22 * field.value"
        priority: 0                                 // required
        format: ["value.degrees_c.toFixed(2).replace('.', ',')"]
    }];

double degrees_c указывает на выходной параметр (переменную)- число с плавающей запятой двойной точности.

Тип выходных данных определяется типом protobuf, ординальные типы в таблице ниже:

Тип protobuf Особенности сериализации
double
float
int32 Переменная длина
int64 Переменная длина
uint32 Переменная длина
uint64 Переменная длина
sint32 ZigZag. Более эффективен для отрицательных чисел
sint64 ZigZag. Более эффективен для отрицательных чисел
fixed32 всегда 4 байт
fixed64 всегда 8 байт
sfixed32 всегда 4 байт
sfixed64 всегда 8 байт
bool
string UTF-8
bytes

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

В опции pkt2.variable.get примера указывается, что значение переменной берется из поля value (field.value) и умножается на константу 1.22.

Запись выражения деоается на языке Javascript и может содержать несколько операторов.

field- это объект, содержащий поля:

  • field.device
  • field.unix_time
  • field.value
  • field.tag (всегда содержит число 255)

Формула:

Формула

1.22 * field.value

ссылается на поле value через объект field.

Опции описания

Опционально можно задать имя(латинскими буквами), короткое и полное название (описание) переменной:

        name: "degrees_c"
        short_name: "Температура"
        full_name: "Температура"

Программы, записывающие результат в базу данных, или отоброажающие результат, могут использовать их при отображении.

Отдельно можно указать единицу измерения для отобоажегия на графике:

        measure_unit: "C"

Опция приоритета может использоваться как уровень детализации при отображении:

        priority: 0                                 // required
        format: ["value.degrees_c.toFixed(2).replace('.', ',')"]

Преобразование в строку делается или неявно, в зависимости от типа, или явно.

Во втором случае нужно задать опцию format. В следующем примере:

        format: ["value.degrees_c.toFixed(2).replace('.', ',')"]

градусы отобоажаются с точностью до второго знака после запятой, и вместо разделителя дробной и целой частей (".") вставляется знак ",".

Опции записи

Для записи в базу данных нужно описание выходных данных снабдить тремя ключами(индексами):

  • uint32 Идентификатор входного пакета
  • uint32 метка времени (index: 1 )
  • uint64 идентификатор(ы) устройства (index: 2)

Идентификатор входного пакета описывается в сообщении или явно опцией id, как в примере:

    option(pkt2.packet) = {
    	id: 5001 

или неявно пакету присваивается номер (хеш имени пакета).

Метка времени и идентификатор устройства задаются опциями index со значениями 1 и 2.

Преобразование в строку(форматирование)

Форматирование пребразует выходные параметры (переменные) в строку.

В предыдущем примере format опции pkt2.variable:

value.degrees_c.toFixed(2).replace('.', ',')

Опция использует объект value, содержащий значения переменных заданного типа (в примере это double).

Выражение может состоять из нескольких опертаторов Javascript. Возвращается последнее вычисленное значение.

Точно также, как в случае с get, format требует указания вложенным сообщений, если они есть. Например:

    // output
    uint32 time5 = 1 [(pkt2.variable) = {
        short_name: "Время"
        full_name: "Время, Unix epoch"
        measure_unit: "s"
        get: "new Date(field.packet8.time5.day_month_year & ((1 << 7) - 1) + 2000, (field.packet8.time5.day_month_year >> 7) & ((1 << 4) - 1) - 1, (field.packet8.time5.day_month_year >> (7 + 4)) & ((1 << 5) - 1), field.packet8.time5.hour, field.packet8.time5.minute, field.packet8.time5.second, 00).getTime() / 1000" 
        priority: 0
        format: ["var d = new Date(value.packet8.time5.time5 * 1000); ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + '.' + d.getFullYear() + ' ' + ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2)"]
    }];

format ссылается на вложенное сообщение time5 вложенного сообщения packet8.

Вложенные сообщения

Пакет можно расписать в одной файле, или разбить его на логические части.

Например, пакет Iridium можно разбить на несколько уровней- уровень входящего пакета, и входящих его вложенных сообщений.

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

В следующем примере сообщение packet8- вложенное:

	bool battery_low = 13 [(pkt2.variable) = {
        name: "battery_low"
        short_name: "низкое бортовое напряжение"
        get: "field.packet8.battery & 0x40"
    }];

В таком случае надо указать вложенность:

field.packet8.battery

вместо

field.battery

Это нужно потому, что уникальность имен ограничена сообщением, и тогда возможно совпадение имен.

Javascript ограничения

Встроенный интерпретатор Javascript Duktape соответствует стандарту Ecmascript E5/E5.1 и имеет [дополнительные встроенные объекты] (http://duktape.org/guide.html#duktapebuiltins).

Список объектов, их свойств, методов(функций) в описании Ecmascript.

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

Имена сообщений

Когда мы ссылаемся на вложенные сообщения, их имена должны включать имя пакета, имя родительского сообщения(или цепочку таких имен), разделенных точкой ("."). Такое "составное" имя указывает принадлежность сообщения другому(родительскому) сообщению, и называется полным именем в терминах protobuf.

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

Файлы .proto

Когда программы начинают выполнение, загружаются все файлы .proto из папки (включая вложенные папки) пакетов. Сообщения об ошибках записываются в журнал.

Каждое сообщение содержит атрибуты целого, вещественного типов, реже типов перечислений и строк. Сообщения могут быть вложенными.

В языке Protobuf есть понятие опций сообщений и атрибутов.

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

В опции сообщения pkt2.packet записываются смещения и размеры полей входного пакета и присваивается имя.

В опции атрибута pkt2.variable записывается код функции преобразования.

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

Если файлы .proto изменены, а программы уже запущены, то процессам надо послать сигнал 1:

kill -1 <номер процесса>

чтобы программы перезапустились с чтением обновленных .proto файлов или остановить выполнение программ и запустить заново.

Плагин компилятора protoc

Программы используют возможность библиотеки Protobuf работать с произвольными сообщениями с помощью дескрипторов сообщений. Дескрипторы сообщений создаются из .proto файлов.

Дескрипторы сообщений придают библиотеке Protobuf гибкости в ущерб производительности. Часть программ может быть оптимизирована, если используются редко изменяемые .proto описания генерацией кода для поддерживаемого языка программирования компилятором protoc.

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

Адреса в пакетах

Пакет имеет название, описание и источник (его адрес) и получатель(его адрес).

  • name имя для файлов, имен переменных (лат.)
  • short_name отображаемое имя
  • full_name описание
  • source.proto = tcp | udp
  • source.addr = IPv4
  • source.port = 0..

По адресу отправителя отпределяется по источник данных.

Если адрес source.addr не указан или равен 0, "0.0.0.0", то это пакет, пришедший с любого адреса.

Если адрес dest.addr не указан или равен 0, "0.0.0.0", то это пакет, пришедший в любой сетевой интерфейс.

Если порт не указан или равен 0, то это пакет, пришедший с любого порта.

Порядок определения пакета

Если один источник данных порождает не один тип пакета, а несколько, то поиск делается перебором зарегистрированных протоколов с подходящими источниками до тех пор, пока функция parse() не вернет признак валидности пакета.

Функция parse() возвращает признак валидности пакета. Если пакет вадиден, он записывается в базу данных.

Если ни один протокол не сообщает о валидности пакета, или ни один протокол не найден по Источнику, пакет не обрабатывается.

Входящий пакет

Входящий пакет описывается в опции option(pkt2.packet) сообщения (message).

Элемент структуры имеет имя, на которое может ссылаться элемент извлекаемых (выходных) данных

  • name имя переменной (лат). Это имя может использоваться для получения значений в
  • type тип переменной в пакете (например, UINT8) (не больше size)
  • endian дополнительные признаки для преобразоваия типа: BIG_ENDIAN,..
  • offset расположение относительно родительского элемента
  • size длина в байтах

Если нужно маскировать, сдвигать биты- это указывется в формуле выходных данных.

Выходные данные

Выходные данные описываются как поля message в опции option(pkt2.variable)

  • name имя переменной (латинкие буквы). Это имя может использоваться для получения значений в
  • type тип переменной в единицах измерения (например, FLOAT)
  • short_name отображаемое короткое имя
  • full_name описание
  • measure_unit название единицы измерения (если не задан values)
  • formula формула приведения значения к единице измерения) (если не задан values). Применяются name в вычислениях.
  • values строки для флагов или перечислений по порядку. Если задан, measure_unit и formula не действуют.
  • priority уровень детализации отображения. 0 (высший)- отображать всегда (по умолчанию), 1- не отображать (не записывать в БД)
  • format преобразование в строку

Тип элемента структуры пакета

Для пакета (входных данных) указывается тип- целое (со знаком или без), вещественное число или последовательность символов (строка). Вместе со значениями size и endian для чисел получается тип.

Типы входных данных:

  • NONE массив байт
  • DOUBLE плавающая запятая
  • INT знаковое целое
  • UINT беззнаковое целое
  • CHAR 8-битный символ, байт
  • STRING NULL-terminated строка символов
Тип переменной (src_type, dst_type)

Тип выходных переменных из числа поддерживаемых Protobuf типов. При преобразовании в строку используется значение format. Не для всех типов возможна конверсия друг в друга.

Типы переменных:

  • DOUBLE
  • FLOAT
  • INT64
  • UINT64
  • INT32
  • FIXED64
  • FIXED32
  • BOOL
  • STRING
  • GROUP
  • MESSAGE // Length-delimited aggregate.
  • BYTES
  • UINT32
  • ENUM
  • SFIXED32
  • SFIXED64
  • SINT32
  • SINT64

Пример описания

Полный пример

syntax = "proto3";

package example1;

import "pkt2.proto";    // описание расширения (опций pkt2.packet, pkt2.variable)

/// Temperature
message TemperaturePkt
{
    // input packet description
    option(pkt2.packet) = { 
        name: "temperature"
        short_name: "Температура"
        full_name: "DEVICE TEMP"
        source: {
            proto: PROTO_TCP
            address: "84.237.104.57"
            port: 0 // any port
        }
        fields: [
        {
            name: "device"
            type: INPUT_UINT
            size: 1
            offset: 0
        },
        {
            name: "unix_time"
            type: INPUT_UINT
            size: 4
            offset: 1
            endian: BIG_ENDIAN
        },
        {
            name: "value"
            type: INPUT_UINT
            size: 2
            offset: 5
            endian: BIG_ENDIAN
        }]
    };

    // output 
    uint32 degrees_c = 1 [(pkt2.variable) = {
        name: "degrees_c"
        type: OUTPUT_DOUBLE
        short_name: "Температура"
        full_name: "Температура, C"
        measure_unit: "C"
        formula: "1.22 * ((value & 0x0f) << 1)"
        priority: 0                                 // required
        format: "%8.2f"
    }];

    uint32 degrees_f = 2 [(pkt2.variable) = {
        name: "degrees_f"
        type: OUTPUT_DOUBLE
        short_name: "Температура"
        full_name: "Температура, F"
        measure_unit: "F"
        formula: "degrees_f * 1.8 + 32"
        priority: 1                                 // optional
        format: "%8.2f"
    }];
}

Шины

Межпроцессное взаимодействие делается с помощью библиотеки nanomsg

Входящие пакеты принимаются одно или несколькими процессами программы tcpreceiver.

tcpreceiver слушает порт, и все всходящие в порт пакеты пересылаются в шину пакетов(на диаграмме внизу- ipc:///tmp/packet.pkt2.

Адрес шины ipc:///tmp/packet.pkt2 применим для межпроцессного взаимодействия в пределах одного компьютера через разделяюмую память.

Для организации шины между несколькими нужно использовать адрес tcp: или udp.
За подробностями обратитесь к документации NanoMsg

К шине пакетов подключается один или несколько процессов программы pkt2receiver.

pkt2receiver осуществляет поиск подходящего протокола по имеющимся у него .proto файлам:

  • по длине пакета
  • по нахождению тегов(маркеров пакета) в сообщенгия и вложеных сообщениях

Когда протокол найден, формируется сообщение из значений пакета и передается далее в шину сообщений.

Так как процесс сопоставления пакетов сообщениям может оказаться затратным, можно запустить несколько процессов pkt2receiver с разделением по размеру пакета.

К шине сообщений подключаются программы- обработчики. Программы- обработчикам можно указать олин из способов:

  • обрабатывать все сообщения
  • фильтровать по имени сообщения

Программы обработчики использую .proto файл в основном для того, чтобь получить значение format для форматирования результата в виде строки.

В фильтре по имении сообщения можно задать один или несколько сообщений по полноми имени сообщения (иимя пакета и имя сообщения).

Дамперы

pkt2dumppq

Программа pkt2dumppq читает пакеты из шины пакетов и записывает их в базу данных напрямую в таблицу packet.

Предварительно нужно создать таблицу:

./pkt2dumppq -vvv --user imz --database iridium --password xxxxxxx --host localhost --createtable 

или в клиенте СУБД:

CREATE TABLE packet (id BIGSERIAL PRIMARY KEY, tag INTEGER NOT NULL, 
	time TIMESTAMP WITH TIME ZONE NOT NULL, value TEXT NOT NULL, 
	src VARCHAR(16), srcport INTEGER, dst VARCHAR(16), dstport INTEGER);

pkt2dumpfcm

Программа pkt2dumpfcm работает в двух режимах:

  • с шиной пакетов
  • вручную

В первом случае pkt2dumpfcm читает пакеты из шины пакетов и отправляет на мобильные устройства через сервис FireBase Cloud Messaging неразобранными пакетами.

Нужно указать подключение к базе данных, требования к таблицам базы даннх см. раздел про обработчик handlerfcm.

handlerfcm, в отличие от дампера, отправляет разобранный пакет как объект, сериализованный в JSON.

Если нужно использовать только отправку пакетов мобильным пользователям, достаточно запустить tcpreceiver, pkt2receiver и pkt2dumpfcm.

./pkt2dumpfcm --user imz --database iridium --password xxxxxx --host db.acme.org
Ручная отправка пакета в дамп

Обычно pkt2dumpfcm получает пакеты из шины с выхода ресиверов tcpreceiver и udpreceiver.

Опция -x --hex программы pkt2dumpfcm предназначена для тестирования отправки уведомлений. Пакет должен быть передан в этой опции в виде строки шестнадцатиричных чисел, как примере:

./pkt2dumpfcm -vvv --user imz --database iridium --password xxxxxx --host db.acme.org -x 01004e01001c83c9145933303032333430363931303530363000000800005a822ce403000b003e08f68195cb0000000202001e0890003e01cf12000031812b70160000450002208a0086d04d244d000a00

Send to: 300234069105060, key: AAAAITL4VBA:APA91bGQwuvaQTt8klgebh8QO1eSU7o5itF0QGnp7kCWJNgMwe8WM3bMh6eGDkeyMbvUAmE2MqtB1My3f0-mHM6MQE1gOjMB0eiAW1Xaqds0hYETRNzqAe0iRh5v-PcxmxrHQeJh6Nuj
tokens: 1 total
dnTVve-KAxg:APA91bEzNLT62cYeYYGot8bpumY9iMMEUavG8LGjbrbnE50F55h3A8owZ3snYQa8ns1XcfztmLQPpxDmPh2a5VKDvH4WHaCvdmvb7z-PpROsqxK4WBXtlq6GUK_N2wvHvvvAbwYRTke5 1806 code: 200

Запустить со значениями по умолчанию

nohup ./pkt2dumpfcm --user imz --database iridium --password xxxxxxx --host db.acme.org &

pkt2js

Программа pkt2js читает входной файл(по умолчанию stdin), пытается найти описание пакета в папке файлов описаний (по умолчанию proto) и записывает JSON файл (по уvолчанию в stdout)

Опции

  • -x входные данные не бинарные, а в виде шестнадцатиричной строки
  • -m FMT - выходной формат FMT: json(по умолчанию), csv, tab, sql, Sql, pbtext, dbg, hex, bin)
  • -p каталог proto

Пример:

cat si13.hex 
01ff0c000000001e00000034000000120000000000000000
cat si13.hex | ./pkt2js  -x
{"temperature":30,"counter1":872415232,"counter2":301989888}

Диаграмма обработки

                                    Шина контроля(лог)
                                    ipc:///tmp/control.pkt2
                                    Шина дамперов
                                    ipc:///tmp/dump.pkt2

               Шина пакетов                                          Шина сообщений
               ipc:///tmp/packet.pkt2                                ipc:///tmp/message.pkt2
                                              Файлы
                                            (плагины)
                                          поддерживаемых
                                            протоколов
                                          +------------+
                                          | Протоколы  |
                                          +------------+
                                          | Протокол 1 |
                                          | ...        |
                                          | Протокол N |-----------------+
                                          +------------+  Описание       |
                                                ^        входящего       |
                                   Определение  |         пакета         |
                                    протокола   |            |           |
                                                |            |           |
  Ресиверы     Сообщение        Приемник          Парсер    Процессор-   | Сообщение           Шлюз
                очередь                                     сериализатор |  очередь
 +-------+    +---------+    +------------+    +-------+    +---------+  | +--------+
 |  TCP  |--->| очередь |--->| вх. пакет  |--->| Поля  |    | Перемен.|  | | Запись |
 +-------+    +---------+    +------------+    +-------+    +---------+  | +--------+
 +-------+        |             |     |        | int x |    | int sum |  | | sum    |    +-------------+
 |  UDP  |--------+             |     |        | int y |--->| = x + y |--->| x1     |--->| Обработчик 1|
 +-------+                      |     |        | int z |    |         |  | | z      | |  +-------------+
                                |     |        +-------+    +---------+  | +--------+ |  |             |
                          сокет |     | сокет         Описание|          +--+         |  +-------------+
                        контроля|     | дамперов      исх.пак.|             |         |  +-------------+
Трансмиттер               Передатчик  |     Композер          |             |         +->| Обработчик 2|
 +-------+    +----+    +-----------+ |  +--------------+    +-------+    +---------+    +-------------+
 |       |<---|    |<---| исх.пакет |<---| "Переменные" |    |Входн. |<---| Запись  |<---|             |
 +-------+    +----+    +-----------+ |  +--------------+    +-------+    +---------+    +-------------+
                        | uint16 a  | |  | float a = x+y|    | x     |                          
                        | uint32 b  |--->| uint16 b = x |    | y     |                          
                        | uint32 c  | |  | uint32 c = z |    | z     |                  
                        +-----------+ |  +--------------+    +-------+                  
                                |     |                                                  
                                |     |  +---------+                                     +----+
                                |     +->| Дамперы |------------------------------------>| БД |
                                |        +---------+                                     +----+
                                |
                        +-----------+ 
                        | Контроль  |
                        +-----------+
Программы
[tcpemitter-iridium]
[tcpemitter-example1]
[tcpemitter]
              tcpreceiver                       pkt2receiver               [pkt2gateway]     handlerpq             
              mqtt-receiver                                                [message2gateway] handler-google-sheets  
              freceiver                                                    [example1message] handlerline
                                                                                             handlerlmdb
                                                                                             handlerfcm
Дамперы
                                         pkt2dumppq
                                         pkt2dumpfcm

В квадратных скобках ([]) тестирующие программы

pkt2receiver делает bind() сокетов шины контроля(логи) ipc:///tmp/control.pkt2 и шины дамперов ipc:///tmp/dump.pkt2.

Программы логгеры и дамперы коннектятся к ipc:///tmp/control.pkt2 или ipc:///tmp/dump.pkt2.

Назначение программ:

  • tcpreceiver слушает TCP порт, передает полученное в шину пакетов
  • freceiver читает файл или устройство в буфер заданного размера, и передает буферы в шину пакетов или печатает в stdout в текстовом виде
  • mqtt-receiver подписывается на указанные топики, передает полученные пакеты из брокера msqt в шину пакетов
  • pkt2receiver чтение пакетов из шины пакетов, нахождение протокола, отправка сообщения в шину сообщений
  • handlerline помещение сообщений в stdout lля последующей обработки скриптами
  • handlerlmdb помещение сообщений в базу данных LMDB
  • handlerpq помещение сообщений в базу данных PostgreSQL
  • handler-google-sheets помещение сообщений в электронную таблицу Google Sheets
  • handlerfcm отправка уведомлений в FireBase Cloud Messaging
  • pkt2js читает пакет из файла, находит proto описание и выдает JSON файл или protobuf данные(бинарные или в шестнадцатиричном виде)

Сокеты по отношению к шине

pkt2receiver- единственный слушатель на каждой из двух шин(пакетов и сообщений). К шине пакетов можно подключить несколько процессов программ tcpreceiver, mqtt-receiver. Так как tcpreceiver прослушивает TCP порт, каждому такому процессу нужно задать отдельный порт. К шине сообщений можно подключить несколько процессов программ handler*. Нужно следить, чтобы запущенные несколько раз процессы не разделяли один и тот же ресурс.

Схема соединения шин с pkt2receiver:


            +---------------+   +---------------+
            | Шина контроля |   | Шина дамперов |
            +---------------+   +---------------+
                 |                       |
                 |   +---------------+   |
                 +-->|               |<--+
                     |  pkt2receiver |
                 +-->|               |<--+
                 |   +---------------+   |
                 |                       |
            +---------------+   +---------------+
            | Шина пакетов  |   | Шина сообщений|
            +---------------+   +---------------+
            

Включение сокетов к шине в программах:

Программа Шина пакетов Шина сообщений Шина контроля Шина дамперов
Имя шины ipc:///tmp/packet.pkt2 ipc:///tmp/message.pkt2 ipc:///tmp/control.pkt2 ipc:///tmp/dump.pkt2
tcpreceiver connect
mqtt-receiver connect
pkt2receiver bind bind bind bind
handlerline connect
handlerlmdb connect
handlerpq connect
handler-google-sheets connect
handlerfcm connect
pkt2dumpfcm connect
pkt2dumppq connect
pkt2receiver-check connect

Контроль прохождения пакетов pkt2receiver

Программа pkt2receiver - единственная программа, имеющая доступ как к входной, так и выходной шинам, поэтому она собирает данные для статистики и контроля.

Собранные данные передаются в шину контроля. К шине контроля на чтение может подключаться несколько программ одновременно.

Пример программы контоля pkt2receiver-check.

Загрузчик

Программа pkt2 запускает программы по конфигурационному файлу.

Конфигурационный файл по умолчанию имеет имя pkt2.js.

Программа pkt2 следит за запущенными ею процессами через файлы /proc/<номер процесса>/cmdline.

Если файл перестает содержать данные, или отсутсвует файл или каталог процесса, pkt2 повторно запускает процесс.

pkt2 не запускает процессы как демоны. Для запуска процессов нужно использовать опцию -d при запуске каждой отдельной программы. pkt2 таким же образом модет быть демонизирован.

Хотя конфигурационный файл является формально программой на языке Javascript, то есть в нем можно делать вычисления параметров, в результате конфигурационный файл должен предоставить "глобальные" переменные:

proto_path = "proto";
max_file_descriptors = 0;
max_buffer_size = 4096;
bus_in = "ipc:///tmp/packet.pkt2";
bus_out = "ipc:///tmp/message.pkt2";

Конфигурация программ- приемников пакетов

Массивы программ- приемников пакетов (они передают полученные данные в шину пакетов):

tcp_listeners = 
[
	{
		"port": 50052,
		"ip": "0.0.0.0",
		"compression_type": 0,
		"escape_code": "",
		"compression_offset": 0,
		"frequence_file": "",
		"codemap_file": "",
		"valid_sizes": []
	}
];

mqtt_listeners = 
[
	{
		"client": "cli01",
		"broker": "tcp://127.0.0.1",
		"topic": "pkt2",
		"port": 1883,
		"qos": 1, 
		"keep-alive": 20
	}
];

// mode: 0- binary, 1- hex lines, 2- integers
// file: default stdin
file_listeners = 
[
	{
		"file": "/dev/device",
		"mode": 0
	}
];

Массивы используются, потому что можно запустить несколько экземпляров программ, за исключением pkt2receiver.

Плэтому в конфигурационном файле нужно указать только один элемент массива конфоигурации pkt2receiver:

packet2message = 
[
	{
		"sizes": 
		[
		],
		"force-message": ""
	}
];

Так как входные и выходные шины заданы для всех программ в глобальных переменных. Если надо запусить друглй экземпляр, используйте pkt2 с другим конфигурационным файлом, использующим другие шины.

Параметры tcp_listeners

Сетевые параметры:

  • port Номер порта. По умолчанию 50052
  • ip Доменное имя или IPv4 адрес сетевого интерфейса. По умолчанию "0.0.0.0" - все интерфейсы

Параметры декомпрессии пакетов(необяательные):

  • compression_type По умолчанию 0. 1- сжатие (Huffman). Для сжатия нудно указать или файл частот для построения таблицы кодов или готовую таблицу кодов
  • escape_code Специальный экранирующий код- префикс(escape- код) в бинарном коде, который используется в качестве префикса для следующих за ним восьми бит значения. По умолчанию не задан. Имеет значение, если compression_type больше 0.
  • compression_offset Смещение в байтах, начиная с которого данные сжимаются. По умолчанию 0. Имеет значение, если compression_type больше 0.
  • frequence_file Файл частот, используемый для построения таблицы кодов Хаффмана. Имеет значение, если compression_type больше 0 и не задан параметр codemap_file.
  • codemap_file Готовая таблицы кодов Хаффмана. Имеет значение, если compression_type больше 0.
  • valid_sizes: [] Допустимые размеры пакета после распаковки. Если не указано ни одного размера, проверка на размер после распаковки не делается. Если указан хотя бы один размер, то если после распаквки размер пакета не равен олдноу из перечисленных, распаквка отменяется, и пакет передается как есть.

Коды Хаффмана

Обработчики
write_file = 
[
	{
		"messages":
		[
			"iridium.IE_Packet"
		],
		"mode": 0,
		"file": "1.txt"
	}
];

write_lmdb = 
[
	{
		"messages":
		[
			"iridium.IE_Packet"
		],
		"dbpath": "db"
	}
];

write_pq = 
[
	{
		"user": "pkt2",
		"password": "123456",
		"scheme": "pkt2",
		"host": "localhost",
		"port": 5432,
		"messages":
		[
			"iridium.IE_Packet"
		]
	}
];

write_fcm =
[
	{
		"key": "AAAAITL4VBA:APA91bGQwuvaQTt8klgebh8QO1eSU7o5itF0QGnp7kCWJNgMwe8WM3bMh6eGDkeyMbvUAmE2MqtB1My3f0-mHM6MQE1gOjMB0eiAW1Xaqds0hYETRNzqAe0iRh5v-PcxmxrHQeJh6Nuj",
		"imei": "imei",
		"user": "pkt2",
		"password": "123456",
		"scheme": "pkt2",
		"host": "localhost",
		"port": 5432,
		"messages":
		[
			"iridium.IE_Packet"
		]
	}
];

write_google_sheets = 
[
	{
		"json_service_file_name": "cert/pkt2-sheets.json",
		"bearer_file_name": ".token-bearer.txt",
		"email": "[email protected]",
		"spreadsheet": "1iDg77CjmqyxWyuFXZHat946NeAEtnhL6ipKtTZzF-mo",
		"sheet": "Temperature",
		"messages":
		[
			"iridium.IE_Packet"
		]
	}
];

Параметры write_fcm:

  • key Токен FireBase Cloud Messaging, по умолчанию "AAAAITL4VBA:APA91bGQwuvaQTt8klgebh8QO1eSU7o5itF0QGnp7kCWJNgMwe8WM3bMh6eGDkeyMbvUAmE2MqtB1My3f0-mHM6MQE1gOjMB0eiAW1Xaqds0hYETRNzqAe0iRh5v-PcxmxrHQeJh6Nuj"
  • imei имя поля (окончание полного имени поля), где записан IMEI. По умолчанию "imei"

Повторители

Повторители копируют сообщения из одной шины в другую.

Запуск:

repeator -i <адрес входной шины> -o <адрес выходной шины> ...

Адреса входной шины могут быть, например, такими:

ipc:///tmp/control.pkt2
ipc:///tmp/message.pkt2

Адрес выходной шины может быть, например, такой:

tcp://84.237.111.36:50001

По умолчанию сокет выходной шины открывается функцией connect(), то есть в примере на интерфейсе 84.237.111.36 должен быть предварительно запущен сторонний сервис на порту 50001.

Если repeator не сможет соединиться со сторонним сервисом, он завершаеится с ошибкой.

Сокет выходной шины можно открывать функцией bind(), указав опцию -s (--bind). В этом случае repeator

iridium_data_server

Сервис ошейников на nova.ysn.ru полученные TCP пакеты ретранслирует на 0.0.0.0:50052 (в tcpreceiver TCP по умолчанию порт 50052).

Сервис ошейников в репозитории collarie-0.1

Выходная

Можно указать несколько выходных шин.

Входная шина по умолчанию- ipc:///tmp/control.pkt2.

В pkt2 в конфигурационном файле можно задать запуск нескольких повторителей для входных шин.

В параметре outs нужно перечислить выходные шины.

repeators = 
[
	{
		"in": "ipc:///tmp/control.pkt2",
		"bind": false,
		"outs": [
			"tcp://0.0.0.0:50000"
		]
	}
];

Скрипты

Для запуска других программ в конфигурации есть массив scripts.

Скрипт должен быть в файле "script".

Параметр -c позволяет вызвать из скрипта другие программы.

Аргументы:

-c "command" (optional) -i input queue (optional) -o output queue (optional)

	scripts = 
	[
		{
			"command": "1"
			"in": "ipc:///tmp/control.pkt2",
			"outs": [
				"tcp://0.0.0.0:50000"
			]
		}
	];

Дампер

Дампер предназначен для записи "черновых" пакетов в базу данных.

pkt2receiver получает пакеты из очереди пакетов.

pkt2receiver должен запускаться отдельно, программа pkt2 не управляет ею.

Вспомогательные программы

Помещение готовых сообщений в шину сообщений

message2gateway читает сериализованные (бинарные) сообщения из сокета или stdin и перемещает их в шину сообщений без адресов откуда и куда пришли

messageemitter читает сообщения в формате JSON из сокета или stdin и перемещает их в шину сообщений без адресов откуда и куда пришли

Генераторы случайных данных

tcpemitter-example1 отправялет в TCP/IP порт пакеты (на вход tcpreceiver) со случайными значениями для примера example/example1.proto

tcpemitter-iridium отправялет в TCP/IP порт пакеты (на вход tcpreceiver) со случайными значениями примера iridim/animals.proto

tcpemitter читает пакеты из файла (hex) или stdin и перемещает их в шину пакетов

Разное

protoc-gen-pkt2 плагин компилятора protoc для создания SQL скриптов

example1message отправляет в stdout сериализованные (бинарные) сообщения для примера example/example1.proto (можно подать на вход прогшраммы tcpemitter)

example1message1 отправляет в stdout одно сообщение без сериализованных полногшо имени сообщения, его длины, адресов. Не используется.

Порядок запуска и останова

Сначала запускаются публикаторы, поэтому нужно запускать ресиверы, затем message2gateway, и потом обработчики (handler*)(по схеме слева направо).

Останов делается в обратном порядке (по схеме справо налево) от обработчиков к ресиверам.

Если по какой то причине остановить программу в левой части схемы или message2gateway, все программы правее должны быть перезапущены.

Обработчики (на схеме самые правые), как конечные потребители, можно перезапускать

Описание выходных данных

Значения по умолчанию

tcpreceiver TCP по умолчанию порт 50052

Имена каналов(очередей) по умолчанию:

  • ipc:///tmp/packet.pkt2
  • ipc:///tmp/message.pkt2

Программы

Основные

  • tcpemitter
  • tcpreceiver
  • pkt2receiver
  • pkt2gateway
  • handlerpq
  • handlerline
  • tcptransmitter
  • message2gateway

Тестирующие

  • example1message1
  • pkt2gateway
  • tcpemitter-example1
  • tcpemitter-iridium

example1message - тестирующая программа для message2gateway. Генерирует сообщения для записи.

показаны на схеме, для передачи данных друг другу используется передача через именованные разделяемые области памяти библиотекой nanomsg (http://nanomsg.org/), эмулирующих межпроцессное взаимодействие с очередями сообщений как с сокетами.

tcpreceiver

./tcpreceiver --help
PKT2 tcp packet listener sends raw packet
  -i, --ipaddr=<IP address> Network interface name or address. Default 0.0.0.0
  -l, --listen=<port>       TCP port to listen. Default 50052
  -o, --output=<bus url>    Default ipc:///tmp/packet.pkt2
  -b, --buffer=<size>       Default 4096 bytes
  -r, --repeat=<n>          Restart listen. Default 0.
  -y, --delay=<seconds>     Delay on restart in seconds. Default 60.
  -c, --compression=<number> 1- Huffman(modified). Default 0- none.
  -e, --escapecode=<BITS>   Escape code is a prefix for 8 bit value(not a Huffman code). Default none.
  -s, --start=<offset>      Valid with -c option. Default 0.
  -f, --freq=<file>         Compression frequence file name (in conjuction with -m) .
  -m, --map=<file>          Compression code dictionary file name.
  -p, --packetsize=<bytes number> Default any sizes.
  -d, --daemonize           Start as daemon/service
  --maxfd=<number>          Set max file descriptors. 0- use default (1024).
  -v, --verbosity           Verbosity level
  -h, --help                Show this help

Опция -p <размер> задает допустимые размеры пакета после распаковки и может быть повторена не более 256 раз, чтобы указать разные пакеты.

Опция -c со значением больше нуля включает распаковку пакета, начиная со смещения, задаваемого опцией -s (если не задана, то с начала).

Опции -f или -m взаимоисключающие, ими можно задать таблицу кодов Хаффмана по таблице частот (-f) или явно (-m).

Формат файла частот

Текст со строками частот(разделитель LF) из двух колонок(разделитель TAB)- код(от 0 до 255) и частота(>=0). В примере ниже частоты заданы десятичными числами:

90	1
0	1
1	999999999999
2	999999999999
3	999999999999
4	999999999999

Формат файла кодов Хаффмана

Текст с кодами(разделитель LF) из двух обязательных колонок(разделитель TAB или пробелы)- значение кодирумого байта(десятичное число), код в двоичной системе. В примере ниже в третьей необязательной колонке(при чтении файла все, что правее второй колонки, игнорируется) сделаны примечания:

0  	01
1  	100
2  	101
3  	11
4  	000
90 	001	конец

Строки без двух колонок(пустые строки) пропускаются.

tcpemitter

tcpemitter -i localhost -l 50052 << messages.txt

Каждая строка должна иметь тип сообщения и значения в формате JSON, разделенный знаком двоеточия ":"

Packet.MessageType:{"json-object-in-one-line"}

handlerlmdb

Значения

  • Record#
  • PK

handlerfcm

handlerfcm отравляет разобранный пакет как объект, сериализованный в JSON на зарегистрированные мобильные усройства пользователяв посредством FireBase Cloud Messaging.

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

Требуется указать параметры соединения с базой данных.

В базе данных должна быть доступны таблицы

  • dev ()
  • device_description (name)
  • devices (IMEI)
  • device (часовой пояс, поле tz в часах, миниутах или секундах
CREATE TABLE users (
	u_id bigserial NOT NULL,
	login varchar(32) NOT NULL,
	password varchar(32) NOT NULL,
	salt varchar NOT NULL,
	"name" varchar NULL,
	patron varchar NULL,
	family varchar NULL,
	mail varchar NULL,
	"comment" text NULL,
	accessdb varchar(8) NULL,
	proctablefieldflag1 int4 NOT NULL DEFAULT '-1'::integer,
	rawtablefieldflag int4 NOT NULL DEFAULT '-1'::integer,
	proctablefieldflag2 int4 NOT NULL DEFAULT '-1'::integer,
	phone varchar NULL,
	dms bool NULL,
	"start" int4 NULL DEFAULT date_part('epoch'::text, now()),
	finish int4 NULL,
	CONSTRAINT u_pkey PRIMARY KEY (u_id),
	CONSTRAINT u_unic UNIQUE (login)
);

CREATE TABLE dev (
	id bigserial NOT NULL,
	userid int8 NOT NULL,
	"instance" text NOT NULL DEFAULT ''::text,
	state int4 NOT NULL DEFAULT 1,
	created int4 NOT NULL DEFAULT date_part('epoch'::text, now()),
	updated int4 NOT NULL DEFAULT date_part('epoch'::text, now()),
	"name" text NOT NULL DEFAULT ''::text,
	notes text NOT NULL DEFAULT ''::text,
	send int4 NOT NULL DEFAULT 0,
	recv int4 NOT NULL DEFAULT 0,
	CONSTRAINT dev_pkey PRIMARY KEY (id),
	CONSTRAINT fk_dev_usrid FOREIGN KEY (userid) REFERENCES users(u_id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE UNIQUE INDEX idx_dev_instance ON dev USING btree (instance);

CREATE TABLE device_description (
	id bigserial NOT NULL,
	imei int4 NOT NULL,
	device_name varchar NULL,
	device_serial_number varchar NULL,
	device_description varchar NULL,
	legend varchar NULL,
	owner int4 NOT NULL,
	color varchar NULL,
	"current" bool NOT NULL DEFAULT true,
	edit_time timestamp NOT NULL DEFAULT (now() + '10:00:00'::interval),
	CONSTRAINT pk_dd PRIMARY KEY (id),
	CONSTRAINT fk2_dd FOREIGN KEY (owner) REFERENCES users(u_id),
	CONSTRAINT fk_dd FOREIGN KEY (imei) REFERENCES devices(id)
);

CREATE TABLE devices (
	id bigserial NOT NULL,
	imei varchar NOT NULL,
	created_time timestamp NOT NULL DEFAULT (now() + '10:00:00'::interval),
	CONSTRAINT pk_d PRIMARY KEY (id),
	CONSTRAINT uni_d UNIQUE (imei)
);

CREATE TABLE device(
	d_imei
	tz

Если не удается считать значение часового пояса, по умолчанию берется часовой пояс +9.

Поле dev.instance содержит токен FireBase устройства, связанный с IMEI devices.imei куда отправлять сообщения. Пакет должен содержать IMEI устройства (15 байт) начиная с байта 10.

	SELECT dev.instance, device_description.device_name
	FROM device_description, devices, dev
	WHERE device_description.imei = devices.id and dev.userid = device_description.owner 
	AND device_description.current = 't'
	AND devices.imei = $imei;

Чтение данных из бинарных и текстовых файлов

Программа freceiver получает данные из файла (устройства) и помещает их в шину данных (по умолчанию в ipc:///tmp/packet.pkt2) или печатает в stdout в текстовом виде.

Формат данных

Аргумент -t

По умолчанию данные в бинарном виде (-t 0)

  • 0 Данные в бинарном виде (по умолчанию)
  • hex Данные в виде текста, где каждый байт представлен двумя символами- шестнадцатричным числом (вместо строки 'hex' можно использовать число 1)
  • int Данные в виде текста, где каждый байт представлен десятичным числом, разделяемым символами CR LF (вместо строки 'int' можно использовать число 2)

Пример: чтение шестнадцатиричных чисел из stdin

./freceiver -t 1

Размер пакета

Аргумент -s, --size=

Пример: чтение RAW данных из stdin пакетами по 30 байт

./freceiver -s 30

Входной файл (устройство)

Аргумент -i, --input=

Дамп (текстовое представление пакета)

Аргумент -m, --print==

Выводит в stdout текстовое представление в заданном формате:

0 JSON
0 CSV
1 JSON
2 Текст, разделенный символом табуляции
3 SQL INSERT выражение для вставки в базу данных, вариант 1
4 SQL INSERT выражение для вставки в базу данных, вариант 2
5 Текстовый формат Protobuf
6 Отладочный формат Protobuf

Пример JSON формата:

"logger60.TemperatureNAngles": {
	"logger60.TemperatureNAngles.t1": "21.2500",
	"logger60.TemperatureNAngles.t2": "20.81250000",
	"logger60.TemperatureNAngles.t3": "20.03125000",
	"logger60.TemperatureNAngles.x1": "87.95654297",
	"logger60.TemperatureNAngles.y1": "-9.03076172",
	"logger60.TemperatureNAngles.z1": "9.86572266",
	"logger60.TemperatureNAngles.x2": "87.16552734",
	"logger60.TemperatureNAngles.y2": "16.21582031",
	"logger60.TemperatureNAngles.z2": "-6.06445313",
	"logger60.TemperatureNAngles.x3": "88.65966797",
	"logger60.TemperatureNAngles.y3": "2.02148438",
	"logger60.TemperatureNAngles.z3": "7.86621094"
}
...

Пример SQL(1) формата:

INSERT INTO "logger60_TemperatureNAngles"("logger60.TemperatureNAngles.t1", "logger60.TemperatureNAngles.t2", "logger60.TemperatureNAngles.t3", "logger60.TemperatureNAngles.x1", "logger60.TemperatureNAngles.y1", "logger60.TemperatureNAngles.z1", "logger60.TemperatureNAngles.x2", "logger60.TemperatureNAngles.y2", "logger60.TemperatureNAngles.z2", "logger60.TemperatureNAngles.x3", "logger60.TemperatureNAngles.y3", "logger60.TemperatureNAngles.z3") VALUES (22.0625, 21.37500000, 0.00000000, 87.20947266, 16.12792969, -5.73486328, 88.68164063, 1.47216797, 7.77832031, 12.96386719, 11.35986328, 0.08789063);

Пример 1: термокоса с инклинометром

Копируем файл с несколькими записями фиксированной длины

scp [email protected]:/home/andrei/src/thermo-php/data/64x83-20170823100233.raw .

Файл содержит 83 записи (бинарные данные) по 64 байта.

Проверяем файл:

./freceiver -s 64 -i 64x83-20170823100233.raw -p proto -m 1
"logger60.TemperatureNAngles",21.2500, 20.81250000, 20.03125000, 87.95654297, -9.03076172, 9.86572266, 87.16552734, 16.21582031, -6.06445313, 88.65966797, 2.02148438, 7.86621094
...

Загружаем файл:

./freceiver -s 64 -i 64x83-20170823100233.raw

Тестирующие

example1message1

Сериализует в stdout одно случайное сообщение (сообщение TemperaturePkt, файл описания example/example1.proto)

./example1message1 > 1
codex -protofile proto/example/example1.proto -message_name TemperaturePkt 1

pkt2gateway

Отправляет подготовленные заранее тестовые сообщения в очередь обработчиков ipc:///tmp/message.pkt2

tcpemitter-example1

Отправляет TCP пакеты как в примере 1.

По умолчанию шлет в порт 50052.

Один экземпляр tcpreceiver, mqtt-receiver или freceiver должен быть запущен. По умолчанию tcpreceiver слушает порт 50052.

tcpemitter-iridium

Отправляет TCP пакеты Iridium пакет 8.

Опция -d 60 задает задержку в 1 минуту между посылками.

Плагин компилятора protoc protoc-gen-pkt2

Плагин компилятора protoc (компилятор скачать можно тут https://github.com/google/protobuf/releases)

Пример использования protoc-gen-pkt2 в скрипте tests/p1.sh:

protoc --proto_path=proto --cpp_out=. proto/pkt2.proto
protoc --proto_path=proto --cpp_out=. proto/example/example1.proto
protoc --proto_path=proto --cpp_out=. proto/iridium/packet8.proto
protoc --plugin=protoc-gen-pkt2="../protoc-gen-pkt2" --proto_path=../proto --pkt2_out=pkt2 ../proto/example1.proto
protoc --plugin=protoc-gen-pkt2="protoc-gen-pkt2" --proto_path=proto --pkt2_out=pkt2 proto/example1.proto

Опции

  • pkt2_out каталог, где будут сохранены сгенерированные файлы
  • plugin имя плагина и путь к его исполнимому файлу

Библиотека libpkt2.a

Необходим заголовочный файл str-pkt2.h, библиотека libpkt2.a.

Также необходимо, чтобы была доступна разделяемая библиотека libprotobuf.so

версии, использованной при сборке библиотеки libpkt2.a.

Разделяемая бибиотека (libpkt2.so) собирается, если раскоментировать строки в Makefile.am:

#lib_LTLIBRARIES = libpkt2.la
#libpkt2_la_SOURCES = $(common_src)
#libpkt2_la_LDFLAGS = libpkt2.a
#libpkt2_la_CXXFLAGS = -I.

Сначала нужно прочитать объявления в файлах .proto:

void* env = initPkt2("proto", 0);

Полученный дескриптор env нужно перелавать в функции:

  • parsePacket() - разбирает пакет и возвращает значения в строке заданного формата
  • parsePacket2ProtobufMessage() - разбирает пакет и возвращает сообщение protobuf
  • headerFields() - вспомогательная функция, возвращает имена полей для пакета (по имени типа сообщения)

В примере выше файлы объявлений находятся в папке proto текущего каталога.

По завершении нужно закрыть десприптор env:

donePkt2(env);

Пример

Функция

bool parsePacket2ProtobufMessage(void **retMessage, void *env, int inputFormat, const std::string &packet, const std::string &forceMessage );

возвращает в retMessage созданный ей объект google::protobuf::Message (он указан как void, а не google::protobuf::Message, чтобы не включать заголовки protobuf)

Если в parsePacket2ProtobufMessage возникает ошибка, функция возврашает false, а значение *retMessage равно NULL.

В случае успеха, protobuf сообщение создается метоом New()

  // Construct a new instance of the same type.  Ownership is passed to the
  // caller.  (This is also defined in MessageLite, but is defined again here
  // for return-type covariance.)
  Message* New() const override = 0;

Нужно не забыть удалить объект

...
delete message;

Параметр inputFormat может принимать значения:

  • INPUT_FORMAT_BINARY (0)
  • INPUT_FORMAT_HEX (1)

INPUT_FORMAT_HEX означает, что пакет передается в строке не в бинарном виде, а в виде шестнадцатиричных чисел.

Если значение параметра forceMessage- пустая строка, то функция пытается определить тип соответствующего пакету сообщения.

Можно указать имя типа сообщения в формате package.message, например, в

iridium.IE_Packet

iridium- это имя package, а IE_Packet- имя message, объявленные в файле proto/animals.proto

Функция

std::string parsePacket(
	void *env, 
	int inputFormat,
	int outputFormat,
	const std::string &packet,
	const std::string &forceMessage
);

возврашает не protobuf сообщение, а строку в одном из заданных форматов в параметре outputFormat

  • OUTPUT_FORMAT_JSON (0)
  • OUTPUT_FORMAT_CSV (1)
  • OUTPUT_FORMAT_TAB (2)
  • OUTPUT_FORMAT_SQL (3)
  • OUTPUT_FORMAT_SQL2 (4)
  • OUTPUT_FORMAT_PBTEXT (5)
  • OUTPUT_FORMAT_DEBUG (6)
  • OUTPUT_FORMAT_HEX (7)
  • OUTPUT_FORMAT_BIN (8)

Пример:

#include <string>
#include <iostream>

#include "pkt2/str-pkt2.h"

int main(int argc, char **argv) {
	void* env = initPkt2("proto", 0);
	std::string hexData = "01004e01001c9a0ba5f633303032333430363032333533343000011900005ab8f59303000b003e68a68143d40000000502001e0810003e01b21200004e812b4e160000390000221400829486247a0d1c09";
	
	std::cout << parsePacket(env, 1, 0, hexData, "") << std::endl;
	donePkt2(env);
}

Баги

protoc --proto_path=proto --cpp_out=. proto/pkt2.proto

Удалить в pkt2.pb.h

#include "descriptor.pb.h"

pkt2.pb.cpp

пару строк:

::google::protobuf::protobuf_InitDefaults_descriptor_2eproto();

::google::protobuf::protobuf_AddDesc_descriptor_2eproto();

SNMP

cp mib/* ~/.snmp/mibs

snmptranslate -On -m +EAS-IKFIA-MIB -IR pkt2
.1.3.6.1.4.1.46956.1.2
snmptranslate -On -m +EAS-IKFIA-MIB -IR memoryPeak
.1.3.6.1.4.1.46956.1.2.1.1.1.6

smilint -l3  -s -p ./mib/EAS-IKFIA-MIB 

Проверка синтаксиса

protoc logger60.proto  --php_out /tmp

Cannot find module (SNMPv2-MIB)

sudo apt-get install snmp-mibs-downloader snmptrapd
sudo download-mibs
sudo sed -i "s/^\(mibs :\)./#\1/" /etc/snmp/snmp.conf
mkdir -vp ~/.snmp/mibs
sudo mkdir -p /root/.snmp/mib
cp mib/* ~/.snmp/mibs
sudo cp mib/* /root/.snmp/mib

snmptranslate -On -m +ONEWAYTICKET-COMMANDUS-MIB -IR onewayticketservice
.1.3.6.1.4.1.46821.1.1

smilint -l3  -s -p ./mib/*

snmpget -v2c -c private 127.0.0.1 ONEWAYTICKET-COMMANDUS-MIB::ticketssold.0
ONEWAYTICKET-COMMANDUS-MIB::ticketssold.0 = INTEGER: 0

snmpget -v2c -c private 127.0.0.1 ONEWAYTICKET-COMMANDUS-MIB::memorycurrent.0
ONEWAYTICKET-COMMANDUS-MIB::memorycurrent.0 = INTEGER: 29784

ERROR: You don't have the SNMP perl module installed.

Warning: no access control information configured.

Warning: no access control information configured.
  (Config search path: /etc/snmp:/usr/share/snmp:/usr/lib/x86_64-linux-gnu/snmp:/home/andrei/.snmp)
  It's unlikely this agent can serve any useful purpose in this state.
  Run "snmpconf -g basic_setup" to help you configure the onewayticketsvc.conf file for this agent.
snmpconf -g basic_setup

SNMP Error opening specified endpoint "udp:161"

161 привелигированный порт, программы нужно запускать с правами пользователя root (uid 0):

sudo ...

Запуск программ и контроль

Опция --maxfd позволяет увеличивать максимальное количество одновременно открытых дескрипторов- сокетов.

Значение по умолчанию для Linux- 1024.

./tcpreceiver --maxfd 100000

Запуск демонов

Опция -d демонизирует процессы. Создаются файлы /var/run/<имя программы>.pid

Файлы с номерами процессов демонов

PID файлы создаются только при наличии прав на запись в каталоге /var/run/:

/var/run/tcpreceiver.pid

то есть демон нужно запускать от имени root.

Опции configure, cmake

automake

Если нужно созжать две статические библиотеки как зависмость для другого проекта lorawan-network-server, пропустите сборку утилит и тестов:

./autogen.sh
./configure
make pkt2.pb.h libpkt2.a libpkt2util.a

pkt2.pb.h требуется для генерации файлов из Protobuf описания расширения Protobuf.

Иначе делайте полную сборку.

Указать компилятору флаги:

./configure CFLAGS='-g -O0' CXXFLAGS='-g -O0'

Отладка

./configure CFLAGS='-g -O0' CXXFLAGS='-g -O0'

или

./configure --enable-debug

Чтобы использовать компилятор clang в automake:

./configure CC=clang CXX=clang++

Чтобы использовать компилятор clang в cmake:

mkdir build
cd build
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
cmake ..

Включить MQTT (нужна библиотека Paho MQTT)

./configure --enable-mqtt

Включить SNMP (нужна библиотека snmp)

./configure --enable-snmp

cmake

Включить только сборку статической библиотеки

mkdir build
cd build
cmake ..
cmake --build . --target pkt2

Emscrypten

Установите emsdk

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
# или
python ./emsdk.py install latest
./emsdk activate latest

Если нет сертификата, проверьте версию python, установите сертификаты

pip3 install --upgrade certifi

Если нет python3, нужно его установить:

sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get install python3.6

и включить Python 3.6 вместо версии 2.7 (python) или 3.5 (python3) по умолчанию:

sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 10
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.5 20
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.6 30
sudo update-alternatives --config python
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.5 20
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 30
sudo update-alternatives --config python3
sudo update-alternatives --install /usr/bin/python-config python-config /usr/bin/python3.6-config 30

Если это не помогает, добавьте строки в скрипт emsdk.py:

import ssl
ssl._create_default_https_context = ssl._create_unverified_context

Включите окружение

source ~/git/emsdk/emsdk_env.sh --build=Release

Проверьте

mkdir hello
cd hello
cat << EOF > hello.c
#include <stdio.h>
int main(int argc, char ** argv) {
  printf("Hello, world!\n");
}
EOF
emcc hello.c -o hello.html
emrun --no_browser --port 8080 .
mkdir build
cd build
emcmake cmake ..
make pkt2

protoc

./example1message1 > 1

protoc -I proto --decode example1.TemperaturePkt proto/example/example1.proto < 1
device: 876648949
time: 1487218294
degrees_c: 22.111469307966281

protoc -I proto --decode_raw  < 1
1: 876648949
2: 1487218294
3: 0x40361c8940a83912

handlerline

Записывает в поток stdout сообщения в текстовом виде. Для отладки.

handlerpq

Для записи значений в базу данных Postgresql.

Два режима записи:

  • 3 SQL "нативный"
  • 4 SQL(2) c использованием view

Режим 4(dict)

Предварительно нужно создать две таблицы:

  • num для числовых данных
  • str для текстовых данных
DROP TABLE IF EXISTS num;
DROP TABLE IF EXISTS str;
DROP SEQUENCE IF EXISTS num_id_seq;
DROP SEQUENCE IF EXISTS str_id_seq;

CREATE SEQUENCE num_id_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 1;

  CREATE SEQUENCE str_id_seq
  INCREMENT 1
  MINVALUE 1
  MAXVALUE 9223372036854775807
  START 1
  CACHE 1;
  
CREATE TABLE num
(
  id bigint NOT NULL DEFAULT nextval('num_id_seq'::regclass),
  "message" bigserial,
  "time" timestamp with time zone NOT NULL SET DEFAULT now(),
  "device" text NOT NULL SET DEFAULT 0,
  "field" text NOT NULL,
  value NUMERIC NOT NULL,
  batch integer NOT NULL DEFAULT 0,
  CONSTRAINT num_pkey PRIMARY KEY (id)
);

CREATE TABLE str
(
  id bigint NOT NULL DEFAULT nextval('str_id_seq'::regclass),
  "message" bigserial,
  "time" timestamp with time zone NOT NULL SET DEFAULT now(),
  "device" text NOT NULL SET DEFAULT 0,
  "field" text NOT NULL,
  value text NOT NULL,
  batch integer NOT NULL DEFAULT 0,
  CONSTRAINT str_pkey PRIMARY KEY (id)
);

CREATE INDEX idx_num_time
    ON public.num USING btree
    ("time" ASC NULLS LAST)
    TABLESPACE pg_default;
CREATE INDEX idx_str_time
    ON public.str USING btree
    ("time" ASC NULLS LAST)
    TABLESPACE pg_default;
Создание представлений

Пример создает представление для даннх logger60:

--drop VIEW public.logger60;
CREATE OR REPLACE VIEW public.logger60 AS
	SELECT trunc(date_part('epoch'::text, num."time"))::numeric AS utc,
		num.device::integer AS dev,
		substr(num.field, 29, 1) AS fld,
		substr(num.field, 30, 1)::integer AS idx,
		num.value AS v
	FROM num
	WHERE num.message = 6000;

	ALTER TABLE public.logger60
	OWNER TO <username>;

	select utc, idx, v 
	from logger60
	where fld = 't' 
	and dev = 0
	and utc >= 1516177820
	and utc <= 1516177902
	order by utc, idx
utc					dev		fld		idx		v
1516177820.37942	0		t		1		21.2500
Пример записей

Числовые даннные

id,message,time.device,field,value
'93321','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_Packet.iridium_version','1'
'93322','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_Packet.iridium_size','78'
'93323','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_IOHeader.cdrref','1364432488'
'93324','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_IOHeader.status','0'
'93325','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_IOHeader.recvno','0'
'93326','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_IOHeader.sentno','0'
'93327','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_Location.iridium_latitude','62.0171'
'93328','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_Location.iridium_longitude','129.316'
'93329','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_Location.cepradius','150994944'
'93330','3008','2017-06-30 15:35:00+09','300234060235340','iridium.GPS_Coordinates.latitude','62.0333333333'
'93331','3008','2017-06-30 15:35:00+09','300234060235340','iridium.GPS_Coordinates.longitude','129.7166666667'
'93332','3008','2017-06-30 15:35:00+09','300234060235340','iridium.GPS_Coordinates.hdop','9'
'93333','3008','2017-06-30 15:35:00+09','300234060235340','iridium.GPS_Coordinates.pdop','10'
'93334','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpsolddata','0'
'93335','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpsencoded','0'
'93336','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpsfrommemory','0'
'93337','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpsnoformat','0'
'93338','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpsnosats','0'
'93339','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpsbadhdop','0'
'93340','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpstime','0'
'93341','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.gpsnavdata','0'
'93342','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.satellite_visible_count','5'
'93343','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.battery_voltage','3.2'
'93344','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.battery_low','0'
'93345','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.battery_high','0'
'93346','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.temperature_c','25'
'93347','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.reserved_2','0'
'93348','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.failurepower','0'
'93349','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.failureeep','0'
'93350','3008','2017-06-30 15:35:00+09','300234060235340','iridium.Packet8.failureclock','0'
...

Текстовые даннные

id,message,time.device,field,value
'4219','3008','2017-06-30 15:35:00+09','300234060235340','iridium.IE_IOHeader.recvtime','30.06.2017 15:35'
...

Режим 3(native)

Предварительно для режима SQL нужно создать таблицы, куда будут вставляться данные.

Запустите с опцией -vv и остановите (Ctrl+C) программу.

./handlerpq -p proto --host localhost --user onewayticket --database onewayticket --password 123456 -vv
Press Ctrl+C
cat handlerpq.INFO

В файле журнала handlerpq.INFO вначале пишутся SQL выражения для создания таблиц следующего вида:

SQL CREATE TABLE statements
===========================
CREATE TABLE "example1_TemperaturePkt"(INTEGER device, INTEGER time, FLOAT degrees_c, id bigint);
CREATE TABLE "iridium_GPS_Coordinates"(FLOAT latitude, FLOAT longitude, INTEGER hdop, INTEGER pdop, id bigint);
CREATE TABLE "iridium_IE_IOHeader"(INTEGER cdrref, VARCHAR(32) imei, INTEGER status, INTEGER recvno, INTEGER sentno, INTEGER recvtime, id bigint);
CREATE TABLE "iridium_IE_Location"(FLOAT iridium_latitude, FLOAT iridium_longitude, INTEGER cepradius, id bigint);
CREATE TABLE "iridium_IE_Packet"(INTEGER iridium_version, INTEGER size, id bigint);
CREATE TABLE "iridium_Packet8"(INTEGER coordinates, INTEGER measure_time, INTEGER gpsolddata, INTEGER gpsencoded, INTEGER gpsfrommemory, INTEGER gpsnoformat, INTEGER gpsnosats, INTEGER gpsbadhdop, INTEGER gpstime, INTEGER gpsnavdata, INTEGER satellite_visible_count, FLOAT battery_voltage, INTEGER battery_low, INTEGER battery_high, INTEGER temperature_c, INTEGER reserved_2, INTEGER failurepower, INTEGER failureeep, INTEGER failureclock, INTEGER failurecable, INTEGER failureint0, INTEGER software_failure, INTEGER failurewatchdog, INTEGER failurenoise, INTEGER failureworking, INTEGER key, id bigint);
CREATE TABLE "iridium_Time5"(INTEGER date_time, id bigint);

Предварительно для режима SQL(2) нужно создать как минимум две таблицы:

CREATE TABLE num (message VARCHAR(255), time INTEGER, device INTEGER, field VARCHAR(255), value NUMERIC(10, 2), batch integer NOT NULL DEFAULT 0);
CREATE TABLE str (message VARCHAR(255), time INTEGER, device INTEGER, field VARCHAR(255), value VARCHAR(255), batch integer NOT NULL DEFAULT 0);

Пример данных

Таблица num:

"id";"message";"time";"device";"field";"value"
"73722";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.Packet8.battery_voltage";3.2
"73721";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.Packet8.satellite_visible_count";5
"73720";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.Packet8.gpsnavdata";0
...
"73712";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.GPS_Coordinates.pdop";10
"73711";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.GPS_Coordinates.hdop";9
"73710";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.GPS_Coordinates.longitude";129.7166666667
"73709";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.GPS_Coordinates.latitude";62.0333333333
"73708";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.IE_Location.cepradius";150994944
"73707";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.IE_Location.iridium_longitude";129.316
"73706";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.IE_Location.iridium_latitude";62.0171
...

Таблица str:

"id";"message";"time";"device";"field";"value"
"3328";"3008";"2017-06-26 09:46:00+09";"300234060235340";"iridium.IE_IOHeader.recvtime";"26.06.2017 09:46"

handler-google-sheets, js2heet

Предназначены для записи данных в Google Sheet

Чтение JSON из файла. Нужно перелать имя файла в опции -i:

./js2sheet -e <e-mail> -s <spreadheet-id> -t <sheet-name>

Или передать JSON из другой программы:

cat 1.js | ./js2sheet -e <e-mail> -s <spreadheet-id> -t <sheet-name>

Электронная таблица (книга, spreasheet) имеет идентификатор, который нужно скопировать из URL:

https://docs.google.com/spreadsheets/d/<spreadhseet-id>/edit#...

Электронная таблица (книга) должна быть доступной пользователям Google Worksheet (быть в домене Google).

Параметр sheet-name укзывает на таблицу в книге ("закладку"). Чтобы избежать возможных трудностей с кодировкой, рекомендутся закладки называть латинскими буквами и цифрами без пробелов и других знаков.

По умолчанию .proto файлы находятся в папке ./proto/

Для работы js2sheet необходим файл ./cert/pkt2-sheet.json для аутентфикации клиента.

Файл pkt2-sheet.json содержит ключи для аутентификации клиента в сервисе Google Sheet.

Ниже описано, как получить этот файл и предоставить права доступа к электронной таблице.

Предоставление доступа приложению js2sheet к Google Sheet в домене Google Workplce

Нужен доступ к

  • проектам Google Cloud Platform (консоль GC)
  • Google Admin панели управления Google Workplace (домену Google)

В консоли необходимо создать проект и добавить в него Google Sheet API, а в Google Admin- добавить клиента и предоставить ему доступные действия

Создайте проект и добавьте в проект Google Sheets API:

  • Создайте проект Google Cloud Platform в Google Cloud Platform Console
  • Выберите проект, выберите API Overview, и нажмите кнопку сверху "+ Enable APIs and services"
  • Выберите Google Sheets API
  • Нажмите Enable

Позднее понадобится идентификатор клиента в Google Admin - управлении Google Workplace.

Сконфигурируйте экран согласия

  • В проекте выберите меню слева Credentials
  • Сконфигурируйте экран согласия - нажмите кнопку "Configure consent screen"
  • На третьем диалоге выберите все scope, начинающиеся с Google Sheets

Создайте Credentials

  • Нажмите кнопку сверху "+ Create credentials"
  • Выберите API key
  • На закладке Keys нажмите кнопку "Add key" - "Create a new key"
  • Выберите формат JSON. Браузер начнет скачивание файла *.json
  • Сохраните скачанный файл в папке cert под именем (по умолчанию) pkt2-sheets.json

Предоставление прав в домене Google Works делается в два шага:

  • добавить клиента
  • предоставить клиенту доступные области действия

Добавьте клиента API по идентификатору в Google Admin (домене):

  • Зайдите Google Admin
  • Выберите меню Безопасность - Управление API - Управление правами доступа сторонних приложений
  • Нажмите Настроить новое приложение - .. Или Идентификатор Клиента

Добавьте области действия клиенту API:

Как создать проект Google Cloud Platform

Удаление (смена) пароля сертификата с приватным ключом сервиса Google Sheets

tools/p12-remove-password cert/pkt2-sheets.p12 
Enter Import Password:[notasecret]
MAC verified OK
Enter Export Password:
Verifying - Enter Export Password:

Ошибки

Ошибка открытия сокета

Socket connect error localhost:50052. Cannot assign requested address

Переполнение стека TCP/IP из-за того, что сервис не успевает обрабатывать данные из сокета.

Ошибки доступа IPC

Operation not permitted [1] (...bipc.c:309)

E0324 13:26:53.405812 16817 tcpreceivernano.cpp:114] Can not connect to the IPC url ipc:///tmp/packet.pkt2

sudo chown <user>:<group> /tmp/packet.pkt2

Тесты

Запуск генератора пакета example/example1 и слушателя TCP

./tcpreceiver -vv & ./tcpemitter-example1 -vv && fg

Ошибки при сборке

fatal error: libpq-fe.h: No such file or directory

Переконфигуриовать:

./configure
make

Баги и особенности реализации

nanomsg

Issue 182

При отключении потока публикатора (PUB) нужно пересоединить сокеты подписчиков (SUB)

Предположение: если поставить sleep(0) в публикаторе, то вроде бы работает.

Eclipse

Работает странно, лучше исптользоать KDevelop

Подсветка ошибок (включить c++ 11)

http://stackoverflow.com/questions/39134872/how-do-you-enable-c11-syntax-in-eclipse-neon

  • Right click on your project and click Properties
  • Navigate to C/C++ General and Preprocessor Include Paths, Macros etc.
  • Select the Providers tab, click on compiler settings row for the compiler you use.
  • Add -std=c++11 to Command to get compiler specs.

При застревании индексатора кода C/C++ Indexer:

rm ~/workspace/.metadata/.plugins/org.eclipse.cdt.core/*

Пропала иконка в Unity launcher

http://askubuntu.com/questions/80013/how-to-pin-eclipse-to-the-unity-launcher

Репозиторий и сборка

Репозиторий /media/dept/Конструкторский отдел/Repo/pkt2

Сборка:

apt install libcurl4-openssl-dev protobuf-compiler libgoogle-glog-dev libsnmp-dev libnanomsg-dev libprotoc-dev libpq-dev
tar xvfz pkt2-0.1.tar.gz
cd pkt2-0.1
./configure
make

Сборка в docker для nova.ysn.ru

Предварительно нужно развернуть образ centos:nova с необходимыми инструментами и библиотеками.

Запустите в docker со смонтированным каталогом с исходниками bash:

docker run -itv /home/andrei/src:/home/andrei/src centos:nova bash

В нем из каталога с исходными кодами пересоберите проект:

cd /home/andrei/src/pkt2
./tools/rebuild-nova

или

cd /home/andrei/src/pkt2
./configure
make clean
make

strip tcpreceiver tcpemitter tcpemitter-example1 tcptransmitter example1message1 example1message tcpemitter-iridium mqtt-emitter-iridium handlerpq handlerline handler-google-sheets handlerlmdb  messageemitter message2gateway protoc-gen-pkt2  pkt2dumppq protoc-gen-pkt2 pkt2gateway pkt2receiver pkt2gateway pkt2receiver pkt2 pkt2receiver-check repeator freceiver

scp tcpreceiver tcpemitter tcpemitter-example1 tcptransmitter example1message1 example1message tcpemitter-iridium mqtt-emitter-iridium handlerpq handlerline handler-google-sheets handlerlmdb  messageemitter message2gateway protoc-gen-pkt2  pkt2dumppq protoc-gen-pkt2 pkt2gateway pkt2receiver pkt2gateway pkt2receiver pkt2receiver-check pkt2 repeator freceiver [email protected]:/home/andrei/pkt2/bin

scp pkt2.js cert/pkt2-sheets.json [email protected]:/home/andrei/pkt2/bin
exit
закоммитить образ
docker ps -a
docker commit stoic_ramanujan
docker images
docker tag c30cb68a6443 centos:nova
# удалить закрытье контейнеры
docker rm $(docker ps -qa --no-trunc --filter "status=exited")

Запуск в nova.ysn.ru

копировать недостающие библиотеки из docker, как:

scp /usr/local/lib/libpaho-mqtt3c.so.1.0 [email protected]:/home/andrei/pkt2/lib/libpaho-mqtt3c.so.1
# прото файлы
scp -rp proto [email protected]:/home/andrei/pkt2/bin

Указать папку с библиотеками

export LD_LIBRARY_PATH=/home/andrei/pkt2/lib

DEBUG

./configure --enable-debug

Запуск с зависимостями

ssh nova.ysn.ru
export LD_LIBRARY_PATH=/home/andrei/pkt2/lib
...

-rwxr-xr-x 1 andrei andrei 97332 Apr 23 19:40 libargtable2.so.0

-rwxr-xr-x 1 andrei andrei 740633 Apr 23 19:41 libglog.so.0

-rwxr-xr-x 1 andrei andrei 266483 Apr 23 19:42 liblmdb.so

-rwxr-xr-x 1 andrei andrei 389454 Apr 23 19:43 libnanomsg.so.5.0.0

-rwxr-xr-x 1 andrei andrei 306544 Apr 23 19:44 libnetsnmpagent.so.20

-rwxr-xr-x 1 andrei andrei 153544 Apr 23 19:53 libnetsnmphelpers.so.20

-rwxr-xr-x 1 andrei andrei 1687840 Apr 23 19:55 libnetsnmpmibs.so.20

-rwxr-xr-x 1 andrei andrei 676416 Apr 23 19:45 libnetsnmp.so.20

-rwxr-xr-x 1 andrei andrei 1485896 Apr 23 19:46 libperl.so

-rwxr-xr-x 1 andrei andrei 22389022 Apr 23 19:37 libprotobuf.so.11

-rwxr-xr-x 1 andrei andrei 394799 Apr 23 19:48 libunwind.so.8

Приложение 1. Номера опций proto3

Опции:

  • packet
  • output
  • variable
extend google.protobuf.MessageOptions {
    pkt2.Packet packet = 50501;
}

extend google.protobuf.MessageOptions {
    pkt2.Output output = 50502;
}

extend google.protobuf.FieldOptions {
    pkt2.Variable variable = 50503;
}

Установка среды

cmake нужен для сбрки nanomsg (если не установлен из пакета)

sudo apt install autoconf libtool make g++ unzip cmake git curl wget
sudo apt install kdevelop code mc 

curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
sudo apt-get install oracle-java9-installer
oracle-java9-installer
sudo apt install oracle-java9-set-default
sudo update-alternatives --config java

Зависимости

Windows

Для Windows перед установкой зависимостей желательно поставить vcpkg и интегрировать его с Visual Studio:

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
bootstrap-vcpkg.bat
.\vcpkg\vcpkg integrate install

Установите protobuf

В Windows

vcpkg install protobuf

Pатем в CMakeLists.txt замените строку

set(PROTOBUF_ROOT /home/andrei/lib/grpc/third_party/protobuf/src)

на

set(PROTOBUF_ROOT "C:/git/vcpkg/buildtrees/protobuf/src/v3.18.0-296107ec8b.clean/src")

где C:/git/vcpkg/buildtrees/protobuf/src/- это путь к исходным файлам, скачанным vcpkg для компиляции.

Скопируйте protoc.exe и *.dll в папку, указанную в переменной окружения PATH.

Это нужно, так как комилятор protoc вызывается из CMakeFLists.txt

Запустите cmake в новой папке build:

mkdir build
cd build
cmake ..
# or cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DCMAKE_TOOLCHAIN_FILE=C:/git/vcpkg/scripts/buildsystems/vcpkg.cmake ..

Если нет cmake, установите его:

winget install -e --id Kitware.CMake

Откройте pkt2.sln в Visual Studio

Выберите в решении проект pkt2 и соберите только его, а не все решение.

В папке build/Release/

Linux

  • libpq-dev

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

./autogen.sh
autoreconf -fi
automake --add-missing
./configure --enable-static --enable-shared
make
sudo make install
make clean
sudo ldconfig

OpenSSL рекомендуется устанавливать версии 1.0.2g (из protobuf удалена, но есть в mqtt и curl)

sudo apt install libcurl4-openssl-dev libpq-dev
git clone git://git.sv.gnu.org/libunwind.git
git clone [email protected]:google/glog.git
git clone [email protected]:jedisct1/libsodium.git
git clone [email protected]:google/protobuf.git
git clone https://github.com/jonathanmarvens/argtable2.git
git clone [email protected]:TokTok/c-toxcore.git
wget -c https://netix.dl.sourceforge.net/project/net-snmp/net-snmp/5.8-pre-releases/net-snmp-5.8.pre2.tar.gz
wget -c https://github.com/openssl/openssl/archive/OpenSSL_1_0_2g.tar.gz 
-or-  sudo apt install libssl-dev (Ubuntu 1.0.2g)
git clone [email protected]:eclipse/paho.mqtt.c.git
git clone [email protected]:LMDB/lmdb.git

curl может зависеть от другой версии libcrypto.a

Для libprotobuf.a, скомпилированным gcc версии до 5.1, нужно в Makefile.am добавить -D_GLIBCXX_USE_CXX11_ABI=0 (если используется gcc 5.1 и выше).

# PROTOBUF_OLD_GCC_COMPAT = -D_GLIBCXX_USE_CXX11_ABI=0
PROTOBUF_OLD_GCC_COMPAT =

In the GCC 5.1 release libstdc++ introduced a new library ABI that includes new implementations of std::string and std::list

Сборка nanomsg и protobuf

Скрипты сборки

tools/install-nanomsg-1.1.5.sh
tools/install-protobuf-3.12.1.sh

Вспомогательные объекты PostgreSQL

Число дублей

Представление num_duplicate показывает число дублей для записей num

CREATE OR REPLACE VIEW public.num_duplicate
AS SELECT n.field,
    n."time",
    n.value,
    count(n.value) as cnt
   FROM num n
  GROUP BY n.field, n."time", n.value
 HAVING count(n.value) > 1;

-- Permissions

ALTER TABLE public.num_duplicate OWNER TO imz;
GRANT ALL ON TABLE public.num_duplicate TO imz;

Представление str_duplicate показывает число дублей для записей num

Дубли num

Функция num_duplicated_ids() возвращает продублированные записи num кроме первой запист(с меньшим значением атрибута id)

CREATE OR REPLACE FUNCTION public.num_duplicated_ids()
RETURNS setof num
LANGUAGE plpgsql
AS $function$
declare
  newr num%rowtype;
  oldr num%rowtype;
  begin
	oldr.field := '';
	oldr."time" := NULL;
	oldr.value := NULL;
	FOR newr in
		select n1.* 
		from num n1, num_duplicate d1 
		where
		n1.field = d1.field
		and n1."time" = d1."time"
		and n1.value = d1.value
		order by n1."time", n1.field, n1.value, id
    loop
    	if not ((newr.field = oldr.field) and (newr."time" = oldr."time") and (newr.value = oldr.value)) then
       		oldr := newr;
       	else
       		RETURN NEXT newr;
		end if;
    END LOOP;
    RETURN;
end
 $function$

Функция str_duplicated_ids тоже для строковых значений

Удаление дублей записей

Запист дублируются, если запущено несколько одиноаковых обработчиков одновременно.

Чтобы удалить продублированные записи, выполните SQL выражение:

delete from num where num.id in (select id from num_duplicated_ids())
delete from str where str.id in (select id from str_duplicated_ids())

Чтобы просмотреть число продублированных записей, выполните SQL выражение:

select count(*) from num_duplicated_ids()

Ошейники

CREATE OR REPLACE VIEW public.collar_num
AS SELECT r.value AS cddref,
    n.device,
    imei2name(n.device) "name",
    n."time",
    lat.value AS latitude,
    lon.value AS longitude,
    n.value AS temperature,
    u.value AS voltage,
    s.value AS satelittes,
    h.value AS hdop,
    p.value AS pdop
   FROM num n,
    num lat,
    num lon,
    num u,
    num s,
    num r,
    num h,
    num p
  WHERE n.field = 'iridium.Packet8.temperature_c'::text AND lat.field = 'iridium.GPS_Coordinates.latitude'::text AND lon.field = 'iridium.GPS_Coordinates.longitude'::text AND u.field = 'iridium.Packet8.battery_voltage'::text AND s.field = 'iridium.Packet8.satellite_visible_count'::text AND r.field = 'iridium.IE_IOHeader.cdrref'::text AND h.field = 'iridium.GPS_Coordinates.hdop'::text AND p.field = 'iridium.GPS_Coordinates.pdop'::text AND n."time" = lat."time" AND n."time" = lon."time" AND n."time" = u."time" AND n."time" = s."time" AND n."time" = r."time" AND n."time" = h."time" AND n."time" = p."time";

-- Permissions

ALTER TABLE public.collar_num OWNER TO imz;
GRANT ALL ON TABLE public.collar_num TO imz;

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published