''' Gets the '/status' from the router config store and send it to a test server. ''' import datetime import urllib.request import urllib.parse from csclient import EventingCSClient cp = EventingCSClient('send_to_server') def post_to_server(): try: # The tree item to get from the router config store tree_item = '/status/system/sdk' start_time = datetime.datetime.now() # Get the item from the router config store tree_data = cp.get(tree_item) cp.log("{}: {}".format(tree_item, tree_data)) time_to_get = datetime.datetime.now() - start_time encode_start_time = datetime.datetime.now() # URL encode the tree_data params = urllib.parse.urlencode(tree_data) # UTF-8 encode the URL encoded data params = params.encode('utf-8')
""" This application will communicate with MS Azure IoT Hub using MQTT. It was developed based on the sample from here: https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/iot-hub/iot-hub-mqtt-support.md Refer to section 'Using the MQTT protocol directly'. """ import os import ssl import urllib.parse from csclient import EventingCSClient from paho.mqtt import client as mqtt cp = EventingCSClient('mqtt_azure_tls') # Path to the TLS certificates file. The certificates were copied from the certs.c file # located here: https://github.com/Azure/azure-iot-sdk-c/blob/master/certs/certs.c path_to_root_cert = os.path.join(os.getcwd(), 'certs.cer') # MS Azure IoT Hub name iot_hub_name = '' # Device name in MS Azure IoT Hub device_id = '' # SAS token for the device id. This can be generated using the Device Explorer Tool. # The format of the token should be similar to: # 'SharedAccessSignature sr={your hub name}.azure-devices.net%2Fdevices%2FMyDevice01%2Fapi-version%3D2016-11-14&sig=vSgHBMUG.....Ntg%3d&se=1456481802' sas_token = ''
from csclient import EventingCSClient import time import os cp = EventingCSClient('cpu_usage') def get_usage(): while True: memory = cp.get('/status/system/memory') load_avg = cp.get('/status/system/load_avg') cpu = cp.get('/status/system/cpu') log_data = ( 'CPU Usage: ' + str( round( float(cpu['nice']) + float(cpu['system']) + float(cpu['user']) * float(100))) + '%, ' + ' Mem Available: ' + str( ('{:,.0f}'.format(memory['memavailable'] / float(1 << 20)) + " MB,")) + ' Mem Total: ' + str( ('{:,.0f}'.format(memory['memtotal'] / float(1 << 20)) + " MB" + "\n"))) cp.log(log_data) time.sleep(15) if __name__ == '__main__':
- Connects to MQTT test server ‘test.mosquitto.org’ - Subscribes to topics as defined in settings.py. - Runs a background thread which publishes data to the topics defined in settings.py every 10 secs. - Generates a log when the MQTT server sends the published information for topics subscribed. """ import os import json import time import settings import paho.mqtt.client as mqtt import paho.mqtt.publish as publish from threading import Thread from csclient import EventingCSClient cp = EventingCSClient('mqtt_app') mqtt_client = None # Called when the broker responds to our connection request. def on_connect(client, userdata, flags, rc): cp.log('MQTT Client connection results: {}'.format( mqtt.connack_string(rc))) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. # QOS 0: The broker will deliver the message once, with no confirmation. # QOS 1: The broker will deliver the message at least once, with confirmation required. # QOS 2: The broker will deliver the message exactly once by using a four step handshake. # # A list of tuples (i.e. topic, qos). Both topic and qos must be present in the tuple.
class DataUsageCheck(object): """ Establish global variables. Set rate shaping values (in Kbps) for 70, 80, 90 & 100% rate tiers. e.g. minbwup_70 & minbwdown_70 refers to upload & download at 70% Each of the rate tiers have a default throttling limit set below: 70% - 6000Kbps Tx/Rx 80% - 3000Kbps Tx/Rx 90% - 1500Kbps Tx/Rx 100% - 600Kbps Tx/Rx """ minbwup_70 = 6000 minbwdown_70 = 6000 minbwup_80 = 3000 minbwdown_80 = 3000 minbwup_90 = 1500 minbwdown_90 = 1500 minbwup_100 = 600 minbwdown_100 = 600 STATUS_DEVS_PATH = '/status/wan/devices' STATUS_DATACAP_PATH = '/status/wan/datacap' CFG_RULES2_PATH = '/config/wan/rules2' def __init__(self): self.cp = EventingCSClient(app_name) def find_modems(self): while True: devs = self.cp.get(self.STATUS_DEVS_PATH) modems_list = [x for x in devs if x.startswith('mdm-')] self.cp.log(f'modems_list: {modems_list}') num_modems = len(modems_list) if not num_modems: self.cp.log('No Modems found at all yet') time.sleep(10) continue else: return modems_list def find_modem_profiles(self): wan_ifcs = self.cp.get(self.CFG_RULES2_PATH) modem_profiles_list = [ x['_id_'] for x in wan_ifcs if x['trigger_string'].startswith('type|is|mdm') ] self.cp.log(f'modem_profiles_list: {modem_profiles_list}') return modem_profiles_list def reset_throttle(self, modem_profiles_list, monthlyreset): for mdm in modem_profiles_list: if monthlyreset: self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_egress') self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_ingress') else: if 'bandwidth_egress' in self.cp.get(self.CFG_RULES2_PATH + '/' + mdm): self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_egress') if 'bandwidth_ingress' in self.cp.get(self.CFG_RULES2_PATH + '/' + mdm): self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_ingress') self.cp.put('config/qos/enabled', False) if monthlyreset: self.cp.log( 'Monthly data usage reset - disabling reduced LTE data rate') message = ( f'Monthly data usage reset - disabling reduced LTE data rate ' f'for {self.system_id} - {self.product_name} - Router ID: ' f'{self.router_id}') self.cp.alert(message) def set_throttle(self, modem_profiles_list, minbwup, minbwdown, tierset): for mdm in modem_profiles_list: self.cp.put(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_egress', minbwup) self.cp.put( self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_ingress', minbwdown) self.cp.put('config/qos/enabled', True) self.cp.log('Exceeded monthly data usage threshold - ' + str(tierset) + '% tier - reducing LTE data rate') message = ( f'Exceeded monthly data usage threshold - reducing LTE data rate ' f'for {self.system_id} - {self.product_name} - Router ID: ' f'{self.router_id}') self.cp.alert(message) def run(self): # Get info from router to populate description field in NCM # alert message self.product_name = self.cp.get('/status/product_info/product_name') self.system_id = self.cp.get('/config/system/system_id') self.router_id = self.cp.get('status/ecm/client_id') # Retrieve list of modems and their profiles modems_list = [str(x.split('-')[1]) for x in self.find_modems()] modem_profiles_list = self.find_modem_profiles() # Reset any throttling to account for router reboots. If a # data cap alert is still active during the monthly cycle, the # appropriate rate shaping will be re-applied monthlyreset = False self.reset_throttle(modem_profiles_list, monthlyreset) time.sleep(5) currtierset = 0 while True: if self.cp.get(self.STATUS_DATACAP_PATH + '/completed_alerts/'): alerts = self.cp.get(self.STATUS_DATACAP_PATH + '/completed_alerts/') limitreached = 0 tierset = 0 for indalert in alerts: for modem in modems_list: if (indalert['alerts'] and indalert['rule_id'] == modem + '-monthly'): if 'email_alert' in indalert['alerts']: limitreached += 1 tierset = 100 minbwup = self.minbwup_100 minbwdown = self.minbwdown_100 continue elif 'early_email-90.0' in indalert['alerts']: limitreached += 1 tierset = 90 minbwup = self.minbwup_90 minbwdown = self.minbwdown_90 continue elif 'early_email-80.0' in indalert['alerts']: limitreached += 1 tierset = 80 minbwup = self.minbwup_80 minbwdown = self.minbwdown_80 continue elif 'early_email-70.0' in indalert['alerts']: limitreached += 1 tierset = 70 minbwup = self.minbwup_70 minbwdown = self.minbwdown_70 continue if limitreached > 0 and currtierset != tierset: currtierset = tierset self.set_throttle(modem_profiles_list, minbwup, minbwdown, currtierset) elif limitreached == 0 and currtierset > 0: currtierset = 0 monthlyreset = True self.reset_throttle(modem_profiles_list, monthlyreset) elif currtierset > 0: currtierset = 0 monthlyreset = True self.reset_throttle(modem_profiles_list, monthlyreset) time.sleep(10)
'Authorization': f'Bearer {dispatcher.config["server_token"]}'} else: headers = {'Content-Type': 'application/json'} debug_log(f'HTTP POST - URL: {url}') debug_log(f'HTTP POST - Headers: {headers}') debug_log(f'HTTP POST - Payload: {payload}') req = requests.post(url, headers=headers, json=payload) cp.log(f'HTTP POST Result: {req.status_code} {req.text}') except Exception as e: logstamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') logs.append(f'{logstamp} Exception in Send to Server: {e}') cp.log(f'Exception in Send to Server: {e}') if __name__ == "__main__": cp = EventingCSClient('Mobile Site Survey') cp.log('Starting...') # Wait for WAN connection while not cp.get('status/ecm/state') == 'connected': time.sleep(1) time.sleep(3) dispatcher = Dispatcher() Thread(target=dispatcher.loop, daemon=True).start() application = tornado.web.Application([ (r"/config", ConfigHandler), (r"/submit", SubmitHandler), (r"/test", TestHandler), (r"/(.*)", tornado.web.StaticFileHandler, {"path": os.path.dirname(__file__), "default_filename": "index.html"}),
if result and result.get('status') in ["error", "done"]: break time.sleep(2) try_count += 1 if try_count == 15: pingstats['error'] = "No Results - Execution Timed Out" else: # Parse results text parsedresults = result.get('result').split('\n') i = 0 index = 1 for item in parsedresults: if item[0:3] == "---": index = i + 1 i += 1 pingstats['tx'] = int(parsedresults[index].split(' ')[0]) pingstats['rx'] = int(parsedresults[index].split(' ')[3]) pingstats['loss'] = float( parsedresults[index].split(' ')[6].split('%')[0]) pingstats['min'] = float(parsedresults[index + 1].split(' ')[5].split('/')[0]) pingstats['avg'] = float(parsedresults[index + 1].split(' ')[5].split('/')[1]) pingstats['max'] = float(parsedresults[index + 1].split(' ')[5].split('/')[2]) return pingstats cp = EventingCSClient('ping_sample') cp.log('Starting...') cp.log('Output:\n' + json.dumps(ping('8.8.8.8')))
""" This app will start an FTP server. This is done by using pyftplib and also asynchat.py and asyncore.py. For detail information about pyftplib, see https://pythonhosted.org/pyftpdlib/. """ import os import sys from csclient import EventingCSClient from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyftpdlib.servers import FTPServer cp = EventingCSClient('ftp_server') # This requires a USB compatible storage device plugged into # the router. It will mount to /var/media. if sys.platform == 'linux2': FTP_DIR = 'FTP' else: FTP_DIR = os.getcwd() try: authorizer = DummyAuthorizer() # Define a new user having full r/w permissions and a read-only # anonymous user authorizer.add_user('survey', 'survey', FTP_DIR, perm='elradfmwM') authorizer.add_anonymous(FTP_DIR) # Instantiate FTP handler class
PIP example: pip(3) install --ignore-install --target=<app directory path> <python module> * note: use pip on Windows and pip3 on Linux or OS X """ import os import sys import pkgutil import platform import collections from importlib import util from csclient import EventingCSClient cp = EventingCSClient('python_module_list') def log_module_list(): # name this file (module) this_module_name = os.path.basename(__file__).rsplit('.')[0] # dict for loaders with their modules loaders = collections.OrderedDict() # names of build-in modules for module_name in sys.builtin_module_names: # find an information about a module by name module_info = util.find_spec(module_name)
""" A reference application to access GNSS on the IBR1700. See the readme.txt for more details. """ import time import socket from inetline import ReadLine from csclient import EventingCSClient cp = EventingCSClient('ibr1700_gnss') gnssd_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: cp.log('Starting...') gnss_addr = ("127.0.0.1", 17488) cp.log("Attempting sock.connect({})".format(gnss_addr)) gnssd_sock.connect(gnss_addr) # Turns on ALL messages. Only way to turn off is to close the socket. cp.log("Attempting sock.send(b'ALL\\r\\n')") gnssd_sock.sendall(b'ALL\r\n') # Enable IMU messages cp.log("Attempting sock.send(b'IMU yes\\r\\n')") gnssd_sock.sendall(b'IMU yes\n\r') receive_line = ReadLine()
from csclient import EventingCSClient import socket import struct cp = EventingCSClient('multicast_to_unicast') ''' MODIFY THESE VARIABLES TO MATCH YOUR ENVIRONMENT ''' MCAST_GRP = '224.1.1.1' # Multicast group to listen on MCAST_PORT = 5001 # Multicast port to listen on MCAST_LISTEN_IP = '192.168.0.1' # Cradlepoint interface IP address to listen on UCAST_DST_IP = '192.168.0.125' #Unicast destination to UCAST_DST_PORT = 5002 ''' DO NOT CHANGE ANYTHING BELOW ''' cp.log("APP STARTED") s = socket.socket(type=socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except AttributeError: pass s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 20) s.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) s.bind(('', MCAST_PORT))
class DataUsageCheck(object): """ Establish global variables. Set rate shaping values (in Kbps) """ # Modem Defaults (as of 7.0.40) - Not used when QoS is Disabled maxbwup = 25000 maxbwdown = 25000 minbwup = 512 minbwdown = 512 capreached = 0 STATUS_DEVS_PATH = '/status/wan/devices' STATUS_DATACAP_PATH = '/status/wan/datacap' CFG_RULES2_PATH = '/config/wan/rules2' def __init__(self): self.cp = EventingCSClient(app_name) def find_modems(self): while True: devs = self.cp.get(self.STATUS_DEVS_PATH) modems_list = [x for x in devs if x.startswith('mdm-')] self.cp.log(f'modems_list: {modems_list}') num_modems = len(modems_list) if not num_modems: self.cp.log('No Modems found at all yet') time.sleep(10) continue else: return modems_list def find_modem_profiles(self): wan_ifcs = self.cp.get(self.CFG_RULES2_PATH) modem_profiles_list = [ x['_id_'] for x in wan_ifcs if x['trigger_string'].startswith('type|is|mdm') ] self.cp.log(f'modem_profiles_list: {modem_profiles_list}') return modem_profiles_list def reset_throttle(self, modem_profiles_list, monthlyreset): for mdm in modem_profiles_list: if monthlyreset: self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_egress') self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_ingress') else: if 'bandwidth_egress' in self.cp.get(self.CFG_RULES2_PATH + '/' + mdm): self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_egress') if 'bandwidth_ingress' in self.cp.get(self.CFG_RULES2_PATH + '/' + mdm): self.cp.delete(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_ingress') self.cp.put('config/qos/enabled', False) if monthlyreset: self.cp.log( 'Monthly data usage reset - disabling reduced LTE data rate') message = ( f'Monthly data usage reset - disabling reduced LTE data rate ' f'for {self.system_id} - {self.product_name} - Router ID: ' f'{self.router_id}') self.cp.alert(message) self.capreached = 0 def set_throttle(self, modem_profiles_list): for mdm in modem_profiles_list: self.cp.put(self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_egress', self.minbwup) self.cp.put( self.CFG_RULES2_PATH + '/' + mdm + '/bandwidth_ingress', self.minbwdown) self.cp.put('config/qos/enabled', True) self.cp.log( 'Exceeded monthly data usage threshold - reducing LTE data rate') message = ( f'Exceeded monthly data usage threshold - reducing LTE data rate ' f'for {self.system_id} - {self.product_name} - Router ID: ' f'{self.router_id}') self.cp.alert(message) self.capreached = 1 def run(self): # Get info from router to populate description field in NCM # alert message self.product_name = self.cp.get('/status/product_info/product_name') self.system_id = self.cp.get('/config/system/system_id') self.router_id = self.cp.get('status/ecm/client_id') # Retrieve list of modems and their profiles modems_list = [str(x.split('-')[1]) for x in self.find_modems()] modem_profiles_list = self.find_modem_profiles() # Reset any throttling to account for router reboots. If a # data cap alert is still active during the monthly cycle, the # appropriate rate shaping will be re-applied monthlyreset = False self.reset_throttle(modem_profiles_list, monthlyreset) time.sleep(5) while True: if self.cp.get(self.STATUS_DATACAP_PATH + '/completed_alerts/'): alerts = self.cp.get(self.STATUS_DATACAP_PATH + '/completed_alerts/') limitreached = 0 for modem in modems_list: if [ x['rule_id'] for x in alerts if x['rule_id'] == modem + '-monthly' if 'email_alert' in x['alerts'] ]: limitreached += 1 if limitreached > 0 and self.capreached == 0: self.set_throttle(modem_profiles_list) elif limitreached == 0 and self.capreached == 1: monthlyreset = True self.reset_throttle(modem_profiles_list, monthlyreset) elif self.capreached == 1: monthlyreset = True self.reset_throttle(modem_profiles_list, monthlyreset) time.sleep(10)
""" Use MQTT and subscribe to the IBR1700 OBD-II PIDs Reference: https://www.eclipse.org/paho/clients/python/docs/ This app does the following: - Connects to the internal MQTT Broker - Subscribes to all OBD-II PIDs. - Outputs logs when the PID publish messages are received. """ import settings from csclient import EventingCSClient import paho.mqtt.client as mqtt cp = EventingCSClient('ibr1700_obdii') mqtt_client = None # Topics for all OBD-II PIDs with QOS topics = [(settings.VEHICLE_SPEED, 0), (settings.ENGINE_SPEED, 0), (settings.THROTTLE_POSITION, 0), (settings.ODOMETER, 0), (settings.FUEL_LEVEL, 0), (settings.ENGINE_COOLANT_TEMPERATURE, 0), (settings.IGNITION_STATUS, 0), (settings.MIL_STATUS, 0), (settings.FUEL_RATE, 0), (settings.PTO_STATUS, 0), (settings.SEATBELT_FASTENED, 0), (settings.MISFIRE_MONITOR, 0), (settings.FUEL_SYSTEM_MONITOR, 0), (settings.COMPREHENSIVE_COMPONENT_MONITOR, 0), (settings.CATALYST_MONITOR, 0), (settings.HEATED_CATALYST_MONITOR, 0), (settings.EVAPORATIVE_SYSTEM_MONITOR, 0), (settings.SECONDARY_AIR_SYSTEM_MONITOR, 0),
# hello_world - log "Hello World!" from csclient import EventingCSClient cp = EventingCSClient('hello_world') cp.log('Hello World!')
""" Copyright (c) 2016 CradlePoint, Inc. <www.cradlepoint.com>. All rights reserved. This file contains confidential information of CradlePoint, Inc. and your use of this file is subject to the CradlePoint Software License Agreement distributed with this file. Unauthorized reproduction or distribution of this file is subject to civil and criminal penalties. """ import os import sys from csclient import EventingCSClient def mkroutes(route, directory): location = "%s/%s" % (os.getcwd(), directory) route_map.append((route, location)) cp = EventingCSClient('hspt') # Build route maps for / and /resources route_map = [] sys.path.append('.') mkroutes('/(.*)', '') mkroutes('/resources/(.*)', 'resources/') value = {'action': 'start', 'routes': route_map, 'server': 'hotspotServer'} cp.put('/control/system/httpserver', value)
ip_data = get_ip_config_info(form) ip_config(ip_data) value = '{} - {}'.format(value, ip_data) if value == 'router_data': self.send_response(HTTPStatus.OK) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write( bytes(json.dumps(get_router_data()), 'utf-8')) return # This is here just to echo back to the client what is receive. self.send_response(HTTPStatus.OK) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write( bytes( '<html><body><h1>Server Received: {}</h1></body></html>'. format(value), 'utf-8')) cp = EventingCSClient('simple_custom_dashboard') server_address = ('', 9001) cp.log('Starting Server: {}'.format(server_address)) httpd = HTTPServer(server_address, WebServerRequestHandler) try: httpd.serve_forever() except KeyboardInterrupt: cp.log('Stopping Server, Key Board interrupt')
from csclient import EventingCSClient cp = EventingCSClient('mqtt_app') MQTT_CLIENT_ID = cp.get('config/system/system_id') # MQTT Server settings MQTT_SERVER = 'test.mosquitto.org' MQTT_PORT = 1883 MQTT_USER_NAME = 'anonymous' MQTT_PASSWORD = '******' # MQTT Topics # Topics are named the same as the path to get the data from the # NCOS device. This was done for simplicity. GPS_TOPIC = '/status/gps/lastpos' MODEM_TEMP_TOPIC = '/status/system/modem_temperature' WAN_CONNECTION_STATE_TOPIC = '/status/wan/connection_state'
if line: output += line + '<br>' return output class ShellHandler(tornado.web.RequestHandler): """handle requests for the /shell endpoint.""" def get(self): """return command response.""" response = '' try: cmd = self.get_argument('cmd') response = shell(cmd) or 'No Response' except Exception as e: cp.log(e) self.write(response) if __name__ == "__main__": cp = EventingCSClient('cp_shell') cp.log(f'Starting webserver on port {server_port}...') application = tornado.web.Application([ (r"/shell", ShellHandler), (r"/(.*)", tornado.web.StaticFileHandler, { "path": static_path, "default_filename": "index.html" }), ]) application.listen(server_port) tornado.ioloop.IOLoop.current().start()
# tornado_sample - simple web server using tornado from csclient import EventingCSClient import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello Cradlepoint!") if __name__ == "__main__": cp = EventingCSClient('tornado_sample') cp.log('Starting...') application = tornado.web.Application([ (r"/", MainHandler), ]) application.listen(9001) tornado.ioloop.IOLoop.current().start()
import time from csclient import EventingCSClient cp = EventingCSClient('ports-status') APP_NAME = 'PORTS_STATUS' DEBUG = False MODELS_WITHOUT_WAN = ['CBA', 'W18', 'W200', 'W400', 'L950', 'IBR200', '4250'] if DEBUG: cp.log("DEBUG ENABLED") if DEBUG: cp.log("Getting Model") """Get model number, since some models don't have ethernet WAN""" model = '' model = cp.get('/status/product_info/product_name') if DEBUG: cp.log(model) while True: try: ports_status = "" is_available_modem = 0 is_available_wan = 0 is_available_wwan = 0 is_configured_wwan = 0 wans = cp.get('/status/wan/devices') mdm_present = False for wan in wans: if 'mdm' in wan:
# Log the Get request cp.log(f'Received Get request: {self.path}') # Send response status code self.send_response(200) # Send headers self.send_header('Content-type', 'text/html') self.end_headers() # Send message back to client # Write content as utf-8 data self.wfile.write(bytes(WEB_MESSAGE, "utf8")) return cp = EventingCSClient('simple_web_server') WEB_MESSAGE = "Hello World from Cradlepoint router!" server_address = ('localhost', 9001) cp.log("Starting Server: {}".format(server_address)) cp.log("Web Message is: {}".format(WEB_MESSAGE)) httpd = HTTPServer(server_address, WebServerRequestHandler) try: httpd.serve_forever() except KeyboardInterrupt: cp.log("Stopping Server, Key Board interrupt")
class SIMSpeedTest(object): MIN_DOWNLOAD_SPD = 0.0 # Mbps MIN_UPLOAD_SPD = 0.0 # Mbps SCHEDULE = 0 # Run Boot2 every {SCHEDULE} minutes. 0 = Only run on boot. NUM_ACTIVE_SIMS = 0 # Number of fastest (download) SIMs to keep active. 0 = all; do not disable SIMs ONLY_RUN_ONCE = False # True means do not run if Boot2 has been run on this device before. STATUS_DEVS_PATH = '/status/wan/devices' CFG_RULES2_PATH = '/config/wan/rules2' CTRL_WAN_DEVS_PATH = '/control/wan/devices' API_URL = 'https://www.cradlepointecm.com/api/v2' CONNECTION_STATE_TIMEOUT = 7 * 60 # 7 Min NETPERF_TIMEOUT = 5 * 60 # 5 Min sims = {} def __init__(self): self.client = EventingCSClient('Boot2') def check_if_run_before(self): if self.ONLY_RUN_ONCE: if self.client.get('/config/wan/rules2/0/_id_' ) == '00000000-1234-1234-1234-1234567890ab': self.client.log( 'ERROR - Boot2 has been run before! /config/wan/rules2/0/_id = 00000000-1234-1234-1234-1234567890ab' ) raise RunBefore( 'ERROR - Boot2 has been run before! /config/wan/rules2/0/_id = 00000000-1234-1234-1234-1234567890ab' ) return False def wait_for_ncm_sync(self): # WAN connection_state if self.client.get('status/wan/connection_state') != 'connected': self.client.log('Waiting until WAN is connected...') timeout_count = 500 while self.client.get('/status/wan/connection_state') != 'connected': timeout_count -= 1 if not timeout_count: raise Timeout('WAN not connecting') time.sleep(2) # ECM State if self.client.get('status/ecm/state') != 'connected': self.client.log('Waiting until NCM is connected...') self.client.put('/control/ecm', {'start': True}) timeout_count = 500 while self.client.get('/status/ecm/state') != 'connected': timeout_count -= 1 if not timeout_count: raise Timeout('NCM not connecting') time.sleep(2) # ECM Sync if self.client.get('status/ecm/sync') != 'ready': self.client.log('Waiting until NCM is synced...') self.client.put('/control/ecm', {'start': True}) timeout_count = 500 while self.client.get('/status/ecm/sync') != 'ready': self.client.put('/control/ecm', {'start': True}) timeout_count -= 1 if not timeout_count: raise Timeout('NCM not connecting') time.sleep(2) return def NCM_suspend(self): self.client.log('Stopping NCM') timeout_count = 500 while not 'ready' == self.client.get('/status/ecm/sync'): timeout_count -= 1 if not timeout_count: raise Timeout('NCM sync not ready') time.sleep(2) self.client.put('/control/ecm', {'stop': True}) timeout_count = 500 while not 'stopped' == self.client.get('/status/ecm/state'): timeout_count -= 1 if not timeout_count: raise Timeout('NCM not stopping') time.sleep(2) def find_sims(self): while True: sims = {} wan_devs = self.client.get(self.STATUS_DEVS_PATH) or {} for uid, status in wan_devs.items(): if uid.startswith('mdm-'): error_text = status.get('status', {}).get('error_text', '') if error_text: if 'NOSIM' in error_text: continue sims[uid] = status num_sims = len(sims) if not num_sims: self.client.log('No SIMs found at all yet') time.sleep(10) continue if num_sims < 2: self.client.log('Only 1 SIM found!') raise OneModem('Only 1 SIM found!') else: break self.client.log(f'Found SIMs: {sims.keys()}') self.sims = sims return True def modem_state(self, sim, state): # Blocking call that will wait until a given state is shown as the modem's status timeout_counter = 0 sleep_seconds = 0 conn_path = '%s/%s/status/connection_state' % (self.STATUS_DEVS_PATH, sim) self.client.log(f'Connecting {self.port_sim(sim)}') while True: sleep_seconds += 5 conn_state = self.client.get(conn_path) self.client.log( f'Waiting for {self.port_sim(sim)} to connect. Current State={conn_state}' ) if conn_state == state: break if timeout_counter > self.CONNECTION_STATE_TIMEOUT: self.client.log(f'Timeout waiting on {self.port_sim(sim)}') raise Timeout(conn_path) time.sleep(min(sleep_seconds, 45)) timeout_counter += sleep_seconds self.client.log(f'{self.port_sim(sim)} connected.') return True def iface(self, sim): iface = self.client.get('%s/%s/info/iface' % (self.STATUS_DEVS_PATH, sim)) return iface def port_sim(self, sim): return f'{self.sims[sim]["info"]["port"]} {self.sims[sim]["info"]["sim"]}' def run_speedtest(self, speedtest): self.client.put('/state/system/netperf', {"run_count": 0}) res = self.client.put("/control/netperf", speedtest) self.client.log(f'Starting Speedtest... {res}') timeout_counter = 0 # wait for results delay = speedtest['input']['options']['limit']['time'] + 8 status_path = "/control/netperf/output/status" while True: time.sleep(delay) status = self.client.get(status_path) if status == 'complete': break if timeout_counter > self.NETPERF_TIMEOUT: self.client.log( f"Timeout waiting on speedtest for {speedtest['input']['options']['ifc_wan']}" ) raise Timeout(status_path) timeout_counter += delay if status != 'complete': self.client.log(f"ERROR: status=%s expected 'complete' {status}") return None # now get the result results_path = self.client.get("/control/netperf/output/results_path") results = None while not results: results = self.client.get(results_path) time.sleep(2) self.client.log('Speedtest Complete.') return results def do_speedtest(self, sim): default_speedtest['input']['options']['ifc_wan'] = self.iface(sim) default_speedtest['input']['options']['send'] = False default_speedtest['input']['options']['recv'] = True tcp_down = self.run_speedtest(default_speedtest).get('tcp_down') default_speedtest['input']['options']['send'] = True default_speedtest['input']['options']['recv'] = False tcp_up = self.run_speedtest(default_speedtest).get('tcp_up') if not tcp_up: self.client.log('do_speedtest tcp_up results missing!') default_speedtest['input']['options']['send'] = True default_speedtest['input']['options']['recv'] = False results = self.run_speedtest(default_speedtest) tcp_up = results.get('tcp_up') or None if not tcp_down: self.client.log('do_speedtest tcp_down results missing!') default_speedtest['input']['options']['send'] = False default_speedtest['input']['options']['recv'] = True results = self.run_speedtest(default_speedtest) tcp_down = results.get('tcp_down') or None down = float(tcp_down.get('THROUGHPUT', 0.0)) if tcp_down else 0.0 up = float(tcp_up.get('THROUGHPUT', 0.0)) if tcp_up else 0.0 return down, up def test_sim(self, device): try: if self.modem_state(device, 'connected'): # Get diagnostics and log it diagnostics = self.client.get( f'{self.STATUS_DEVS_PATH}/{device}/diagnostics') self.sims[device]['diagnostics'] = diagnostics self.client.log( f'Modem Diagnostics: {self.port_sim(device)} RSRP:{diagnostics.get("RSRP")}' ) # Do speedtest and log results self.sims[device]['download'], self.sims[device][ 'upload'] = self.do_speedtest(self.sims[device]) self.client.log( f'Speedtest Results: {self.port_sim(device)} TCP Download: ' f'{self.sims[device]["download"]}Mbps TCP Upload: {self.sims[device]["upload"]}Mbps' ) # Verify minimum speeds if self.sims[device].get( 'download', 0.0) > self.MIN_DOWNLOAD_SPD and self.sims[device].get( 'upload', 0.0) > self.MIN_UPLOAD_SPD: return True else: # Did not meet minimums self.client.log( f'{self.port_sim(device)} Failed to meet minimums! MIN_DOWNLOAD_SPD: {self.MIN_DOWNLOAD_SPD} MIN_UPLOAD_SPD: {self.MIN_UPLOAD_SPD}' ) return False except Timeout: message = f'Timed out running speedtest on {self.port_sim(device)}' self.client.log(message) self.client.alert(message) self.sims[device]['download'] = self.sims[device]['upload'] = 0.0 return False def create_message(self, uid, *args): message = '' for arg in args: if arg == 'download': message = "DL:{:.2f}Mbps".format( self.sims[uid]['download']) if not message else ' '.join([ message, "DL:{:.2f}Mbps".format( self.sims[uid]['download']) ]) elif arg == 'upload': message = "UL:{:.2f}Mbps".format( self.sims[uid]['upload']) if not message else ' '.join([ message, "UL:{:.2f}Mbps".format( self.sims[uid]['upload']) ]) elif arg in ['PRD', 'HOMECARRID', 'RFBAND']: # Do not include labels for these fields message = "{}".format( self.sims[uid]['diagnostics'][arg] ) if not message else ' '.join( [message, "{}".format(self.sims[uid]['diagnostics'][arg])]) else: # Include field labels (e.g. "RSRP:-82") message = "{}:{}".format( arg, self.sims[uid]['diagnostics'] [arg]) if not message else ' '.join([ message, "{}:{}".format( arg, self.sims[uid]['diagnostics'][arg]) ]) return message def lock_sim(self, sim): rules = [{ "_id_": "00000000-1234-1234-1234-123456789000", "priority": 0, "trigger_name": f"{self.sims[sim]['info']['port']} {self.sims[sim]['info']['sim']}", "trigger_string": f"type|is|mdm%sim|is|{self.sims[sim]['info']['sim']}%port|is|{self.sims[sim]['info']['port']}" }] for i, uid in enumerate(self.sims): if uid != sim: rule = { "_id_": f"0000000{i+1}-1234-1234-1234-123456789000", "priority": -9 + i, "trigger_name": f"{self.sims[sim]['info']['port']} {self.sims[sim]['info']['sim']}", "trigger_string": f"type|is|mdm%sim|is|{self.sims[sim]['info']['sim']}%port|is|{self.sims[sim]['info']['port']}", "disabled": True } rules.append(rule) self.client.put('config/wan/rules2', rules) time.sleep(2) def create_rules(self, sim_list): wan_rules = [{ "_id_": "00000000-1234-1234-1234-1234567890ab", "priority": -10, "trigger_name": "Ethernet", "trigger_string": "type|is|ethernet" }] for i in range(0, len(sim_list)): rule = { "_id_": f"0000000{i+1}-1234-1234-1234-123456789000", "priority": -9 + i, "trigger_name": f"{self.sims[sim_list[i]]['info']['port']} {self.sims[sim_list[i]]['info']['sim']}", "trigger_string": f"type|is|mdm%sim|is|{self.sims[sim_list[i]]['info']['sim']}%port|is|{self.sims[sim_list[i]]['info']['port']}" } if self.NUM_ACTIVE_SIMS and i >= self.NUM_ACTIVE_SIMS: rule['disabled'] = True wan_rules.append(rule) req = self.client.put('config/wan/rules2/', wan_rules) time.sleep(2) if self.client.get('config/wan/rules2/0/_id_' ) == '00000000-1234-1234-1234-1234567890ab': self.client.log(f'Updated WAN rules') else: self.client.log(f'WAN Rules not updated! : {req}') return def run(self): # *** Main Application Starts Here *** self.client.log( f'Boot2 Starting... MIN_DOWNLOAD_SPD:{self.MIN_DOWNLOAD_SPD} MIN_UPLOAD_SPD:{self.MIN_UPLOAD_SPD} ' f'SCHEDULE:{self.SCHEDULE} NUM_ACTIVE_SIMS:{self.NUM_ACTIVE_SIMS} ONLY_RUN_ONCE:{self.ONLY_RUN_ONCE}' ) self.check_if_run_before() self.wait_for_ncm_sync() # Get info from router product_name = self.client.get("/status/product_info/product_name") system_id = self.client.get("/config/system/system_id") router_id = self.client.get('status/ecm/client_id') self.find_sims() # Find active SIM slots # Send startup alert message = f'Boot2 Starting! {system_id} - {product_name} - Router ID: {router_id}' self.client.log(f'Sending alert to NCM: {message}') self.client.alert(message) # Pause for 3 seconds to allow NCM Alert to be sent before suspending NCM time.sleep(3) self.NCM_suspend() success = False # Boot2 Success Status - Becomes True when a SIM meets minimum speeds # Test the connected SIM first primary_device = self.client.get('status/wan/primary_device') if 'mdm-' in primary_device: # make sure its a modem if self.test_sim(primary_device): success = True # test remaining SIMs for sim in self.sims: if not self.sims[sim].get('download'): self.lock_sim(sim) if self.test_sim(sim): success = True # Prioritizes SIMs based on download speed sorted_results = sorted(self.sims, key=lambda x: self.sims[x]['download'], reverse=True) # Create WAN rules self.create_rules(sorted_results) time.sleep(3) # Build text for custom1 field results_text = datetime.datetime.now().strftime( '%m/%d/%y %H:%M:%S') # Start with a timestamp if not success: results_text += f' FAILED TO MEET MINIMUMS! MIN_DOWNLOAD_SPD:{self.MIN_DOWNLOAD_SPD} MIN_UPLOAD_SPD:{self.MIN_UPLOAD_SPD}' for uid in sorted_results: # Add the results of each SIM with the fields specified: results_text = ' | '.join([ results_text, self.create_message(uid, 'PRD', 'HOMECARRID', 'RFBAND', 'RSRP', 'download', 'upload') ]) # put messages to NCM custom fields self.wait_for_ncm_sync() if apikeys.get('X-ECM-API-ID') != 'YOUR': self.client.log( f'X-ECM-API-ID: {apikeys["X-ECM-API-ID"]} X-CP-API-ID: {apikeys["X-CP-API-ID"]}' ) req = requests.put(f'{self.API_URL}/routers/{router_id}/', headers=apikeys, json={'custom1': results_text[:255]}) self.client.log(f'NCM PUT Custom1 Result: {req.status_code}') else: self.client.log( 'No NCM API Keys configured, skipping PUT to custom1') # Complete! Send results. message = f"Boot2 Complete! {system_id} Results: {results_text}" self.client.log(message) self.client.alert(message)
- Enable Global Data Usage in Connection manager - Establish data cap alert thresholds on the appropriate cellular interface profile(s) """ app_name = 'throttle_cellular_datacap_rate_tiered' try: from csclient import EventingCSClient import sys import time import json import traceback import logging.handlers except Exception as ex: EventingCSClient().log(f'Import failure: {ex}') EventingCSClient().log(f'Traceback: {traceback.format_exc()}') sys.exit(-1) class DataUsageCheck(object): """ Establish global variables. Set rate shaping values (in Kbps) for 70, 80, 90 & 100% rate tiers. e.g. minbwup_70 & minbwdown_70 refers to upload & download at 70% Each of the rate tiers have a default throttling limit set below: 70% - 6000Kbps Tx/Rx 80% - 3000Kbps Tx/Rx 90% - 1500Kbps Tx/Rx
def __init__(self): self.client = EventingCSClient('Boot2')
def __init__(self): self.cp = EventingCSClient(app_name)
# app_template_csclient - app framework using csclient.py from csclient import EventingCSClient cp = EventingCSClient('app_template_csclient') cp.log('Starting...')
# # Changes and improvements suggested by Steve Majewski. # Modified by Jack to work on the mac. # Modified by Siebren to support docstrings and PASV. # Modified by Phil Schwartz to add storbinary and storlines callbacks. # Modified by Giampaolo Rodola' to add TLS support. # from csclient import EventingCSClient import os import sys import socket import warnings from socket import _GLOBAL_DEFAULT_TIMEOUT cp = EventingCSClient('ftp_client') __all__ = ["FTP"] # Magic number from <socket.h> MSG_OOB = 0x1 # Process data out of band # The standard FTP server control port FTP_PORT = 21 # The sizehint parameter passed to readline() calls MAXLINE = 8192 # Exception raised when an error or invalid response is received class Error(Exception): pass
""" Probe the GPS hardware and log the results. """ from csclient import EventingCSClient cp = EventingCSClient('gps_probe') gps_enabled = cp.get('/config/system/gps/enabled') if not gps_enabled: cp.log('GPS Function is NOT Enabled') else: cp.log('GPS Function is Enabled') gps_data = cp.get('/status/gps') cp.log(gps_data)
""" This app will create a file and then upload it to an FTP server. The file will be deleted when the app is stopped. """ from csclient import EventingCSClient from ftplib import FTP cp = EventingCSClient('ftp_client') TEMP_FILE = 'my_file.txt' cp.log('ftp_client send_ftp_file()...') # Create a temporary file to upload to an FTP server try: f = open(TEMP_FILE, 'w') f.write('This is a test!!') f.write('This is another test!!') f.close() except OSError as msg: cp.log('Failed to open file: {}. error: {}'.format(TEMP_FILE, msg)) try: # Connect to an FTP test server ftp = FTP('speedtest.tele2.net') # Login to the server reply = ftp.login('anonymous', 'anonymous') cp.log('FTP login reply: {}'.format(reply)) # Change to the proper directory for upload ftp.cwd('/upload/')
# shell_sample - execute linux shell_sample command and return output from csclient import EventingCSClient def shell(cmd): """ executes a linux shell command and returns the output :param cmd: string command to be executed. e.g. "ls -al" :return: string output of shell command """ from subprocess import Popen, PIPE output = '' cmd = cmd.split(' ') tail = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) for line in iter(tail.stdout.readline, ''): if tail.returncode: break if line: output += line return output cp = EventingCSClient('shell_sample') cp.log('Starting...') cp.log('Output:\n' + shell('ls -al'))