"Fossies" - the Fresh Open Source Software Archive

Member "netxms-3.8.166/src/server/core/accesspoint.cpp" (23 Feb 2021, 17895 Bytes) of package /linux/misc/netxms-3.8.166.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "accesspoint.cpp" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 3.7.145_vs_3.8.120.

    1 /*
    2 ** NetXMS - Network Management System
    3 ** Copyright (C) 2003-2021 Victor Kirhenshtein
    4 **
    5 ** This program is free software; you can redistribute it and/or modify
    6 ** it under the terms of the GNU General Public License as published by
    7 ** the Free Software Foundation; either version 2 of the License, or
    8 ** (at your option) any later version.
    9 **
   10 ** This program is distributed in the hope that it will be useful,
   11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
   12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13 ** GNU General Public License for more details.
   14 **
   15 ** You should have received a copy of the GNU General Public License
   16 ** along with this program; if not, write to the Free Software
   17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   18 **
   19 ** File: accesspoint.cpp
   20 **/
   21 
   22 #include "nxcore.h"
   23 
   24 /**
   25  * Default constructor
   26  */
   27 AccessPoint::AccessPoint() : super(), m_macAddr(MacAddress::ZERO)
   28 {
   29     m_nodeId = 0;
   30    m_index = 0;
   31     m_vendor = nullptr;
   32     m_model = nullptr;
   33     m_serialNumber = nullptr;
   34     m_radioInterfaces = nullptr;
   35     m_apState = AP_ADOPTED;
   36    m_prevState = m_apState;
   37 }
   38 
   39 /**
   40  * Constructor for creating new access point object
   41  */
   42 AccessPoint::AccessPoint(const TCHAR *name, uint32_t index, const MacAddress& macAddr) : super(name), m_macAddr(macAddr)
   43 {
   44     m_nodeId = 0;
   45    m_index = index;
   46     m_vendor = nullptr;
   47     m_model = nullptr;
   48     m_serialNumber = nullptr;
   49     m_radioInterfaces = nullptr;
   50     m_apState = AP_ADOPTED;
   51    m_prevState = m_apState;
   52     m_isHidden = true;
   53 }
   54 
   55 /**
   56  * Destructor
   57  */
   58 AccessPoint::~AccessPoint()
   59 {
   60     MemFree(m_vendor);
   61     MemFree(m_model);
   62     MemFree(m_serialNumber);
   63     delete m_radioInterfaces;
   64 }
   65 
   66 /**
   67  * Create object from database data
   68  */
   69 bool AccessPoint::loadFromDatabase(DB_HANDLE hdb, UINT32 dwId)
   70 {
   71    m_id = dwId;
   72 
   73    if (!loadCommonProperties(hdb) || !super::loadFromDatabase(hdb, dwId))
   74    {
   75       DbgPrintf(2, _T("Cannot load common properties for access point object %d"), dwId);
   76       return false;
   77    }
   78 
   79     TCHAR query[256];
   80     _sntprintf(query, 256, _T("SELECT mac_address,vendor,model,serial_number,node_id,ap_state,ap_index FROM access_points WHERE id=%d"), (int)m_id);
   81     DB_RESULT hResult = DBSelect(hdb, query);
   82     if (hResult == nullptr)
   83         return false;
   84 
   85     m_macAddr = DBGetFieldMacAddr(hResult, 0, 0);
   86     m_vendor = DBGetField(hResult, 0, 1, nullptr, 0);
   87     m_model = DBGetField(hResult, 0, 2, nullptr, 0);
   88     m_serialNumber = DBGetField(hResult, 0, 3, nullptr, 0);
   89     m_nodeId = DBGetFieldULong(hResult, 0, 4);
   90     m_apState = (AccessPointState)DBGetFieldLong(hResult, 0, 5);
   91    m_prevState = (m_apState != AP_DOWN) ? m_apState : AP_ADOPTED;
   92    m_index = DBGetFieldULong(hResult, 0, 6);
   93     DBFreeResult(hResult);
   94 
   95    // Load DCI and access list
   96    loadACLFromDB(hdb);
   97    loadItemsFromDB(hdb);
   98    for(int i = 0; i < m_dcObjects->size(); i++)
   99       if (!m_dcObjects->get(i)->loadThresholdsFromDB(hdb))
  100          return false;
  101    loadDCIListForCleanup(hdb);
  102 
  103    // Link access point to node
  104     bool success = false;
  105    if (!m_isDeleted)
  106    {
  107       shared_ptr<NetObj> object = FindObjectById(m_nodeId, OBJECT_NODE);
  108       if (object != nullptr)
  109       {
  110          object->addChild(self());
  111          addParent(object);
  112          success = true;
  113       }
  114       else
  115       {
  116          nxlog_write(NXLOG_ERROR, _T("Inconsistent database: access point %s [%u] linked to non-existent node [%u]"), m_name, m_id, m_nodeId);
  117       }
  118    }
  119    else
  120    {
  121       success = true;
  122    }
  123 
  124    return success;
  125 }
  126 
  127 /**
  128  * Save object to database
  129  */
  130 bool AccessPoint::saveToDatabase(DB_HANDLE hdb)
  131 {
  132    bool success = super::saveToDatabase(hdb);
  133 
  134    // Lock object's access
  135    if (success && (m_modified & MODIFY_OTHER))
  136    {
  137       DB_STATEMENT hStmt;
  138       if (IsDatabaseRecordExist(hdb, _T("access_points"), _T("id"), m_id))
  139          hStmt = DBPrepare(hdb, _T("UPDATE access_points SET mac_address=?,vendor=?,model=?,serial_number=?,node_id=?,ap_state=?,ap_index=? WHERE id=?"));
  140       else
  141          hStmt = DBPrepare(hdb, _T("INSERT INTO access_points (mac_address,vendor,model,serial_number,node_id,ap_state,ap_index,id) VALUES (?,?,?,?,?,?,?,?)"));
  142       if (hStmt != nullptr)
  143       {
  144          lockProperties();
  145          DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, m_macAddr);
  146          DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, m_vendor, DB_BIND_STATIC);
  147          DBBind(hStmt, 3, DB_SQLTYPE_VARCHAR, m_model, DB_BIND_STATIC);
  148          DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, m_serialNumber, DB_BIND_STATIC);
  149          DBBind(hStmt, 5, DB_SQLTYPE_INTEGER, m_nodeId);
  150          DBBind(hStmt, 6, DB_SQLTYPE_INTEGER, (INT32)m_apState);
  151          DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, m_index);
  152          DBBind(hStmt, 8, DB_SQLTYPE_INTEGER, m_id);
  153          success = DBExecute(hStmt);
  154          DBFreeStatement(hStmt);
  155          unlockProperties();
  156       }
  157       else
  158       {
  159          success = false;
  160       }
  161    }
  162 
  163    return success;
  164 }
  165 
  166 /**
  167  * Delete object from database
  168  */
  169 bool AccessPoint::deleteFromDatabase(DB_HANDLE hdb)
  170 {
  171    bool success = super::deleteFromDatabase(hdb);
  172    if (success)
  173       success = executeQueryOnObject(hdb, _T("DELETE FROM access_points WHERE id=?"));
  174    return success;
  175 }
  176 
  177 /**
  178  * Create CSCP message with object's data
  179  */
  180 void AccessPoint::fillMessageInternal(NXCPMessage *msg, UINT32 userId)
  181 {
  182    super::fillMessageInternal(msg, userId);
  183    msg->setField(VID_IP_ADDRESS, m_ipAddress);
  184     msg->setField(VID_NODE_ID, m_nodeId);
  185     msg->setField(VID_MAC_ADDR, m_macAddr);
  186     msg->setField(VID_VENDOR, CHECK_NULL_EX(m_vendor));
  187     msg->setField(VID_MODEL, CHECK_NULL_EX(m_model));
  188     msg->setField(VID_SERIAL_NUMBER, CHECK_NULL_EX(m_serialNumber));
  189    msg->setField(VID_STATE, (UINT16)m_apState);
  190    msg->setField(VID_AP_INDEX, m_index);
  191 
  192    if (m_radioInterfaces != nullptr)
  193    {
  194       msg->setField(VID_RADIO_COUNT, (WORD)m_radioInterfaces->size());
  195       UINT32 varId = VID_RADIO_LIST_BASE;
  196       for(int i = 0; i < m_radioInterfaces->size(); i++)
  197       {
  198          RadioInterfaceInfo *rif = m_radioInterfaces->get(i);
  199          msg->setField(varId++, (UINT32)rif->index);
  200          msg->setField(varId++, rif->name);
  201          msg->setField(varId++, rif->macAddr, MAC_ADDR_LENGTH);
  202          msg->setField(varId++, rif->channel);
  203          msg->setField(varId++, (UINT32)rif->powerDBm);
  204          msg->setField(varId++, (UINT32)rif->powerMW);
  205          varId += 4;
  206       }
  207    }
  208    else
  209    {
  210       msg->setField(VID_RADIO_COUNT, (WORD)0);
  211    }
  212 }
  213 
  214 /**
  215  * Modify object from message
  216  */
  217 UINT32 AccessPoint::modifyFromMessageInternal(NXCPMessage *request)
  218 {
  219    if (request->isFieldExist(VID_FLAGS))
  220    {
  221       UINT32 mask = request->isFieldExist(VID_FLAGS_MASK) ? request->getFieldAsUInt32(VID_FLAGS_MASK) : 0xFFFFFFFF;
  222       m_flags &= ~mask;
  223       m_flags |= request->getFieldAsUInt32(VID_FLAGS) & mask;
  224    }
  225    return super::modifyFromMessageInternal(request);
  226 }
  227 
  228 /**
  229  * Attach access point to node
  230  */
  231 void AccessPoint::attachToNode(UINT32 nodeId)
  232 {
  233     if (m_nodeId == nodeId)
  234         return;
  235 
  236     if (m_nodeId != 0)
  237     {
  238         shared_ptr<NetObj> currNode = FindObjectById(m_nodeId, OBJECT_NODE);
  239         if (currNode != nullptr)
  240         {
  241             currNode->deleteChild(*this);
  242             deleteParent(*currNode);
  243         }
  244     }
  245 
  246     shared_ptr<NetObj> newNode = FindObjectById(nodeId, OBJECT_NODE);
  247     if (newNode != nullptr)
  248     {
  249         newNode->addChild(self());
  250         addParent(newNode);
  251     }
  252 
  253     lockProperties();
  254     m_nodeId = nodeId;
  255     setModified(MODIFY_OTHER);
  256     unlockProperties();
  257 }
  258 
  259 /**
  260  * Update radio interfaces information
  261  */
  262 void AccessPoint::updateRadioInterfaces(const ObjectArray<RadioInterfaceInfo> *ri)
  263 {
  264     lockProperties();
  265     if (m_radioInterfaces == nullptr)
  266         m_radioInterfaces = new ObjectArray<RadioInterfaceInfo>(ri->size(), 4, Ownership::True);
  267     m_radioInterfaces->clear();
  268     for(int i = 0; i < ri->size(); i++)
  269     {
  270         RadioInterfaceInfo *info = new RadioInterfaceInfo;
  271         memcpy(info, ri->get(i), sizeof(RadioInterfaceInfo));
  272         m_radioInterfaces->add(info);
  273     }
  274     unlockProperties();
  275 }
  276 
  277 /**
  278  * Check if given radio interface index (radio ID) is on this access point
  279  */
  280 bool AccessPoint::isMyRadio(int rfIndex)
  281 {
  282     bool result = false;
  283     lockProperties();
  284     if (m_radioInterfaces != nullptr)
  285     {
  286         for(int i = 0; i < m_radioInterfaces->size(); i++)
  287         {
  288             if (m_radioInterfaces->get(i)->index == rfIndex)
  289             {
  290                 result = true;
  291                 break;
  292             }
  293         }
  294     }
  295     unlockProperties();
  296     return result;
  297 }
  298 
  299 /**
  300  * Check if given radio MAC address (BSSID) is on this access point
  301  */
  302 bool AccessPoint::isMyRadio(const BYTE *macAddr)
  303 {
  304     bool result = false;
  305     lockProperties();
  306     if (m_radioInterfaces != nullptr)
  307     {
  308         for(int i = 0; i < m_radioInterfaces->size(); i++)
  309         {
  310          if (!memcmp(m_radioInterfaces->get(i)->macAddr, macAddr, MAC_ADDR_LENGTH))
  311             {
  312                 result = true;
  313                 break;
  314             }
  315         }
  316     }
  317     unlockProperties();
  318     return result;
  319 }
  320 
  321 /**
  322  * Get radio name
  323  */
  324 void AccessPoint::getRadioName(int rfIndex, TCHAR *buffer, size_t bufSize)
  325 {
  326     buffer[0] = 0;
  327     lockProperties();
  328     if (m_radioInterfaces != nullptr)
  329     {
  330         for(int i = 0; i < m_radioInterfaces->size(); i++)
  331         {
  332             if (m_radioInterfaces->get(i)->index == rfIndex)
  333             {
  334                 _tcslcpy(buffer, m_radioInterfaces->get(i)->name, bufSize);
  335                 break;
  336             }
  337         }
  338     }
  339     unlockProperties();
  340 }
  341 
  342 /**
  343  * Get access point's parent node
  344  */
  345 shared_ptr<Node> AccessPoint::getParentNode() const
  346 {
  347    return static_pointer_cast<Node>(FindObjectById(m_nodeId, OBJECT_NODE));
  348 }
  349 
  350 /**
  351  * Update access point information
  352  */
  353 void AccessPoint::updateInfo(const TCHAR *vendor, const TCHAR *model, const TCHAR *serialNumber)
  354 {
  355     lockProperties();
  356 
  357     MemFree(m_vendor);
  358     m_vendor = MemCopyString(vendor);
  359 
  360     MemFree(m_model);
  361     m_model = MemCopyString(model);
  362 
  363     MemFree(m_serialNumber);
  364     m_serialNumber = MemCopyString(serialNumber);
  365 
  366     setModified(MODIFY_OTHER);
  367     unlockProperties();
  368 }
  369 
  370 /**
  371  * Update access point state
  372  */
  373 void AccessPoint::updateState(AccessPointState state)
  374 {
  375    if (state == m_apState)
  376       return;
  377 
  378     lockProperties();
  379    if (state == AP_DOWN)
  380       m_prevState = m_apState;
  381    m_apState = state;
  382    if (m_status != STATUS_UNMANAGED)
  383    {
  384       switch(state)
  385       {
  386          case AP_ADOPTED:
  387             m_status = STATUS_NORMAL;
  388             break;
  389          case AP_UNADOPTED:
  390             m_status = STATUS_MAJOR;
  391             break;
  392          case AP_DOWN:
  393             m_status = STATUS_CRITICAL;
  394             break;
  395          default:
  396             m_status = STATUS_UNKNOWN;
  397             break;
  398       }
  399    }
  400    setModified(MODIFY_OTHER);
  401     unlockProperties();
  402 
  403    if ((state == AP_ADOPTED) || (state == AP_UNADOPTED) || (state == AP_DOWN))
  404    {
  405       static const TCHAR *names[] = { _T("id"), _T("name"), _T("macAddr"), _T("ipAddr"), _T("vendor"), _T("model"), _T("serialNumber") };
  406       PostSystemEventWithNames((state == AP_ADOPTED) ? EVENT_AP_ADOPTED : ((state == AP_UNADOPTED) ? EVENT_AP_UNADOPTED : EVENT_AP_DOWN),
  407          m_nodeId, "isHAsss", names,
  408          m_id, m_name, &m_macAddr, &m_ipAddress,
  409          CHECK_NULL_EX(m_vendor), CHECK_NULL_EX(m_model), CHECK_NULL_EX(m_serialNumber));
  410    }
  411 }
  412 
  413 /**
  414  * Do status poll
  415  */
  416 void AccessPoint::statusPollFromController(ClientSession *session, UINT32 rqId, ObjectQueue<Event> *eventQueue,
  417          Node *controller, SNMP_Transport *snmpTransport)
  418 {
  419    m_pollRequestor = session;
  420 
  421    sendPollerMsg(rqId, _T("   Starting status poll on access point %s\r\n"), m_name);
  422    sendPollerMsg(rqId, _T("      Current access point status is %s\r\n"), GetStatusAsText(m_status, true));
  423 
  424    AccessPointState state = controller->getAccessPointState(this, snmpTransport, m_radioInterfaces);
  425    if ((state == AP_UNKNOWN) && m_ipAddress.isValid())
  426    {
  427       DbgPrintf(6, _T("AccessPoint::statusPoll(%s [%d]): unable to get AP state from driver"), m_name, m_id);
  428       sendPollerMsg(rqId, POLLER_WARNING _T("      Unable to get AP state from controller\r\n"));
  429 
  430         uint32_t icmpProxy = 0;
  431 
  432       if (IsZoningEnabled() && (controller->getZoneUIN() != 0))
  433         {
  434             shared_ptr<Zone> zone = FindZoneByUIN(controller->getZoneUIN());
  435             if (zone != nullptr)
  436             {
  437                 icmpProxy = zone->getProxyNodeId(this);
  438             }
  439         }
  440 
  441         if (icmpProxy != 0)
  442         {
  443             sendPollerMsg(rqId, _T("      Starting ICMP ping via proxy\r\n"));
  444             DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): ping via proxy [%u]"), m_id, m_name, icmpProxy);
  445             shared_ptr<Node> proxyNode = static_pointer_cast<Node>(g_idxNodeById.get(icmpProxy));
  446             if ((proxyNode != nullptr) && proxyNode->isNativeAgent() && !proxyNode->isDown())
  447             {
  448                 DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): proxy node found: %s"), m_id, m_name, proxyNode->getName());
  449                 shared_ptr<AgentConnection> conn = proxyNode->createAgentConnection();
  450                 if (conn != nullptr)
  451                 {
  452                     TCHAR parameter[64], buffer[64];
  453 
  454                     _sntprintf(parameter, 64, _T("Icmp.Ping(%s)"), m_ipAddress.toString(buffer));
  455                     if (conn->getParameter(parameter, buffer, 64) == ERR_SUCCESS)
  456                     {
  457                         DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): proxy response: \"%s\""), m_id, m_name, buffer);
  458                         TCHAR *eptr;
  459                         long value = _tcstol(buffer, &eptr, 10);
  460                         if ((*eptr == 0) && (value >= 0))
  461                         {
  462                      if (value < 10000)
  463                      {
  464                         sendPollerMsg(rqId, POLLER_ERROR _T("      responded to ICMP ping\r\n"));
  465                         if (m_apState == AP_DOWN)
  466                            state = m_prevState;  /* FIXME: get actual AP state here */
  467                      }
  468                      else
  469                      {
  470                         sendPollerMsg(rqId, POLLER_ERROR _T("      no response to ICMP ping\r\n"));
  471                         state = AP_DOWN;
  472                      }
  473                         }
  474                     }
  475                     conn->disconnect();
  476                 }
  477                 else
  478                 {
  479                     DbgPrintf(7, _T("AccessPoint::StatusPoll(%d,%s): cannot connect to agent on proxy node"), m_id, m_name);
  480                     sendPollerMsg(rqId, POLLER_ERROR _T("      Unable to establish connection with proxy node\r\n"));
  481                 }
  482             }
  483             else
  484             {
  485                nxlog_debug(7, _T("AccessPoint::StatusPoll(%d,%s): proxy node not available"), m_id, m_name);
  486                 sendPollerMsg(rqId, POLLER_ERROR _T("      ICMP proxy not available\r\n"));
  487             }
  488         }
  489         else    // not using ICMP proxy
  490         {
  491          TCHAR buffer[64];
  492             sendPollerMsg(rqId, _T("      Starting ICMP ping\r\n"));
  493             nxlog_debug(7, _T("AccessPoint::StatusPoll(%d,%s): calling IcmpPing on %s, timeout=%d, size=%d"), m_id, m_name,
  494                      m_ipAddress.toString(buffer), g_icmpPingTimeout, g_icmpPingSize);
  495             UINT32 responseTime;
  496             UINT32 dwPingStatus = IcmpPing(m_ipAddress, 3, g_icmpPingTimeout, &responseTime, g_icmpPingSize, false);
  497             if (dwPingStatus == ICMP_SUCCESS)
  498          {
  499                 sendPollerMsg(rqId, POLLER_ERROR _T("      responded to ICMP ping\r\n"));
  500             if (m_apState == AP_DOWN)
  501                state = m_prevState;  /* FIXME: get actual AP state here */
  502          }
  503          else
  504             {
  505                 sendPollerMsg(rqId, POLLER_ERROR _T("      no response to ICMP ping\r\n"));
  506             state = AP_DOWN;
  507             }
  508             nxlog_debug(7, _T("AccessPoint::StatusPoll(%d,%s): ping result %d, state=%d"), m_id, m_name, dwPingStatus, state);
  509         }
  510    }
  511 
  512    updateState(state);
  513 
  514    sendPollerMsg(rqId, _T("      Access point status after poll is %s\r\n"), GetStatusAsText(m_status, true));
  515     sendPollerMsg(rqId, _T("   Finished status poll on access point %s\r\n"), m_name);
  516 }
  517 
  518 /**
  519  * Perform configuration poll on this data collection target. Default implementation do nothing.
  520  */
  521 void AccessPoint::configurationPoll(PollerInfo *poller, ClientSession *session, UINT32 rqId)
  522 {
  523    lockProperties();
  524    if (m_isDeleteInitiated || IsShutdownInProgress())
  525    {
  526       m_configurationPollState.complete(0);
  527       unlockProperties();
  528       return;
  529    }
  530    unlockProperties();
  531 
  532    poller->setStatus(_T("wait for lock"));
  533    pollerLock(configuration);
  534 
  535    if (IsShutdownInProgress())
  536    {
  537       pollerUnlock();
  538       return;
  539    }
  540 
  541    m_pollRequestor = session;
  542    nxlog_debug(5, _T("Starting configuration poll for access point %s (ID: %d), m_flags: %d"), m_name, m_id, m_flags);
  543 
  544    poller->setStatus(_T("autobind"));
  545    if (ConfigReadBoolean(_T("Objects.AccessPoints.TemplateAutoApply"), false))
  546       applyUserTemplates();
  547    if (ConfigReadBoolean(_T("Objects.AccessPoints.ContainerAutoBind"), false))
  548       updateContainerMembership();
  549 
  550    // Execute hook script
  551    poller->setStatus(_T("hook"));
  552    executeHookScript(_T("ConfigurationPoll"), rqId);
  553 
  554    sendPollerMsg(rqId, _T("Finished configuration poll for access point %s\r\n"), m_name);
  555 
  556    lockProperties();
  557    m_runtimeFlags &= ~ODF_CONFIGURATION_POLL_PENDING;
  558    m_runtimeFlags |= ODF_CONFIGURATION_POLL_PASSED;
  559    unlockProperties();
  560 
  561    pollerUnlock();
  562    nxlog_debug(5, _T("Finished configuration poll for access point %s (ID: %d)"), m_name, m_id);
  563 }
  564 
  565 /**
  566  * Create NXSL object for this object
  567  */
  568 NXSL_Value *AccessPoint::createNXSLObject(NXSL_VM *vm) const
  569 {
  570    return vm->createValue(new NXSL_Object(vm, &g_nxslAccessPointClass, new shared_ptr<AccessPoint>(self())));
  571 }
  572 
  573 /**
  574  * Get access point's zone UIN
  575  */
  576 int32_t AccessPoint::getZoneUIN() const
  577 {
  578    shared_ptr<Node> node = getParentNode();
  579    return (node != nullptr) ? node->getZoneUIN() : 0;
  580 }
  581 
  582 /**
  583  * Serialize object to JSON
  584  */
  585 json_t *AccessPoint::toJson()
  586 {
  587    json_t *root = super::toJson();
  588 
  589    lockProperties();
  590    json_object_set_new(root, "index", json_integer(m_index));
  591    json_object_set_new(root, "ipAddress", m_ipAddress.toJson());
  592    json_object_set_new(root, "nodeId", json_integer(m_nodeId));
  593    TCHAR macAddrText[64];
  594    json_object_set_new(root, "macAddr", json_string_t(m_macAddr.toString(macAddrText)));
  595    json_object_set_new(root, "vendor", json_string_t(m_vendor));
  596    json_object_set_new(root, "model", json_string_t(m_model));
  597    json_object_set_new(root, "serialNumber", json_string_t(m_serialNumber));
  598    json_object_set_new(root, "radioInterfaces", json_object_array(m_radioInterfaces));
  599    json_object_set_new(root, "state", json_integer(m_apState));
  600    json_object_set_new(root, "prevState", json_integer(m_prevState));
  601    unlockProperties();
  602 
  603    return root;
  604 }