Гидроакустическая станция серийная, называется Echologger MRS-900. Однолучевая, луч сканирует по кругу или в секторе. Связь с ПК по интерфейсу RS-232/RS-485. Штатное программное обеспечение есть, работает под Windows. От меня потребовалась версия под Linux.

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

Приложение должно было оформлено как библиотека и вызываться совместно с другой программой, которая обеспечивает интерфейс пользователя. Было требование совместимости с любой Linux и с любой ЭВМ, включающей видеокарту с 3D ускорителем. После обсуждения Заказчик согласился ограничиться Debian 9/10 версии, одной из текущих версий Ubuntu и одной архитектурой ЭВМ, на которой он сам проверять будет. Не самой быстрой и новой, что-то чуть ниже среднего на тот момент.

До этого был опыт разработки программы для научного гидролокатора, многолучевого, секторного обзора. Программа под Windows, на Visual C++. Картинка из этой программы:

Приходилось вникать в тему гидроакустики, читать про похожие серийные гражданские гидроакустические станции. У меня сложилось впечатление, что в большинстве гражданских гидроакустических станций применяется ОС Windows. Свои программисты у Заказчика были. Видимо, поэтому нас и наняли как специалистов по разработке ПО под Linux, одновременно имеющих какой-то опыт в гидроакустике.

Предлагаемая к разработке программа была с одной стороны проще - входной поток данных не нуждался в серьезной дополнительной обработке вроде наложения различных ВАРУ, выделения линии дна, обнаружения косяков рыбы и определения их параметров. Оставалась только простая обработка - наложение "мертвой зоны", усиление, регулировка яркости и контрастности. Это всё на уровне очень простых операций с отсчетами, сложение и умножение.

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

Представьте луч шириной 2 градуса. Луч движется по кругу или в заданном секторе. Угловая скорость зависит от режима работы, например, от выставленной максимальной дальности. Периодически передатчик излучает короткий импульс, затем приемник начинает принимать эхосигналы. Период излучения также зависит от настроек ( выбранная дальность, и т.д.). Принятые сигналы обрабатываются, реализуется временная автоматическая регулировка усиления (ВАРУ), результаты в виде набора отсчётов поступают через последовательный интерфейс для отображения.

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

При тех параметрах гидролокатора, что были указаны на официальном сайте, размер набора отсчетов будет не менее 700 отсчетов, при периоде излучения не менее 10мс. Для отладки программного обеспечения Заказчик передал записи, сделанные на его реальном гидролокаторе. Конечно, при таком потоке входных данных лучше Python и не пробовать, только C/C++.

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

Для каждой дискреты предварительно рассчитывались таблицы координат точек x, y. Вот пример функций расчёта:

// maximum amplitude samples in one beam
#define MAX_SAMPLES_TO_SHOW 1024

// beams per 360 degrees, maximum angles with step 0.1125 corresponf to 3600 beams, 720 beams correpond to 0.5 degrees
#define BEAMS 1440

// limit of reserved real screen pixels for each sample
#define MAX_PIXELS_PER_SAMPLE 32

// arrays of pixels coordinates for each sonar ADC sample 
int16_t pixel_table_x[BEAMS][MAX_SAMPLES_TO_SHOW][MAX_PIXELS_PER_SAMPLE],\
    pixel_table_y[BEAMS][MAX_SAMPLES_TO_SHOW][MAX_PIXELS_PER_SAMPLE],\
    pixel_table_n[BEAMS][MAX_SAMPLES_TO_SHOW];

// samples number equal to diameter
int samples_per_beam; // have to be lower than MAX_SAMPLES_TO_SHOW


// calculate angle between vector (x,y) and X axis, the result in the sonar coordinates
float angle(float x, float y) {
	float alpha, pi=3.1415926, tpi = 2*pi;
	if (abs(x)<0.0001) { 
		if (y>0) alpha=pi/2.0;
		else alpha=3.0*pi/2.0;
	}
    else if(abs(y)<0.0001) {
        if (x>0) alpha=0;
        else alpha=pi;
    }
	else {
		alpha = atan(y/x);
		if( (x<0)&&(y>0) ) alpha += pi;
		if( (x<0)&&(y<0) ) alpha += pi;
		if( (x>0)&&(y<0) ) alpha += tpi;
	}
    alpha = 3*pi/2.0-alpha;
    
    if ( alpha<0 ) alpha+=tpi;
    if ( alpha>tpi) alpha-=tpi;
    
	return alpha;
}


