final degree paper state
This commit is contained in:
		
							parent
							
								
									45c2e06bf8
								
							
						
					
					
						commit
						cff343e959
					
				@ -3,11 +3,11 @@ cmake_minimum_required(VERSION 3.20)
 | 
				
			|||||||
SET(APP_NAME wfr)
 | 
					SET(APP_NAME wfr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
project(${APP_NAME})
 | 
					project(${APP_NAME})
 | 
				
			||||||
set(CMAKE_CXX_STANDARD 14)
 | 
					set(CMAKE_CXX_STANDARD 17)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_executable(${APP_NAME} main.cpp src/ALSADevice.cpp src/ALSADevice.h src/MemoryPool.cpp src/MemoryPool.h utils/Semaphore.h utils/Semaphore.cpp src/SampleWriter.h src/Timestamp.h
 | 
					add_executable(${APP_NAME} main.cpp src/ALSADevice.cpp src/ALSADevice.h src/MemoryPool.cpp src/MemoryPool.h utils/Semaphore.h utils/Semaphore.cpp src/SampleWriter.h src/Timestamp.h
 | 
				
			||||||
        #src/SampleReceiver.h
 | 
					        #src/SampleReceiver.h
 | 
				
			||||||
        src/audio_types.h src/ServerBeacon.cpp src/ServerBeacon.h)
 | 
					        src/audio_types.h src/ServerBeacon.cpp src/ServerBeacon.h src/GUI.h src/globals.h src/Callback.h src/MultiStreamReceiver.cpp src/MultiStreamReceiver.h)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
find_package(Threads REQUIRED)
 | 
					find_package(Threads REQUIRED)
 | 
				
			||||||
if (THREADS_FOUND)
 | 
					if (THREADS_FOUND)
 | 
				
			||||||
@ -16,6 +16,26 @@ endif (THREADS_FOUND)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
find_package(ALSA REQUIRED)
 | 
					find_package(ALSA REQUIRED)
 | 
				
			||||||
if (ALSA_FOUND)
 | 
					if (ALSA_FOUND)
 | 
				
			||||||
    include_directories(${ALSA_INCLUDE_DIRS})
 | 
					    target_include_directories(${APP_NAME} PUBLIC ${ALSA_INCLUDE_DIRS})
 | 
				
			||||||
    target_link_libraries(${APP_NAME} ${ALSA_LIBRARIES})
 | 
					    target_link_libraries(${APP_NAME} ${ALSA_LIBRARIES})
 | 
				
			||||||
endif (ALSA_FOUND)
 | 
					endif (ALSA_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					find_package(PkgConfig REQUIRED)
 | 
				
			||||||
 | 
					pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
 | 
				
			||||||
 | 
					if (GTK3_FOUND)
 | 
				
			||||||
 | 
					    target_include_directories(${APP_NAME} PUBLIC ${GTK3_INCLUDE_DIRS})
 | 
				
			||||||
 | 
					    target_link_directories(${APP_NAME} PUBLIC ${GTK3_LIBRARY_DIRS})
 | 
				
			||||||
 | 
					    add_definitions(${GTK3_CFLAGS_OTHER})
 | 
				
			||||||
 | 
					    target_link_libraries(${APP_NAME} ${GTK3_LIBRARIES})
 | 
				
			||||||
 | 
					endif(GTK3_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pkg_check_modules(GTKMM REQUIRED gtkmm-3.0)
 | 
				
			||||||
 | 
					if (GTKMM_FOUND)
 | 
				
			||||||
 | 
					    target_include_directories(${APP_NAME} PUBLIC ${GTKMM_INCLUDE_DIRS})
 | 
				
			||||||
 | 
					    target_link_directories(${APP_NAME} PUBLIC ${GTKMM_LIBRARY_DIRS})
 | 
				
			||||||
 | 
					    target_link_libraries(${APP_NAME} ${GTKMM_LIBRARIES})
 | 
				
			||||||
 | 
					endif(GTKMM_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install(TARGETS ${APP_NAME} RUNTIME DESTINATION ${PROJECT_SOURCE_DIR}/runtime ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/runtime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-rdynamic")
 | 
				
			||||||
							
								
								
									
										15
									
								
								MATLAB/read_multistream_dir.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								MATLAB/read_multistream_dir.m
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					function read_multistream_dir(d, prefix, plotrange, onlydata)
 | 
				
			||||||
 | 
					  % get directories containing samples
 | 
				
			||||||
 | 
					  hint_file_name = strcat('./runtime/', d, '/node_dir_hint.txt');
 | 
				
			||||||
 | 
					  hint_file = fopen(hint_file_name);
 | 
				
			||||||
 | 
					  node_dirs = textscan(hint_file, '%s');
 | 
				
			||||||
 | 
					  fclose(hint_file);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  node_names = strrep(node_dirs{1,1}, prefix, "");
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  node_dirs = strcat('./runtime/', d, '/', node_dirs{1,1});
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  sync_datasets(node_dirs, node_names, plotrange, onlydata);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  print(strcat('./runtime/', d, '/figure.svg'), '-dsvg');
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
function sds = sync_datasets(dss)
 | 
					function sds = sync_datasets(dss, node_names, PLOTRANGE, ONLYDATA)
 | 
				
			||||||
  % load datasets
 | 
					  % load datasets
 | 
				
			||||||
  len = length(dss);
 | 
					  len = length(dss);
 | 
				
			||||||
  sds = {};
 | 
					  sds = {};
 | 
				
			||||||
@ -22,15 +22,28 @@ function sds = sync_datasets(dss)
 | 
				
			|||||||
    sds{i} = ds;
 | 
					    sds{i} = ds;
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  % reference dataset and timescale
 | 
				
			||||||
 | 
					  ds_ref = sds{1};
 | 
				
			||||||
 | 
					  ts_ref = ds_ref(:,1);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  % create plot
 | 
					  % create plot
 | 
				
			||||||
  figure(1)
 | 
					  if (~ONLYDATA)
 | 
				
			||||||
 | 
					    figure('Position', [100 100 1000 1000])
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    figure('Position', [100 100 1000 400])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  clf
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  % plot samples
 | 
					  % plot samples
 | 
				
			||||||
  subplot(2,1,1);
 | 
					  if (~ONLYDATA)
 | 
				
			||||||
 | 
					    subplot(3,1,1);
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  marks = ['-', '-o'];
 | 
					  marks = ["-x", "-o"];
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  PLOTRANGE = 1:100;
 | 
					  %PLOTRANGE = 1017:1020;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  VOLT_PER_BIN = 42.80E-06;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  % get beginning indices
 | 
					  % get beginning indices
 | 
				
			||||||
  for i=1:len
 | 
					  for i=1:len
 | 
				
			||||||
@ -39,34 +52,90 @@ function sds = sync_datasets(dss)
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
    s = size(ds);
 | 
					    s = size(ds);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    %for k = 2:s(2);
 | 
					    for k = 2:s(2)
 | 
				
			||||||
    for k = 2:2
 | 
					    %for k = 2:2
 | 
				
			||||||
      %plot(ts(1:100),ds(1:100,k), marks(k-1));
 | 
					      if (ONLYDATA)
 | 
				
			||||||
      plot(ts(PLOTRANGE),ds(PLOTRANGE,k), 'x');
 | 
					        mark = "-";
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        mark = marks(k-1);
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					      plot(ts(PLOTRANGE), ds(PLOTRANGE,k) * VOLT_PER_BIN, mark);
 | 
				
			||||||
      hold on
 | 
					      hold on
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  grid on
 | 
					  grid on
 | 
				
			||||||
  xlabel("Time [s]");
 | 
					  
 | 
				
			||||||
  ylabel("Sample");
 | 
					  if (ONLYDATA)
 | 
				
			||||||
  xlim([ts(PLOTRANGE(1)) ts(PLOTRANGE(end))]);
 | 
					    xlabel("Idő [s]");
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  ylabel("Feszültség [V]");
 | 
				
			||||||
 | 
					  xlim([ts_ref(PLOTRANGE(1)) ts_ref(PLOTRANGE(end))]);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					  legend_lines = {};
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  for i=1:length(node_names)
 | 
				
			||||||
 | 
					    for ch=1:2 
 | 
				
			||||||
 | 
					      legend_lines{2 * (i - 1) + ch, 1} = strcat(node_names{i,1}, " CH", num2str(ch));
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  legend(legend_lines);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (ONLYDATA)
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  % plot signal difference
 | 
				
			||||||
 | 
					  subplot(3,1,2);
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  % normalize signals
 | 
				
			||||||
 | 
					  ds = sds{1};
 | 
				
			||||||
 | 
					  ds_norm_range{1} = ds(PLOTRANGE,2:end) ./ max(abs(ds(PLOTRANGE,2:end)));
 | 
				
			||||||
 | 
					  legend_lines = {};
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  for i=2:length(node_names)
 | 
				
			||||||
 | 
					    ds = sds{i};
 | 
				
			||||||
 | 
					    ds_norm_range{i} = ds(PLOTRANGE,2:end) ./ max(abs(ds(PLOTRANGE,2:end)));
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for ch=1:2 
 | 
				
			||||||
 | 
					      legend_lines{2 * (i - 2) + ch, 1} = strcat(node_names{i,1}, "-", node_names{1,1}, " CH", num2str(ch));
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    ts = ds(:,1);
 | 
				
			||||||
 | 
					    diff_data = ((ds_norm_range{i} - ds_norm_range{1})); % ./ ds_norm_range{1});
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					%     for l=1:length(diff_data)
 | 
				
			||||||
 | 
					%       diff_data(l,:) = diff_data(l,:) ./ ds_norm_range{1}(l,:);
 | 
				
			||||||
 | 
					%     end
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    mark = marks(k-1);
 | 
				
			||||||
 | 
					    plot(ts(PLOTRANGE), diff_data, mark);
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  legend(legend_lines);
 | 
				
			||||||
 | 
					  ylabel("Erősítéshibával kompenzált különbség")
 | 
				
			||||||
 | 
					  xlim([ts_ref(PLOTRANGE(1)) ts_ref(PLOTRANGE(end))]);
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  % plot timestamp errors
 | 
					  % plot timestamp errors
 | 
				
			||||||
  subplot(2,1,2);
 | 
					  subplot(3,1,3);
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  ds_ref = sds{1};
 | 
					  legend_lines = {};
 | 
				
			||||||
  ts_ref = ds_ref(:,1);
 | 
					 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  for i = 2:len
 | 
					  for i = 2:len
 | 
				
			||||||
    ts_err = ts(PLOTRANGE) - ts_ref(PLOTRANGE);
 | 
					    ts_err = ts(PLOTRANGE) - ts_ref(PLOTRANGE);
 | 
				
			||||||
    plot(ts_ref(PLOTRANGE), ts_err * 1E+09);
 | 
					    plot(ts_ref(PLOTRANGE), ts_err * 1E+09, "-o");
 | 
				
			||||||
    hold on
 | 
					    hold on
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    legend_lines{i - 1, 1} = strcat(node_names{i,1}, "-", node_names{1,1});
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  grid on
 | 
					  grid on
 | 
				
			||||||
  xlabel("Time [s]");
 | 
					  xlabel("Idő [s]");
 | 
				
			||||||
  ylabel("Time error [ns]");
 | 
					  ylabel("Időhiba [ns]");
 | 
				
			||||||
  xlim([ts(PLOTRANGE(1)) ts(PLOTRANGE(end))]);
 | 
					  legend(legend_lines);
 | 
				
			||||||
 | 
					  xlim([ts_ref(PLOTRANGE(1)) ts_ref(PLOTRANGE(end))]);
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
endfunction
 | 
					end
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										151
									
								
								main.cpp
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								main.cpp
									
									
									
									
									
								
							@ -5,12 +5,75 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <alsa/asoundlib.h>
 | 
					#include <alsa/asoundlib.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <math.h>
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
#include <sys/socket.h>
 | 
					#include <sys/socket.h>
 | 
				
			||||||
#include <netinet/in.h>
 | 
					#include <netinet/in.h>
 | 
				
			||||||
#include "src/ALSADevice.h"
 | 
					#include "src/ALSADevice.h"
 | 
				
			||||||
#include "src/SampleWriter.h"
 | 
					#include "src/SampleWriter.h"
 | 
				
			||||||
#include "src/audio_types.h"
 | 
					#include "src/audio_types.h"
 | 
				
			||||||
 | 
					#include "src/ServerBeacon.h"
 | 
				
			||||||
 | 
					#include "src/GUI.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//uint8_t pRecvBuf[8096] __attribute__ ((aligned (32)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::shared_ptr<Globals> globs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main(int argc, char * argv[]) {
 | 
				
			||||||
 | 
					    if (argc < 3) {
 | 
				
			||||||
 | 
					        std::cout << "At least 2 parameters needed:" << std::endl << "wfr data_port multicast_if_addr" << std::endl;
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string mcastIFAddr = argv[2];
 | 
				
			||||||
 | 
					    unsigned short beaconPort = atoi(argv[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // -----------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //ALSADevice dev(PERIOD_LEN, SAMPLE_RATE);
 | 
				
			||||||
 | 
					    //MemoryPool<int16_t> pool(STEREO_BUF_LEN, 1000);
 | 
				
			||||||
 | 
					    //SampleWriter<AudioSampleType> sw(std::string("test_") + argv[1], 2, STEREO_BUF_LEN / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create globals
 | 
				
			||||||
 | 
					    globs = std::make_shared<Globals>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create beacon
 | 
				
			||||||
 | 
					    auto beacon = globs->beacon = std::make_shared<ServerBeacon>();
 | 
				
			||||||
 | 
					    beacon->setInterfaceAddr(mcastIFAddr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // init and start GUI
 | 
				
			||||||
 | 
					    Glib::init();
 | 
				
			||||||
 | 
					    GUI gui(globs);
 | 
				
			||||||
 | 
					    gui.run();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //AudioPayload * pAudioPayload = reinterpret_cast<AudioPayload *>(pRecvBuf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//    while (true) {
 | 
				
			||||||
 | 
					//    //while (sw.getSampleCnt() < 100000) {
 | 
				
			||||||
 | 
					//        auto p = pool.alloc();
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        recv(soc, pRecvBuf, 8096, 0);
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        //std::cout << pAudioPayload->timestamp_s << ' ' << pAudioPayload->timestamp_ns << std::endl;
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        Timestamp ts = { pAudioPayload->timestamp_s, pAudioPayload->timestamp_ns };
 | 
				
			||||||
 | 
					//        //sw.addSamples(pAudioPayload->pData, ts);
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        memcpy(p->pBlock, pAudioPayload->pData, STEREO_BUF_LEN * sizeof(AudioSampleType));
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        dev.write(p);
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//        //std::cout << pool.avail() << std::endl;
 | 
				
			||||||
 | 
					//    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //close(soc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//int main() {
 | 
					//int main() {
 | 
				
			||||||
//    long loops;
 | 
					//    long loops;
 | 
				
			||||||
@ -117,34 +180,27 @@
 | 
				
			|||||||
//    return 0;
 | 
					//    return 0;
 | 
				
			||||||
//}
 | 
					//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define Fs (48000) // sampling frequency
 | 
					//#define Fs (48000) // sampling frequency
 | 
				
			||||||
#define F (440) // signal frequency
 | 
					//#define F (440) // signal frequency
 | 
				
			||||||
#define K (0.1) // amplitude
 | 
					//#define K (0.1) // amplitude
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
void generate_sine(int16_t *pBuf, uint32_t n) {
 | 
					//void generate_sine(int16_t *pBuf, uint32_t n) {
 | 
				
			||||||
    double y;
 | 
					//    double y;
 | 
				
			||||||
    static double phase = 0, dt = 1.0 / Fs;
 | 
					//    static double phase = 0, dt = 1.0 / Fs;
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
    uint32_t i = 0;
 | 
					//    uint32_t i = 0;
 | 
				
			||||||
    for (i = 0; i < n; i++) {
 | 
					//    for (i = 0; i < n; i++) {
 | 
				
			||||||
        y = K * 0.5 * (1 + sin(phase));
 | 
					//        y = K * 0.5 * (1 + sin(phase));
 | 
				
			||||||
        phase += 2 * M_PI * dt * F;
 | 
					//        phase += 2 * M_PI * dt * F;
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
        if (phase > (2 * M_PI)) {
 | 
					//        if (phase > (2 * M_PI)) {
 | 
				
			||||||
            phase -= 2 * M_PI;
 | 
					//            phase -= 2 * M_PI;
 | 
				
			||||||
        }
 | 
					//        }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
        pBuf[2 * i + 1] = pBuf[2 * i] = ((int16_t) (y * 0x7FFF));
 | 
					//        pBuf[2 * i + 1] = pBuf[2 * i] = ((int16_t) (y * 0x7FFF));
 | 
				
			||||||
    }
 | 
					//    }
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
}
 | 
					//}
 | 
				
			||||||
 | 
					 | 
				
			||||||
uint8_t pRecvBuf[8096] __attribute__ ((aligned (32)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int main(int argc, char * argv[]) {
 | 
					 | 
				
			||||||
    ALSADevice dev(PERIOD_LEN, SAMPLE_RATE);
 | 
					 | 
				
			||||||
    MemoryPool<int16_t> pool(STEREO_BUF_LEN, 1000);
 | 
					 | 
				
			||||||
    //SampleWriter<AudioSampleType> sw(std::string("test_") + argv[1], 2, STEREO_BUF_LEN / 2);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
//std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
					//std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -158,40 +214,3 @@ int main(int argc, char * argv[]) {
 | 
				
			|||||||
while (true) {
 | 
					while (true) {
 | 
				
			||||||
    std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
					    std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
				
			||||||
}*/
 | 
					}*/
 | 
				
			||||||
 | 
					 | 
				
			||||||
    int soc = socket(AF_INET, SOCK_DGRAM, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int opt = 1;
 | 
					 | 
				
			||||||
    setsockopt(soc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sockaddr_in addr;
 | 
					 | 
				
			||||||
    addr.sin_family = AF_INET;
 | 
					 | 
				
			||||||
    addr.sin_addr.s_addr = INADDR_ANY;
 | 
					 | 
				
			||||||
    addr.sin_port = htons(atoi(argv[1]));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bind(soc, (const sockaddr *)&addr, sizeof(addr));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    AudioPayload * pAudioPayload = reinterpret_cast<AudioPayload *>(pRecvBuf);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while (true) {
 | 
					 | 
				
			||||||
    //while (sw.getSampleCnt() < 100000) {
 | 
					 | 
				
			||||||
        auto p = pool.alloc();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        recv(soc, pRecvBuf, 8096, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //std::cout << pAudioPayload->timestamp_s << ' ' << pAudioPayload->timestamp_ns << std::endl;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Timestamp ts = { pAudioPayload->timestamp_s, pAudioPayload->timestamp_ns };
 | 
					 | 
				
			||||||
        //sw.addSamples(pAudioPayload->pData, ts);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        memcpy(p->pBlock, pAudioPayload->pData, STEREO_BUF_LEN * sizeof(AudioSampleType));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        dev.write(p);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //std::cout << pool.avail() << std::endl;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    close(soc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										29
									
								
								src/Callback.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Callback.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// Created by epagris on 2021. 11. 18..
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef WFR_CALLBACK_H
 | 
				
			||||||
 | 
					#define WFR_CALLBACK_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... ParamTypes>
 | 
				
			||||||
 | 
					class CallbackBase {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    virtual void operator()(ParamTypes... params) = 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename T, typename... ParamTypes>
 | 
				
			||||||
 | 
					class Callback : public CallbackBase<ParamTypes...> {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    T * pObj; // pointer to object to call the function on
 | 
				
			||||||
 | 
					    void(T::*pFN)(ParamTypes... params); // member function pointer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Callback(T * pObj, void(T::*pFN)(ParamTypes... params)) : pObj(pObj), pFN(pFN) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void operator()(ParamTypes... params) override {
 | 
				
			||||||
 | 
					        std::invoke(pFN, *pObj, params...);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif //WFR_CALLBACK_H
 | 
				
			||||||
							
								
								
									
										355
									
								
								src/GUI.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								src/GUI.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,355 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// Created by epagris on 2021. 11. 14..
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef WFR_GUI_H
 | 
				
			||||||
 | 
					#define WFR_GUI_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gtk/gtk.h>
 | 
				
			||||||
 | 
					#include <gtkmm-3.0/gtkmm.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "ServerBeacon.h"
 | 
				
			||||||
 | 
					#include "globals.h"
 | 
				
			||||||
 | 
					#include "Callback.h"
 | 
				
			||||||
 | 
					#include "MultiStreamReceiver.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GUI : public Gtk::Application {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    template<typename T>
 | 
				
			||||||
 | 
					    using RefPtr = Glib::RefPtr<T>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // GLOBALS
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    std::shared_ptr<Globals> mGlobs;
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					    // tree view managing client nodes
 | 
				
			||||||
 | 
					    class ClNodeTW {
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        Gtk::TreeView *mpTW;
 | 
				
			||||||
 | 
					    protected:
 | 
				
			||||||
 | 
					        class ClNodeLSColumns : public Gtk::TreeModel::ColumnRecord {
 | 
				
			||||||
 | 
					        public:
 | 
				
			||||||
 | 
					            Gtk::TreeModelColumn<Glib::ustring> mColIP;
 | 
				
			||||||
 | 
					            Gtk::TreeModelColumn<bool> mColUse;
 | 
				
			||||||
 | 
					            Gtk::TreeModelColumn<in_addr_t> mColInAddr;
 | 
				
			||||||
 | 
					        public:
 | 
				
			||||||
 | 
					            ClNodeLSColumns() {
 | 
				
			||||||
 | 
					                add(mColIP);
 | 
				
			||||||
 | 
					                add(mColUse);
 | 
				
			||||||
 | 
					                add(mColInAddr);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        ClNodeLSColumns cols; // columns
 | 
				
			||||||
 | 
					        RefPtr<Gtk::ListStore> listStore; // list store object
 | 
				
			||||||
 | 
					        Gtk::CellRendererText ipR; // IP-renderer
 | 
				
			||||||
 | 
					        Gtk::CellRendererToggle useR; // use? renderer
 | 
				
			||||||
 | 
					        std::shared_ptr<CallbackBase<ClNodeTW *>> selectChangeCB; // kiválasztás változásakor hívódik meg
 | 
				
			||||||
 | 
					    private:
 | 
				
			||||||
 | 
					        void onToggleUse(const Glib::ustring &path) {
 | 
				
			||||||
 | 
					            auto row = *(listStore->get_iter(path));
 | 
				
			||||||
 | 
					            row[cols.mColUse] = !row[cols.mColUse];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (selectChangeCB != nullptr) {
 | 
				
			||||||
 | 
					                (*selectChangeCB)(this);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /*void onRowActivate(const Gtk::TreePath &tp, Gtk::TreeViewColumn *pTVC) {
 | 
				
			||||||
 | 
					            auto row = *(listStore->get_iter(tp));
 | 
				
			||||||
 | 
					            auto command = "xdg-open http://" + row[cols.mColIP] + "/";
 | 
				
			||||||
 | 
					            system(command.c_str());
 | 
				
			||||||
 | 
					        }*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected:
 | 
				
			||||||
 | 
					        void addRenderers() {
 | 
				
			||||||
 | 
					            mpTW->append_column("IP Address", ipR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            useR.set_activatable(true);
 | 
				
			||||||
 | 
					            useR.signal_toggled().connect(sigc::mem_fun(*this, &ClNodeTW::onToggleUse));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mpTW->append_column("Use?", useR);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            auto twCols = mpTW->get_columns();
 | 
				
			||||||
 | 
					            twCols[0]->add_attribute(ipR, "text", 0);
 | 
				
			||||||
 | 
					            twCols[1]->add_attribute(useR, "active", 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public:
 | 
				
			||||||
 | 
					        explicit ClNodeTW(Gtk::TreeView *pTW) : mpTW(pTW) {
 | 
				
			||||||
 | 
					            listStore = Gtk::ListStore::create(cols); // construct list store
 | 
				
			||||||
 | 
					            mpTW->set_model(listStore); // set model
 | 
				
			||||||
 | 
					            //mpTW->set_activate_on_single_click(true);
 | 
				
			||||||
 | 
					            addRenderers(); // add cell renderers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            //mpTW->signal_row_activated().connect(sigc::mem_fun(*this, &ClNodeTW::onRowActivate));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void addRow(in_addr_t addr) {
 | 
				
			||||||
 | 
					            auto row = *(listStore->append());
 | 
				
			||||||
 | 
					            row[cols.mColInAddr] = addr;
 | 
				
			||||||
 | 
					            row[cols.mColIP] = inet_ntoa({addr});
 | 
				
			||||||
 | 
					            row[cols.mColUse] = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void setList(const std::list<in_addr_t> &addrs) {
 | 
				
			||||||
 | 
					            // set of entries already in the list
 | 
				
			||||||
 | 
					            std::list<in_addr_t> entriesFound;
 | 
				
			||||||
 | 
					            std::list<Gtk::TreeIter> entriesToDelete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bool atLeastOneSelectedLineDeleted = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // check for existing lines
 | 
				
			||||||
 | 
					            listStore->foreach([&](const Gtk::TreePath &path, const Gtk::TreeIter &iter) {
 | 
				
			||||||
 | 
					                in_addr_t addr = (*iter)[cols.mColInAddr];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // if entry is already in the list, then don't add it again
 | 
				
			||||||
 | 
					                if (std::find(addrs.begin(), addrs.end(), addr) != addrs.end()) {
 | 
				
			||||||
 | 
					                    entriesFound.push_back(addr);
 | 
				
			||||||
 | 
					                } else { // if value is not on the novel list, remove it
 | 
				
			||||||
 | 
					                    if ((*iter)[cols.mColUse]) {
 | 
				
			||||||
 | 
					                        atLeastOneSelectedLineDeleted |= true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    entriesToDelete.push_back(iter);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // delete non-existent lines
 | 
				
			||||||
 | 
					            for (const auto &entry: entriesToDelete) {
 | 
				
			||||||
 | 
					                listStore->erase(entry);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // add non-existent, novel lines
 | 
				
			||||||
 | 
					            for (auto const &addr: addrs) {
 | 
				
			||||||
 | 
					                if (std::find(entriesFound.begin(), entriesFound.end(), addr) == entriesFound.end()) {
 | 
				
			||||||
 | 
					                    addRow(addr);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // send notification if a selected row is being deleted
 | 
				
			||||||
 | 
					            if (atLeastOneSelectedLineDeleted && (selectChangeCB != nullptr)) {
 | 
				
			||||||
 | 
					                (*selectChangeCB)(this);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::vector<in_addr_t> getSelectedLines() const {
 | 
				
			||||||
 | 
					            std::vector<in_addr_t> selectedLines;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // check for existing lines
 | 
				
			||||||
 | 
					            listStore->foreach([&](const Gtk::TreePath &path, const Gtk::TreeIter &iter) {
 | 
				
			||||||
 | 
					                if ((*iter)[cols.mColUse]) {
 | 
				
			||||||
 | 
					                    selectedLines.push_back((*iter)[cols.mColInAddr]);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return selectedLines;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        in_addr_t getHighlightedRow() const {
 | 
				
			||||||
 | 
					            auto iter = mpTW->get_selection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (iter->count_selected_rows() > 0) {
 | 
				
			||||||
 | 
					                return (*mpTW->get_selection()->get_selected())[cols.mColInAddr];
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // BUILDER-RELATED THINGS
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					    RefPtr<Gtk::Builder> mBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    template<typename T>
 | 
				
			||||||
 | 
					    T *getWidget(const Glib::ustring &name) { // get widget by name
 | 
				
			||||||
 | 
					        Gtk::Widget *pWidget;
 | 
				
			||||||
 | 
					        mBuilder->get_widget(name, pWidget);
 | 
				
			||||||
 | 
					        return reinterpret_cast<T *>(pWidget);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected: // WIDGETS
 | 
				
			||||||
 | 
					    Gtk::Button *wNodeRefreshBtn;
 | 
				
			||||||
 | 
					    Gtk::Button *wStartStopCaptureBtn;
 | 
				
			||||||
 | 
					    Gtk::FileChooserButton *wFolderChooser;
 | 
				
			||||||
 | 
					    Gtk::TextView *wInfoPanel;
 | 
				
			||||||
 | 
					    Gtk::TreeView *wClNodeTW;
 | 
				
			||||||
 | 
					    std::shared_ptr<ClNodeTW> mClNodeTW; // instance
 | 
				
			||||||
 | 
					    Gtk::Menu *wClNodeCtxMenu;
 | 
				
			||||||
 | 
					    Gtk::MenuItem *wCtxEntryWebInterface;
 | 
				
			||||||
 | 
					    Gtk::MenuItem *wCtxEntryTelnetTerm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // status flags
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					    struct {
 | 
				
			||||||
 | 
					        bool clNodesSelected;
 | 
				
			||||||
 | 
					        bool destinationFolderSelected;
 | 
				
			||||||
 | 
					        bool captureRunning;
 | 
				
			||||||
 | 
					    } mFlags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // stream receiver
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					    std::shared_ptr<MultiStreamReceiver> mpMSR; // pointer to a multistream receiver object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    void onActivateApp() {
 | 
				
			||||||
 | 
					        Gtk::Window *pMainWin;
 | 
				
			||||||
 | 
					        mBuilder->get_widget("main_win", pMainWin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add_window(*pMainWin);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //mClNodeTW->addRow("10.42.0.64", true);
 | 
				
			||||||
 | 
					        //mClNodeTW->addRow("10.42.0.65", false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void onScanDone(ServerBeacon *pSB) {
 | 
				
			||||||
 | 
					        auto nodes = pSB->getNodesOnNetwork();
 | 
				
			||||||
 | 
					        std::list<in_addr_t> ipList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // convert binary IP addresses to strings
 | 
				
			||||||
 | 
					        for (auto const &node: nodes) {
 | 
				
			||||||
 | 
					            ipList.push_back(node.s_addr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mClNodeTW->setList(ipList);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void onRefreshBtnToggled() {
 | 
				
			||||||
 | 
					        auto beacon = mGlobs->beacon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wNodeRefreshBtn->set_sensitive(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::shared_ptr<Callback<GUI, ServerBeacon *>> scanCB = std::make_shared<Callback<GUI, ServerBeacon *>>(this, &GUI::onScanDone);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // start beacon
 | 
				
			||||||
 | 
					        beacon->stopBeacon();
 | 
				
			||||||
 | 
					        beacon->setScanCallback(scanCB);
 | 
				
			||||||
 | 
					        beacon->startBeacon();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        std::this_thread::sleep_for(std::chrono::milliseconds(200));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mGlobs->beacon->stopBeacon();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wNodeRefreshBtn->set_sensitive(true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void onNodeSelectionChanged(ClNodeTW *pTW) {
 | 
				
			||||||
 | 
					        auto selectedIPs = pTW->getSelectedLines();
 | 
				
			||||||
 | 
					        mFlags.clNodesSelected = !selectedIPs.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manageCaptureBtnState();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void onFolderSelect() {
 | 
				
			||||||
 | 
					        mFlags.destinationFolderSelected = !wFolderChooser->get_filename().empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manageCaptureBtnState();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void onStartStopCaptureBtnClick() {
 | 
				
			||||||
 | 
					        if (!mFlags.captureRunning) { // start capture
 | 
				
			||||||
 | 
					            wFolderChooser->set_sensitive(false);
 | 
				
			||||||
 | 
					            wClNodeTW->set_sensitive(false);
 | 
				
			||||||
 | 
					            wStartStopCaptureBtn->set_label("Stop capture");
 | 
				
			||||||
 | 
					            mFlags.captureRunning = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            mpMSR = std::make_shared<MultiStreamReceiver>(mClNodeTW->getSelectedLines(), wFolderChooser->get_filename());
 | 
				
			||||||
 | 
					            mpMSR->start();
 | 
				
			||||||
 | 
					            mGlobs->beacon->execCmdOnAllTerms(std::string("snd connect ") + mGlobs->beacon->getInterfaceAddr() + " " + std::to_string(mpMSR->port));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            mGlobs->beacon->execCmdOnAllTerms("snd disconnect");
 | 
				
			||||||
 | 
					            mpMSR = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            wFolderChooser->set_sensitive(true);
 | 
				
			||||||
 | 
					            wClNodeTW->set_sensitive(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            wStartStopCaptureBtn->set_label("Start capture");
 | 
				
			||||||
 | 
					            mFlags.captureRunning = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool onShowCtxPopupMenu(GdkEventButton *event) {
 | 
				
			||||||
 | 
					        if (event->type == GDK_2BUTTON_PRESS) {
 | 
				
			||||||
 | 
					            if (!wClNodeCtxMenu->get_attach_widget()) {
 | 
				
			||||||
 | 
					                wClNodeCtxMenu->attach_to_widget(*wClNodeTW);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            wClNodeCtxMenu->popup(event->button, event->time);
 | 
				
			||||||
 | 
					            return true; //It has been handled.
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void onOpenWebInterface() {
 | 
				
			||||||
 | 
					        in_addr_t node_addr = mClNodeTW->getHighlightedRow();
 | 
				
			||||||
 | 
					        if (node_addr != 0) {
 | 
				
			||||||
 | 
					            auto command = std::string("xdg-open http://") + inet_ntoa({node_addr}) + "/";
 | 
				
			||||||
 | 
					            system(command.c_str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void onOpenTelnetClient() {
 | 
				
			||||||
 | 
					        in_addr_t node_addr = mClNodeTW->getHighlightedRow();
 | 
				
			||||||
 | 
					        if (node_addr != 0) {
 | 
				
			||||||
 | 
					            auto port = mGlobs->beacon->getNodeNettermPort({node_addr});
 | 
				
			||||||
 | 
					            auto command = std::string("putty -raw ") + inet_ntoa({node_addr}) + " " + std::to_string(port) + " &";
 | 
				
			||||||
 | 
					            system(command.c_str());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					    void manageCaptureBtnState() {
 | 
				
			||||||
 | 
					        wStartStopCaptureBtn->set_sensitive(mFlags.clNodesSelected && mFlags.destinationFolderSelected);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void fetchWidgets() {
 | 
				
			||||||
 | 
					        wNodeRefreshBtn = getWidget<Gtk::ToggleButton>("node_refresh_btn");
 | 
				
			||||||
 | 
					        wStartStopCaptureBtn = getWidget<Gtk::Button>("start_stop_capture_btn");
 | 
				
			||||||
 | 
					        wFolderChooser = getWidget<Gtk::FileChooserButton>("folder_chooser_btn");
 | 
				
			||||||
 | 
					        wInfoPanel = getWidget<Gtk::TextView>("info_panel");
 | 
				
			||||||
 | 
					        wClNodeTW = getWidget<Gtk::TreeView>("client_node_tw");
 | 
				
			||||||
 | 
					        mClNodeTW = std::make_shared<ClNodeTW>(wClNodeTW);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wClNodeCtxMenu = getWidget<Gtk::Menu>("node_tw_ctx_menu");
 | 
				
			||||||
 | 
					        wCtxEntryWebInterface = getWidget<Gtk::MenuItem>("ctx_web_interface");
 | 
				
			||||||
 | 
					        wCtxEntryTelnetTerm = getWidget<Gtk::MenuItem>("ctx_telnet_term");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void connectSignals() {
 | 
				
			||||||
 | 
					        signal_activate().connect(sigc::mem_fun(*this, &GUI::onActivateApp)); // app activate
 | 
				
			||||||
 | 
					        wNodeRefreshBtn->signal_clicked().connect(sigc::mem_fun(*this, &GUI::onRefreshBtnToggled)); // refresh button
 | 
				
			||||||
 | 
					        mClNodeTW->selectChangeCB = std::make_shared<Callback<GUI, ClNodeTW *>>(this, &GUI::onNodeSelectionChanged); // select change callback
 | 
				
			||||||
 | 
					        wClNodeTW->signal_button_press_event().connect(sigc::mem_fun(*this, &GUI::onShowCtxPopupMenu)); // right click context menu popup
 | 
				
			||||||
 | 
					        wFolderChooser->signal_file_set().connect(sigc::mem_fun(*this, &GUI::onFolderSelect)); // folder selection
 | 
				
			||||||
 | 
					        wStartStopCaptureBtn->signal_clicked().connect(sigc::mem_fun(*this, &GUI::onStartStopCaptureBtnClick)); // start/stop click
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        wCtxEntryWebInterface->signal_activate().connect(sigc::mem_fun(*this, &GUI::onOpenWebInterface));
 | 
				
			||||||
 | 
					        wCtxEntryTelnetTerm->signal_activate().connect(sigc::mem_fun(*this, &GUI::onOpenTelnetClient));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void resetFlags() {
 | 
				
			||||||
 | 
					        memset(&mFlags, 0, sizeof(mFlags));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    explicit GUI(const std::shared_ptr<Globals> &globs) : Gtk::Application("com.epagris.samrecv"), mGlobs(globs) { // constr.
 | 
				
			||||||
 | 
					        resetFlags();
 | 
				
			||||||
 | 
					        mBuilder = Gtk::Builder::create_from_file("gui/samprecv.glade");
 | 
				
			||||||
 | 
					        fetchWidgets();
 | 
				
			||||||
 | 
					        connectSignals(); // connect signals
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif //WFR_GUI_H
 | 
				
			||||||
							
								
								
									
										154
									
								
								src/MultiStreamReceiver.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/MultiStreamReceiver.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,154 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// Created by epagris on 2021. 11. 22..
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <arpa/inet.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MultiStreamReceiver.h"
 | 
				
			||||||
 | 
					#include "audio_types.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MultiStreamReceiver::MultiStreamReceiver(const std::vector<in_addr_t> &nodes, const std::string& targetDir, unsigned short port) : port(port) {
 | 
				
			||||||
 | 
					    mTargetDir = targetDir;
 | 
				
			||||||
 | 
					    mRunning = false;
 | 
				
			||||||
 | 
					    mClientNodes = nodes;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MultiStreamReceiver::~MultiStreamReceiver() {
 | 
				
			||||||
 | 
					    stop();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiStreamReceiver::startNetworking() {
 | 
				
			||||||
 | 
					    // open socket
 | 
				
			||||||
 | 
					    mSoc = socket(AF_INET, SOCK_DGRAM, 0);
 | 
				
			||||||
 | 
					    if (mSoc == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error(std::string("Could not create UDP-socket in ") + __FUNCTION__ + "!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int opt = 1;
 | 
				
			||||||
 | 
					    if (setsockopt(mSoc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error(std::string("Could not set socket options in ") + __FUNCTION__ + "!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sockaddr_in addr;
 | 
				
			||||||
 | 
					    addr.sin_family = AF_INET;
 | 
				
			||||||
 | 
					    addr.sin_addr.s_addr = INADDR_ANY;
 | 
				
			||||||
 | 
					    addr.sin_port = htons(port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // bind
 | 
				
			||||||
 | 
					    if (bind(mSoc, (const sockaddr *)&addr, sizeof(addr)) == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error(std::string("Could not bind socket options in ") + __FUNCTION__ + "!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiStreamReceiver::createSampleWriters() {
 | 
				
			||||||
 | 
					    struct stat sb;
 | 
				
			||||||
 | 
					    if (stat(mTargetDir.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) { // if directory exists then leave it as it is
 | 
				
			||||||
 | 
					        //unlink(datasetName.c_str()); FIXME
 | 
				
			||||||
 | 
					    } else if (mkdir(mTargetDir.c_str(), 0777) == -1) { // else: create directory for dataset
 | 
				
			||||||
 | 
					        throw std::runtime_error("Could not create directory " + mTargetDir + "!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (chdir(mTargetDir.c_str()) == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error("Could not change to target directory!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string nodeDirHint = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (unsigned int & mClientNode : mClientNodes) {
 | 
				
			||||||
 | 
					        std::string datasetName = std::string("node_") + inet_ntoa({ mClientNode });
 | 
				
			||||||
 | 
					        mpSampleWriters.emplace_back(std::make_shared<SampleWriter<int16_t>>(datasetName, 2, STEREO_BUF_LEN / 2));
 | 
				
			||||||
 | 
					        nodeDirHint += datasetName + "\n";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::ofstream hintFile("node_dir_hint.txt", std::ios_base::out);
 | 
				
			||||||
 | 
					    hintFile << nodeDirHint;
 | 
				
			||||||
 | 
					    hintFile.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (chdir("..") == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error("Could not change to target directory!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiStreamReceiver::start() {
 | 
				
			||||||
 | 
					    if (mRunning) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ----------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    createSampleWriters();
 | 
				
			||||||
 | 
					    startNetworking();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ----------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mRunning = true;
 | 
				
			||||||
 | 
					    mRecvThread = std::make_shared<std::thread>(fnRecv, this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiStreamReceiver::stop() {
 | 
				
			||||||
 | 
					    if (!mRunning) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mRunning = false;
 | 
				
			||||||
 | 
					    mRecvThread->join();
 | 
				
			||||||
 | 
					    close(mSoc);
 | 
				
			||||||
 | 
					    mpSampleWriters.clear();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define RECV_BUFFER_SIZE (16000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MultiStreamReceiver::fnRecv(MultiStreamReceiver *pMSR) {
 | 
				
			||||||
 | 
					    // select-related structures
 | 
				
			||||||
 | 
					    fd_set read_fds;
 | 
				
			||||||
 | 
					    timeval tv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // receive buffer
 | 
				
			||||||
 | 
					    std::shared_ptr<uint32_t> pWordBuf = std::shared_ptr<uint32_t>(new uint32_t[(RECV_BUFFER_SIZE / 4) + 1]); // to have 4-byte alignment
 | 
				
			||||||
 | 
					    auto * pRecvBuf = reinterpret_cast<uint8_t *>(pWordBuf.get());
 | 
				
			||||||
 | 
					    auto * pAudioPayload = reinterpret_cast<AudioPayload *>(pWordBuf.get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (pMSR->mRunning) {
 | 
				
			||||||
 | 
					        // fd-set
 | 
				
			||||||
 | 
					        FD_ZERO(&read_fds);
 | 
				
			||||||
 | 
					        FD_SET(pMSR->mSoc, &read_fds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // timeout
 | 
				
			||||||
 | 
					        tv.tv_sec = 0;
 | 
				
			||||||
 | 
					        tv.tv_usec = 1E+06 / 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // wait for data
 | 
				
			||||||
 | 
					        int fd_cnt = select(pMSR->mSoc + 1, &read_fds, nullptr, nullptr, &tv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // if data is available on the socket
 | 
				
			||||||
 | 
					        if ((fd_cnt > 0) && FD_ISSET(pMSR->mSoc, &read_fds)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ssize_t recv_size;
 | 
				
			||||||
 | 
					            if ((recv_size = recv(pMSR->mSoc, pRecvBuf, RECV_BUFFER_SIZE, 0)) <= 0) {
 | 
				
			||||||
 | 
					                throw std::runtime_error("Receive error!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // get sample writer for the specific address
 | 
				
			||||||
 | 
					            std::shared_ptr<SampleWriter<int16_t>> pSampleWriter = nullptr;
 | 
				
			||||||
 | 
					            for (size_t i = 0; i < pMSR->mClientNodes.size(); i++) {
 | 
				
			||||||
 | 
					                if (pMSR->mClientNodes[i] == pAudioPayload->addr) {
 | 
				
			||||||
 | 
					                    pSampleWriter = pMSR->mpSampleWriters[i];
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // if sample writer is found
 | 
				
			||||||
 | 
					            if (pSampleWriter != nullptr) {
 | 
				
			||||||
 | 
					                pSampleWriter->addSamples(pAudioPayload->pData, { pAudioPayload->timestamp_s, pAudioPayload->timestamp_ns });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/MultiStreamReceiver.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/MultiStreamReceiver.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// Created by epagris on 2021. 11. 22..
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef WFR_MULTISTREAMRECEIVER_H
 | 
				
			||||||
 | 
					#define WFR_MULTISTREAMRECEIVER_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <netinet/in.h>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include "SampleWriter.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MultiStreamReceiver {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    static constexpr unsigned short DEFAULT_PORT = 20220; // default port
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    std::vector<in_addr_t> mClientNodes; // client node addresses
 | 
				
			||||||
 | 
					    int mSoc; // UDP-socket for receiving samples
 | 
				
			||||||
 | 
					    std::shared_ptr<std::thread> mRecvThread; // thread for reception
 | 
				
			||||||
 | 
					    static void fnRecv(MultiStreamReceiver * pMSR); // routine function running in separate thread
 | 
				
			||||||
 | 
					    bool mRunning;
 | 
				
			||||||
 | 
					    std::vector<std::shared_ptr<SampleWriter<int16_t>>> mpSampleWriters; // sample writers for streams
 | 
				
			||||||
 | 
					    std::string mTargetDir; // target directory
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    void startNetworking(); // start networking
 | 
				
			||||||
 | 
					    void createSampleWriters(); // construct sample writers
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    const unsigned short port; // port
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    explicit MultiStreamReceiver(const std::vector<in_addr_t>& nodes, const std::string& targetDir, unsigned short port = DEFAULT_PORT);
 | 
				
			||||||
 | 
					    void start(); // start reception
 | 
				
			||||||
 | 
					    void stop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual ~MultiStreamReceiver();
 | 
				
			||||||
 | 
					    // stop reception
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif //WFR_MULTISTREAMRECEIVER_H
 | 
				
			||||||
@ -17,7 +17,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#define DIRSEP '/'
 | 
					#define DIRSEP '/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<typename T> // ch: number of channels, T: sample datatype
 | 
					template<typename T> // T: sample datatype
 | 
				
			||||||
class SampleWriter {
 | 
					class SampleWriter {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    static const std::string TS_FILENAME; // filename for timestamp file
 | 
					    static const std::string TS_FILENAME; // filename for timestamp file
 | 
				
			||||||
 | 
				
			|||||||
@ -5,17 +5,18 @@
 | 
				
			|||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <unistd.h>
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
#include "ServerBeacon.h"
 | 
					#include "ServerBeacon.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const in_addr ServerBeacon::DEFAULT_MULTICAST_ADDR = {inet_addr("224.0.2.21")};
 | 
					const in_addr ServerBeacon::DEFAULT_MULTICAST_ADDR = {inet_addr("224.0.2.21")};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ServerBeacon::ServerBeacon() : mRunning(false) {
 | 
					ServerBeacon::ServerBeacon() : mRunning(false), mScanCallback(nullptr) {
 | 
				
			||||||
    setMulticastAddr(DEFAULT_MULTICAST_ADDR);
 | 
					    setMulticastAddr(DEFAULT_MULTICAST_ADDR);
 | 
				
			||||||
    setPort(DEFAULT_PORT);
 | 
					    setPort(DEFAULT_PORT);
 | 
				
			||||||
    setAnnouncePeriod(DEFAULT_ANNOUNCE_PERIOD_MS);
 | 
					    setAnnouncePeriod(DEFAULT_ANNOUNCE_PERIOD_MS);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ServerBeacon::ServerBeacon(const in_addr &addr, unsigned short port, size_t announcePeriod_ms) : mRunning(false) {
 | 
					ServerBeacon::ServerBeacon(const in_addr &addr, unsigned short port, size_t announcePeriod_ms) : mRunning(false), mScanCallback(nullptr) {
 | 
				
			||||||
    setMulticastAddr(addr);
 | 
					    setMulticastAddr(addr);
 | 
				
			||||||
    setPort(port);
 | 
					    setPort(port);
 | 
				
			||||||
    setAnnouncePeriod(announcePeriod_ms);
 | 
					    setAnnouncePeriod(announcePeriod_ms);
 | 
				
			||||||
@ -62,18 +63,28 @@ void ServerBeacon::startBeacon() {
 | 
				
			|||||||
    int opt = 1;
 | 
					    int opt = 1;
 | 
				
			||||||
    setsockopt(mBeaconSoc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
 | 
					    setsockopt(mBeaconSoc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // bind to ip-address and port
 | 
					    // fill-in ip-address and port
 | 
				
			||||||
    sockaddr_in addr;
 | 
					    mMulticastAddr.sin_family = AF_INET;
 | 
				
			||||||
    addr.sin_family = AF_INET;
 | 
					    mMulticastAddr.sin_addr = mAddr;
 | 
				
			||||||
    addr.sin_addr = mAddr;
 | 
					    mMulticastAddr.sin_port = htons(mBeaconPort);
 | 
				
			||||||
    addr.sin_port = htons(mBeaconPort);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (bind(mBeaconSoc, (const sockaddr *) &addr, sizeof(addr)) == -1) {
 | 
					    // bind to port
 | 
				
			||||||
 | 
					    sockaddr_in bindAddr = mMulticastAddr;
 | 
				
			||||||
 | 
					    bindAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (bind(mBeaconSoc, (const sockaddr *) &bindAddr, sizeof(sockaddr_in)) == -1) {
 | 
				
			||||||
        std::cerr << "Could not bind to IP address and/or port in " << __FUNCTION__ << "!" << std::endl;
 | 
					        std::cerr << "Could not bind to IP address and/or port in " << __FUNCTION__ << "!" << std::endl;
 | 
				
			||||||
        close(mBeaconSoc);
 | 
					        close(mBeaconSoc);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // join multicast group
 | 
				
			||||||
 | 
					    ip_mreq mreq;
 | 
				
			||||||
 | 
					    mreq.imr_multiaddr = mAddr;
 | 
				
			||||||
 | 
					    mIFAddr.s_addr = mreq.imr_interface.s_addr = inet_addr(mIFAddrStr.c_str());
 | 
				
			||||||
 | 
					    setsockopt(mBeaconSoc, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
 | 
				
			||||||
 | 
					    setsockopt(mBeaconSoc, IPPROTO_IP, IP_MULTICAST_IF, &mreq.imr_interface, sizeof(in_addr));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // the beacon is running now!
 | 
					    // the beacon is running now!
 | 
				
			||||||
    mRunning = true;
 | 
					    mRunning = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -82,11 +93,27 @@ void ServerBeacon::startBeacon() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ServerBeacon::stopBeacon() {
 | 
					void ServerBeacon::stopBeacon() {
 | 
				
			||||||
 | 
					    if (!mRunning){
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    mRunning = false;
 | 
					    mRunning = false;
 | 
				
			||||||
 | 
					    mpBeaconThread->join();
 | 
				
			||||||
 | 
					    close(mBeaconSoc);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::list<in_addr> ServerBeacon::getNodesOnNetwork() {
 | 
					std::list<in_addr> ServerBeacon::getNodesOnNetwork() {
 | 
				
			||||||
    return std::list<in_addr>();
 | 
					    std::list<in_addr> nodeAddrs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mBeaconMtx.lock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (auto nodeInfo : mNodes) {
 | 
				
			||||||
 | 
					        nodeAddrs.push_back(nodeInfo.addr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mBeaconMtx.unlock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return nodeAddrs;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -96,12 +123,22 @@ void ServerBeacon::fn_BeaconThread(ServerBeacon *pSB) {
 | 
				
			|||||||
    // beacon message used to send and receive information
 | 
					    // beacon message used to send and receive information
 | 
				
			||||||
    BeaconMsg msg;
 | 
					    BeaconMsg msg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // select-related structures
 | 
					    // select-related structures
 | 
				
			||||||
    fd_set read_fds;
 | 
					    fd_set read_fds;
 | 
				
			||||||
    timeval tv;
 | 
					    timeval tv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // list of detected nodes
 | 
				
			||||||
 | 
					    std::list<ClientNodeInfo> clNodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while (pSB->mRunning) {
 | 
				
			||||||
 | 
					        msg.senderAddr = pSB->mIFAddr; // set our address
 | 
				
			||||||
 | 
					        msg.server_nClient = 1; // we are the server
 | 
				
			||||||
 | 
					        msg.terminalPort = 0; // we have no inbound terminal port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sendto(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0, (sockaddr *) &pSB->mMulticastAddr, sizeof(sockaddr_in)); // send the announce message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // ------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // fill-in select parameters
 | 
					        // fill-in select parameters
 | 
				
			||||||
        FD_ZERO(&read_fds);
 | 
					        FD_ZERO(&read_fds);
 | 
				
			||||||
        FD_SET(pSB->mBeaconSoc, &read_fds);
 | 
					        FD_SET(pSB->mBeaconSoc, &read_fds);
 | 
				
			||||||
@ -110,24 +147,119 @@ void ServerBeacon::fn_BeaconThread(ServerBeacon *pSB) {
 | 
				
			|||||||
        tv.tv_sec = pSB->mAnnPeriod_ms / 1000;
 | 
					        tv.tv_sec = pSB->mAnnPeriod_ms / 1000;
 | 
				
			||||||
        tv.tv_usec = (pSB->mAnnPeriod_ms % 1000) * 1000;
 | 
					        tv.tv_usec = (pSB->mAnnPeriod_ms % 1000) * 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while (pSB->mRunning) {
 | 
					        // clear list
 | 
				
			||||||
        msg.server_nClient = 1; // we are the server
 | 
					        clNodes.clear();
 | 
				
			||||||
        msg.terminalPort = 0; // we have no inbound terminal port
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        send(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0); // send the announce message
 | 
					        while (select(pSB->mBeaconSoc + 1, &read_fds, nullptr, nullptr, &tv) != 0) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // ------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // copy parameters
 | 
					 | 
				
			||||||
        fd_set cur_rfds = read_fds;
 | 
					 | 
				
			||||||
        timeval cur_tv = tv;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (select(1, &cur_rfds, nullptr, nullptr, &cur_tv) != 0) {
 | 
					 | 
				
			||||||
            // receive response
 | 
					            // receive response
 | 
				
			||||||
            recv(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0);
 | 
					            recv(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // consider only client responses
 | 
				
			||||||
 | 
					            if (msg.server_nClient == 0) {
 | 
				
			||||||
                // store response
 | 
					                // store response
 | 
				
			||||||
 | 
					                ClientNodeInfo clInfo;
 | 
				
			||||||
 | 
					                clInfo.addr = msg.senderAddr;
 | 
				
			||||||
 | 
					                clInfo.terminalPort = msg.terminalPort;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                clNodes.push_back(clInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                //std::cout << inet_ntoa(clInfo.addr) << std::endl;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // -------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fill-in select parameters
 | 
				
			||||||
 | 
					            FD_ZERO(&read_fds);
 | 
				
			||||||
 | 
					            FD_SET(pSB->mBeaconSoc, &read_fds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // fill-in timeout value
 | 
				
			||||||
 | 
					            tv.tv_sec = pSB->mAnnPeriod_ms / 1000;
 | 
				
			||||||
 | 
					            tv.tv_usec = (pSB->mAnnPeriod_ms % 1000) * 1000;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // copy list of detected nodes
 | 
				
			||||||
 | 
					        pSB->mBeaconMtx.lock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pSB->mNodes = clNodes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pSB->mBeaconMtx.unlock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // invoke callback
 | 
				
			||||||
 | 
					        if (pSB->mScanCallback != nullptr) {
 | 
				
			||||||
 | 
					            (*pSB->mScanCallback)(pSB);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ServerBeacon::setScanCallback(const std::shared_ptr<CallbackBase<ServerBeacon *>>& scanCB) {
 | 
				
			||||||
 | 
					    mScanCallback = scanCB;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ServerBeacon::setInterfaceAddr(const std::string &interfaceAddr) {
 | 
				
			||||||
 | 
					    mIFAddrStr = interfaceAddr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string ServerBeacon::getInterfaceAddr() const {
 | 
				
			||||||
 | 
					    return mIFAddrStr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unsigned short ServerBeacon::getNodeNettermPort(in_addr addr) {
 | 
				
			||||||
 | 
					    mBeaconMtx.lock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int nettermPort = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const auto& nodeInfo : mNodes) {
 | 
				
			||||||
 | 
					        if (nodeInfo.addr.s_addr == addr.s_addr) {
 | 
				
			||||||
 | 
					            nettermPort = nodeInfo.terminalPort;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mBeaconMtx.unlock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (nettermPort == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error(std::string("Node not found in database! (") + inet_ntoa(addr) + ")!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (unsigned short)nettermPort;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ServerBeacon::sendNettermCmd(in_addr addr, unsigned short port, const std::string &cmd) {
 | 
				
			||||||
 | 
					    int soc = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
 | 
				
			||||||
 | 
					    if (soc == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error("Could not create TCP socket to execute netterm commands!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sockaddr_in serverAddr;
 | 
				
			||||||
 | 
					    memset(&serverAddr, 0, sizeof(sockaddr_in));
 | 
				
			||||||
 | 
					    serverAddr.sin_addr = addr;
 | 
				
			||||||
 | 
					    serverAddr.sin_port = htons(port);
 | 
				
			||||||
 | 
					    serverAddr.sin_family = AF_INET;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (connect(soc, (const sockaddr *)&serverAddr, sizeof(sockaddr_in)) == -1) {
 | 
				
			||||||
 | 
					        throw std::runtime_error(std::string("Could not connect to netterm! (") + inet_ntoa(addr) + ":" + std::to_string(port) + ")");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static std::string nodeftty_cmd = "nodeftty";
 | 
				
			||||||
 | 
					    send(soc, "nodeftty", nodeftty_cmd.size(), 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // send command
 | 
				
			||||||
 | 
					    send(soc, cmd.c_str(), cmd.size(), 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    close(soc);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ServerBeacon::execCmdOnNode(in_addr addr, const std::string &cmd) {
 | 
				
			||||||
 | 
					    unsigned short port = getNodeNettermPort(addr);
 | 
				
			||||||
 | 
					    sendNettermCmd(addr, port, cmd);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ServerBeacon::execCmdOnAllTerms(const std::string &cmd) {
 | 
				
			||||||
 | 
					    mBeaconMtx.lock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const auto& nodeInfo : mNodes) {
 | 
				
			||||||
 | 
					        sendNettermCmd(nodeInfo.addr, nodeInfo.terminalPort, cmd);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mBeaconMtx.unlock();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -10,6 +10,8 @@
 | 
				
			|||||||
#include <netinet/in.h>
 | 
					#include <netinet/in.h>
 | 
				
			||||||
#include <arpa/inet.h>
 | 
					#include <arpa/inet.h>
 | 
				
			||||||
#include <thread>
 | 
					#include <thread>
 | 
				
			||||||
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					#include "Callback.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ServerBeacon {
 | 
					class ServerBeacon {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
@ -24,20 +26,28 @@ private:
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    int mBeaconSoc; // beacon socket
 | 
					    int mBeaconSoc; // beacon socket
 | 
				
			||||||
    in_addr mAddr; // address
 | 
					    in_addr mAddr; // multicast address
 | 
				
			||||||
 | 
					    sockaddr_in mMulticastAddr; // multicast address structure
 | 
				
			||||||
    unsigned short mBeaconPort; // port
 | 
					    unsigned short mBeaconPort; // port
 | 
				
			||||||
    size_t mAnnPeriod_ms; // announce period
 | 
					    size_t mAnnPeriod_ms; // announce period
 | 
				
			||||||
    std::list<ClientNodeInfo> mNodes; // nodes
 | 
					    std::list<ClientNodeInfo> mNodes; // nodes
 | 
				
			||||||
    bool mRunning; // does the beacon operate?
 | 
					    bool mRunning; // does the beacon operate?
 | 
				
			||||||
 | 
					    std::mutex mBeaconMtx; // beacon mutex
 | 
				
			||||||
 | 
					    std::string mIFAddrStr; // multicast interface address STRING
 | 
				
			||||||
 | 
					    in_addr mIFAddr; // multicast interface address
 | 
				
			||||||
 | 
					    std::shared_ptr<CallbackBase<ServerBeacon *>> mScanCallback; // scan callback
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    std::shared_ptr<std::thread> mpBeaconThread; // thread running the beacon
 | 
					    std::shared_ptr<std::thread> mpBeaconThread; // thread running the beacon
 | 
				
			||||||
    static void fn_BeaconThread(ServerBeacon * pSB); // function running in beacon thread
 | 
					    static void fn_BeaconThread(ServerBeacon * pSB); // function running in beacon thread
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    // structure for beacon MSG
 | 
					    // structure for beacon MSG
 | 
				
			||||||
    struct BeaconMsg {
 | 
					    struct BeaconMsg {
 | 
				
			||||||
        uint8_t server_nClient; // server or client
 | 
					        in_addr senderAddr; // address of sender
 | 
				
			||||||
        uint16_t terminalPort; // port of terminal
 | 
					        uint16_t terminalPort; // port of terminal
 | 
				
			||||||
 | 
					        uint8_t server_nClient; // server or client
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    void sendNettermCmd(in_addr addr, unsigned short port, const std::string& cmd); // send netterm cmd
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    ServerBeacon(); // constr.
 | 
					    ServerBeacon(); // constr.
 | 
				
			||||||
    explicit ServerBeacon(const in_addr& addr, unsigned short port, size_t announcePeriod_ms); // constr. with beacon port and announce period
 | 
					    explicit ServerBeacon(const in_addr& addr, unsigned short port, size_t announcePeriod_ms); // constr. with beacon port and announce period
 | 
				
			||||||
@ -47,9 +57,15 @@ public:
 | 
				
			|||||||
    unsigned short getPort() const; // get beacon port
 | 
					    unsigned short getPort() const; // get beacon port
 | 
				
			||||||
    void setAnnouncePeriod(size_t period_ms); // set announce period
 | 
					    void setAnnouncePeriod(size_t period_ms); // set announce period
 | 
				
			||||||
    size_t getAnnouncePeriod() const; // get announce period
 | 
					    size_t getAnnouncePeriod() const; // get announce period
 | 
				
			||||||
 | 
					    void setInterfaceAddr(const std::string &interfaceAddr); // set multicast interface address
 | 
				
			||||||
 | 
					    std::string getInterfaceAddr() const; // get multicast interface address
 | 
				
			||||||
    void startBeacon(); // start the beacon
 | 
					    void startBeacon(); // start the beacon
 | 
				
			||||||
    void stopBeacon(); // stop the beacon
 | 
					    void stopBeacon(); // stop the beacon
 | 
				
			||||||
    std::list<in_addr> getNodesOnNetwork(); // get nodes connected to the same network
 | 
					    std::list<in_addr> getNodesOnNetwork(); // get nodes connected to the same network
 | 
				
			||||||
 | 
					    unsigned short getNodeNettermPort(in_addr addr); // get network terminal port of ndoe
 | 
				
			||||||
 | 
					    void setScanCallback(const std::shared_ptr<CallbackBase<ServerBeacon *>>& scanCB); // set scan callback
 | 
				
			||||||
 | 
					    void execCmdOnNode(in_addr addr, const std::string& cmd); // execute command on a node through netterm
 | 
				
			||||||
 | 
					    void execCmdOnAllTerms(const std::string& cmd); // multicast command to all netterms
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -16,6 +16,7 @@ typedef int16_t AudioSampleType;
 | 
				
			|||||||
typedef struct {
 | 
					typedef struct {
 | 
				
			||||||
    uint32_t timestamp_s, timestamp_ns; // timestamp
 | 
					    uint32_t timestamp_s, timestamp_ns; // timestamp
 | 
				
			||||||
    uint32_t sample_cnt; // count of samples in packet
 | 
					    uint32_t sample_cnt; // count of samples in packet
 | 
				
			||||||
 | 
					    in_addr_t addr; // client node address
 | 
				
			||||||
    AudioSampleType pData[STEREO_BUF_LEN]; // buffer for stereo data
 | 
					    AudioSampleType pData[STEREO_BUF_LEN]; // buffer for stereo data
 | 
				
			||||||
} AudioPayload;
 | 
					} AudioPayload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								src/globals.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/globals.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// Created by epagris on 2021. 11. 16..
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef WFR_GLOBALS_H
 | 
				
			||||||
 | 
					#define WFR_GLOBALS_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include "ServerBeacon.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Globals {
 | 
				
			||||||
 | 
					    std::shared_ptr<ServerBeacon> beacon; // beacon to detect client nodes
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif //WFR_GLOBALS_H
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user