Преобразование bytea -> real - вариант решения

Доброго времени суток всем.
Встал вопрос преобразования массива из 8 байт, приходящего с устройства по сети CAN, в два дробных числа.
Кому интересно - подробности

Используйте данную ссылку, что бы развернуть или свернуть блок текста
Оконечные устройства - контроллеры Fatswell CPM711, собирают много аналоговых и цифровых сигналов. С цифровыми проблем нет. Аналоговые значения имеют длину 4 байта (числа с плавающей точкой), упаковываются по-парно в один пакет CAN, со своим CAN-ID на одну пару. Есть сигналы, которые укладываются в пакет по-одиночке. Т.е.пакет - переменной длины - либо 4, либо 8 байт, по карте оборудования.
Порядок байт - его собственный, настраивается ли - не могу сказать, не моя сфера деятельности (программируют и прошивают ребята из параллельной фирмы, через CoDeSys). Я получаю данные "как есть".
Данные читаются через свою программу-сервер сети CAN (написана Qt5.5 в линуксе, x64), укладываются в БД PostgreSQL 9.5, для передачи в СКАДу. Нужно было переложить парсинг пакета на БД, чтобы разгрузить сервер. То, что на С++ решалось в одну строчку:
    float_value1 = *((float*) &data[0]) * 1.000;
    float_value2 = *((float*) &data[4]) * 1.000;
на plsql оказалось серьезной проблемой.
Единственное руководство помогло только в общих чертах - куда копать.
Проблема была - как получить массив байт в программе на С++: там нет типа bytea.
Методом научного тыка для 4-байтового bytea было найдено такое решение.
1. Написал программу на С:
#include "postgres.h"
#include "fmgr.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
 
PG_FUNCTION_INFO_V1(bytea_to_real);
 
Datum
bytea_to_real(PG_FUNCTION_ARGS)
{
	float4 c_val = *((float4 *) (PG_GETARG_POINTER(0) + 4));
	PG_RETURN_FLOAT4(c_val);
}
(почему +4 - здесь
Используйте данную ссылку, что бы развернуть или свернуть блок текста
Нет описания, в каком виде передаются данные через PG_GETARG_*. Есть только файл fmgr.h.
Попытки прочитать "в лоб" входящий параметр как FLOAT4 или FLOAT8 ни к чему не привели. Осталось использовать только PG_GETARG_POINTER(0).
Размер входящих данных - 8 байт, как у указателя. Но размер данных по этому адресу - тоже 8 байт.
Стал анализировать так (код функции на С++ без префикса):
bytea_to_real(PG_FUNCTION_ARGS)
{
	float8 can_val = *((float8 *) PG_GETARG_POINTER(0));
	uint8 *ba_ptr = (uint8 *) &can_val;
	uint8 ba_src[4];
	int i;
	FOR(i = 0; i < 4; i++) {
		//ba_src[i] = *(ba_ptr + i); // младшие 4 байта
		ba_src[i] = *(ba_ptr + i + 4); // старшие 4 байта
	}
	float4 res = ba_src[3] * 1000000.0 + ba_src[2] * 1000.0 + ba_src[1] * 1.0 + ba_src[0] / 1000.0;
	PG_RETURN_FLOAT4(res);
}
Т.е.в миллионах- старший байт, в тысячах - предпоследний, и т.д. (в десятичном виде)
Для младших 4 байт стабильно получал 0.032, а для старших 4 - моя "родная" строка, которую и видел в 16-ричном виде в выборке из таблицы:
SELECT 
  substring(log.can_value FROM 1 FOR 4) AS ss_1_4,
  bytea_to_real(substring(log.can_value FROM 1 FOR 4)) AS ba_f_1,
  substring(log.can_value FROM 5 FOR 4) AS ss_5_4,
  bytea_to_real(substring(log.can_value FROM 5 FOR 4)) AS ba_f_2,
  log.can_value
FROM can_log log
ORDER BY log.dt DESC 
LIMIT 100;
(поле dt - типа timestamp, автозаполняемое текущими датой-временем; сейчас пакеты - пока все по 8 байт, и перестраховка не нужна)
2. Положил для компиляции файл в каталог /opt/PostgreSQL/9.5/include/postgresql/server/, чтобы не указывать в #include пути.
3. Скомпилировал по указанному руководству; для линукса это:
cc -fpic -c bytea_to_real.c
cc -shared -o bytea_to_real.so bytea_to_real.o
4. Скомпилированный файл bytea_to_real.so перенес в папку /opt/PostgreSQL/9.5/lib/bytea_to_real.so, где ему и место.
5. В БД PG создал функцию:
CREATE OR REPLACE FUNCTION public.bytea_to_real(IN DATA bytea) RETURNS real AS
'/opt/PostgreSQL/9.5/lib/bytea_to_real.so', 'bytea_to_real' -- каталог окончательного размещения
--'/opt/PostgreSQL/9.5/include/postgresql/server/bytea_to_real.so', 'bytea_to_real' -- каталог компиляции на время отладки
--'bytea_to_real.so', 'bytea_to_real' -- не находит; видно, что-то с путями
LANGUAGE C STRICT;
6. Закрыл окно запроса, и открыл новое. Выполнил запрос:
SELECT 
  substring(log.can_value FROM 1 FOR 4) AS ss_1_4,
  bytea_to_real(substring(log.can_value FROM 1 FOR 4)) AS ba_f_1,
  substring(log.can_value FROM 5 FOR 4) AS ss_5_4,
  bytea_to_real(substring(log.can_value FROM 5 FOR 4)) AS ba_f_2,
  log.can_value
FROM can_log log
ORDER BY log.dt DESC 
LIMIT 100;

И - все... Работает. "Приходи, кума, любоваться".
Дальше функцию привязал к триггеру trg_can_log_bi, где уже лежала функция для цифровых данных, но это уже детали. Работает и там.

Вот... хотел поделиться работающим вариантом. Когда искал примеры сам - не нашел; наверное, плохо искал...
Надеюсь, кому-то пригодится. Да еще в комментариях, может быть, и поправят, и улучшат те, кто знает С++ и PostgreSQL лучше меня.
Не судите строго.

Всем - удачи.

Back to top

(С) Виктор Вислобоков, 2008-2023