// calculate table of the pixels
void pixel_table_ini( int diagram_diameter_in_pixels ) {
	float r, alpha;
	int beam, i; // indexes in the table
	int j;
	float xc,yc,pi=3.1415926;
	float dyy=0;

    // reset the tables
	memset( pixel_table_n, 0, sizeof( pixel_table_n ) ); 
	memset( pixel_table_x, 0, sizeof( pixel_table_x ) ); 
	memset( pixel_table_y, 0, sizeof( pixel_table_y ) ); 
    
    // check for MAX_SAMPLES_TO_SHOW
    if (diagram_diameter_in_pixels > (MAX_SAMPLES_TO_SHOW*2))
        diagram_diameter_in_pixels = MAX_SAMPLES_TO_SHOW*2;
    
    samples_per_beam = diagram_diameter_in_pixels/2;
    float Radius = diagram_diameter_in_pixels / 2.0;

    // check all pixels inside diameter*diameter bar to fill pixels arrays
	for(float x=0; x < diagram_diameter_in_pixels; x++ ) {
        
		for(float y=0; y < diagram_diameter_in_pixels; y++ ) {
			xc = x - Radius;
			yc = y - Radius;
			r = sqrt( xc*xc + yc*yc ); // distance to center
			alpha = angle(xc,yc);

			beam = alpha/(3.1415926*2.0) * (float)BEAMS;
			i = (r + 0.5)*2.0; // index of the sample inside the beam
			i /= 2;
			if ( (i<Radius)&&(beam>=0)&&(beam<=BEAMS) ) {
				j = pixel_table_n[beam][i]; // index of the pixel in the table
				if( (j<MAX_PIXELS_PER_SAMPLE) ) {
					pixel_table_x[beam][i][j] = x;
					pixel_table_y[beam][i][j] = y;
					++pixel_table_n[beam][i];
				}
			}
		}
    }
}

Графическую библиотеку выбрал SDL2. Был позитивный опыт её использования под Windows, как раз для ПО научного гидролокатора, совместно с Visual C++. Ещё одна картинка из этой программы:

Позднее использовал совместно c CodeBlocks и MinGW. Звук был не нужен. Для установки SDL2 в Debian 10 мне было достаточно команды:

sudo apt-get install libsdl2-dev libsdl2-ttf-dev libsdl2-image-dev libsdl2-gfx-dev

Для компиляции программы как библиотеку c именем libsonarwindow.so можно использовать одну команду, пример:

g++ -std=c++0x -shared -fPIC -pedantic main.cpp -o libsonarwindow.so `sdl2-config --cflags --libs` -lSDL2_image -lSDL2_ttf -lpthread

Для компиляции в исполняемый файл:

g++ -std=c++0x -pedantic main.cpp -o main `sdl2-config --cflags --libs` -lSDL2_image -lSDL2_ttf -lpthread

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

//----------------------- показать один луч ( набор отсчетов ) -----------------------------            
if (isDataBufferReady || usefile) {

    if (usefile) {
        // прочитать один набор отсчетов из файла
        GetUnpackedDesc( saved_beam_i, dataHeader); 
    }
    else {															
        dataHeader = dataHeaderStream;                    
        pUnpackedSamples = (uint32_t*)(comBuffer);
    }
    
    // предварительная обработка
    AddDeadZone( deadzone_value ); // default 5
    AddGain( gain_value, dataHeader.samples); // default 8
    AddBrightness( brightness_value, dataHeader.samples); // default 400
    AddContrast( contrast_value, dataHeader.samples); // default 2
    
    isDataBufferReady = false;
    
    int samples_n = dataHeader.samples; // number of samples in the burst

    // определить фактическую дальность
    current_range = 10.0; // значение по умолчанию, при этом реально в файле может быть 1392, по таблице 1376
    for (int i=0;i<15;i++) {
        if ( samples_set[i] == samples_n ) {
            current_range = range_set[i];
            break;
        }
    }

    // вычислить углы и номер луча для предварительно вычисленных координат точек на экране
    tetha = dataHeader.angle*0.0125/360.0;
    ray = tetha*BEAMS;
    beam = ray;
    
    // сделать аппроксимацию, если samplesnum >= samples в таблице
    if ( samples_per_beam <= samples_n ) { // too much samples
        
        float kpN = (float)samples_n / (float)samples_per_beam; // scale ratio
            for(int i=0; i<samples_per_beam; i++) {

                int start = kpN*(float)i - 0.5;
                if (start<0) start=0;
                end   = kpN*(float)(i+1) + 0.5;
                if( end >= samples_n ) end = samples_n - 1;

                for( int j=start; j<=end; j++) {
                    a = pUnpackedSamples[j];
                    if(j==start) Amax=a;
                    else if (a>Amax) Amax=a;
                }
                
                samples_to_show[i] = Amax;
            }
    }
    else { // если samplesnum < samples в таблице
        float kpN = (float)samples_n / (float)samples_per_beam;
        int j; // sample inside beam index
        for(int i=0; i<samples_per_beam; i++) {
            j = (kpN*(float)i);
            samples_to_show[i] = pUnpackedSamples[j];
        }
    }

    
    // после аппроксимации заполнить samples_drawn[] правильными точками,
    // это преобразование из samples_to_show[] в реальные точки в текстуре
    for(int i=0; i < samples_per_beam; i++ ) {
        sample = samples_to_show[i];

        if (sample>4096) sample=4095;
    
        for ( int j = 0; j < pixel_table_n[beam][i]; j++ ) {
            x = pixel_table_x[beam][i][j];
            y = pixel_table_y[beam][i][j];
            samples_drawn[x+y*MAX_SAMPLES_TO_SHOW] = sample;
        }
    }

}

