Push GPS coordinates to PMP450SMs?

Hello,

Is there a way to push The GPS coordinates from cnMaestro to the PMP450 SMs.  For instance we have all our clients geo-located in Powercode and we can export that data, is there a way to get cnmaestro to push that data out to the SMs so that they show up on the maps??

-Sean

Yes.  You can navigate to individual devices in the left-hand tree while in the configuration context (Configure -> Configure Devices).  There will be fields for entering Latitude and Longitude GPS coordinate.  Once you click save after changing them, they will be automatically pushed to the SM as a configuration update.  This configuration push is applicable for ePMP and PMP devices.

yes i realize i can do this individually but that's not practical for thousands of devices.

is there a way to automate a push of the data via cnmaestro (or SNMP if necessary) to populate the GPS data for thousdands of SMs?

thanks,

sean

This currently cannot be done programmatically.   We will release a RESTful API in Q2 for cnMaestro On-Premises that will initially focus on monitoring, but will expand over time.  This should eventually incorporate the API that is used to set individual device latitude/longitude.

I recommend using a script to push SNMP values in the short term since you have all the values available in an export file.  The relevant OIDs should be available in the PMP450 MIB file.

Relevant OIDs for PMP and ePMP device shown below.

OIDs for PMP devices

OIDs for ePMP devices

2 Likes

Any update to this what is available to be done via cnMaestro?

1 Like

Hi lmcginnis,

Yes, the RESTful API is now available for cnMaestro On-Premises. The user guide can be found on Support Center and full Swagger documentation can be found in cnMaestro itself via Services > API Clients.

You’ll need to create an API client on that page first before using the API. Once a client is added to the table there will be a Try It Out link to the Swagger page that provides full documentation and the ability to test out APIs.

You will be interested in the PUT /devices/{mac} API. It allows setting values for latitude and longitude that will be set within cnMaestro and also pushed to devices as a configuration update.

I get a 400 back when trying to update the latitude and longitude:

DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): 10.10.10.10:443
send: b’PUT /api/v1/devices/0A:00:3E:EE:CD:EA HTTP/1.1\r\nHost: 10.10.10.10\r\nUser-Agent: python-requests/2.24.0\r\nAccept-Encoding: gzip, deflate\r\naccept: application/json\r\nConnection: keep-alive\r\ncontent-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer 27c4f9dd22f9d1fd255a94499df101e8cb4ca6\r\nContent-Length: 37\r\n\r\n’
send: b’latitude=34.92485&longitude=-90.17976
reply: ‘HTTP/1.1 400 Bad Request\r\n’
header: Server: nginx
header: Date: Fri, 20 Nov 2020 21:10:31 GMT
header: Content-Type: application/json; charset=utf-8
header: Content-Length: 77
header: Connection: keep-alive
header: Vary: Accept-Encoding
header: Strict-Transport-Security: max-age=31536000; includeSubDomains
DEBUG:urllib3.connectionpool:https://10.10.10.10:443 “PUT /api/v1/devices/0A:00:3E:EE:CD:EA HTTP/1.1” 400 77
b’{“error”:{“cause”:“InvalidInputError”,“message”:“latitude should be number”}}’

I’m clearly sending a number, a float in fact, not that it should matter because everything I am sending is interpreted first as a string by nginx. Is there some other way to format this? Updating the device description using this method works, but not the coordinates.

Have you tried sending the data as application/json rather than application/x-www-form-urlencoded?

1 Like

I believe what Simon has pointed out is the solution. Try making the request with a JSON payload instead of URL encoding.

Thank you for the feedback. Changing the content-type to application/json is what’s necessary.

send: b’PUT /api/v1/devices/0A:00:3E:EE:CD:EA HTTP/1.1\r\nHost: 10.10.10.10\r\nUser-Agent: python-requests/2.24.0\r\nAccept-Encoding: gzip, deflate\r\naccept: application/json\r\nConnection: keep-alive\r\ncontent-type: application/json\r\nAuthorization: Bearer c5dd5296932325fb99ac\r\nContent-Length: 46\r\n\r\n’
send: b’{“latitude”: 35.92485, “longitude”: -103.17976}’
reply: ‘HTTP/1.1 200 OK\r\n’
header: Server: nginx
header: Date: Tue, 01 Dec 2020 14:13:26 GMT
header: Content-Type: application/json; charset=utf-8
header: Transfer-Encoding: chunked
header: Connection: keep-alive
header: Vary: Accept-Encoding
header: Strict-Transport-Security: max-age=31536000; includeSubDomains
header: Content-Encoding: gzip
DEBUG:urllib3.connectionpool:https://10.10.10.10:443 “PUT /api/v1/devices/0A:00:3E:EE:CD:EA HTTP/1.1” 200 None
b’{“message”:“Success”}’

1 Like

That’s great to hear!

All write APIs are implemented to support a JSON payload. So if an “InvalidInputError” error shows up for any other API, ensure the correct content-type is being used.

