/* * 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 * * 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 */ /*! \file common/utils/websrv/websrv_scope.c * \brief: implementation of web API specific for oai softscope * \author Francois TABURET * \date 2022 * \version 0.1 * \company NOKIA BellLabs France * \email: francois.taburet@nokia-bell-labs.com * \note * \warning */ #include #include #include #include #include #include "common/config/config_userapi.h" #include "common/utils/LOG/log.h" #include "common/utils/websrv/websrv.h" #include "executables/softmodem-common.h" #include "common/ran_context.h" #include "common/utils/websrv/websrv_noforms.h" #include "openair1/PHY/TOOLS/phy_scope.h" #include "openair1/PHY/TOOLS/phy_scope_interface.h" #include "common/utils/load_module_shlib.h" extern PHY_VARS_NR_UE ***PHY_vars_UE_g; static websrv_scope_params_t scope_params = {0, 1000, NULL, NULL, 65535}; static websrv_params_t *websrvparams_ptr; void websrv_websocket_send_scopemessage(char msg_type, char *msg_data, struct _websocket_manager *websocket_manager) { websrv_websocket_send_message(WEBSOCK_SRC_SCOPE, msg_type, msg_data, websocket_manager); } void websrv_scope_senddata(int numd, int dsize, websrv_scopedata_msg_t *msg) { msg->header.src = WEBSOCK_SRC_SCOPE; int diff = scope_params.num_datamsg_sent; char strbuff[128]; diff = __atomic_sub_fetch(&diff, scope_params.num_datamsg_ack, __ATOMIC_SEQ_CST); if ((diff < scope_params.num_datamsg_max) || !(scope_params.statusmask & SCOPE_STATUSMASK_DATAACK)) { int st = ulfius_websocket_send_message(websrvparams_ptr->wm, U_WEBSOCKET_OPCODE_BINARY, (numd * dsize) + WEBSOCK_HEADSIZE, (char *)msg); if (st != U_OK) LOG_I(UTIL, "Error sending scope IQs, status %i\n", st); else { scope_params.num_datamsg_sent++; if (diff > 0 && ((scope_params.num_datamsg_sent % 10) == 0)) { snprintf(strbuff, sizeof(strbuff), "%lu|%d", scope_params.num_datamsg_skipped, diff); websrv_websocket_send_scopemessage(SCOPEMSG_TYPE_DATAFLOW, strbuff, websrvparams_ptr->wm); } } } else { LOG_W(UTIL, "[websrv] %i messages with no ACK\n", diff); scope_params.num_datamsg_skipped++; snprintf(strbuff, sizeof(strbuff), "%lu|%d", scope_params.num_datamsg_skipped, diff); websrv_websocket_send_scopemessage(SCOPEMSG_TYPE_DATAFLOW, strbuff, websrvparams_ptr->wm); } }; void websrv_websocket_process_scopemessage(char msg_type, char *msg_data, struct _websocket_manager *websocket_manager) { LOG_I(UTIL, "[websrv] processing scope message type %i\n", msg_type); switch (msg_type) { case SCOPEMSG_TYPE_DATAACK: scope_params.num_datamsg_ack++; break; default: LOG_W(UTIL, "[websrv] Unprocessed scope message type: %c /n", msg_type); break; } } int websrv_scope_manager(uint64_t lcount, websrv_params_t *websrvparams) { time_t linuxtime; struct tm loctime; char strtime[64]; if (scope_params.statusmask & SCOPE_STATUSMASK_STARTED) { if (lcount % 10 == 0) { linuxtime = time(NULL); localtime_r(&linuxtime, &loctime); snprintf(strtime, sizeof(strtime), "%d/%d/%d %d:%d:%d", loctime.tm_mday, loctime.tm_mon, loctime.tm_year + 1900, loctime.tm_hour, loctime.tm_min, loctime.tm_sec); websrv_websocket_send_scopemessage(SCOPEMSG_TYPE_TIME, strtime, websrvparams_ptr->wm); } if ((lcount % scope_params.refrate) == 0) { if (IS_SOFTMODEM_GNB && (scope_params.statusmask & SCOPE_STATUSMASK_STARTED)) { phy_scope_gNB(scope_params.scopeform, scope_params.scopedata, scope_params.selectedTarget + 1 /* ue id, +1 as used in loop < limit */); } if (IS_SOFTMODEM_5GUE && (scope_params.statusmask & SCOPE_STATUSMASK_STARTED)) { phy_scope_nrUE(((scopeData_t *)((PHY_VARS_NR_UE *)(scope_params.scopedata))->scopeData)->liveData, scope_params.scopeform, (PHY_VARS_NR_UE *)scope_params.scopedata, scope_params.selectedTarget /* gNB id */, 0); } } } return 0; } /* free scope resources, as websrv scope interface can be stopped and started */ void websrv_scope_stop(void) { IS_SOFTMODEM_DOSCOPE = false; scope_params.statusmask &= ~SCOPE_STATUSMASK_STARTED; OAI_phy_scope_t *sp = (OAI_phy_scope_t *)scope_params.scopeform; if (sp != NULL) for (int i = 0; sp->graph[i].graph != NULL; i++) { websrv_free_xyplot(sp->graph[i].graph); } free(sp); scope_params.scopeform = NULL; } char *websrv_scope_initdata(void) { scope_params.num_datamsg_max = 200; if (IS_SOFTMODEM_GNB) { scopeParms_t p; p.ru = RC.ru[0]; p.gNB = RC.gNB[0]; gNBinitScope(&p); scope_params.scopedata = p.gNB->scopeData; scope_params.scopeform = create_phy_scope_gnb(); scope_params.statusmask |= SCOPE_STATUSMASK_AVAILABLE; return "gNB"; } else if (IS_SOFTMODEM_5GUE) { scope_params.scopedata = PHY_vars_UE_g[0][0]; nrUEinitScope(PHY_vars_UE_g[0][0]); scope_params.scopeform = create_phy_scope_nrue(scope_params.selectedTarget); scope_params.statusmask |= SCOPE_STATUSMASK_AVAILABLE; return "5GUE"; } else { LOG_I(UTIL, "[websrv] SoftScope web interface not implemented for this softmodem\n"); } return "none"; } /* websrv_scope_init */ /* callback to process control commands received from frontend */ int websrv_scope_callback_set_params(const struct _u_request *request, struct _u_response *response, void *user_data) { websrv_params_t *websrvparams = (websrv_params_t *)user_data; websrv_dump_request("scope set params ", request, websrvparams->dbglvl); json_error_t jserr; json_t *jsbody = ulfius_get_json_body_request(request, &jserr); int httpstatus = 404; char errmsg[128]; if (jsbody == NULL) { LOG_W(UTIL, "cannot find json body in %s %s\n", request->http_url, jserr.text); httpstatus = 400; } else { errmsg[0] = 0; websrv_printjson("websrv_scope_callback_set_params: ", jsbody, websrvparams->dbglvl); json_t *J = json_object_get(jsbody, "name"); const char *vname = json_string_value(J); J = json_object_get(jsbody, "value"); const char *vval = json_string_value(J); if (strcmp(vname, "startstop") == 0) { if (strcmp(vval, "start") == 0) { if (scope_params.statusmask & SCOPE_STATUSMASK_AVAILABLE) { scope_params.num_datamsg_sent = 0; scope_params.num_datamsg_ack = 0; scope_params.num_datamsg_skipped = 0; websrv_scope_initdata(); scope_params.statusmask |= SCOPE_STATUSMASK_STARTED; scope_params.selectedTarget = 1; // 1 UE to be received from GUI (for xNB scope's IS_SOFTMODEM_DOSCOPE = true; // to trigger data copy in scope buffers } httpstatus = 200; } else if (strcmp(vval, "stop") == 0) { scope_params.statusmask &= ~SCOPE_STATUSMASK_STARTED; IS_SOFTMODEM_DOSCOPE = false; httpstatus = 200; } else { LOG_W(UTIL, "invalid startstop command value: %s\n", vval); httpstatus = 400; } } else if (strcmp(vname, "refrate") == 0) { scope_params.refrate = (uint32_t)strtol(vval, NULL, 10); httpstatus = 200; } else if (strcmp(vname, "enabled") == 0) { J = json_object_get(jsbody, "graphid"); const int gid = json_integer_value(J); OAI_phy_scope_t *sp = (OAI_phy_scope_t *)scope_params.scopeform; if (sp == NULL) { httpstatus = 500; } else { sp->graph[gid].enabled = (strcmp(vval, "true") == 0) ? true : false; httpstatus = 200; } } else if (strcmp(vname, "xmin") == 0) { scope_params.xmin = strtol(vval, NULL, 10); httpstatus = 200; } else if (strcmp(vname, "xmax") == 0) { scope_params.xmax = strtol(vval, NULL, 10); httpstatus = 200; } else if (strcmp(vname, "ymin") == 0) { scope_params.ymin = strtol(vval, NULL, 10); httpstatus = 200; } else if (strcmp(vname, "ymax") == 0) { scope_params.ymax = strtol(vval, NULL, 10); httpstatus = 200; } else if (strcmp(vname, "llrxmax") == 0) { scope_params.llrxmax = strtol(vval, NULL, 10); httpstatus = 200; } else if (strcmp(vname, "llrxmin") == 0) { scope_params.llrxmin = strtol(vval, NULL, 10); httpstatus = 200; } else if (strcmp(vname, "TargetSelect") == 0) { scope_params.selectedTarget = strtol(vval, NULL, 10); if (IS_SOFTMODEM_GNB && scope_params.selectedTarget > NUMBER_OF_UE_MAX) { snprintf(errmsg, sizeof(errmsg) - 1, "max UE index is %d for this gNB", NUMBER_OF_UE_MAX); httpstatus = 500; scope_params.selectedTarget = 1; } else if (IS_SOFTMODEM_5GUE && scope_params.selectedTarget > 0) { snprintf(errmsg, sizeof(errmsg) - 1, "UE currently supports only one gNB"); httpstatus = 500; scope_params.selectedTarget = 0; } else { httpstatus = 200; } } else if (strcmp(vname, "DataAck") == 0) { if (strcasecmp(vval, "true") == 0) { scope_params.statusmask |= SCOPE_STATUSMASK_DATAACK; } else { scope_params.statusmask &= (~SCOPE_STATUSMASK_DATAACK); } httpstatus = 200; } else if (strcmp(vname, "llrythresh") == 0) { scope_params.llr_ythresh = strtol(vval, NULL, 10); httpstatus = 200; } else { httpstatus = 500; snprintf(errmsg, sizeof(errmsg) - 1, "Unkown scope command: %s\n", vname); } } // jsbody not null if (httpstatus == 200) { ulfius_set_empty_body_response(response, httpstatus); } else { websrv_params_t *websrvparams = (websrv_params_t *)user_data; websrv_string_response(errmsg, response, httpstatus, websrvparams->dbglvl); } return U_CALLBACK_COMPLETE; } int websrv_scope_callback_get_desc(const struct _u_request *request, struct _u_response *response, void *user_data) { websrv_params_t *websrvparams = (websrv_params_t *)user_data; websrv_dump_request("scope get desc ", request, websrvparams->dbglvl); json_t *jgraph = json_array(); char gtype[20]; char stitle[64]; websrv_scope_stop(); // in case it's not the first connection if (IS_SOFTMODEM_DOSCOPE | IS_SOFTMODEM_ENB | IS_SOFTMODEM_4GUE) { strcpy(stitle, "none"); } else { strcpy(stitle, websrv_scope_initdata()); } OAI_phy_scope_t *sp = (OAI_phy_scope_t *)scope_params.scopeform; if (sp != NULL && (scope_params.statusmask & SCOPE_STATUSMASK_AVAILABLE)) for (int i = 0; sp->graph[i].graph != NULL; i++) { json_t *agraph = NULL; switch (sp->graph[i].chartid) { case SCOPEMSG_DATAID_IQ: strcpy(gtype, "IQs"); agraph = json_pack("{s:s,s:s,s:i,s:i,s:i,s:i}", "title", sp->graph[i].graph->label, "type", gtype, "id", sp->graph[i].datasetid, "srvidx", i, "w", sp->graph[i].w, "h", sp->graph[i].h); break; case SCOPEMSG_DATAID_LLR: strcpy(gtype, "LLR"); agraph = json_pack("{s:s,s:s,s:i,s:i,s:i,s:i}", "title", sp->graph[i].graph->label, "type", gtype, "id", sp->graph[i].datasetid, "srvidx", i, "w", sp->graph[i].w, "h", sp->graph[i].h); break; case SCOPEMSG_DATAID_WF: strcpy(gtype, "WF"); agraph = json_pack("{s:s,s:s,s:i,s:i,s:i,s:i}", "title", sp->graph[i].graph->label, "type", gtype, "id", sp->graph[i].datasetid, "srvidx", i, "w", sp->graph[i].w, "h", sp->graph[i].h); break; case SCOPEMSG_DATAID_TRESP: strcpy(gtype, "TRESP"); agraph = json_pack("{s:s,s:s,s:i,s:i,s:i,s:i}", "title", sp->graph[i].graph->label, "type", gtype, "id", sp->graph[i].datasetid, "srvidx", i, "w", sp->graph[i].w, "h", sp->graph[i].h); break; default: break; } if (agraph != NULL) json_array_append_new(jgraph, agraph); } json_t *jbody = json_pack("{s:s,s:o}", "title", stitle, "graphs", jgraph); websrv_jbody(response, jbody, 200, websrvparams->dbglvl); return U_CALLBACK_COMPLETE; } void websrv_init_scope(websrv_params_t *websrvparams) { int (*callback_functions_scope[3])(const struct _u_request *request, struct _u_response *response, void *user_data) = { websrv_callback_okset_softmodem_cmdvar, websrv_scope_callback_set_params, websrv_scope_callback_get_desc}; char *http_methods[3] = {"OPTIONS", "POST", "GET"}; websrvparams_ptr = websrvparams; websrv_add_endpoint(http_methods, 3, "oaisoftmodem", "scopectrl", callback_functions_scope, websrvparams); } websrv_scope_params_t *websrv_scope_getparams(void) { return &scope_params; }