// нарисовать красный курсор по текущему положению луча, если это задано в настройках
if (pointer_show)
    FillRedCursor(cursor, diameter, tetha);
           
is_beam_ready = false;

// преобразования точек по палитре, добавление курсора и шкал дальности, результат в pixels[]
int samples_index = 0;
int pixels_index = 0;
for(y=0;y<diameter;y++) {
    for(x=0;x<diameter;x++) {
        sample = samples_drawn[x+samples_index];
        if (sample<4096) {
            
            if (show_in_rainbow_palette) pixels[pixels_index] = palette_rainbow[ sample ];
            else pixels[pixels_index] = palette_brown[ sample ];
        }
        else pixels[pixels_index]=background_color;
        
        if (cursor[pixels_index])
            pixels[pixels_index] = cursor[pixels_index];
        else 
            if (grid_show) 
                if (net[pixels_index]) pixels[pixels_index] = net[pixels_index];
                            
        ++pixels_index;
    }
    samples_index+=MAX_SAMPLES_TO_SHOW;
}


// преобразовать pixels[] в SDL2 текстуру
SDL_UpdateTexture(texture, NULL, pixels, diameter * sizeof(uint32_t));

// ----        вычисления, какую часть текстуры отображать на экране,         ----
// ---- чтобы обеспечить масштабирование и передвижение по картинке от сонара ----

//--- значения по умолчанию, куда копировать
dest.w = diameter;
dest.h = diameter;

// центрирование
dest.x = (window_width-diameter)/2;  
dest.y = (window_height-diameter)/2;
if (dest.y>16) dest.y-=16;

// прямоугольник, который копировать из большой текстуры
src.x = source_rect.x;
src.y = source_rect.y;
src.w = source_rect.w;
src.h = source_rect.h;


if ( window_height < window_width ) { // альбомная ориентация окна отображения
    float window_ratio = (float)window_width/((float)window_height-10);
    // .h не менять, увеличивать .w:
    dest.x = dest.x-(window_ratio-1.0)*(float)dest.w/2;
    src.x  = src.x-(window_ratio-1.0)*(float)src.w/2;

    dest.w = dest.w * window_ratio;
    src.w  = src.w  * window_ratio;
}
else { // портретная ориентация окна отображения
    float window_ratio = ((float)window_height-10)/((float)window_width);

    dest.y = dest.y-(window_ratio-1.0)*(float)dest.h/2;
    src.y  = src.y-(window_ratio-1.0)*(float)src.h/2;

    dest.h = dest.h * window_ratio;
    src.h  = src.h  * window_ratio;
}

if ((src.w+src.x)>diameter) {
    float k = (float)(diameter - src.x)/(float)src.w;
    src.w = diameter - src.x;
    dest.w *= k;
    dest.x = 0;
}

if (src.x<0) {
    float k_short = (float)(-src.x)/(float)src.w;
    float k = (float)(src.w+src.x)/(float)src.w;
    src.w = src.w+src.x; // cut left side
    src.x = 0;
    dest.x = (float)dest.w * k_short;
    dest.w *= k;
}

