From cf75c5c85538c3962b0168838602031341c226cc Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 6 Nov 2026 21:21:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BE=D0=B1=D1=89=D1=83=D1=8E=20=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=83=20=D0=BF=D1=80=D0=B8=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=B0=20DEIF=20=D0=B4=D0=BB=D1=8F=20HDMI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- c/set-protocol/CMakeLists.txt | 1 + c/set-protocol/include/set_deif_gauge.h | 25 +++++++++++++++++ c/set-protocol/src/set_deif_gauge.c | 37 +++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 c/set-protocol/include/set_deif_gauge.h create mode 100644 c/set-protocol/src/set_deif_gauge.c diff --git a/c/set-protocol/CMakeLists.txt b/c/set-protocol/CMakeLists.txt index 0b60de6..43dc0c4 100644 --- a/c/set-protocol/CMakeLists.txt +++ b/c/set-protocol/CMakeLists.txt @@ -45,6 +45,7 @@ set(SETPROTOCOL_LEGACY_SOURCES set(SETPROTOCOL_SOURCES src/deif_xdi.c # Общий DEIF-кодек и автомат SLAVE используются всеми платформами. + src/set_deif_gauge.c # Переносимая графика DEIF повторяет шкалы и движитель SETGUI без зависимости от Qt. src/set_regmap.c src/set_emu_ump.c src/set_emu_tms.c diff --git a/c/set-protocol/include/set_deif_gauge.h b/c/set-protocol/include/set_deif_gauge.h new file mode 100644 index 0000000..d6d9a4e --- /dev/null +++ b/c/set-protocol/include/set_deif_gauge.h @@ -0,0 +1,25 @@ +#ifndef SET_DEIF_GAUGE_H /* Переносимая геометрия прибора подключается один раз. */ +#define SET_DEIF_GAUGE_H /* Макет повторяет прибор SETGUI в логических пикселях 600×510. */ +#include /* Цвет передаётся как RGB888 независимо от framebuffer и Qt. */ +#include "pcan_abi.h" /* Общий макрос экспорта делает графику доступной также из shared-библиотеки. */ +#ifdef __cplusplus /* Qt/C++ использует те же немодифицированные имена функций C. */ +extern "C" { /* Открывает переносимый ABI без привязки к конкретному GUI. */ +#endif /* Начало совместимости с C++. */ +typedef struct { /* Снимок содержит физические значения, а не wire-байты CAN. */ + double value[6], angle, command[3]; /* Порядок value совпадает с deif_xdi.h; command: POWER%, RPM%, градусы. */ + unsigned valid, angle_kind, commands, role, node, source; /* Маска свежести, тип угла, черновики и подписи роли/узлов; role=2 обозначает MASTER, остальные роли показывают принятые данные. */ +} setp_deif_gauge_values; /* Выбор свежести и фактического угла выполняет общее протокольное ядро. */ +typedef struct { /* Потребитель преобразует логические пиксели в свой холст. */ + void *user; /* Непрозрачный контекст рисующего порта. */ + void (*line)(void *,double,double,double,double,uint32_t,double); /* Отрезок x1/y1/x2/y2, RGB888 и толщина в пикселях эталона; порт сохраняет сглаживание при масштабировании. */ + void (*rect)(void *,double,double,double,double,uint32_t); /* Прямоугольник x/y/w/h; ненулевой старший байт задаёт alpha, RGB888 без старшего байта означает непрозрачный цвет. */ + void (*text)(void *,double,double,double,double,const char *,uint32_t,double,unsigned); /* UTF-8, RGB888, размер Qt в пунктах; style: 1 bold, 2 stretch80%, 4 left, 8 right. */ + void (*triangle)(void *,const double *,uint32_t); /* Три пары логических координат задают заполненные указатели движителя без копирования геометрии в порт. */ + void (*azimuth)(void *,unsigned); /* Значение 1 включает одинаковый масштаб X/Y вокруг центра компаса; 0 возвращает растяжение остальных элементов макета. */ +} setp_deif_gauge_painter; /* Ни Qt, ни Linux, ни выделение памяти не входят в контракт. */ +PCAN_ABI_API void setp_deif_gauge_draw(const setp_deif_gauge_values *v,const setp_deif_gauge_painter *p); /* Рисует шкалы и движитель; ноль азимута направлен вверх. */ +PCAN_ABI_API int setp_deif_gauge_hit(double x,double y,double *value); /* Возвращает 1=POWER%, 2=RPM%, 3=угол либо 0 вне активных зон. */ +#ifdef __cplusplus /* Закрывает единый ABI при включении заголовка из Qt/C++. */ +} /* Конец extern C. */ +#endif /* Чистая C99-сборка не получает лишних конструкций C++. */ +#endif /* Конец общего интерфейса графики DEIF. */ diff --git a/c/set-protocol/src/set_deif_gauge.c b/c/set-protocol/src/set_deif_gauge.c new file mode 100644 index 0000000..a0be71c --- /dev/null +++ b/c/set-protocol/src/set_deif_gauge.c @@ -0,0 +1,37 @@ +#include "set_deif_gauge.h" /* Геометрия общая для всех графических портов DEIF. */ +#include /* Тригонометрия связывает компас с физическим азимутом в градусах. */ +#include /* Числа форматируются в ограниченный локальный буфер. */ +static double bounded(double v,double lo,double hi) { return fmax(lo,fmin(hi,v)); } /* Защищает положение риски, не меняя отображаемое число. */ +static double ordinate(double v,double lo) { return 126+(120-v)*270/(120-lo); } /* Шкала SETGUI занимает y=126…396 и заканчивается на 120%. */ +int setp_deif_gauge_hit(double x,double y,double *value) { /* Обратная геометрия используется touch и мышью одинаково. */ + if(!value || !isfinite(x) || !isfinite(y)) return 0; for(unsigned i=0;i<2;++i) { double axis=64+128*i,lo=i?-20:0; if(x>=axis-18 && x<=axis+52 && y>=110 && y<=410) { *value=bounded(120-(y-126)*(120-lo)/270,lo,120); return (int)i+1; } } /* Области шкал и предел реверса −20% совпадают с прибором SETGUI. */ + double dx=x-411,dy=y-264,r=hypot(dx,dy); if(r<12 || r>155) return 0; *value=atan2(dx,-dy)*57.29577951308232; return 3; /* Мёртвая зона 12 пикселей исключает неустойчивый угол в центре; ноль сверху. */ +} /* Возвращает физическое задание без формирования либо передачи CAN-кадров. */ +static void number(const setp_deif_gauge_painter *p,double x,double y,double w,double h,double value,int valid,const char *unit,double size,uint32_t color) { /* В точности сохраняет отдельные числовые подписи оригинального Propulsor. */ + char s[64]; if(valid) snprintf(s,sizeof(s),"%g%s",value,unit); else snprintf(s,sizeof(s),"—"); p->text(p->user,x,y,w,h,s,color,size,0); /* Отсутствие данных обозначается длинным тире; единица выводится только вместе с числом, как в SETGUI. */ +} /* Форматирование не меняет телеметрию и черновики MASTER. */ +static void point(double angle,double x,double y,double scale,double *ox,double *oy) { double a=(angle+90)*0.017453292519943295; *ox=411+(x*cos(a)-y*sin(a))*scale; *oy=264+(x*sin(a)+y*cos(a))*scale; } /* +90° поворачивает исходный направленный влево силуэт SETGUI к нулю сверху компаса. */ +static void segment(const setp_deif_gauge_painter *p,double angle,double x,double y,double xx,double yy,uint32_t color) { double a,b,c,d; point(angle,x-460,y-610,125.0/315,&a,&b); point(angle,xx-460,yy-610,125.0/315,&c,&d); p->line(p->user,a,b,c,d,color,5.3*125/315); } /* Центр исходного контура (460,610), радиус 125/315 и толщина 5,3 повторяют преобразование QPainter оригинала. */ +static void bezier(const setp_deif_gauge_painter *p,double angle,const double *q,uint32_t color) { /* Кубические контуры сохраняют все управляющие точки корпуса и обеих лопастей. */ + double x=q[0],y=q[1]; for(unsigned i=1;i<=40;++i) { double t=i/40.0,u=1-t,xx=u*u*u*q[0]+3*u*u*t*q[2]+3*u*t*t*q[4]+t*t*t*q[6],yy=u*u*u*q[1]+3*u*u*t*q[3]+3*u*t*t*q[5]+t*t*t*q[7]; segment(p,angle,x,y,xx,yy,color); x=xx; y=yy; } /* Сорок сглаженных отрезков на кривую сохраняют форму даже при растяжении на весь HDMI. */ +} /* Дискретизация остаётся общей для framebuffer и будущих других портов. */ +void setp_deif_gauge_draw(const setp_deif_gauge_values *v,const setp_deif_gauge_painter *p) { /* Повторяет полный макет Propulsor SETGUI, сохраняя его геометрию, цвета и порядок слоёв. */ + if(!v || !p || !p->line || !p->rect || !p->text || !p->triangle || !p->azimuth) return; p->rect(p->user,0,0,600,510,0x090c0e); char title[80]; if(v->role==1) snprintf(title,sizeof(title),"SLAVE %u from Node %u",v->node,v->source); else snprintf(title,sizeof(title),"%s",v->role==2?"MASTER":"MONITOR"); p->text(p->user,15,12,570,40,title,0xe9edf4,22,0); /* Заголовок располагается в исходном прямоугольнике; background и основной текст взяты из SETGUI без изменения RGB. */ + double target=v->role==2?v->command[2]:v->value[5]; int has_target=v->role==2?(v->commands&4u)!=0:(v->valid&32u)!=0; p->text(p->user,205,54,95,25,"Задание:",0x8798ad,11,4); char command[48]; if(has_target) snprintf(command,sizeof(command),"%.1f°",target); else snprintf(command,sizeof(command),"—°"); p->text(p->user,300,54,95,25,command,0x8798ad,11,8); /* Сетевое задание остаётся отдельной верхней подписью, а не дополнительной выдуманной стрелкой азимута. */ + for(unsigned i=0;i<2;++i) { /* POWER и RPM используют собственные проценты и абсолютные значения без подмены одним другим. */ + double x=64+128*i,lo=i?-20:0; unsigned f=i?4:3; const double range[4]={lo,0,100,120}; const uint32_t colors[3]={0xae616b,0x657caf,0xb6c799}; /* Точные исходные цвета реверса, рабочего диапазона и перегрузки: красный, синий и зелёный. */ + for(unsigned j=0;j<3;++j) if(range[j+1]>range[j]) p->rect(p->user,x,ordinate(range[j+1],lo),34,ordinate(range[j],lo)-ordinate(range[j+1],lo),colors[j]); /* Цветные зоны присутствуют также при отсутствии данных, затем поверх применяется исходное полупрозрачное затемнение. */ + for(int tick=(int)lo;tick<=120;tick+=20) { double y=ordinate(tick,lo); char s[16]; p->line(p->user,x,y,x+34,y,0x202b3a,1); if(tick==(int)lo || tick==0 || tick==40 || tick==80 || tick==120) { snprintf(s,sizeof(s),"%d",tick); p->text(p->user,x-40,y-12,36,24,s,0xe9edf4,12,0); } } /* Шаг 20%, цвет рисок и расположение опорных чисел полностью соответствуют вертикальным шкалам SETGUI. */ + if(v->valid&(1u<value[f],lo,120),lo); p->line(p->user,x-3,y,x+38,y,0xffffff,4); } else p->rect(p->user,x,126,34,270,0x8210151d); /* Белое показание имеет толщину 4; отсутствие данных перекрывает исходную шкалу цветом (16,21,29,130), сохраняя её оттенки. */ + if(v->commands&(1u<command[i],lo,120),lo); p->line(p->user,x-8,y,x+42,y,0xeeef69,3); } /* Жёлтый черновик MASTER имеет исходную длину и толщину 3 пикселя эталона. */ + number(p,x-35,89,110,30,v->value[f],v->valid&(1u<text(p->user,x-45,410,128,28,i?"RPM":"POWER",0xe9edf4,17,0); number(p,x-52,444,145,30,v->value[i],v->valid&(1u<text(p->user,x-40,478,125,22,i?"rpm":"RAW",0x8798ad,11,0); /* Название, число и единица занимают три отдельные строки исходного макета без сдвига между количеством цифр. */ + } /* Завершает независимые шкалы POWER и RPM. */ + p->azimuth(p->user,1); /* Кольцо, подписи, движитель и указатели образуют круглый узел с одинаковым масштабом X/Y независимо от растяжения остальных шкал. */ + for(unsigned degree=0;degree<360;++degree) { double a=degree*0.017453292519943295,b=(degree+1)*0.017453292519943295; p->line(p->user,411+120*sin(a),264-120*cos(a),411+120*sin(b),264-120*cos(b),degree<180?0x78f033:0xff4935,10); } /* Правая зелёная и левая красная полуокружности толщиной 10 повторяют QPainter drawArc исходного DEIF. */ + for(unsigned degree=0;degree<360;degree+=5) { double a=degree*0.017453292519943295; unsigned major=degree%30==0; double r=125-(major?10:7),rr=125+(major?12:1); p->line(p->user,411+r*sin(a),264-r*cos(a),411+rr*sin(a),264-rr*cos(a),0xe9f3ff,major?2.3:1); if(major) { char label[16]; unsigned value=degree<180?degree:360-degree; snprintf(label,sizeof(label),"%u%s",value,degree%90?"":"°"); p->text(p->user,411+156*sin(a)-26,264-156*cos(a)-16,52,32,label,0xe9f3ff,17,3); } } /* Риски идут через 5°, крупные через 30°; обе стороны подписаны 0…180, кардинальные углы имеют знак градуса и жирный шрифт stretch80%. */ + static const double curves[][8]={{399,550,478,516,577,525,630,562},{630,562,679,594,672,632,626,661},{626,661,570,701,478,702,399,674},{318,563,278,575,278,619,312,642},{315,629,341,659,370,676,399,674},{384,631,348,615,307,555,317,506},{317,506,329,466,350,418,366,438},{366,438,384,466,392,524,384,631},{315,629,304,681,320,780,350,785},{350,785,388,792,386,712,373,670}}; /* Все управляющие точки корпуса, ступицы и обеих лопастей перенесены из исходного Propulsor без упрощения формы. */ + unsigned has_angle=v->role==2 && (v->commands&4u)?1:v->angle_kind; double angle=v->role==2 && (v->commands&4u)?v->command[2]:v->angle; uint32_t outline=has_angle?(v->role==2?0xeeef69:0xffffff):0x657181; if(!has_angle) angle=0; for(unsigned i=0;itriangle(p->user,triangle,outline); } } /* Два заполненных треугольника на исходных радиусах заменяют прежнюю упрощённую радиальную линию. */ + number(p,300,435,225,36,v->angle,v->angle_kind!=0,"°",25,0xe9edf4); p->text(p->user,278,478,275,22,v->angle_kind==2?"AZIMUTH · ЗАДАНИЕ":"AZIMUTH",0x8798ad,11,0); /* Крупное число остаётся принятым показанием; использование сетевого задания подписывается явно и независимо от черновика MASTER. */ + p->azimuth(p->user,0); /* Возвращаем порту исходное преобразование после рисования круглого азимута. */ +} /* Все геометрические размеры и цвета прибора определены только здесь, в templates. */