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);"
В сообщении(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 использовалось бы где-то, тогда пришлось бы все равно вводить еще одно двухбайтно поле под размер.
Из выражений на языке 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 не применяется к нечисловым типам, как массивы, сообщегия и строки, а также к числам, помещающимся в одном байте.
Поля указывают только на области памяти, откуда могут быть извлечены данные.
Приложение указывает, какие данные ему нужны для дальнейшей обработки в опции 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 Duktape соответствует стандарту Ecmascript E5/E5.1 и имеет [дополнительные встроенные объекты] (http://duktape.org/guide.html#duktapebuiltins).
Список объектов, их свойств, методов(функций) в описании Ecmascript.
В случае обнаружения синтаксических ошибок в выражениях, в случае возникновения исключительных ситуаций программы завершаются с ошибкой.
Когда мы ссылаемся на вложенные сообщения, их имена должны включать имя пакета, имя родительского сообщения(или цепочку таких имен), разделенных точкой ("."). Такое "составное" имя указывает принадлежность сообщения другому(родительскому) сообщению, и называется полным именем в терминах protobuf.
Так как сообщения могут быть включены в состав разных родительских сообщений, то полное имя одного и того же вложенного сообшения будет разным в зависимости от родителей.
Когда программы начинают выполнение, загружаются все файлы .proto из папки (включая вложенные папки) пакетов. Сообщения об ошибках записываются в журнал.
Каждое сообщение содержит атрибуты целого, вещественного типов, реже типов перечислений и строк. Сообщения могут быть вложенными.
В языке Protobuf есть понятие опций сообщений и атрибутов.
В этом приложении опции сообщения используются для записи структуры входящих пакетов, а опции атрибутов- описание того, как значение атрибута получается из входного пакета.
В опции сообщения pkt2.packet записываются смещения и размеры полей входного пакета и присваивается имя.
В опции атрибута pkt2.variable записывается код функции преобразования.
Два атрибута должны помечаться в опциях как индексные поля времени и номера устройства для всех сообщений, которые планируется записывать в базу данных. Вложенные сообщения не должны иметь индексы, перекрывающие индексы сообщений, куда они вложены.
Если файлы .proto изменены, а программы уже запущены, то процессам надо послать сигнал 1:
kill -1 <номер процесса>
чтобы программы перезапустились с чтением обновленных .proto файлов или остановить выполнение программ и запустить заново.
Программы используют возможность библиотеки 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 строка символов
Тип выходных переменных из числа поддерживаемых 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 читает пакеты из шины пакетов и записывает их в базу данных напрямую в таблицу 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 читает пакеты из шины пакетов и отправляет на мобильные устройства через сервис 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 читает входной файл(по умолчанию 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-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 с другим конфигурационным файлом, использующим другие шины.
Сетевые параметры:
- 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
Сервис ошейников на 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 --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 -i localhost -l 50052 << messages.txt
Каждая строка должна иметь тип сообщения и значения в формате JSON, разделенный знаком двоеточия ":"
Packet.MessageType:{"json-object-in-one-line"}
Значения
- Record#
- PK
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);
Копируем файл с несколькими записями фиксированной длины
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
Сериализует в stdout одно случайное сообщение (сообщение TemperaturePkt, файл описания example/example1.proto)
./example1message1 > 1
codex -protofile proto/example/example1.proto -message_name TemperaturePkt 1
Отправляет подготовленные заранее тестовые сообщения в очередь обработчиков ipc:///tmp/message.pkt2
Отправляет TCP пакеты как в примере 1.
По умолчанию шлет в порт 50052.
Один экземпляр tcpreceiver, mqtt-receiver или freceiver должен быть запущен. По умолчанию tcpreceiver слушает порт 50052.
Отправляет TCP пакеты Iridium пакет 8.
Опция -d 60 задает задержку в 1 минуту между посылками.
Плагин компилятора 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 имя плагина и путь к его исполнимому файлу
Необходим заголовочный файл 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();
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
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
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
161 привелигированный порт, программы нужно запускать с правами пользователя root (uid 0):
sudo ...
Опция --maxfd позволяет увеличивать максимальное количество одновременно открытых дескрипторов- сокетов.
Значение по умолчанию для Linux- 1024.
./tcpreceiver --maxfd 100000
Опция -d демонизирует процессы. Создаются файлы /var/run/<имя программы>.pid
PID файлы создаются только при наличии прав на запись в каталоге /var/run/:
/var/run/tcpreceiver.pid
то есть демон нужно запускать от имени root.
Если нужно созжать две статические библиотеки как зависмость для другого проекта 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
Включить только сборку статической библиотеки
mkdir build
cd build
cmake ..
cmake --build . --target pkt2
Установите 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
./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
Записывает в поток stdout сообщения в текстовом виде. Для отладки.
Для записи значений в базу данных Postgresql.
Два режима записи:
- 3 SQL "нативный"
- 4 SQL(2) c использованием view
Предварительно нужно создать две таблицы:
- 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'
...
Предварительно для режима 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"
Предназначены для записи данных в 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.
Ниже описано, как получить этот файл и предоставить права доступа к электронной таблице.
Нужен доступ к
- проектам 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:
- Выберите меню Безопасность - Управление API - Делегирование доступа к данным в домене
- Нажмите Настроить делегирование доступа к данным в домене
- Нажмите кнопку Добавить
- Введите идентификатор клиента
- Укажите области действия https://www.googleapis.com/auth/spreadsheets, https://www.googleapis.com/auth/drive (другие области)
Как создать проект Google Cloud Platform
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 из-за того, что сервис не успевает обрабатывать данные из сокета.
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
При отключении потока публикатора (PUB) нужно пересоединить сокеты подписчиков (SUB)
Предположение: если поставить sleep(0) в публикаторе, то вроде бы работает.
Работает странно, лучше исптользоать 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/*
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
Предварительно нужно развернуть образ 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")
копировать недостающие библиотеки из 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
./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
Опции:
- 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 перед установкой зависимостей желательно поставить 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/
- 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 =
Скрипты сборки
tools/install-nanomsg-1.1.5.sh
tools/install-protobuf-3.12.1.sh
Представление 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_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;