if ((src.h+src.y)>diameter) {
    float k = (float)(diameter - src.y)/(float)src.h;
    src.h = diameter - src.y;
    dest.h *= k;
    dest.y = 0;
}

if (src.y<0) {
    float k_short = (float)(-src.y)/(float)src.h;
    float k = (float)(src.h+src.y)/(float)src.h;
    src.h = src.h+src.y; // cut left side
    src.y = 0;
    dest.y = (float)dest.h * k_short;
    dest.h *= k;
}

// ---- конец вычислений, какую часть текстуры отображать на экране ----

// копирование в окно отображения прямоугольную часть большой картины
SDL_RenderCopy(renderer, texture, &src, &dest);

Результат отображения эхосигналов без масштабирования:

Та же панорама, но с максимальным увеличением участка:

Отображение в другой палитре:

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

Пример использования совместно с Qt, файл main.cpp :

#include "mainwindow.h"
#include "sonarwindow.h"
#include <QApplication>
#include <QWidget>

// to compile:
// Build->Rebuild all

// to launch from command line copy gost_b.ttf, hrs900_20150526_063659.bin
// to directory ~/build-qt_example-Desktop-Debug  , then:

// cd ~/build-qt_example-Desktop-Debug
// export LD_LIBRARY_PATH=/home/<user>/qt_example:$LD_LIBRARY_PATH
// ./qt_example

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;

    // 
    File_Read("hrs900_20150526_063659.bin");
    File_Play(1); // 1 максимальная скорость, 2,3,4 медленнее

    View_setting(8, 5, 1, 400, 0, true, true, true, true, true);
    Create_Window(100,120,820,820);

    //Change_Window(200,200,620,720); 

    w.move(95,40);
    w.show();
    w.raise();
    w.activateWindow();

    return a.exec();
}

Так пример с Qt выглядит на экране:

Заголовочный файл библиотеки sonarwindow.h :

void View_setting(int gain, int deadzone, int contrast, int brightness, bool pallete, bool grid, bool autocontrast, bool pointer, bool fitwindow, bool repeatplayback);

// рекомендованные значения:
// bool grid_show = true, show_in_rainbow_palette = false, pointer = true, repeat_playback = true;
// int contrast_value = 1, deadzone_value = 5, gain_value = 8, brightness_value = 400;

int Create_Window( int x_pos, int y_pos, int width, int height );
int Change_Window( int x_pos, int y_pos, int width, int height );
int FillRawComBufferArray(unsigned char* inArray, int inArraySize);
int File_Read(const char* afilename);
int File_Play(int speed);
void File_Hold();
void File_Stop();

Пример использования библиотеки с GUI библиотекой FLTK:

#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

#include "sonarwindow.h"

#include <pthread.h>

#include <string.h>
#include <fcntl.h> // Contains file controls like O_RDWR
#include <errno.h> // Error integer and strerror() function
#include <termios.h> // Contains POSIX terminal control definitions
#include <unistd.h> // write(), read(), close()

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Menu_Bar.H>

// To install FLTK:
// sudo apt install fltk1.3-dev

// To compile and run:
// g++ -L/home/tolik/sonar_FLTK -o test main.cpp -lsonarwindow -L/usr/local/lib -lfltk -lXext -lXinerama -lXft -lX11 -lXi -lm -lpthread
// export LD_LIBRARY_PATH=/home/user/sonar_FLTK:$LD_LIBRARY_PATH
// ./test

// FLTK buttons callbacks
void start_button_callback(Fl_Widget *w, void *data) {
    File_Play(1);
    printf(" start \n");
}

void stop_button_callback(Fl_Widget *w, void *data) {
    File_Stop();
    printf(" stop \n");
}

bool stop_serial_reading=false;
bool usefile = false;
const char* USBname=NULL;
int USB; //USB port id
char buf = '\0';
struct termios tty;
struct termios tty_old;

#define DEMOCOMBUFSIZE 128
unsigned char DemoComBuffer[DEMOCOMBUFSIZE];
int DemoComBufferLast=0;

