/* * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The OpenAirInterface Software Alliance licenses this file to You under * the OAI Public License, Version 1.1 (the "License"); you may not use this file * except in compliance with the License. * You may obtain a copy of the License at * * http://www.openairinterface.org/?page_id=698 * * Author and copyright: Laurent Thomas, open-cells.com * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *------------------------------------------------------------------------------- * For more information about the OpenAirInterface (OAI) Software Alliance: * contact@openairinterface.org */ /* Form definition file generated by fdesign */ #include #include "executables/softmodem-common.h" #include "executables/nr-softmodem-common.h" #ifdef WEBSRVSCOPE #include #include "common/utils/websrv/websrv.h" #include "common/utils/websrv/websrv_noforms.h" /* STATICFORXSCOPE will be used to make function being called from websrv code non static when we compile * with WEBSRVSCOPE mocro defined, but static when we compile for Xforms scope */ #define STATICFORXSCOPE #define fl_add_canvas websrv_fl_add_canvas #define fl_add_xyplot websrv_fl_add_xyplot #define fl_get_xyplot_data_pointer websrv_fl_get_xyplot_data_pointer #else #include #define STATICFORXSCOPE static static const int scope_enb_num_ue = 1; #endif #include "nr_phy_scope.h" #include "phy_scope.h" const FL_COLOR rx_antenna_colors[4] = {FL_RED,FL_BLUE,FL_GREEN,FL_YELLOW}; const FL_COLOR water_colors[4] = {FL_BLUE,FL_GREEN,FL_YELLOW,FL_RED}; static void drawsymbol(FL_OBJECT *obj, int id, FL_POINT *p, int n, int w, int h) { fl_points( p, n, FL_YELLOW); } // button callback example #if 0 static void dl_traffic_on_off( FL_OBJECT *button, long arg) { if (fl_get_button(button)) { fl_set_object_label(button, "DL Traffic ON"); otg_enabled = 1; fl_set_object_color(button, FL_GREEN, FL_GREEN); } else { fl_set_object_label(button, "DL Traffic OFF"); otg_enabled = 0; fl_set_object_color(button, FL_RED, FL_RED); } } #endif #define WATERFALL 10000 #ifdef WEBSRVSCOPE /* copy data from softmodem buffer to message body */ int websrv_cpiqbuff_tomsg(OAIgraph_t *graph, scopeSample_t *c, int n, int id, int base) { int I = 0; websrv_scope_params_t *WP = websrv_scope_getparams(); int newn = n; websrv_scopedata_msg_t *msg; websrv_nf_getdata(graph->graph, id, &msg); if (n > MAX_NIQ_WEBSOCKMSG) { LOG_E(UTIL, "Buffer id %i too small for %i iqs...\n", id, n); return 0; } /* copy and sort the chart data on x to improve frontend perf */ int16_t *data_xy = msg->data_xy + base; int16_t max_x = INT16_MIN; for (int i = 0; i < n; i++) { if (c[i].r < WP->xmin || c[i].i < WP->ymin || c[i].r > WP->xmax || c[i].i > WP->ymax) { newn--; continue; } if (max_x <= c[i].r) { data_xy[I] = max_x = c[i].r; data_xy[I + 1] = c[i].i; I = I + 2; } else { for (int j = I + (base * 2); j >= 0; j = j - 2) { if (msg->data_xy[j] <= c[i].r || j == 0) { for (int k = I + (base * 2); k > j; k = k - 2) { msg->data_xy[k + 2] = msg->data_xy[k]; msg->data_xy[k + 3] = msg->data_xy[k + 1]; } msg->data_xy[j + 2] = c[i].r; msg->data_xy[j + 3] = c[i].i; I = I + 2; break; } } } } return newn; } int websrv_cpllrbuff_tomsg(OAIgraph_t *graph, int16_t *llrs, int n, int id, int iteration, int max_iteration) { websrv_scopedata_msg_t *msg; websrv_nf_getdata(graph->graph, id, &msg); websrv_scope_params_t *WP = websrv_scope_getparams(); if (n > MAX_LLR_WEBSOCKMSG) { LOG_E(UTIL, "Buffer id %i too small for %i iqs...\n", id, n); return 0; } /* save number of points (xmax) at beginning of buffer, as we try to minimize data sent */ /* all iterations (UE pdschh case) have same n (number of data points) */ if (iteration == 0) { int32_t *iptr = (int32_t *)(msg->data_xy); *iptr = n; } /* for each point we save llr and the corresponding offset if llr's below the configured threshold have been skipped */ /* offset is saved in 8bits so llr always transmitted when offset reach the 8 bits max. offset relative to x of previously transmitted point */ int16_t *dptr = msg->data_xy; int newn = 2; // 2 first int16 used for saving maxx int xoffset = 1; int xres = (n / 1000 > CHAR_MAX) ? CHAR_MAX : (n / 1000); int16_t latestllr = 0; int imin = WP->llrxmin; int imax = WP->llrxmax; if (imax > n) imax = n; if (imin > imax) imin = imax; /* to keep data sorted, we insert points of each iteration at their x position, */ for (int i = imin; i < imax; i++) { if (((llrs[i] - latestllr) >= WP->llr_ythresh) || ((latestllr - llrs[i]) >= WP->llr_ythresh) || (xoffset >= xres)) { dptr[newn + iteration] = llrs[i]; latestllr = llrs[i]; dptr[newn + iteration + 1] = (int16_t)xoffset; xoffset = 1; newn = newn + max_iteration + 2; } else { xoffset++; } } /* discard points which cannot be distinguished on graph: x and llr have almost identical values for ( int i=4; i<(newn-1); i=i+2) { for (int j=i+2 ; j<(i+(xres*2)) && j<(newn-1); j=j+2) { if ( ((dptr[j] - dptr[i]) < yres) || ((dptr[i] - dptr[j]) < yres) ) { newn=newn-2; for (int k = j; k< newn; k++) { ((int8_t *)(msg->data_xy))[k] = (int8_t)msg->data_xy[k+2]; ((int8_t *)(msg->data_xy))[k+1] = (int8_t)msg->data_xy[k+3]; } } } } */ return newn; } void websrv_get_WF_buffers(OAIgraph_t *graph, websrv_scopedata_msg_t *msgp[]) { for (int i = 0; i < sizeof(water_colors) / sizeof(FL_COLOR); i++) { websrv_scopedata_msg_t *msg; websrv_nf_getdata(graph->graph, i, &msg); msg->header.msgtype = SCOPEMSG_TYPE_DATA; msg->header.chartid = graph->chartid; msg->header.datasetid = i; msg->header.update = (i == (sizeof(water_colors) / sizeof(FL_COLOR) - 1)) ? 1 : 0; msg->data_xy[0] = 0; msgp[i] = msg; } } void websrv_setpoint(int x, int y, websrv_scopedata_msg_t *msg) { msg->data_xy[0]++; msg->data_xy[msg->data_xy[0]] = (int16_t)x; msg->data_xy[0]++; msg->data_xy[msg->data_xy[0]] = (int16_t)y; } #endif static void commonGraph(OAIgraph_t *graph, int type, FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h, const char *label, FL_COLOR pointColor) { memset(graph, 0, sizeof(*graph)); if (type==WATERFALL) { graph->waterFallh=h-15; graph->waterFallAvg=malloc(sizeof(*graph->waterFallAvg) * graph->waterFallh); for (int i=0; i< graph->waterFallh; i++) graph->waterFallAvg[i]=0; graph->graph=fl_add_canvas(FL_NORMAL_CANVAS, x, y, w, graph->waterFallh, label); graph->text=fl_add_text(FL_NORMAL_TEXT, x, y+graph->waterFallh, w, 15, label); fl_set_object_lcolor(graph->text,FL_WHITE); fl_set_object_color(graph->text, FL_BLACK, FL_BLACK); fl_set_object_lalign(graph->text, FL_ALIGN_CENTER ); } else { graph->graph=fl_add_xyplot(type, x, y, w, h, label); fl_set_object_lcolor(graph->graph, FL_WHITE ); // Label color fl_set_object_color(graph->graph, FL_BLACK, pointColor); for (int i=0; i< FL_MAX_XYPLOTOVERLAY; i++) fl_set_xyplot_symbol(graph->graph, i, drawsymbol); } graph->x=x; graph->y=y; graph->w=w; graph->h=h; graph->maxX=0; graph->maxY=0; graph->minX=0; graph->minY=0; graph->initDone=false; graph->iteration=0; #ifdef WEBSRVSCOPE graph->enabled = false; #else graph->enabled = true; #endif } static OAIgraph_t gNBcommonGraph( void (*funct) (OAIgraph_t *graph, scopeData_t *p, int UE_id), int type, FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h, const char *label, FL_COLOR pointColor) { OAIgraph_t graph; commonGraph(&graph, type, x, y, w, h, label, pointColor); graph.gNBfunct=funct; graph.nrUEfunct=NULL; return graph; } static OAIgraph_t nrUEcommonGraph( void (*funct) (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id), int type, FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h, const char *label, FL_COLOR pointColor) { OAIgraph_t graph; commonGraph(&graph, type, x, y, w, h, label, pointColor); graph.gNBfunct=NULL; graph.nrUEfunct=funct; return graph; } #ifndef WEBSRVSCOPE static void setRange(OAIgraph_t *graph, float minX, float maxX, float minY, float maxY) { if (maxX > graph->maxX || minX < graph->minX || fabs(maxX - graph->maxX) > fabs(graph->maxX) / 2 || fabs(maxX - graph->maxX) > fabs(graph->maxX) / 2) { graph->maxX/=2; graph->minX/=2; graph->maxX=max(graph->maxX,maxX); graph->minX=min(graph->minX,minX); fl_set_xyplot_xbounds(graph->graph, graph->minX*1.2, graph->maxX*1.2); } if (maxY > graph->maxY || minY < graph->minY || fabs(maxY - graph->maxY) > fabs(graph->maxY) / 2 || fabs(maxY - graph->maxY) > fabs(graph->maxY) / 2) { graph->maxY/=2; graph->minY/=2; graph->maxY=max(graph->maxY,maxY); graph->minY=min(graph->minY,minY); fl_set_xyplot_ybounds(graph->graph, graph->minY*1.2, graph->maxY*1.2); } } static void oai_xygraph_getbuff(OAIgraph_t *graph, float **x, float **y, int len, int layer) { float *old_x; float *old_y; int old_len=-1; if (graph->iteration >1) fl_get_xyplot_data_pointer(graph->graph, layer, &old_x, &old_y, &old_len); if (old_len != len) { LOG_W(HW, "allocating graph of %d points\n", len); float values[len]; float time[len]; // make time in case we will use it for (int i=0; igraph,time,values,len,"","",""); else fl_add_xyplot_overlay(graph->graph,layer,time,values,len,rx_antenna_colors[layer]); fl_get_xyplot_data_pointer(graph->graph, layer, &old_x, &old_y, &old_len); AssertFatal(old_len == len, "graph len %i old_len %i\n", len, old_len); } *x=old_x; *y=old_y; } #endif static void oai_xygraph(OAIgraph_t *graph, float *x, float *y, int len, int layer, bool NoAutoScale) { #ifdef WEBSRVSCOPE websrv_scopedata_msg_t *msg = NULL; websrv_nf_getdata(graph->graph, layer, &msg); msg->header.msgtype = SCOPEMSG_TYPE_DATA; msg->header.chartid = graph->chartid; msg->header.datasetid = graph->datasetid; msg->header.msgseg = 0; msg->header.update = 1; websrv_scope_senddata(len, (msg->header.chartid == SCOPEMSG_DATAID_LLR) ? 2 : 4, msg); #else fl_redraw_object(graph->graph); if ( NoAutoScale && graph->iteration%NoAutoScale == 0) { float maxX=0, maxY=0, minX=0, minY=0; for (int k=0; kiteration++; } static void genericWaterFall (OAIgraph_t *graph, scopeSample_t *values, const int datasize, const int divisions, const char *label) { if ( values == NULL ) return; #ifdef WEBSRVSCOPE websrv_scopedata_msg_t *msgp[sizeof(water_colors) / sizeof(FL_COLOR)]; websrv_get_WF_buffers(graph, msgp); #endif fl_winset(FL_ObjWin(graph->graph)); const int samplesPerPixel=datasize/graph->w; int displayPart=graph->waterFallh-ScaleZone; int row=graph->iteration%displayPart; double avg=0; for (int i=0; i < displayPart; i++) avg+=graph->waterFallAvg[i]; avg/=displayPart; graph->waterFallAvg[row]=0; for (int pix=0; pixw; pix++) { scopeSample_t *end=values+(pix+1)*samplesPerPixel; end-=2; AssertFatal(end <= values+datasize,"diff : %tu", end-values+datasize); double val=0; for (scopeSample_t *s=values+(pix)*samplesPerPixel; s waterFallAvg[row]+=val/graph->w; int col=0; if (val > avg*2 ) col=1; if (val > avg*10 ) col=2; if (val > avg*100 ) col=3; #ifdef WEBSRVSCOPE websrv_setpoint(pix, graph->iteration % displayPart, msgp[col]); #else fl_point(pix, graph->iteration%displayPart, water_colors[col]); #endif } #ifdef WEBSRVSCOPE for (int i = 0; i < sizeof(water_colors) / sizeof(FL_COLOR); i++) { msgp[i]->header.msgseg = graph->iteration % displayPart; websrv_scope_senddata(msgp[i]->data_xy[0], 2, msgp[i]); } #else if (graph->initDone==false) { for ( int i=0; i < graph->waterFallh; i++ ) for ( int j = 0 ; j < graph->w ; j++ ) fl_point(j, i, FL_BLACK); for ( int i=1; iwaterFallh; j++) fl_point(i*(graph->w/divisions),j, FL_WHITE); graph->initDone=true; } fl_set_object_label_f(graph->text, "%s, avg I/Q pow: %4.1f", label, 0/*sqrt(avg)*/); #endif graph->iteration++; } static void genericPowerPerAntena(OAIgraph_t *graph, const int nb_ant, const scopeSample_t **data, const int len) { #ifdef WEBSRVSCOPE websrv_scopedata_msg_t *msg = NULL; websrv_nf_getdata(graph->graph, 0, &msg); float *values = (float *)msg->data_xy; #else float *values, *time; oai_xygraph_getbuff(graph, &time, &values, len, 0); #endif for (int ant=0; antheader.msgtype = SCOPEMSG_TYPE_DATA; msg->header.chartid = graph->chartid; msg->header.datasetid = graph->datasetid; msg->header.msgseg = 0; msg->header.update = (ant == (nb_ant - 1)) ? 1 : 0; websrv_scope_senddata(len, 4, msg); #else oai_xygraph(graph,time,values, len, ant, 10); #endif } } } static void gNBWaterFall (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { NR_DL_FRAME_PARMS *frame_parms=&p->gNB->frame_parms; //use 1st antenna genericWaterFall(graph, (scopeSample_t *)p->ru->common.rxdata[0], frame_parms->samples_per_frame, frame_parms->slots_per_frame, "X axis:one frame in time"); } /* replaced by waterfall static void timeSignal (OAIgraph_t *graph, PHY_VARS_gNB *phy_vars_gnb, RU_t *phy_vars_ru, const int nb_UEs) { // Received signal in time domain of receive antenna 0 if (!phy_vars_ru->common.rxdata) return; NR_DL_FRAME_PARMS *frame_parms=&phy_vars_gnb->frame_parms; genericLogPowerPerAntena(graph, frame_parms->nb_antennas_rx, (const scopeSample_t **)phy_vars_ru->common.rxdata, frame_parms->samples_per_frame); } */ static void timeResponse (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { scopeGraphData_t *val = p->liveData[gNBulDelay]; if (!val || !val->dataSize) return; #ifdef WEBSRVSCOPE websrv_scopedata_msg_t *msg = NULL; websrv_nf_getdata(graph->graph, 0, &msg); float *values = (float *)msg->data_xy; #else float *values, *time; oai_xygraph_getbuff(graph, &time, &values, val->lineSz, 0); #endif // We display UEs randomly, with one buffer c16_t *samples = (c16_t *)(val + 1); for (int i = 0; i < val->lineSz; i++) { values[i] = SquaredNorm(samples[i]); } #ifdef WEBSRVSCOPE msg->header.msgtype = SCOPEMSG_TYPE_DATA; msg->header.chartid = graph->chartid; msg->header.datasetid = graph->datasetid; msg->header.msgseg = 0; msg->header.update = 1; websrv_scope_senddata(val->lineSz, 4, msg); #else oai_xygraph(graph, time, values, val->lineSz, 0, 10); #endif } static void gNBfreqWaterFall (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { NR_DL_FRAME_PARMS *frame_parms=&p->gNB->frame_parms; //use 1st antenna genericWaterFall(graph, (scopeSample_t *)p->liveData[gNBRxdataF], frame_parms->samples_per_frame_wCP, frame_parms->slots_per_frame, "X axis: Frequency domain, one subframe"); } /* static void frequencyResponse (OAIgraph_t *graph, PHY_VARS_gNB *phy_vars_gnb, RU_t *phy_vars_ru, int nb_UEs) { NR_DL_FRAME_PARMS *frame_parms=&phy_vars_gnb->frame_parms; genericLogPowerPerAntena(graph, frame_parms->nb_antennas_rx, (const scopeSample_t **)phy_vars_ru->common.rxdataF, frame_parms->samples_per_slot_wCP); } */ static void puschLLR (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { #ifdef WEBSRVSCOPE int uestart = nb_UEs - 1; // web scope shows one UE signal, that can be selected from GUI #else int uestart = 0; // xforms scope designed to display nb_UEs signals #endif NR_DL_FRAME_PARMS *frame_parms=&p->gNB->frame_parms; int num_re = frame_parms->N_RB_UL*12*frame_parms->symbols_per_slot; int Qm = 2; int coded_bits_per_codeword = num_re*Qm; for (int ue = uestart; ue < nb_UEs; ue++) { if (p->gNB->pusch_vars && p->gNB->pusch_vars[ue].llr ) { int16_t *pusch_llr = (int16_t *)p->gNB->pusch_vars[ue].llr; float *llr=NULL, *bit=NULL; int nx = coded_bits_per_codeword; #ifdef WEBSRVSCOPE nx = websrv_cpllrbuff_tomsg(graph, pusch_llr, coded_bits_per_codeword, ue, 0, 0); #else oai_xygraph_getbuff(graph, &bit, &llr, coded_bits_per_codeword, ue); for (int i=0; igNB->frame_parms; int sz=frame_parms->N_RB_UL*12*frame_parms->symbols_per_slot; int newsz = sz; #ifdef WEBSRVSCOPE int uestart = nb_UEs - 1; // web scope shows one UE signal, that can be selected from GUI #else int uestart = 0; // xforms scope designed to display nb_UEs signals #endif for (int ue = uestart; ue < nb_UEs; ue++) { if (p->gNB->pusch_vars && p->gNB->pusch_vars[ue].rxdataF_comp && p->gNB->pusch_vars[ue].rxdataF_comp[0] ) { scopeSample_t *pusch_comp = (scopeSample_t *)p->gNB->pusch_vars[ue].rxdataF_comp[0]; float *I=NULL, *Q=NULL; #ifdef WEBSRVSCOPE newsz = websrv_cpiqbuff_tomsg(graph, pusch_comp, sz, 0, 0); #else oai_xygraph_getbuff(graph, &I, &Q, sz, ue); for (int k=0; kpucch1ab_stats[UE_id]; int32_t *pucch1_comp = (int32_t *) NULL; //phy_vars_gnb->pucch1_stats[UE_id]; float I_pucch[10240],Q_pucch[10240],A_pucch[10240],B_pucch[10240],C_pucch[10240]; for (int ind=0; ind<10240; ind++) { I_pucch[ind] = (float)pucch1ab_comp[2*(ind)]; Q_pucch[ind] = (float)pucch1ab_comp[2*(ind)+1]; A_pucch[ind] = pucch1_comp?(10*log10(pucch1_comp[ind])):0; B_pucch[ind] = ind; int32_t *pucch1_thres = (int32_t *) NULL; // phy_vars_gnb->pucch1_stats_thres[UE_id]; C_pucch[ind] = pucch1_thres?(float)pucch1_thres[ind]:0; } fl_set_xyplot_data(graph,I_pucch,Q_pucch,10240,"","",""); fl_set_xyplot_data(graph,B_pucch,A_pucch,1024,"","",""); fl_add_xyplot_overlay(graph,1,B_pucch,C_pucch,1024,FL_RED); fl_set_xyplot_ybounds(graph,-5000,5000); fl_set_xyplot_xbounds(graph,-5000,5000); fl_set_xyplot_ybounds(graph,0,80); } */ } static void pucchIQ (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { } static void puschThroughtput (OAIgraph_t *graph, scopeData_t *p, int nb_UEs) { // PUSCH Throughput /* float tput_time_enb[MAX_MOBILES_PER_GNB][TPUT_WINDOW_LENGTH] = {{0}}; float tput_enb[MAX_MOBILES_PER_GNB][TPUT_WINDOW_LENGTH] = {{0}}; memmove( tput_time_enb[UE_id], &tput_time_enb[UE_id][1], (TPUT_WINDOW_LENGTH-1)*sizeof(float) ); memmove( tput_enb[UE_id], &tput_enb[UE_id][1], (TPUT_WINDOW_LENGTH-1)*sizeof(float) ); tput_time_enb[UE_id][TPUT_WINDOW_LENGTH-1] = (float) 0; // tput_enb[UE_id][TPUT_WINDOW_LENGTH-1] = ((float) total_dlsch_bitrate)/1000.0; fl_set_xyplot_data(graph,tput_time_enb[UE_id],tput_enb[UE_id],TPUT_WINDOW_LENGTH,"","",""); // fl_get_xyplot_ybounds(form->pusch_tput,&ymin,&ymax); // fl_set_xyplot_ybounds(form->pusch_tput,0,ymax); */ } STATICFORXSCOPE OAI_phy_scope_t *create_phy_scope_gnb(void) { FL_OBJECT *obj; OAI_phy_scope_t *fdui = calloc_or_fail((sizeof *fdui), 1); // Define form fdui->phy_scope = fl_bgn_form( FL_NO_BOX, 800, 800 ); fl_set_form_dblbuffer(fdui->phy_scope, 1); // This the whole UI box obj = fl_add_box( FL_BORDER_BOX, 0, 0, 800, 800, "" ); fl_set_object_color( obj, FL_BLACK, FL_WHITE ); int curY=0,x,y,w,h; // Received signal fdui->graph[0] = gNBcommonGraph( gNBWaterFall, WATERFALL, 0, curY, 400, 100, "Received Signal (Time-Domain, one frame)", FL_RED ); fdui->graph[0].chartid = SCOPEMSG_DATAID_WF; // tells websrv frontend to use WF chart for displaying fdui->graph[0].datasetid = 0; // not used for WF // Time-domain channel response fdui->graph[1] = gNBcommonGraph( timeResponse, FL_NORMAL_XYPLOT, 410, curY, 400, 100, "SRS Frequency Response (samples, abs)", FL_RED ); fl_get_object_bbox(fdui->graph[1].graph,&x, &y,&w, &h); curY+=h; fdui->graph[1].chartid = SCOPEMSG_DATAID_TRESP; // Frequency-domain channel response fdui->graph[2] = gNBcommonGraph( gNBfreqWaterFall, WATERFALL, 0, curY, 800, 100, "Channel Frequency domain (RE, one frame)", FL_RED ); fl_get_object_bbox(fdui->graph[2].graph,&x, &y,&w, &h); curY+=h+20; fdui->graph[2].chartid = SCOPEMSG_DATAID_WF; // tells websrv frontend to use WF chart for displaying fdui->graph[2].datasetid = 0; // not used for WF // LLR of PUSCH fdui->graph[3] = gNBcommonGraph( puschLLR, FL_POINTS_XYPLOT, 0, curY, 500, 200, "PUSCH Log-Likelihood Ratios (LLR, mag)", FL_YELLOW ); fdui->graph[3].chartid = SCOPEMSG_DATAID_LLR; // tells websrv frontend to use LLR chart for displaying fdui->graph[3].datasetid = 0; // tells websrv frontend to use dataset index 0 in LLR chart // I/Q PUSCH comp fdui->graph[4] = gNBcommonGraph( puschIQ, FL_POINTS_XYPLOT, 500, curY, 300, 200, "PUSCH I/Q of MF Output", FL_YELLOW ); fl_get_object_bbox(fdui->graph[3].graph,&x, &y,&w, &h); curY+=h; fdui->graph[4].chartid = SCOPEMSG_DATAID_IQ; // tells websrv frontend to use constellation chart for displaying fdui->graph[4].datasetid = 0; // tells websrv frontend to use dataset 0 of constellation chart // I/Q PUCCH comp (format 1) fdui->graph[5] = gNBcommonGraph( pucchEnergy, FL_POINTS_XYPLOT, 0, curY, 300, 100, "PUCCH1 Energy (SR)", FL_YELLOW ); // fl_set_xyplot_xgrid( fdui->pusch_llr,FL_GRID_MAJOR); // I/Q PUCCH comp (fromat 1a/b) fdui->graph[6] = gNBcommonGraph( pucchIQ, FL_POINTS_XYPLOT, 500, curY, 300, 100, "PUCCH I/Q of MF Output", FL_YELLOW ); fl_get_object_bbox(fdui->graph[6].graph,&x, &y,&w, &h); curY+=h; fdui->graph[6].chartid = SCOPEMSG_DATAID_IQ; // tells websrv frontend to use constellation chart for displaying fdui->graph[6].datasetid = 1; // tells websrv frontend to use dataset 1 of constellation chart // Throughput on PUSCH fdui->graph[7] = gNBcommonGraph( puschThroughtput, FL_NORMAL_XYPLOT, 0, curY, 500, 100, "PUSCH Throughput [frame]/[kbit/s]", FL_WHITE ); fdui->graph[8].graph=NULL; fl_end_form( ); if (fdui->phy_scope) fdui->phy_scope->fdui = fdui; fl_show_form (fdui->phy_scope, FL_PLACE_HOTSPOT, FL_FULLBORDER, "NR UL SCOPE gNB"); return fdui; } STATICFORXSCOPE void phy_scope_gNB(OAI_phy_scope_t *form, scopeData_t *p, int UE_id) { static OAI_phy_scope_t *rememberForm=NULL; if (form==NULL) form=rememberForm; else rememberForm=form; if (form==NULL) return; int i=0; while (form->graph[i].graph) { if (form->graph[i].enabled) form->graph[i].gNBfunct(form->graph + i, p, UE_id); i++; } //fl_check_forms(); } #ifndef WEBSRVSCOPE static void *scope_thread_gNB(void *arg) { scopeData_t *p=(scopeData_t *) arg; size_t stksize=0; pthread_attr_t atr; pthread_attr_init(&atr); pthread_attr_getstacksize(&atr, &stksize); pthread_attr_setstacksize(&atr,32*1024*1024 ); sleep(3); // no clean interthread barriers int fl_argc=1; char *name="5G-gNB-scope"; fl_initialize (&fl_argc, &name, NULL, 0, 0); int nb_ue=min(MAX_MOBILES_PER_GNB, scope_enb_num_ue); OAI_phy_scope_t *form_gnb = create_phy_scope_gnb(); while (!oai_exit) { fl_freeze_form(form_gnb->phy_scope); phy_scope_gNB(form_gnb, p, nb_ue); fl_unfreeze_form(form_gnb->phy_scope); fl_redraw_form(form_gnb->phy_scope); usleep(99*1000); } return NULL; } #endif static void scopeUpdaterGnb(enum PlotTypeGnbIf plotType, int numElt) { switch (plotType) { case puschLLRe: /* update PUSCH LLR plot */ break; case puschIQe: /* update PUSCH IQ plot */ break; } } STATICFORXSCOPE void gNBinitScope(scopeParms_t *p) { AssertFatal(p->gNB->scopeData = calloc_or_fail(sizeof(scopeData_t), 1), ""); scopeData_t *scope=(scopeData_t *) p->gNB->scopeData; scope->argc=p->argc; scope->argv=p->argv; scope->ru=p->ru; scope->gNB=p->gNB; scope->scopeUpdater = scopeUpdaterGnb; scope->copyData = copyData; #ifndef WEBSRVSCOPE pthread_t forms_thread; threadCreate(&forms_thread, scope_thread_gNB, p->gNB->scopeData, "scope", -1, OAI_PRIORITY_RT_LOW); copyDataMutexInit(scope); #endif } static void ueWaterFall (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) { // Received signal in time domain of receive antenna 0 genericWaterFall(graph, (scopeSample_t *) phy_vars_ue->common_vars.rxdata[0], phy_vars_ue->frame_parms.samples_per_frame, phy_vars_ue->frame_parms.slots_per_frame, "X axis: one frame time"); } /* replaced by waterfall static void ueTimeResponse (OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) { // Received signal in time domain of receive antenna 0 genericLogPowerPerAntena(graph, phy_vars_ue->frame_parms.nb_antennas_rx, (const scopeSample_t **) phy_vars_ue->common_vars.rxdata, phy_vars_ue->frame_parms.samples_per_frame); } */ static void ueChannelResponse (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) { enum scopeDataType typ = (phy_vars_ue->sl_mode) ? psbchDlChEstimateTime : pbchDlChEstimateTime; // Channel Impulse Response if (!data[typ]) return; const scopeSample_t *tmp = (scopeSample_t *)(data[typ] + 1); genericPowerPerAntena(graph, data[typ]->colSz, &tmp, data[typ]->lineSz); } static void ueFreqWaterFall (scopeGraphData_t **data, OAIgraph_t *graph,PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id ) { //use 1st antenna if (!data[commonRxdataF]) return; const int sz = data[commonRxdataF]->lineSz; scopeSample_t *rxdataF = (scopeSample_t *)(data[commonRxdataF]+1); genericWaterFall(graph, rxdataF, sz, 1, "X axis: one slot frequency" ); } /* static void uePbchFrequencyResp (OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) { // Channel Frequency Response (includes 5 complex sample for filter) if (!phy_vars_ue->pbch_vars[eNB_id]->dl_ch_estimates) return; NR_DL_FRAME_PARMS *frame_parms = &phy_vars_ue->frame_parms; uint8_t nb_antennas_rx = frame_parms->nb_antennas_rx; uint8_t nb_antennas_tx = frame_parms->nb_antenna_ports_gNB; scopeSample_t **chest_f = (scopeSample_t **) phy_vars_ue->pbch_vars[eNB_id]->dl_ch_estimates; int ind = 0; float *freq, *chest_f_abs; oai_xygraph_getbuff(graph, &freq, &chest_f_abs, frame_parms->ofdm_symbol_size, 0); for (int atx=0; atxofdm_symbol_size; k++) { freq[ind] = (float)ind; chest_f_abs[ind] = (short)10*log10(1.0+SquaredNorm(chest_f[(atx<<1)+arx][6144+k])); ind++; } } } } // tx antenna 0 //fl_set_xyplot_xbounds(form->chest_f,0,nb_antennas_rx*nb_antennas_tx*nsymb_ce); //fl_set_xyplot_xtics(form->chest_f,nb_antennas_rx*nb_antennas_tx*frame_parms->symbols_per_tti,2); // fl_set_xyplot_xtics(form->chest_f,nb_antennas_rx*nb_antennas_tx*2,2); //fl_set_xyplot_xgrid(form->chest_f,FL_GRID_MAJOR); oai_xygraph(graph,freq,chest_f_abs,frame_parms->ofdm_symbol_size,0,10); } */ static void uePbchLLR (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) { enum scopeDataType typ = (phy_vars_ue->sl_mode) ? psbchLlr : pbchLlr; // PBCH LLRs if (!data[typ]) return; const int sz = data[typ]->lineSz; // const int antennas=data[typ]->colSz; // We take the first antenna only for now int16_t *llrs = (int16_t *)(data[typ] + 1); float *llr_pbch=NULL, *bit_pbch=NULL; int nx = sz; #ifdef WEBSRVSCOPE nx = websrv_cpllrbuff_tomsg(graph, llrs, sz, UE_id, 0, 0); #else oai_xygraph_getbuff(graph, &bit_pbch, &llr_pbch, sz, 0); for (int i=0; isl_mode) ? psbchRxdataF_comp : pbchRxdataF_comp; // PBCH I/Q of MF Output if (!data[typ]) return; scopeSample_t *pbch_comp = (scopeSample_t *)(data[typ] + 1); const int sz = data[typ]->lineSz; int newsz = sz; float *I=NULL, *Q=NULL; #ifdef WEBSRVSCOPE newsz = websrv_cpiqbuff_tomsg(graph, pbch_comp, sz, 0, 0); #else oai_xygraph_getbuff(graph, &I, &Q, sz, 0); for (int i=0; iN_RB_DL*num_pdcch_symbols //int Qm = 2; const int sz=data[pdcchLlr]->lineSz; float *llr=NULL, *bit=NULL; int nx = sz; int16_t *pdcch_llr = (int16_t *)(data[pdcchLlr]+1); #ifdef WEBSRVSCOPE nx = websrv_cpllrbuff_tomsg(graph, pdcch_llr, sz, UE_id, 0, 0); #else oai_xygraph_getbuff(graph, &bit, &llr, sz, 0); for (int i=0; ilineSz; int newsz = sz; //const int antennas=data[pdcchRxdataF_comp]->colSz; // We take the first antenna only for now float *I=NULL, *Q=NULL; scopeSample_t *pdcch_comp = (scopeSample_t *) (data[pdcchRxdataF_comp]+1); #ifdef WEBSRVSCOPE newsz = websrv_cpiqbuff_tomsg(graph, pdcch_comp, sz, 0, 0); #else oai_xygraph_getbuff(graph, &I, &Q, sz, 0); for (int i=0; ilineSz; float *llr=NULL, *bit=NULL; int nx = sz; int16_t *pdsch_llr = (int16_t *)(data[pdschLlr]+1); #ifdef WEBSRVSCOPE nx = websrv_cpllrbuff_tomsg(graph, pdsch_llr, sz, UE_id, 0, 0); #else oai_xygraph_getbuff(graph, &bit, &llr, sz, 0); for (int i=0; ipdsch_llr,0,coded_bits_per_codeword); oai_xygraph(graph, bit, llr, nx, 0, 10); } static void uePdschIQ (scopeGraphData_t **data, OAIgraph_t *graph, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) { // PDSCH I/Q of MF Output if (!data[pdschRxdataF_comp]) return; const int sz=data[pdschRxdataF_comp]->lineSz; int nz = sz; float *I=NULL, *Q=NULL; scopeSample_t *pdsch_comp = (scopeSample_t *) (data[pdschRxdataF_comp]+1); #ifdef WEBSRVSCOPE nz += websrv_cpiqbuff_tomsg(graph, pdsch_comp, sz, 0, 0); #else oai_xygraph_getbuff(graph, &I, &Q, sz, 0); for (int s=0; s tput_ue_max[UE_id]) { tput_ue_max[UE_id] = tput_ue[UE_id][TPUT_WINDOW_LENGTH-1]; } fl_set_xyplot_data(form->pdsch_tput,tput_time_ue[UE_id],tput_ue[UE_id],TPUT_WINDOW_LENGTH,"","",""); fl_set_xyplot_ybounds(form->pdsch_tput,0,tput_ue_max[UE_id]); */ } STATICFORXSCOPE OAI_phy_scope_t *create_phy_scope_nrue(int ID) { FL_OBJECT *obj; OAI_phy_scope_t *fdui = calloc_or_fail((sizeof *fdui), 1); // Define form fdui->phy_scope = fl_bgn_form( FL_NO_BOX, 800, 900 ); fl_set_form_dblbuffer(fdui->phy_scope, 1); // This the whole UI box obj = fl_add_box( FL_BORDER_BOX, 0, 0, 800, 900, "" ); fl_set_object_color( obj, FL_BLACK, FL_BLACK ); int curY=0,x,y,w,h; // Received signal fdui->graph[0] = nrUEcommonGraph(ueWaterFall, WATERFALL, 0, curY, 400, 100, "Received Signal (Time-Domain, one frame)", FL_RED ); fdui->graph[0].chartid = SCOPEMSG_DATAID_WF; // tells websrv frontend to use WF chart for displaying fdui->graph[0].datasetid = 0; // not used for WF // Time-domain channel response fdui->graph[1] = nrUEcommonGraph(ueChannelResponse, FL_NORMAL_XYPLOT, 400, curY, 400, 100, "Channel Impulse Response (samples, abs)", FL_RED ); fdui->graph[1].chartid = SCOPEMSG_DATAID_TRESP; fl_get_object_bbox(fdui->graph[1].graph,&x, &y,&w, &h); curY+=h; // Frequency-domain channel response fdui->graph[2] = nrUEcommonGraph(ueFreqWaterFall, WATERFALL, 0, curY, 800, 100, "Channel Frequency (RE, one slot)", FL_RED ); fl_get_object_bbox(fdui->graph[2].graph,&x, &y,&w, &h); curY+=h+20; fdui->graph[2].chartid = SCOPEMSG_DATAID_WF; // tells websrv frontend to use WF chart for displaying fdui->graph[2].datasetid = 0; // not used for WF // LLR of PBCH fdui->graph[3] = nrUEcommonGraph(uePbchLLR, FL_POINTS_XYPLOT, 0, curY, 500, 100, "PBCH Log-Likelihood Ratios (LLR, mag)", FL_GREEN ); fl_set_xyplot_xgrid(fdui->graph[3].graph,FL_GRID_MAJOR); fdui->graph[3].chartid = SCOPEMSG_DATAID_LLR; // tells websrv frontend to use LLR chart for displaying fdui->graph[3].datasetid = 0; // tells websrv frontend to use dataset index 0 in LLR chart // I/Q PBCH comp fdui->graph[4] = nrUEcommonGraph(uePbchIQ, FL_POINTS_XYPLOT, 500, curY, 300, 100, "PBCH I/Q of MF Output", FL_GREEN ); fl_get_object_bbox(fdui->graph[3].graph,&x, &y,&w, &h); curY += h; fdui->graph[4].chartid = SCOPEMSG_DATAID_IQ; // tells websrv frontend to use constellation chart for displaying fdui->graph[4].datasetid = 0; // tells websrv frontend which dataset to use in the window // LLR of PDCCH fdui->graph[5] = nrUEcommonGraph(uePcchLLR, FL_POINTS_XYPLOT, 0, curY, 500, 100, "PDCCH Log-Likelihood Ratios (LLR, mag)", FL_CYAN ); fl_set_xyplot_xgrid(fdui->graph[5].graph,FL_GRID_MAJOR); fdui->graph[5].chartid = SCOPEMSG_DATAID_LLR; // tells websrv frontend to use LLR chart for displaying fdui->graph[5].datasetid = 1; // tells websrv frontend to use dataset index 1 in LLR chart // I/Q PDCCH comp fdui->graph[6] = nrUEcommonGraph(uePcchIQ, FL_POINTS_XYPLOT, 500, curY, 300, 100, "PDCCH I/Q of MF Output", FL_CYAN ); fl_get_object_bbox(fdui->graph[5].graph,&x, &y,&w, &h); curY+=h; fdui->graph[6].chartid = SCOPEMSG_DATAID_IQ; // tells websrv frontend to use constellation chart for displaying fdui->graph[6].datasetid = 1; // tells websrv frontend which dataset to use in the window // LLR of PDSCH fdui->graph[7] = nrUEcommonGraph(uePdschLLR, FL_POINTS_XYPLOT, 0, curY, 500, 200, "PDSCH Log-Likelihood Ratios (LLR, mag)", FL_YELLOW ); fl_set_xyplot_xgrid(fdui->graph[7].graph,FL_GRID_MAJOR); fdui->graph[7].chartid = SCOPEMSG_DATAID_LLR; // tells websrv frontend to use LLR chart for displaying fdui->graph[7].datasetid = 2; // tells websrv frontend to use dataset index 2 in LLR chart // I/Q PDSCH comp fdui->graph[8] = nrUEcommonGraph(uePdschIQ, FL_POINTS_XYPLOT, 500, curY, 300, 200, "PDSCH I/Q of MF Output", FL_YELLOW ); fl_get_object_bbox(fdui->graph[8].graph,&x, &y,&w, &h); curY+=h; fdui->graph[8].chartid = SCOPEMSG_DATAID_IQ; // tells websrv frontend to use constellation chart for displaying fdui->graph[8].datasetid = 2; // tells websrv frontend which dataset to use in the window // Throughput on PDSCH fdui->graph[9] = nrUEcommonGraph(uePdschThroughput, FL_NORMAL_XYPLOT, 0, curY, 500, 100, "PDSCH Throughput [frame]/[kbit/s]", FL_WHITE ); fdui->graph[10].graph=NULL; // Generic UE Button #if 0 fdui->button_0 = fl_add_button( FL_PUSH_BUTTON, 540, 720, 240, 40, "" ); fl_set_object_lalign(fdui->button_0, FL_ALIGN_CENTER ); //openair_daq_vars.use_ia_receiver = 0; fl_set_button(fdui->button_0,0); fl_set_object_label(fdui->button_0, "IA Receiver OFF"); fl_set_object_color(fdui->button_0, FL_RED, FL_RED); fl_set_object_callback(fdui->button_0, ia_receiver_on_off, 0 ); fl_hide_object(fdui->button_0); #endif fl_end_form( ); if (fdui->phy_scope) fdui->phy_scope->fdui = fdui; char buf[100]; sprintf(buf,"NR DL SCOPE UE %d", ID); fl_show_form (fdui->phy_scope, FL_PLACE_HOTSPOT, FL_FULLBORDER, buf); return fdui; } STATICFORXSCOPE void phy_scope_nrUE(scopeGraphData_t **UEliveData, OAI_phy_scope_t *form, PHY_VARS_NR_UE *phy_vars_ue, int eNB_id, int UE_id) { static OAI_phy_scope_t *remeberForm=NULL; if (form==NULL) form=remeberForm; else remeberForm=form; if (form==NULL) return; int i=0; while (form->graph[i].graph) { if (form->graph[i].enabled) form->graph[i].nrUEfunct(UEliveData, form->graph + i, phy_vars_ue, eNB_id, UE_id); i++; } //fl_check_forms(); } #ifndef WEBSRVSCOPE static void *nrUEscopeThread(void *arg) { PHY_VARS_NR_UE *ue=(PHY_VARS_NR_UE *)arg; size_t stksize; pthread_attr_t atr= {0}; pthread_attr_getstacksize(&atr, &stksize); pthread_attr_setstacksize(&atr,32*1024*1024 ); int fl_argc=1; char *name="5G-UE-scope"; fl_initialize (&fl_argc, &name, NULL, 0, 0); OAI_phy_scope_t *form_nrue=create_phy_scope_nrue(0); while (!oai_exit) { fl_freeze_form(form_nrue->phy_scope); phy_scope_nrUE((( scopeData_t *)ue->scopeData)->liveData, form_nrue, ue, 0,0); fl_unfreeze_form(form_nrue->phy_scope); fl_redraw_form(form_nrue->phy_scope); usleep(99*1000); } pthread_exit((void *)arg); } #endif STATICFORXSCOPE void nrUEinitScope(PHY_VARS_NR_UE *ue) { AssertFatal(ue->scopeData = calloc_or_fail(sizeof(scopeData_t), 1), ""); scopeData_t *scope=(scopeData_t *) ue->scopeData; scope->copyData = copyData; #ifndef WEBSRVSCOPE pthread_t forms_thread; threadCreate(&forms_thread, nrUEscopeThread, ue, "scope", -1, OAI_PRIORITY_RT_LOW); copyDataMutexInit(scope); #endif } void nrscope_autoinit(void *dataptr) { AssertFatal((IS_SOFTMODEM_GNB || IS_SOFTMODEM_5GUE), "Scope cannot find NRUE or GNB context"); if (IS_SOFTMODEM_GNB) gNBinitScope(dataptr); else nrUEinitScope(dataptr); } // Kept to put back the functionality soon #if 0 //FD_stats_form *form_stats=NULL,*form_stats_l2=NULL; //char title[255]; //static pthread_t forms_thread; //xforms static void reset_stats_gNB(FL_OBJECT *button, long arg) { int i,k; //PHY_VARS_gNB *phy_vars_gNB = RC.gNB[0][0]; for (i=0; idlsch[i][0]->Mlimit; j++) { phy_vars_gNB->UE_stats[i].dlsch_NAK[k][j]=0; phy_vars_gNB->UE_stats[i].dlsch_ACK[k][j]=0; phy_vars_gNB->UE_stats[i].dlsch_trials[k][j]=0; } phy_vars_gNB->UE_stats[i].dlsch_l2_errors[k]=0; phy_vars_gNB->UE_stats[i].ulsch_errors[k]=0; phy_vars_gNB->UE_stats[i].ulsch_consecutive_errors=0; phy_vars_gNB->UE_stats[i].dlsch_sliding_cnt=0; phy_vars_gNB->UE_stats[i].dlsch_NAK_round0=0; phy_vars_gNB->UE_stats[i].dlsch_mcs_offset=0;*/ } } } static FD_stats_form *create_form_stats_form(int ID) { FL_OBJECT *obj; FD_stats_form *fdui = calloc_or_fail(( sizeof *fdui ),1); fdui->vdata = fdui->cdata = NULL; fdui->ldata = 0; fdui->stats_form = fl_bgn_form( FL_NO_BOX, 1115, 900 ); obj = fl_add_box( FL_UP_BOX, 0, 0, 1115, 900, "" ); //fdui->stats_text = obj = fl_add_text( FL_NORMAL_TEXT, 60, 50, 1000, 810, "test" ); //fl_set_object_lsize( obj, FL_TINY_SIZE ); fdui->stats_text = obj = fl_add_browser( FL_NORMAL_BROWSER, 60, 50, 1000, 810, "test" ); fl_set_browser_fontsize(obj,FL_TINY_SIZE); fdui->stats_button = obj = fl_add_button( FL_PUSH_BUTTON, 60, 10, 130, 30, "Reset Stats" ); fl_set_object_lalign( obj, FL_ALIGN_CENTER ); fl_set_object_color( obj, FL_GREEN, FL_GREEN); fl_end_form( ); fdui->stats_form->fdui = fdui; return fdui; } #endif