Yes, we use python with easysnmp to push lat/long, customer, and the ISP to the SMs (https://easysnmp.readthedocs.io/en/latest/). The code below is updated for Python 3.

Below is my SNMP wrapper library (config has the configurations for snmp strings, etc you can hard code your values there instead if needed)

bbsnmp.py

import time
from easysnmp import Session, EasySNMPConnectionError, EasySNMPTimeoutError, EasySNMPError
from . import config as snmpconfig
from . import ping

#======== SM MAP ========#
# This SNMP MAP info was in a config file until a refactor (along with the update functionality)
# It can probably be refactored into a nice module with a map passing in, but until that is needed, leaving it all in this base file

# SM Record 
CONTACT = 'contact'
LATITUDE = 'latitude'
LONGITUDE = 'longitude'
LOCATION = 'location'
NAME = 'name'

snmp_map = {
	CONTACT: ['sysContact.0',None],
	LATITUDE: ['.1.3.6.1.4.1.161.19.3.3.2.88.0','OCTEETSTR'],
	LONGITUDE: ['.1.3.6.1.4.1.161.19.3.3.2.89.0','OCTETSTR'],
	LOCATION: ['sysLocation.0',None],
	NAME: ['sysName.0',None],
}

snmp_record_map = {
	CONTACT: 'rsp',
	LATITUDE: 'latitude',
	LONGITUDE: 'longitude',
	LOCATION: 'location',
	NAME: 'customer',
}

#======== Base Methods ========#
def get(session, key):
  return session.get(key).value
      
# 'OCTETSTR'
def set(session, key, old, new, stype=None):
  if new != old:
    session.set(key, new, stype)
      
def update(session, field, value):
  key = snmp_map[field][0]
  stype = snmp_map[field][1]
  old = get(session, key)    
  set(session, key, old, value, stype)
  time.sleep(.01)

#======== Cambium AP Methods ========#
# SMs attached to AP 
# IP of AP passed in
def get_ap_sms(ip):
  session = Session(hostname=ip,community=snmpconfig.SNMP_AP_COMMUNITY_RO,version=snmpconfig.SNMP_AP_VERSION)
  return session.walk('.1.3.6.1.4.1.161.19.3.1.4.1.69')

          
#======== Cambium SM Methods ========#
# Auth username field
def get_username(ip):
  session = Session(hostname=ip,community=snmpconfig.SNMP_COMMUNITY_RO,version=snmpconfig.SNMP_VERSION)
  return get(session,'1.3.6.1.4.1.161.19.3.2.7.8.0')

# Utilizes AP SNMP string, so be careful when writing to the AP
def drop_session(ap_ip, sm_mac):
  session = Session(hostname=ap_ip,community=snmpconfig.SNMP_AP_COMMUNITY_RW,version=snmpconfig.SNMP_AP_VERSION)
  session.set('.1.3.6.1.4.1.161.19.3.1.1.87.0', sm_mac, 'OCTETSTR')
  time.sleep(.01)
       
def clear_idle(ap_ip):
  #print ap_ip
  session = Session(hostname=ap_ip,community=snmpconfig.SNMP_AP_COMMUNITY_RW,version=snmpconfig.SNMP_AP_VERSION)
  session.set('.1.3.6.1.4.1.161.19.3.1.1.108.0', 1, 'INTEGER')
  time.sleep(.01)

# 1.  Is ip in excluded list? - Update:  IP coming from cnmaestro should be 
#     valid, so currently there should be no need to check it
# 2.  Is IP reachable?
def send(record):
  # if ip is valid, continue updating SM fields
  if ping.isUp(record.ip):
    try:
      # Setup session
      session = Session(hostname=record.ip,community=snmpconfig.SNMP_COMMUNITY_RW,version=snmpconfig.SNMP_VERSION)
      [update(session,key,record[value]) for key,value in snmp_record_map.items()]
    # This radio could be having connection issues, so do not end program on timeout/connection error
    # https://easysnmp.readthedocs.io/en/latest/exceptions.html
    except EasySNMPTimeoutError:
      pass
    except EasySNMPConnectionError:     
      pass
    except EasySNMPError as e:
      print(str(e))

Then to use the function I call a method similar to this (I have a csv file I read the values from):

from bbnetworking import snmp as bbsnmp

def update_sms():
  sms = []

  with open(MERGE_FILE) as csvfile:
    filereader = csv.reader( csvfile )
    for row in filereader:
      record = SMRecord()
      record.customer = str(row[0]).replace("'",'').strip()
      record.ip = str(row[1]).strip()
      record.rsp = str(row[2]).strip()
      record.location = str(row[3]).strip()
      record.latitude = str(row[4]).strip()
      record.longitude = str(row[5]).strip()

      print(record.customer, record.ip, record.rsp, record.location, record.latitude, record.longitude)

      sms.append(record)

   # This is outside the above while loop of course
   for item in sms:
     print(item.customer, item.ip, item.rsp, item.location, item.latitude, item.longitude)
     bbsnmp.send(item)
2 Likes

Does this cause the device to reboot?

It does not cause the device to reboot.