int UpdateRawComBuffer()
{		
	while (DemoComBufferLast<sizeof(DemoComBuffer))
	{
		int testReading=read( USB, &DemoComBuffer[DemoComBufferLast], 1);
		if (testReading==-1)
		{
			break; //COM is empty				
		}		
		DemoComBufferLast++;		
		while((DemoComBufferLast<sizeof(DemoComBuffer)) && (testReading!=-1))
		{				
			testReading=read( USB, &DemoComBuffer[DemoComBufferLast++], 1);				
			//if (testReading!=-1) TotalComRead++;
			//printf("+%c", DemoComBuffer[DemoComBufferLast-1]);
		}	
		if (testReading==-1) DemoComBufferLast--;		
	}		
	int UsedElements=FillRawComBufferArray(&DemoComBuffer[0], DemoComBufferLast);		
	memmove((unsigned char*)&DemoComBuffer[0], ((unsigned char*)&DemoComBuffer[0])+UsedElements, sizeof(DemoComBuffer)-UsedElements);
	DemoComBufferLast-=UsedElements;
    return 0;
}    


void* thread_serial_port_parsing_auto2( void* args ) {      
    printf("thread_serial_port_parsing_auto2 - started\n");
    while(!stop_serial_reading) {                   
        UpdateRawComBuffer();
    }
    return &stop_serial_reading;
}




int main( int argc, char **argv ) {
	
    usefile = true;
    
    if( !usefile) { // just to communicate via serial port
        
        // select COM or file
        USBname="/dev/ttyS1";
        printf("%s\n", USBname);        
        USB = open( USBname, O_RDWR| O_NOCTTY | O_NONBLOCK);	
        if (USB < 0) printf("Error %i from open: %s\n", errno, strerror(errno));		
        memset (&tty, 0, sizeof tty);
        
        if ( tcgetattr ( USB, &tty ) != 0 )
        {
            //std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
            printf("Error");
        }
        
        tty_old = tty;
        /* Set Baud Rate */
        //cfsetospeed (&tty, (speed_t)B38400);
        //cfsetispeed (&tty, (speed_t)B38400);
        
        cfsetospeed (&tty, (speed_t)B115200);
        cfsetispeed (&tty, (speed_t)B115200);		
        /* Setting other Port Stuff */
        tty.c_cflag     &=  ~PARENB;            // Make 8n1
        tty.c_cflag     &=  ~CSTOPB;
        tty.c_cflag     &=  ~CSIZE;
        tty.c_cflag     |=  CS8;
        tty.c_cflag     &=  ~CRTSCTS;           // no flow control
        tty.c_cc[VMIN]   =  0;                  // read doesn't block
        tty.c_cc[VTIME]  =  0;                  // 
        tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines
        /* Make raw */
        cfmakeraw(&tty);
        /* Flush Port, then applies attributes */
        tcflush( USB, TCIFLUSH );
        if ( tcsetattr ( USB, TCSANOW, &tty ) != 0)
        {
            //std::cout << "Error " << errno << " from tcsetattr" << std::endl;	
            printf("Error 1");
        }
        int n = 0,
        spot = 0;	
        n = read( USB, &buf, 1 );
        if (n < 0) {
            printf("Error 2\n");//std::cout << "Error reading: " << strerror(errno) << std::endl;	
        }			
        
        // launch COM serial reading thread (should be external)
        pthread_t id;    
        if (!usefile) {                
            int ret;        
            ret = pthread_create(&id, NULL, &thread_serial_port_parsing_auto2, NULL);
            if(ret==0) printf("main(): thread_serial_port_parsing_auto2() thread created successfully.\n");
            else{
                printf("main(): thread_serial_port_parsing_auto2() thread not created.\n");
                return 0; /*return from main*/
            }        
        }	

    }
    
    Fl_Menu_Item menuitems[] = {
      { "&File",              0, 0, 0, FL_SUBMENU },
        { "E&xit", 0, (Fl_Callback *)stop_button_callback, 0 },
        { 0 },
      { "&Settings", 0, 0, 0, FL_SUBMENU },
        { "&Language",       0, (Fl_Callback *)stop_button_callback, 0, FL_MENU_DIVIDER },
        { "&Sonar settings",        FL_COMMAND + 's', (Fl_Callback *)stop_button_callback },
        { "&View settings",       0, (Fl_Callback *)stop_button_callback },
        { "&Working folder",      0, (Fl_Callback *)stop_button_callback },
        { "&Autoscreenshot",     0, (Fl_Callback *)stop_button_callback },
        { 0 },
      { "&Help", 0, 0, 0, FL_SUBMENU },
        { "&About...",       0, (Fl_Callback *)stop_button_callback },
        { 0 },
      { 0 }
    };
    
    
    Fl_Window *window = new Fl_Window(80,10,850,800,"Control");
    Fl_Button *button_start = new Fl_Button(710, 130, 100, 25, "Start");
    Fl_Button *button_stop = new Fl_Button(710, 170, 100, 25, "Stop");

    Fl_Menu_Bar *m = new Fl_Menu_Bar(0, 0, 680, 30);
    m->copy(menuitems);

    int xyz_data;
    button_start->callback(start_button_callback, &xyz_data);
    button_stop->callback(stop_button_callback, &xyz_data);
  
    File_Read("hrs900_20150526_063659.bin");
    File_Play(1);
    View_setting(8, 5, 1, 400, true, true, true, true, true, true);
    
    Create_Window(100,100,650,800);   
    sleep(1);
    
    // Change_Window(200,200,620,720);
    window->end();
    window->show(argc, argv);
  
    return Fl::run();
}

Установка FLTK для Debian/Ubuntu:

sudo apt install fltk1.3-dev

Компиляция примера совместно с нашей библиотекой:

g++ -L/home/tolik/sonar_FLTK -o test main.cpp -lsonarwindow -L/usr/local/lib -lfltk -lXext -lXinerama -lXft -lX11 -lXi -lm -lpthread
 export LD_LIBRARY_PATH=/home/user/sonar_FLTK:$LD_LIBRARY_PATH

Работа программа по записанным данным на видео:

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

Нам было интересно попробовать откомпилировать и запустить эту программу отображения под Raspberry Pi. Rasbian хоть и на базе Debian, но другая архитектура, хорошая проверка с точки зрения кроссплатформенности. Установить SDL2 получилось, откомпилировать получилось, запустить получилось. Работала программа существенно медленнее, чем на моем старом i3-2100, со старенькой видеокартой Radeon 512Мб. Но работала, без доработок, что не может не радовать. Заказчик использовал эту библиотеку в основном с Ubuntu.

Комментарии (11)


  1. crea7or
    15.09.2022 14:56

    Из удивительного тут std=c++0x что ли?


  1. baldr
    15.09.2022 15:18

    В следующий раз вы бы большие куски кода прятали под спойлер, может быть? Или даже на github опубликовать?

    А как заказчик относится к вашей публикации его исходников? Какая лицензия предполагается?


    1. raspberry_pi_soft Автор
      15.09.2022 15:21

      С заказчиком согласовано. К тому же, это очень малая часть исходников, как комментарий к тексту. А после публикации могу спрятать под спойлер?


  1. AAngstrom
    15.09.2022 16:00
    +2

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


    1. raspberry_pi_soft Автор
      15.09.2022 16:08

      Спасибо, думаю, Вы правы.


  1. Chelidonium
    15.09.2022 19:44
    +1

    интересно, а штатное ПО работало через Wine или CrossOver в Debian ?


    1. raspberry_pi_soft Автор
      15.09.2022 20:14
      +1

      Не пробовали. Предполагаю, что с COM портом не было бы связи, и притормаживал бы, по крайней мере на Wine. CrossOver никогда не пробовал. Позднее на сайте Заказчика прочитал, что они сделали подобную библиотеку под Windows. Почему-то им нужна была именно часть, связанная с круговой диаграммой, отдельно от всего. Ещё интересно, что оригинальная программа под Windows работала целиком через WinApi.

      Кстати, очень давно был интересный опыт использования старого графического ПО, написанного ещё под DOS, с использованием эмулятора dosemu в ArchLinux. Аппаратура была новая, на лету конвертировали данные от новой аппаратуры в старый протокол и передавали в старую программу через общий RAM disk.


  1. j_aleks
    16.09.2022 04:20
    +1

    а у Вас "PING" локатора "снаружи", или что это за структура?


    1. raspberry_pi_soft Автор
      16.09.2022 08:32

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

      Сразу после Ping'a первые отсчеты очень большие, потом уменьшаются. Их просто отбрасывают, называют "мертвой зоной".


  1. pvvv
    16.09.2022 13:13

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


    1. raspberry_pi_soft Автор
      17.09.2022 10:15

      gnuplot хорошая библиотека. Наверно, всё же больше подходит для графиков уже полученных, для реального времени я бы не рискнул. Ещё надо учесть, что у Заказчика постоянно возникали идеи что-то улучшить или изменить в отображении, мне проще это было делать в SDL2.