#!/usr/bin/python3.5
# coding=utf-8
# WLMon Wireless Monitor for LC Register
# 
# commands
# 

# nmcli
# nmcli device wifi list
# nmcli -p -f general,wifi-properties device show wlan0
# nmcli general permissions
# nmcli general logging
# 
# nmcli -f SSID,MODE,CHAN,SIGNAL,SECURITY device wifi list

import sys
import os
import logging
import signal
import fcntl
import errno
import select
import subprocess
import json
import platform
import time
import inspect
from logging.handlers import RotatingFileHandler

# introspection
import gi
# arguments
import argparse

import re
# URL parsing
from urllib.parse import urlparse

# OS and Distro info
name=os.name
system=platform.system()
release=platform.release()
distro=platform.linux_distribution()[0]
version=platform.linux_distribution()[1]

doexit = False

out = []
err = []

outline = ''
errline = ''

cmdpopen=""
cmderror=""

outobj = {}
errobj = {}

logger = None

fd = -1

ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')

# URI Parser

def uriparse(uri):
    logger.debug("URI parsing: " + str(uri))
    try:
        p = URI(uri)
        return p
    except:
        logger.logerrr("Erorr parsing URI " + str(uri))
        return {}

def urlencode(string):
    """ Percent-encodes a string """
    return ''.join(c if re.match(URI.UNRESERVED_CHAR, c) else '%' + c.encode('hex').upper() for c in string)

class URI(object):
    """ Utility class to handle URIs """

    SCHEME_REGEX = "[a-z][a-z0-9+.-]"
    UNRESERVED_CHAR = "[a-zA-Z0-9\-\.\_\~]"
    HOSTNAME_CHAR =  "[a-zA-Z0-9\.\-\_]" # PW allow capital letters and _

    @staticmethod
    def unreserved(string):
        """ Checks that the given string is only made of "unreserved" characters """
        return all(re.match(URI.UNRESERVED_CHAR, c) for c in string)

    @staticmethod
    def valid_hostname(hostname):
        """ Checks if the given hostname is valid """
        return all(re.match(URI.HOSTNAME_CHAR, c) for c in hostname)

    def __init__(self, uri, strict=False):
        """ Parses the given URI """
        uri = uri.strip()

        # URI scheme is case-insensitive
        self.scheme = uri.split(':')[0].lower()

        if not re.match(URI.SCHEME_REGEX, self.scheme):
            raise ValueError("Invalid URI scheme: '{}'".format(self.scheme))

        self.path = uri[len(self.scheme) + 1:]

        # URI fragments
        self.fragment = None
        if '#' in self.path:
            self.path, self.fragment = self.path.split('#')

        # URI fragments
        self.fragment = None
        if '#' in self.path:
            self.path, self.fragment = self.path.split('#')

        # Query parameters (for instance: http://mysite.com/page?key=valueprint("### not unreserved " + self.user_information.replace(':', ''))other_key=value2)
        self.parameters = dict()
        if '?' in self.path:
            separator = '&' if '&' in self.path else ';'
            query = '?'.join(self.path.split('?')[1:])
            query_params = query.split(separator)
            query_params = map(lambda p : p.split('='), query_params)

            if not URI.unreserved(query.replace('=', '').replace(separator, '')):     
                if strict:
                    raise ValueError('Invalid query: {}'.format(query))
                else:
                    query_params = [map(urlencode, couple) for couple in query_params]

            self.parameters = {key : value for key, value in query_params}
            self.path = self.path.split('?')[0]


        # For URIs that have a path starting with '//', we try to fetch additional info:
        self.authority = None
        self.username = ''
        self.password = ''
        if self.path.startswith('//'):
            self.path = self.path.lstrip('//')
            uri_tokens = self.path.split('/')

            self.authority = uri_tokens[0]
            self.hostname = self.authority
            self.path = self.path[len(self.authority):]

            # Fetching authentication data. For instance: "http://login:password@site.com"
            self.authenticated = '@' in self.authority
            if self.authenticated:
                self.user_information, self.hostname = self.authority.split('@')
                
                # Validating userinfo
                if URI.unreserved(self.user_information.replace(':', '')):
                    if strict:
                        raise ValueError("Invalid user information: '{}'".format(self.user_information))
                    else:
                        userinfo_tokens = self.user_information.split(':')
                        self.user_information = ':'.join(map(urlencode, userinfo_tokens))
                        if userinfo_tokens[0]:
                            self.username = userinfo_tokens[0]
                        try:
                            self.password = userinfo_tokens[1]
                        except:
                            pass
                else:
                    pass

            # Fetching port
            self.port = None
            if ':' in self.hostname:
                self.hostname, self.port = self.hostname.split(':')
                try:
                    self.port = int(self.port)
                    if not 0 <= self.port <= 65535:
                        raise ValueError
                except ValueError:
                    raise ValueError("Invalid port: '{}'".format(self.port))

            # Hostnames are case-insensitive
            # PW disable this!
            #self.hostname = self.hostname.lower()

            # Validating the hostname and path
            if not URI.valid_hostname(self.hostname):
                raise ValueError("Invalid hostname: '{}'".format(self.hostname))
            
            if self.path and not URI.unreserved(self.path.replace('/', '')):
                if strict:
                    raise ValueError("Invalid path: '{}'".format(self.path))
                else:
                    path_tokens = self.path.split('/')
                    self.path = '/'.join(map(urlencode, path_tokens))

        elif len(self.path.split('@')) > 2 or not URI.unreserved(self.path.split('@')[-1]):
            raise ValueError("Invalid path: '{}'".format(self.path))

    def remove_fragment(self):
        self.fragment = None

    def remove_query(self):
        self.parameters = dict()

    def remove_port(self):
        self.port = None

    def query(self):
        """ Returns a serialized representation of the query parameters. """
        return '&'.join('{}={}'.format(key, value) for key, value in sorted(self.parameters.iteritems()))

    def summary(self):
        """ Summary of the URI object. Useful for debugging. """
        uri_repr = '{}\n'.format(self)
        uri_repr += '\n'
        uri_repr += "* Scheme name: '{}'\n".format(self.scheme)

        if self.authority:
            uri_repr += "* Authority path: '{}'\n".format(self.authority)

            uri_repr += "  . Hostname: '{}'\n".format(self.hostname)
            if self.authenticated:
                uri_repr += "  . User information = '{}'\n".format(self.user_information)
            if self.port:
                uri_repr += "  . Port = '{}'\n".format(self.port)

        uri_repr += "* Path: '{}'\n".format(self.path)
        if self.parameters:
            uri_repr += "* Query parameters: '{}'\n".format(self.parameters)
        if self.fragment:
            uri_repr += "* Fragment: '{}'\n".format(self.fragment)
        return uri_repr

    def json(self):
        """ JSON serialization of the URI object """
        return json.dumps(self.__dict__, sort_keys=True, indent=2)

    def __str__(self):
        """ Outputs the URI as a normalized string """
        uri = '{}:'.format(self.scheme)
        
        if self.authority:
            uri += '//'
            if self.authenticated:
                uri += self.user_information + '@'
            uri += self.hostname
            if self.port:
                uri += ':{}'.format(self.port)

        uri += self.path
        if self.parameters:
            uri += '?' + self.query()
        if self.fragment:
            uri += '#' + urlencode(self.fragment)
        return uri

    def __eq__(self, uri):
        """ Equivalence between two URIs: the equivalence of their respective normalized string representation """
        return str(self) == str(uri)


def parse_ifconfig(input):
    mo = re.search(r'^(?P<interface>eth\d+|eth\d+:\d+)\s+' +
                        r'Link encap:(?P<link_encap>\S+)\s+' +
                        r'(HWaddr\s+(?P<hardware_address>\S+))?' +
                        r'(\s+inet addr:(?P<ip_address>\S+))?' +
                        r'(\s+Bcast:(?P<broadcast_address>\S+)\s+)?' +
                        r'(Mask:(?P<net_mask>\S+)\s+)?',
                        input, re.MULTILINE )
    if mo:
        info = mo.groupdict('')
        info['running'] = False
        info['up'] = False
        info['multicast'] = False
        info['broadcast'] = False
        if 'RUNNING' in input:
            info['running'] = True
        if 'UP' in input:
            info['up'] = True
        if 'BROADCAST' in input:
            info['broadcast'] = True
        if 'MULTICAST' in input:
            info['multicast'] = True
        return info
    return {}

def run_subprocess(cmd):
    global logger
    logger.info("cmd "+str(cmd)+"returned ")
    cmdpopen = str(cmd)
    global out
    global err
    try:
        sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        ret = sub.communicate()
        res = sub.returncode
        out = ret[0]
        err = ret[1]
        logger.info("Ran Command:{}".format(cmd))
        logger.info("stdout: {}".format(out))
        logger.info("stderr: {}".format(err))
    except Exception as e:
        logger.error(str(e))
        cmderror = e
        return [-4, ret]
    return [res,ret]

def run_wpacli(arg):
    global logger
    global outline
    global errline
    cmdln = ["/sbin/wpa_cli"]
    cmdln.extend(arg)
    ret = run_subprocess(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

def run_dhclient(arg):
    global logger
    global outline
    global errline
    cmdln = ["/sbin/dhclient"]
    cmdln.extend(arg)
    ret = run_subprocess(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

### TDB
wpa_cli_status_active="""
wpa_state=ACTIVE
address=c4:17:fe:8a:e9:d9
uuid=b276001f-fc4f-502c-bada-d66ae6ca01b3
"""

wpa_cli_status_inactive="""
wpa_state=INACTIVE
address=c4:17:fe:8a:e9:d9
uuid=b276001f-fc4f-502c-bada-d66ae6ca01b3
"""

wpa_cli_status_down="""
Failed to connect to non-global ctrl_ifname: wlp12s0  error: No such file or directory
"""

wpa_cli_status_disabled="""
wpa_state=INTERFACE_DISABLED
address=c4:17:fe:8a:e9:d9
uuid=b276001f-fc4f-502c-bada-d66ae6ca01b3
"""

# Scan
wpa_cli_scan="""
OK
"""
# wpa_state 
# WPA_DISCONNECTED          DISCONNECTED            UP
# WPA_INTERFACE_DISABLED    INTERFACE_DISABLED      DOWN
# WPA_INACTIVE              INACTIVE                UP
# WPA_SCANNING              SCANNING                UP
# WPA_AUTHENTICATING        AUTHENTICATING          UP
# WPA_ASSOCIATING           ASSOCIATING             UP
# WPA_ASSOCIATED            ASSOCIATED              UP
# WPA_4WAY_HANDSHAKE        4WAY_HANDSHAKE          UP
# WPA_GROUP_HANDSHAKE       GROUP_HANDSHAKE         UP
# WPA_COMPLETED             COMPLETED               CONNECTED

def parse_wpacli_status(input):
    global logger
    status={}
    logger.info(str(input))
    lines = input.strip()
    for line in lines.split("\n"):
        if '=' not in line:
            continue
        else:
            param, value = line.split('=',1)
            #status.append({param:value})
            status[param]=value
    statusobj = {"status":status}
    return statusobj

def parse_wpacli_scan(input):
    global logger
    scan="Not OK"
    logger.info(str(input))
    lines = input.strip()
    for line in lines.split("\n"):
        if 'OK' not in line:
            continue
        else:
            scan="OK"
    scanobj = {"scan":scan}
    return scanobj

def parse_wpacli_scan_results(scan):
    global logger
    connnections=[]
    consdict={}
    lines = scan.strip()
    if "bssid" in lines and len (lines.split("\n") ) > 1:
        for line in lines.split("\n")[1:]:
            logger.info(line)
            b, fr, s, f = line.split("\t")[:4]
            logger.info(b+fr+s+f)
            ss = " ".join(line.split()[4:])
            if ss in consdict:
                pass
            else:
                consdict[ss] = { "flags":f, "freq":fr, "sig":s, "ssid":ss, "bssid":b}
                connnections.append({ "flags":f, "freq":fr, "sig":s, "ssid":ss, "bssid":b})
    scan_resultsobj={"scan_results":connnections}  
    return scan_resultsobj

# systemctl
def run_systemctl(arg):
    global logger
    global outline
    global errline
    cmdln = ["/bin/systemctl"]
    cmdln.extend(arg)
    ret = run_subprocess(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

def parse_systemctl(input):
    global logger
    systemctl="OK"
    logger.info(str(input))
    lines = input.strip()
    for line in lines.split("\n"):
        if 'OK' not in line:
            continue
        else:
            pass
    systemctlobj = {"systemctl":systemctl}
    return systemctlobj

# ifconfig
def run_ifconfig(arg):
    global logger
    global outline
    global errline
    cmdln = ["/sbin/ifconfig"]
    cmdln.extend(arg)
    ret = run_subprocess(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res


def run_nmcli(arg):
    global logger
    global outline
    global errline
    cmdln = ["/usr/bin/nmcli"]
    cmdln.extend(arg)
    ret = run_subprocess(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

# 
def parse_nmcli_status(input):
    nmcli = {"nmcli": input}
    return nmcli

def parse_nmcli_connect(input):
    res = 0
    if 'Error' in input:
        nmcli = {"errline": input}
        e = re.search('\((\d+)\)', input)
        try:
            res = int(e.group(1))
        except:
            print('e : '+e.group(1))
            res = 1
    else:
        nmcli = {"nmcli": input}
    return res, nmcli

# handle Wireless commands
if distro  == "Ubuntu":
# on Ubuntu
    wifidev='wlp2s0'
else:
# on Register
    wifidev='wlan0'

bldev='hci0'

def wl_get_dev_from_arg(arg):
    if arg == 'all':
        return [wifidev, bldev]

    if arg == 'wifi':
        return [wifidev]

    if arg == 'bl':
        return [bldev]

    # throw exception ? 
    return []

def wifi_get_dev_from_arg(arg):
    if arg == 'all':
        return wifidev
    if arg == '0':
        return wifidev
    return ""

def bl_get_dev_from_arg(arg):
    if arg == 'all':
        return bldev
    if arg == '0':
        return bldev
    return ""

# start both wifi and bl
def wl_start(arg):
    res = 0
    reswifi = 0
    resbl = 0
    devices = wl_get_dev_from_arg(arg)
    for dev in devices:
        # wifi start supplicant
        if dev == wifidev:
            reswifi = wifi_start(dev)
        # bluetooth?
        if dev == bldev :
            resbl = bl_start(dev)
    res = reswifi
    if resbl < reswifi:
        res = reswifi
    return res


# shutdown both wifi and bl
def wl_shutdown(arg):
    global logger
    res = 0
    reswifi = 0
    resbl = 0
    devices = wl_get_dev_from_arg(arg)
    for dev in devices:
        # wifi start supplicant
        if dev == wifidev:
            reswifi = wifi_shutdown(dev)
        # bluetooth?
        if dev == bldev :
            resbl = bl_shutdown(dev)
    res = reswifi
    if resbl < reswifi:
        res = reswifi
    return res

def wl_configure(arg):
    return -1

# nmcli -f STATE,CONNECTIVITY,WIFI-HW,WIFI g status
def wl_status(arg):
    global logger
    global outline
    global errline
    args = ["-f", "STATE,CONNECTIVITY,WIFI-HW,WIFI", "g", "status"]
    #args = ["general", "status"]
    res = run_nmcli(args)
    if res == 0:
        pr = parse_nmcli_status(out.decode())
        outobj = pr
        errline = err.decode()
        return 0
    errline = err.decode()
    return res


def wl_status_ifconfig(arg):
    global logger
    global outline
    global errline
    res = run_ifconfig([str(arg)])
    if res == 0:
        pr = parse_ifconfig(out.decode())
        outline =""
        outline += str(pr['hardware_address']) + " "
        outline += str(pr['interface']) + " "
        outline += str(pr['up']) + " "
        errline = err.decode()
        return 0
    errline = err.decode()
    return res


def wl_diag(arg):
    global logger
    return -1

# perform any cleanup need before exiting 
def wl_exit(arg):
    global logger
    global doexit
    global outline
    global errline
    outline =" Exiting "
    errline = "" 
    doexit = True
    return 0

def wl_info(arg):
    global logger
    global outobj
    global errobj
    outobj ={"info": { "name":name, "system":system, "release":release, "distro":distro, "version":version }}
    return 0

def wl_log(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    # Logging level
    logLevel = str(arg)
    log_level = logging.CRITICAL

    if logLevel.upper() == "DEBUG":
        log_level = logging.DEBUG
    elif logLevel.upper() == "INFO":
        log_level = logging.INFO
    elif logLevel.upper() == "WARNING":
        log_level = logging.WARNING
    elif logLevel.upper() == "ERROR":
        log_level = logging.ERROR
    elif logLevel.upper() == "CRITICAL":
        log_level = logging.CRITICAL
    else:
        log_level = logging.WARNING

    logger.setLevel(log_level)
    outline='Level ' + str(log_level) + ' ' + logLevel.upper()
    outobj= {"log": outline}
    return 0


def handle_wl(args):
    global logger
    try:
        if args.cmd == "start":
            return wl_start(args.arg)
        
        if args.cmd == "shutdown":
            return wl_shutdown(args.arg)

        if args.cmd == "configure":
            return wl_configure(args.arg)

        if args.cmd == "status":
            return wl_status(args.arg)

        if args.cmd == "diag":
            return wl_diag(args.arg)

        if args.cmd == "exit":
            return wl_exit(args.arg)
        
        if args.cmd == "info":
            return wl_info(args.arg)

        if args.cmd == "log":
            return wl_log(args.arg)
        

    except:
        return -2
    return -3

# handle WiFi commands

# WiFi start
# /sbin/ifconfig wlan0 up
# /bin/systemctl start wpa_supplicant
def wifi_start(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    # ifconfig device UP
    device = wifi_get_dev_from_arg(arg)
    res = run_ifconfig([device, 'up'])
    if res < 0:
        return res
    # wifi start supplicant
    args = ["start", "wpa_supplicant"]
    res = run_systemctl(args)
    if res == 0:
        pr = parse_systemctl(out.decode())
        outobj = pr
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

# WiFi shutdown
# /sbin/ifconfig wlan0 down
# /bin/systemctl stop wpa_supplicant
def wifi_shutdown(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    # ifconfig device DOWN
    device = wifi_get_dev_from_arg(arg)
    res = run_ifconfig([device, 'down'])
    if res < 0:
        return res
    # wifi stop supplicant
    args = ["stop", "wpa_supplicant"]
    res = run_systemctl(args)
    if res == 0:
        pr = parse_systemctl(out.decode())
        outobj = pr
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

def wifi_configure(arg):
    return -1

def wifi_diag(arg):
    return -1

# WiFi configure
# TBI
# WiFi status
def wifi_status(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    # wpa_cli -i wlan0 scan
    device = wifi_get_dev_from_arg(arg)
    args = ["-i", device, "status"]
    res = run_wpacli(args)
    if res == 0:
        pr = parse_wpacli_status(outline)
        outobj = pr
        errline = err.decode()
        return 0
    errline = err.decode()
    return res


def wifi_status_iwconfig(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    device = wifi_get_dev_from_arg(arg)
    ret = run_subprocess(["/sbin/iwconfig", device])
    res = ret[0]
    if res == 0:
        return 0
    return res


# WiFi state
# WiFi diag

# WiFi scan_on scan
def wifi_scan_on(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    # wpa_cli -i wlan0 scan
    device = wifi_get_dev_from_arg(arg)
    args = ["-i", device, "scan"]
    res = run_wpacli(args)
    if res == 0:
        scanobj = parse_wpacli_scan(outline)
        outobj = scanobj
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

# WiFi scan_off
def wifi_scan_off(arg):
    global logger
    global outline
    global errline
    global outobjwi
    global errobj
    # wpa_cli -i wlan0 scan
    device = wifi_get_dev_from_arg(arg)
    #args = ["-i", device, "abort_scan"] # TBV abort_scan only in newer wpa_cli?
    args = ["-i", device, "ping"] # TBV use ping just to get the status
    res = run_wpacli(args)
    if res == 0:
        scanobj = parse_wpacli_scan(outline)
        outobj = scanobj
        errline = err.decode()
        return 0
    errline = err.decode()
    return res


# WiFi scan_results
def wifi_scan_results(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj    
    # wpa_cli -i wlan0 scan
    device = wifi_get_dev_from_arg(arg)
    args = ["-i", device, "scan_results"]
    res = run_wpacli(args)
    if res == 0:
        connections = parse_wpacli_scan_results(outline)
        outobj = connections
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

# WiFi connect
def run_and_check_wpacli(args):
    global logger
    res = run_wpacli(args)
    if  res != 0:
        raise Exception(res)

def run_and_check_dhclient(args):
    global logger
    res = run_dhclient(args)
    if  res != 0:
        raise Exception(res)


def wifi_do_connect(device,user,password,ssid,port,secure):
    global logger
    global outline
    global errline
    global outobj
    global errobj  
    try:
        # wpa_cli -iwlan0 disconnect
        run_and_check_wpacli(["-i", device, "disconnect"])
        # wpa_cli -iwlan0 remove_network all
        run_and_check_wpacli(["-i", device, "remove_network", "all"])
        # wpa_cli -iwlan0 add_network
        run_and_check_wpacli(["-i", device, "add_network"])
    except Exception as e:
        errline = str(e)
        return 155
    
    try:   
        # wpa_cli -iwlan0 set_network 0 mode 0
        run_and_check_wpacli(["-i", device, "set_network", "0", "mode", "0"])
        # wpa_cli -iwlan0 set_network 0 ssid $SSID
        run_and_check_wpacli(["-i", device, "set_network", "0", "ssid", '\"'+str(ssid)+'\"'])    
    except Exception as e:
        errline = str(e)
        return 156
        
    try:
        if secure:
            # wpa_cli -iwlan0 set_network 0 auth_alg OPEN
            run_and_check_wpacli(["-i", device, "set_network", "0", "auth_alg", "OPEN"])
            # wpa_cli -iwlan0 set_network 0 key_mgmt WPA-PSK
            run_and_check_wpacli(["-i", device, "set_network", "0", "key_mgmt", "WPA-PSK"])
            # wpa_cli -iwlan0 set_network 0 proto RSN
            run_and_check_wpacli(["-i", device, "set_network", "0", "proto", "RSN"])
            # wpa_cli -iwlan0 set_network 0 psk $PASSWORD 
            run_and_check_wpacli(["-i", device, "set_network", "0", "psk", '\"'+str(password)+'\"'])
        else:
            # wpa_cli -iwlan0 set_network 0 auth_alg OPEN
            run_and_check_wpacli(["-i", device, "set_network", "0", "auth_alg", "OPEN"])
            # wpa_cli -iwlan0 set_network 0 key_mgmt NONE
            run_and_check_wpacli(["-i", device, "set_network", "0", "key_mgmt", "NONE"])    
    except Exception as e:
        errline = str(e)
        return 157
    
    try:
        # wpa_cli -iwlan0 set_network 0 scan_ssid 1
        run_and_check_wpacli(["-i", device, "set_network", "0", "scan_ssid", "1"])
        # wpa_cli -iwlan0 select_network 0
        run_and_check_wpacli(["-i", device, "select_network", "0"])
        # wpa_cli -iwlan0 enable_network 0
        run_and_check_wpacli(["-i", device, "enable_network", "0"])
        # wpa_cli -iwlan0 reassociate
        run_and_check_wpacli(["-i", device, "reassociate"])
    except Exception as e:
        errline = str(e)
        return 158
    try:
        # dhclient -r device
        run_and_check_dhclient(["-r", device])
        # dhclient device
        run_and_check_dhclient([device])
    except Exception as e:
        errline = str(e)
        return 159
    # wpa_cli -iwlan0 status
    return wifi_status('0')

# URL
# wifi://user:password@ssid:port
# Open Wi-Fi network
# wifi://ssid
# wifi://:@ssid:port
# WPA Personal network with password
# wifi://password@ssid
# wifi://:password@ssid:port
# WPA Enterprise with username
# wifi://user@ssid
# wifi://user:@ssid:port
# WPA Enterprise with username and password
# wifi://user:password@ssid
# wifi://user:password@ssid:port
#
# wifi connect wifi://:Not4IDEX@lcvisitor
#
def wifi_connect_wpa_supplicant(arg):
    global logger
    logger.info("wifi_connect")
    global outline
    global errline
    global outobj
    global errobj
    device = wifi_get_dev_from_arg('0')
    password=''
    user=''
    port=-1
    ssid=''
    secure = False
    url = arg
    logger.info("wifi_connect 0")
    p=uriparse(url)
    logger.info("wifi_connect 1")
    # required
    if p.scheme != 'wifi':
        return -1
    # extract password if any
    if  p.password:
        password = p.password
        secure = True
        logger.info("wifi_connect 2")
    else:
        password =''
    logger.info("wifi_connect 3")
    # extract username if any
    if  p.username:
        username = p.username
        password = username + password 
        secure = True
        logger.info("wifi_connect 4")
    else:
        username =''
    # extract port if any
    logger.info("wifi_connect 5")
    if  p.port:
        port = p.port
        logger.info("wifi_connect 6")
    else:
        port =-1
    # extract SSID if any - required
    logger.info("wifi_connect 7")
    if  p.hostname:
        ssid = p.hostname
        logger.info("wifi_connect 8")
    else:
        ssid =''
        logger.info("wifi_connect -2")
        return -2
    logger.info("wifi_connect 9")
    # execute connect
    res = wifi_do_connect(device,user,password,ssid,port,secure)
    if res == 0:
        outline=""
        errline=""
        outobj={}
        errobj={}
        logger.info("wifi_connect 10")
        return 0
    outline=""
    errline="Error in Wifi connect"
    outobj={}
    errobj={}
    logger.info("wifi_connect 11")
    return res
    

# nmcli based connect
def wifi_delete_all_nm_connections():
    global logger
    global outline
    global errline
    logger.info("wifi_delete_all_nm_connections 0")
    cmdln = "nmcli --fields UUID,TYPE con show|grep 802-11-wireless|awk '{print $1}'|while read line; do nmcli con delete uuid $line; done 2>&1 > /dev/null"
    os.system(cmdln)
    logger.info("wifi_delete_all_nm_connections 1")
    return 0


def isJson(arg):
    """
    ======================================================================
    Function: isJson
    ----------------------------------------------------------------------
    Authors          Dates     Revision  Change Log
    Kade Cox         09/27/24    1.18     Added JSON validation logic and
                                          logging for debugging purposes.
    ----------------------------------------------------------------------
    Function:
    Validates if the provided argument is a well-formed JSON string.
    This function attempts to parse the string using the `json` module.
    If parsing is successful, the string is considered valid JSON.
    
    Inputs:
      arg (str) - A string argument that is expected to contain JSON data.
    
    Outputs:
      Returns True if `arg` is a valid JSON string; otherwise, returns False.
    ----------------------------------------------------------------------
    """
    # Check if arg contains a valid JSON string
    logger.debug("Checking for valid JSON: {}".format(arg))
    try:
        json_object = json.loads(arg)
        # If parsing was successful, it's a JSON string
        logger.debug("Valid JSON detected:", json_object)
        return True
    except json.JSONDecodeError:
        # If parsing failed, it's not a JSON string
        logger.debug("Invalid JSON.")
        return False

def parseJson(arg):
    """
    ======================================================================
    Function: parseJson
    ----------------------------------------------------------------------
    Authors          Dates     Revision  Change Log
    Kade Cox         09/27/24    1.18     Created helper function for
                                          creating JsonData instances.
    ----------------------------------------------------------------------
    Function:
    Creates a `JsonData` object from the provided JSON string.
    This function initializes a new instance of `JsonData` which processes
    and stores JSON data in an accessible format.
    
    Inputs:
      arg (str) - A string argument expected to be a valid JSON structure.
    
    Outputs:
      Returns a `JsonData` object initialized with the parsed JSON data.
    ----------------------------------------------------------------------
    """
    return JsonData(arg)

class JsonData:
    """
    ======================================================================
    Class: JsonData
    ----------------------------------------------------------------------
    Authors          Dates     Revision  Change Log
    Kade Cox         09/27/24    1.18     Added class for structured
                                          representation of JSON data.
    ----------------------------------------------------------------------
    Function:
    Represents and processes JSON data as a Python object. Parses the input
    JSON string and extracts key properties for easier access and usage,
    specifically focusing on potential WiFi connection information.
    
    Attributes:
      data (dict)      - A dictionary containing the parsed JSON data.
      scheme (str)     - The data scheme being used, defaulted to 'wifi'.
      password (str)   - The password extracted from the JSON, if present.
      hostname (str)   - The hostname (or name) extracted from the JSON.
      username (str)   - The username, if applicable and available (default: None).
      port (int)       - The port number, if provided (default: None).
    
    Inputs:
      json_str (str) - A string representing the JSON data to be parsed.
    
    Outputs:
      An instance of `JsonData` with extracted fields from the JSON string.
    ----------------------------------------------------------------------
    """
    def __init__(self, json_str):
        self.data = json.loads(json_str)
        self.scheme = 'wifi'
        if 'password' in self.data:
            self.password = self.data['password']
        self.hostname = self.data['name']
        self.username = None
        self.port = None

def wifi_connect(arg):
    global logger
    logger.debug("wifi_connect: {}".format(arg))
    global outline
    global errline
    global outobj
    global errobj
    global err
    ipfile='/etc/lcriqip.conf'
    ip="0.0.0.0"
    ipgateway="0.0.0.0"
    bsetStaticIP= False
    device = wifi_get_dev_from_arg('0')
    password=''
    user=''
    port=-1
    ssid=''
    secure = False
    url = arg
    logger.debug("wifi_connect 0")
    if not isJson(arg):
        p=uriparse(url)
    else:
        p=parseJson(arg)
    logger.debug("wifi_connect 1")
    # required
    if p.scheme != 'wifi':
        return -1
    # extract password if any
    if  p.password:
        password = p.password
        secure = True
        logger.debug("wifi_connect 2")
    else:
        password =''
    logger.debug("wifi_connect 3")
    # extract username if any
    if  p.username:
        username = p.username
        password = username + password 
        secure = True
        logger.debug("wifi_connect 4")
    else:
        username =''
    # extract port if any
    logger.debug("wifi_connect 5")
    if  p.port:
        port = p.port
        logger.debug("wifi_connect 6")
    else:
        port =-1
    # extract SSID if any - required
    logger.debug("wifi_connect 7")
    if  p.hostname:
        ssid = p.hostname
        logger.debug("wifi_connect 8")
    else:
        ssid =''
        logger.debug("wifi_connect -2")
        return -2
    logger.debug("wifi_connect 9")
    # check for static IP
    try:
        f = open(ipfile, encoding='utf8')
        ip = f.read().strip()
        parts={}
        try:
            parts = ip.split('.')
            if len(parts) == 4 and all(0 <= int(part) < 256 for part in parts):
                bsetStaticIP = True
            else:
                logger.error("Static IP address "+ip+" one of the 'parts' not in range integer")
                bsetStaticIP = False
        except ValueError:
            logger.error("Static IP address "+ip+" one of the 'parts' not convertible to integer")
            bsetStaticIP = False
        ipgateway = parts[0]+"."+parts[1]+"."+parts[2]+".1"
        logger.info("wifi_connect 10 static IP "+ip+ " gateway "+ipgateway)
    except OSError as err:
        bsetStaticIP = False
        ip = "0.0.0.0"
        logger.info("wifi_connect 11 - no ipfile")
        pass
    # execute connect
    # nmcli device wifi connect NETGEAR75 password 12345678
    # device,user,password,ssid,port,secure
    args = ["device", "wifi", "connect", ssid , "password", password]
    res = run_nmcli(args)
    logger.debug("wifi_connect 12")
    if res == 0:
        logger.debug("wifi_connect 13")
        res, pr = parse_nmcli_connect(out.decode())
        if res == 0:
            outobj = pr
            errobj = {}
            errline = err.decode()
            logger.debug("wifi_connect 14")
            if not bsetStaticIP:
                return 0
        else:
            logger.debug("wifi_connect 15")
            err = out
    logger.debug("wifi_connect 16")


    if res == 0 and ip != "0.0.0.0":
        #  modify connection if using static IP
        #  nmcli c modify name
        # ipv4.method manual ipv4.address 192.168.1.30/16
        args = ["con", "modify", ssid , "ipv4.method", "manual",  "ipv4.address", ip+"/16"]
        res = run_nmcli(args)
        if res == 0:
            logger.debug("wifi_connect 17")
            res, pr = parse_nmcli_connect(out.decode())
            if res == 0:
                outobj = pr
                errobj = {}
                errline = err.decode()
                logger.debug("wifi_connect 18")
            else:
                logger.debug("wifi_connect 19")
                err = out
        logger.debug("wifi_connect 20")
        # bring it up again with new IP
        # nmcli con up con-name
        args = ["con", "up", ssid]
        res = run_nmcli(args)
        if res == 0:
            logger.debug("wifi_connect 21")
            res, pr = parse_nmcli_connect(out.decode())
            if res == 0:
                outobj = pr
                errobj = {}
                errline = err.decode()
                logger.debug("wifi_connect 22")
                return 0
            else:
                logger.debug("wifi_connect 23")
                err = out
        logger.debug("wifi_connect 24")
    outobj = {}
    errobj = {}
    errline = err.decode()
    if res != 0:
        wifi_delete_all_nm_connections()
    return res

# WiFi disconnect
def wifi_do_disconnect(device):
    global logger
    try:
        # wpa_cli -iwlan0 disconnect
        run_and_check_wpacli(["-i", device, "disconnect"])
        # wpa_cli -iwlan0 remove_network all
        run_and_check_wpacli(["-i", device, "remove_network", "all"])
        # wpa_cli -iwlan0 add_network
        run_and_check_wpacli(["-i", device, "add_network"])
    except Exception as e:
        return int(e)
    return 0

def wifi_disconnect_wpa_cli(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    device = wifi_get_dev_from_arg('0')
    # execute disconnect
    res = wifi_do_disconnect(device)
    if res == 0:
        outline=""
        errline=""
        outobj={}
        errobj={}
        return 0
    outline=""
    errline="Error in Wifi disconnect"
    outobj={}
    errobj={}
    return res

def wifi_disconnect(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    global err
    device = wifi_get_dev_from_arg('0')
    # get status
    # {"status":
    #   {   "pairwise_cipher": "CCMP",
    #       "ssid": "NETGEAR75",
    #       "bssid": "8c:3b:ad:f8:f6:65",
    #       "p2p_device_address": "3c:a3:08:aa:7d:eb",
    #       "group_cipher": "CCMP",
    #       "uuid": "5c5a5174-f72d-5d87-ad47-ea933eb5e5d7",
    #       "address": "3c:a3:08:aa:7d:ea",
    #       "freq": "2452",
    #       "mode": "station",
    #       "id": "0",
    #       "ip_address": "192.168.1.13",
    #       "key_mgmt": "WPA2-PSK",
    #       "wpa_state": "COMPLETED"
    #   }
    # }
    logger.debug("wifi_disconnect 0")
    res = wifi_status('0')
    if res != 0:
        logger.debug("wifi_disconnect 1")
        return res
    ###print(str(outobj))
    status = outobj["status"]
    logger.debug("wifi_disconnect 2")
    ###print("status  : " + str(status))
    ssid=""
    state=""
    try:
        ssid = status["ssid"]
        logger.debug("wifi_disconnect 3")
    except:
        ssid = ""
        logger.debug("wifi_disconnect 4")
    try:
        state = status["wpa_state"]
        logger.debug("wifi_disconnect 5")
    except:
        ssid = ""
        logger.debug("wifi_disconnect 6")
    # execute disconnect
    ###print("ssid    : " + str(ssid))
    ###print("state   : " + str(state))
    # nmcli con delete id NETGEAR75
    # device,user,password,ssid,port,secure
    logger.debug("wifi_disconnect 7 " + ssid)
    args = ["con", "delete", ssid ]
    res = run_nmcli(args)
    logger.debug("wifi_disconnect 8")
    if res == 0:
        logger.debug("wifi_disconnect 9")
        res, pr = parse_nmcli_connect(out.decode())
        if res == 0:
            outobj = pr
            errobj = {}
            errline = err.decode()
            logger.debug("wifi_disconnect 10")
            return 0
        else:
            logger.debug("wifi_disconnect 11")
            err = out
    logger.debug("wifi_disconnect 11")
    outobj = {}
    errobj = {}
    errline = err.decode()
    return res

def handle_wifi(args):
    global logger
    logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
    theArg = None
    if len(args.arg) > 1:
        theArg = ' '.join(args.arg)  # Join only if more than one
    else:
        theArg = args.arg[0]  # Use the single argument directly
    try:
        if args.cmd == "start":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_start(theArg)
        
        if args.cmd == "shutdown":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_shutdown(theArg)

        if args.cmd == "configure":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_configure(theArg)

        if args.cmd == "status":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_status(theArg)
        
        if args.cmd == "diag":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_diag(theArg)

        if args.cmd == "scan_on":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_scan_on(theArg)
        
        if args.cmd == "scan_off":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_scan_off(theArg)

        if args.cmd == "scan_results":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_scan_results(theArg)

        if args.cmd == "connect":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            logger.debug("args: {}".format(args))
            return wifi_connect(theArg)

        if args.cmd == "disconnect":
            logger.debug("handle_wifi line {}".format(inspect.currentframe().f_back.f_lineno))
            return wifi_disconnect(theArg)
        
    except:
        return -2
    return -3

# handle Bluetooth commands

# START - Bluetooth handling

# rfcomm open and close
def open_rfcomm(file, mode):
    try:
        return os.open(file, mode | os.O_EXCL | os.O_NONBLOCK | os.O_NOCTTY) # | os.O_EXCL why exclusive?
    except OSError as err:
        if err.errno == errno.EBUSY:
            logger.warning('file %s is busy, delaying 2 seconds' % file)
            time.sleep(2)
            return open_rfcomm(file, mode)
        else:
            logger.error('OSError :'+str(err))
            raise

def close_rfcomm(fd):
    try:
        return os.close(fd)
    except OSError as err:
        if err.errno == errno.EBUSY:
            logger.warning('file fd %d is busy, delaying 2 seconds' % fd)
            time.sleep(2)
            return close_rfcomm(fd)
        else:
            logger.error('OSError :'+str(err))
            raise

def read_all(proc, tmout=0.1):
    lines = []
    chunks = bytearray()
    # read all output from the process
    while proc.poll() == None:
        readx = select.select([proc.stdout.fileno()], [], [], tmout)[0]
        if readx:
            chunk = proc.stdout.read()
            chunks.extend(chunk)
        else:
            logger.info('Done')
            break
    lines3 = chunks.decode()
    lines2 = lines3.replace('\r','')
    lines1 = ansi_escape.sub('', lines2)
    lines = lines1.split('\n')
    return lines

# bluetoothctl
def run_subprocess_quit_OLD(cmd,incmds):
    global logger
    global out
    global err
    quitcmd="\t\nquit\t\n"
    incmds+=quitcmd
    logger.info('run_subprocess_quit 1')
    try:
        logger.info(str(cmd))
        logger.info(str(incmds))
        sub = subprocess.run(cmd, input=incmds.encode(), check=True, capture_output=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        res = sub.returncode
        if sub.stdout:
            out = sub.stdout
        else:
            out = 'OK'.encode()
        if sub.stderr:
            err = sub.stderr
        else:
            err = 'ER'.encode()
        ret = out,err
    except Exception as e:
        out = ''
        err = str(e).encode('utf-8')
        ret = out,err
        cmderror = e
        return [-4, ret]
    return [res,ret]


def run_bluetoothctl(arg, tmout=1, tmoutresp=0.1):
    global logger
    global outline
    global errline
    cmdln = ["/usr/bin/bluetoothctl"]
    incmds=' '.join(arg)
    ret = run_subprocess_quit(cmdln, incmds, tmout, tmoutresp)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res
    
def parse_bluetoothctl(res,outout,errout):
    global logger
    if res ==0:
        bluetoothctl="OK"
    else:
        bluetoothctl="ERROR"
#    lines = outout.strip()
#    for line in lines.split("\n"):
#        if 'OK' not in line:
#            continue
#        else:
#            pass
    bluetoothctlobj = {"bluetoothctl":bluetoothctl}
    return bluetoothctlobj


def run_rfcomm(arg):
    global logger
    global outline
    global errline
    cmdln = ["/usr/bin/rfcomm"]
    #incmds=''
    #cmdln.extend(arg)
    #ret = run_subprocess_nocmd(cmdln, incmds)
    ret = run_subprocess_bl(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

#b'rfcomm0: CC:78:AB:68:54:B8 channel 1 clean \n\n'
def parse_rfcomm_status_line(line_string):
    """Parse a string corresponding to rfcomm device."""
    global logger
    device = {}
    block_list = ["[\x1b[0;"]
    string_valid = not any(keyword in line_string for keyword in block_list)

    if string_valid:
        try:
            device_position = line_string.index("rfcomm0:")
        except ValueError:
            pass
        else:
            if device_position > -1:
                attribute_list = line_string[device_position:].split(" ", 5)
                device = {
                    "name": attribute_list[0],
                    "bssid": attribute_list[1],
                    "channel": attribute_list[3],
                    "state": attribute_list[4],
                    "other":''
                }

    return device

def parse_rfcomm_status(res,outout,errout):
    global logger
    status_results=[]
    if res == 0:
        #lines = outout.strip()
        lines = outout.split('\n')
        lines = filter(lambda line: line not in ['list', 'quit'], lines)
        lines = list(lines)
        logger.debug('lines:'+str(lines))
        for line in lines:
            device = parse_rfcomm_status_line(line)
            if device:
                status_results.append(device)
    status_resultsobj = status_results
    return status_resultsobj


def parse_rfcomm(res,outout,errout):
    global logger
    if res ==0:
        rfcomm="OK"
    else:
        rfcomm="ERROR"
    rfcommobj = {"rfcomm":rfcomm}
    return rfcommobj

def parse_device_line(line_string):
    """Parse a string corresponding to a device."""
    global logger
    device = {}
    block_list = ["[\x1b[0;", "removed"]
    string_valid = not any(keyword in line_string for keyword in block_list)

    if string_valid:
        try:
            device_position = line_string.index("Device")
        except ValueError:
            pass
        else:
            if device_position > -1:
                attribute_list = line_string[device_position:].split(" ", 2)
                device = {
                    "bssid": attribute_list[1],
                    "ssid": attribute_list[2],
                    "flags":'',
                    "sig":'',
                    "freq":''
                }

    return device

def parse_bluetoothctl_scan_results(res,outout,errout):
    global logger
    scan_results=[]
    if res == 0:
        #lines = outout.strip()
        lines = outout.split('\n')
        lines = filter(lambda line: line not in ['list', 'quit'], lines)
        lines = list(lines)
        logger.debug('lines:'+str(lines))
        for line in lines:
            device = parse_device_line(line)
            if device:
                scan_results.append(device)
    scan_resultsobj = {"scan_results":scan_results}
    return scan_resultsobj


# btmgmt
def run_btmgmt(arg):
    global logger
    global outline
    global errline
    cmdln = ["/usr/bin/btmgmt"]
    cmdln.extend(arg)
    ret = run_subprocess(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

def parse_btmgmt(input):
    global logger
    btmgmt="OK"
    logger.debug(str(input))
    lines = input.strip()
    for line in lines.split("\n"):
        if 'OK' not in line:
            continue
        else:
            pass
    btmgmtobj = {"btmgmt":btmgmt}
    return btmgmtobj

def parse_btmgmt_scan_results(input):
    global logger
    scan_results=[]
    logger.debug(str(input))
    lines = input.strip()
    for line in lines.split("\n"):
        if 'OK' not in line:
            continue
        else:
            pass
    scan_resultsobj = {"scan_results":scan_results}
    return scan_resultsobj

# Run subprocess and get the return
def run_subprocess_bl(cmd):
    global logger
    logger.debug("cmd "+str(cmd)+"returned ")
    cmdpopen = str(cmd)
    global out
    global err
    try:
        sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        ret = sub.communicate()
        res = sub.returncode
        out = ret[0]
        err = ret[1]
        logger.debug(str(out))
        logger.debug(str(err))
    except Exception as e:
        logger.error(str(e))
        cmderror = e
        return [-4, ret]
    return [res,ret]

def run_subprocess_quit(cmd, incmds, tmout=1, tmoutresp=0.1):
    global logger
    global out
    global err
    response=[]
    quitcmd=b'quit\t\n'
    try:
        sub = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT, stdin=subprocess.PIPE)

        fcntl.fcntl(
            sub.stdout.fileno(),
            fcntl.F_SETFL,
            fcntl.fcntl(sub.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK,
        )

        read_all(sub)

        if sub.poll() != None:
            logger.info('sub is gone 0')

        bincmds = bytearray()
        bincmds += incmds.encode()
        bincmds += b'\n'
        sub.stdin.write(bincmds)
        sub.stdin.flush()
        if sub.poll() != None:
            logger.info('sub is gone 1')

        time.sleep(tmout)

        response = read_all(sub, tmoutresp) # get only the response to commannd

        sub.stdin.write(quitcmd)
        sub.stdin.flush()
        if sub.poll() != None:
            logger.info('sub is gone 2')

        read_all(sub)

        responselines = '\n'.join(response)
        responselines += '\n'
        res = sub.returncode
        
        if sub.stdout:
            out = responselines.encode()
            #out = sub.stdout
        else:
            out = 'OK'.encode()
        if sub.stderr:
            err = responselines.encode()
            #err = sub.stderr
        else:
            err = 'ER'.encode()
        ret = out,err
    except Exception as e:
        out = ''
        err = str(e).encode('utf-8')
        ret = out,err
        cmderror = e
        return [-4, ret]
    return [res,ret]



def run_subprocess_nocmd(cmd, incmds='', tmout=1, tmoutresp=0.1):
    global logger
    global out
    global err
    response=[]
    try:
        logger.debug(str(cmd))
        logger.debug(str(incmds))
        sub = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT, stdin=subprocess.PIPE)

        fcntl.fcntl(
            sub.stdout.fileno(),
            fcntl.F_SETFL,
            fcntl.fcntl(sub.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK,
        )

        if sub.poll() != None:
            logger.debug('sub is gone 0')

        response = read_all(sub)
        
        responselines = '\n'.join(response)
        responselines += '\n'
        res = sub.returncode
        if sub.stdout:
            out = responselines.encode()
            #out = sub.stdout
        else:
            out = 'OK'.encode()
        if sub.stderr:
            err = responselines.encode()
            #err = sub.stderr
        else:
            err = 'ER'.encode()
        ret = out,err
    except Exception as e:
        out = ''
        err = str(e).encode('utf-8')
        ret = out,err
        cmderror = e
        return [-4, ret]
    return [res,ret]

# BL start
# systemctl start bluetooth
# or
# rfkill unblock bluetooth
def bl_start(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    logger.debug("bl_start 1")
    # systemctl bluetooth start
    args = ["start", "bluetooth"]
    res = run_systemctl(args)
    if res != 0:
        pr = parse_systemctl(out.decode())
        errobj = pr
        errline = err.decode()
        logger.debug("bl_start 2")
        return res

    # systemctl sr1000-rfcomm start
    args = ["start", "sr1000-rfcomm"]
    res = run_systemctl(args)
    if res != 0:
        pr = parse_systemctl(out.decode())
        errobj = pr
        errline = err.decode()
        logger.debug("bl_start 2.5")
        return res


    args = ['hci0','up','piscan']
    res = run_hciconfig(args)
    logger.debug("bl_start 3")
    if res != 0:
        pr = parse_hciconfig(res,outline, errline)
        errobj = pr
        errline = err.decode()
        logger.debug("bl_start 4")
        return res

    logger.debug("bl_start 5")
    outline = out.decode()
    return res


# BL shutdown
# systemctl stop bluetooth
# or
# rfkill block bluetooth
def bl_shutdown(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    # systemctl bluetooth start
    args = ["stop", "bluetooth"]
    res = run_systemctl(args)
    if res == 0:
        pr = parse_systemctl(out.decode())
        outobj = pr
        errline = err.decode()
        return 0
    errline = err.decode()
    return res

# BL configure
def bl_configure(args):
    global logger
    pass


# hciconfig
def run_hciconfig(arg):
    global logger
    global outline
    global errline
    global out
    global err
    cmdln = ["/bin/hciconfig"]
    cmdln.extend(arg)
    logger.debug("run_hciconfig 1")
    ret = run_subprocess_bl(cmdln)
    res = ret[0]
    if res == 0:
        outline = out.decode()
        errline = err.decode()
        logger.debug("run_hciconfig 2")
        return 0
    errline = err.decode()
    logger.debug("run_hciconfig 3")
    return res

#root@var-som-mx6:~# systemctl stop  bluetooth 
#root@var-som-mx6:~# hciconfig 0
#hci0:	Type: Primary  Bus: UART
#	BD Address: 0C:B2:B7:7E:1A:06  ACL MTU: 1021:6  SCO MTU: 180:4
#	DOWN 
#	RX bytes:238522 acl:533 sco:0 events:5849 errors:0
#	TX bytes:70881 acl:532 sco:0 commands:3395 errors:0

#root@var-som-mx6:~# systemctl start  bluetooth 
#root@var-som-mx6:~# hciconfig 0
#hci0:	Type: Primary  Bus: UART
#	BD Address: 0C:B2:B7:7E:1A:06  ACL MTU: 1021:6  SCO MTU: 180:4
#	UP RUNNING PSCAN 
#	RX bytes:239215 acl:533 sco:0 events:5890 errors:0
#	TX bytes:73101 acl:532 sco:0 commands:3436 errors:0


# DOWN
# UP RUNNING
# UP RUNNING PSCAN

def parse_hciconfig(res,outout,errout):
    global logger
    status="DOWN"
    lines = outout.strip()
    if res == 0:
        for line in lines.split("\n"):
            if 'DOWN' in line:
                break
            if 'UP' in line:
                status="UP"
            if 'RUNNING' in line:
                status="UP"
# PW Ignore PSCAN
#            if 'PSCAN' in line:
#                status="SCANNIG"
#                logger.info("parse_hciconfig 8")
            if 'ISCAN' in line:
                status="SCANNIG"
            if 'INQUIRY' in line:
                status="SCANNIG"
    else:
        status="ERROR"
    statusobj = {"status":status}
    return statusobj

# BL status
def bl_status_orig(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    logger.debug("bl_status_orig 1")
    args = [str(arg)]
    res = run_hciconfig(args)
    logger.debug("bl_status_orig 2")
    if res == 0:
        pr = parse_hciconfig(res,outline, errline)
        outobj = pr
        logger.debug("bl_status_orig 3")
        return 0
    return res

# BL status
def bl_status(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    global fd
    logger.debug("bl_status 1")
    # check if bluetoothd is running
    res = run_pidof(["bluetoothd"])
    if res !=0:
        # no it is not
        return bl_status_orig(arg)

    # check if rfcomm0 is connected
    res = run_rfcomm([''])
    logger.debug("bl_status 2")
    if res == 0:
        pr = parse_rfcomm_status(res,outline, errline)
        if not pr: 
            # no it is not connected and fd > 1
            pass
        else:
            # yes it is connected and fd > 1
            outobj["status"]="CONNECTED"
            outobj["devices"]=pr
            logger.debug("bl_status 3")
            return 0
    # not it is not connected
    return bl_status_orig(arg)

# BL diag
def bl_diag(args):
    global logger
    return 0

def run_pidof(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    cmdln = ["/bin/pidof"]
    cmdln.extend(arg)
    ret = run_subprocess_bl(cmdln)
    res = ret[0]
    if res == 0:
        return 0
    errline='Error: process not running'
    return res

# BL discoverable
# bluetoothctl discoverable <on/off>
def bl_discoverable(on):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    logger.debug('bl_discoverable 1')
    args = ["bluetoothd"]
    res = run_pidof(args)
    if res == 0:
        logger.debug('bl_discoverable 2')
        if on:
            # bluetoothctl discoverable on
            args = ["discoverable", "on"]
        else:
            args = ["discoverable", "off"]
        res = run_bluetoothctl(args)
        if res == 0:
            pr = parse_bluetoothctl(res,outline, errline)
            outobj = pr
            logger.debug('bl_discoverable 3')
            return 0
    logger.debug('bl_discoverable 4')
    return res

# BL pairable
# bluetoothctl pairable <on/off>
def bl_pairable(on):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    logger.debug('bl_pairable 1')
    args = ["bluetoothd"]
    res = run_pidof(args)
    if res == 0:
        logger.debug('bl_pairable 2')
        if on:
            # bluetoothctl pairable on
            args = ["pairable", "on"]
        else:
            args = ["pairable", "off"]
        res = run_bluetoothctl(args)
        if res == 0:
            pr = parse_bluetoothctl(res,outline, errline)
            outobj = pr
            logger.debug('bl_pairable 3')
            return 0
    logger.debug('bl_pairable 4')
    return res

# BL power
# bluetoothctl power <on/off>
def bl_power(on):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    logger.debug('bl_power 1')
    args = ["bluetoothd"]
    res = run_pidof(args)
    if res == 0:
        logger.debug('bl_power 2')
        if on:
            # bluetoothctl power on
            args = ["power", "on"]
        else:
            args = ["power", "off"]
        res = run_bluetoothctl(args)
        if res == 0:
            pr = parse_bluetoothctl(res,outline, errline)
            outobj = pr
            logger.debug('bl_power 3')
            return 0
    logger.debug('bl_power 4')
    return res

# BL scan_on
# bluetoothctl scan on
def bl_scan_on(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    logger.debug('bl_scan_on 1')
    args = ["bluetoothd"]
    res = run_pidof(args)
    # Turn on discoverable and pairable for other devices
    if res == 0:
        res = bl_discoverable(True)
    if res == 0:
        res = bl_pairable(True)
    if res == 0:
        logger.debug('bl_scan_on 2')
        # bluetoothctl scan on
        args = ["scan", "on"]
        res = run_bluetoothctl(args,20,1) # PW needs tuning!
        if res == 0:
            pr = parse_bluetoothctl(res,outline, errline)
            outobj = pr
            logger.debug('bl_scan_on 3')
            return 0
    logger.debug('bl_scan_on 4')
    return res

# BL scan_off
# bluetoothctl scan off
def bl_scan_off(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    logger.debug('bl_scan_off 1')
    args = ["bluetoothd"]
    res = run_pidof(args)
    # Turn off discoverable and pairable
    if res == 0:
        res = bl_discoverable(False)
    if res == 0:
        res = bl_pairable(False)
    if res == 0:
        logger.debug('bl_scan_off 2')
        # bluetoothctl scan off
        args = ["scan", "off"]
        res = run_bluetoothctl(args)
        if res == 0:
            pr = parse_bluetoothctl(res,outline, errline)
            outobj = pr
            logger.debug('bl_scan_off 3')
            return 0
    logger.debug('bl_scan_off 4')
    return res


# BL scan_results
def bl_merge_scan_results(connected, pr1, pr2):
    global logger
    seen={}
    seen_connected={}
    seen_paired={}
    devices=[]
    devices_connected=[]
#    {"scan_results1": pr1["scan_results"] + pr2["scan_results"]}
    logger.debug("connected")
    for dev in connected:
        logger.debug(str(dev))
        dev['flags']='[CONNECTED]'
        try:
            seen_connected[dev["bssid"]]=dev
            logger.debug("added to seen connected device "+dev["bssid"])
        except:
            logger.debug("failed to add to seen connected devices "+dev["bssid"])
    logger.debug("paired")
    pr1res = pr1["scan_results"]
    pr2res = pr2["scan_results"]
    for dev in pr1res:
        logger.debug(str(dev))
        try:
            s=seen[dev["bssid"]]
            logger.debug("seen this device "+dev["bssid"])
        except:
            seen[dev["bssid"]]=dev
            seen_paired[dev["bssid"]]=dev
            try:
                ss=seen_connected[dev["bssid"]]
                dev['flags']=dev['flags']+'[CONNECTED]'
                devices_connected.append(dev)
            except:
                dev['flags']=dev['flags']+'[PAIRED]'
                devices.append(dev)
    logger.debug("devices")
    for dev in pr2res:
        logger.debug(str(dev))
        try:
            s=seen[dev["bssid"]]
            logger.debug("seen this device "+dev["bssid"])
        except:
            dev['flags']=dev['flags']+'[DISCONNECTED]'
            seen[dev["bssid"]]=dev
            devices.append(dev)
    pr = {"scan_results": devices_connected + devices}
    return pr

# bluetoothctl devices
def bl_scan_results(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    pr1=[]
    pr2=[]
    connected = []
    logger.debug('bl_scan_results 1')
    device = str(arg)
    args = ["bluetoothd"]
    res = run_pidof(args)
    if res == 0:
        res = bl_status(arg)
        status = outobj["status"]
        try:
            connected = outobj["devices"]
        except:
            connected = []
    if res == 0:
        logger.debug('bl_scan_results 2')
        # bluetoothctl devices
        # get paired devices
        args = ["paired-devices"]
        res = run_bluetoothctl(args)
        if res == 0:
            pr1 = parse_bluetoothctl_scan_results(res, outline, errline)
            outobj = pr1
            errline = err.decode()
            logger.debug('bl_scan_results 3')
            # get scanned devices
            args = ["devices"]
            res = run_bluetoothctl(args)
            if res == 0:
                pr2 = parse_bluetoothctl_scan_results(res, outline, errline)
                pr = bl_merge_scan_results(connected, pr1, pr2)
                outobj = pr
                errline = err.decode()
                logger.debug('bl_scan_results 4')
                return 0
    logger.debug('bl_scan_results 5')
    errline = err.decode()
    return res

# BL URL
# bl://pin@ssid:port
# No pin no port
# bl://ssid
# Pin no port
# bl://pin@ssid
# pin 1234
# device 74:2B:62:D6:63:DA
# bl pair  bl://1234@74_2B_62_D6_63_DA
def bl_parse_url_and_check(url):
    pin=''
    port=-1
    device=''
    secure = False
    p=uriparse(url)
    # required
    if p.scheme != 'bl':
        return 1
    # extract password if any
    if  p.password:
        pin = p.password
        secure = True
    else:
        pin =''
    # extract username if any
    if  p.username:
        pin = p.username
        secure = True
    else:
        pin =''
    # extract port if any
    if  p.port:
        port = p.port
    else:
        port =-1
    # extract SSID if any - required
    if  p.hostname:
        device = p.hostname
        device=device.replace('_',':') # back to XX:XX:XX:XX:XX:XX
    else:
        device =''
        logger.error("bl_parse_url_and_check -2")
        return 2
    logger.debug("bl_parse_url_and_check pin: "+pin)
    logger.debug("bl_parse_url_and_check device: "+device)
    return 0

def bl_parse_url_get_device(url):
    device=''
    p=uriparse(url)
    # extract SSID if any - required
    if  p.hostname:
        device = p.hostname
        device=device.replace('_',':') # back to XX:XX:XX:XX:XX:XX
    else:
        device =''
        logger.error("bl_parse_url_get_device -2")
    logger.debug("bl_parse_url_get_device: " + device)
    return device

def bl_parse_url_get_pin(url):
    pin=''
    p=uriparse(url)
    # extract username if any
    if  p.username:
        pin = p.username
        secure = True
    else:
        pin =''
        logger.error("bl_parse_url_get_pin -2")
    logger.debug("bl_parse_url_get_pin: " + pin)
    return pin

# BL pair
def parse_bluetoothctl(res,outout,errout):
    global logger
    if res ==0:
        bluetoothctl="OK"
    else:
        bluetoothctl="ERROR"
    bluetoothctlobj = {"bluetoothctl": bluetoothctl}
    return bluetoothctlobj


# bluetoothctl pair [dev]
def bl_pair(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    pin=''
    device=''
    secure = False
    res = bl_parse_url_and_check(arg)
    if res != 0:
        errline="Error: parsing BL URL"
        return res
    # execute pair
    device = bl_parse_url_get_device(arg)
    pin = bl_parse_url_get_pin(arg)
    if pin != '':
        secure = True
    
    logger.debug("bl_pair 1")
    args = ["pair", device]

    # SSP has to be ON
    logger.debug("bl_pair ssp on 2")
    ret = run_subprocess_nocmd(['/usr/bin/btmgmt', 'ssp', 'on'], '')
    logger.debug(ret)
    res = ret[0]
    if res !=0:
        errline = "SSP ON failed"
        return res

    logger.debug("bl_pair trust 3")
    ret = run_subprocess_quit('/usr/bin/bluetoothctl', 'trust '+device)
    logger.debug(ret)
    res = ret[0]
    if res !=0:
        errline = "Trust failed"
        return res

    # bluetoothctl pair [dev]
    logger.debug("bl_pair pair 4")
    ret = run_subprocess_quit(['/usr/bin/bluetoothctl', '--agent=NoInputNoOutput'], 'pair '+device, 4, 2)
    logger.debug(ret)
    res = ret[0]
    if res !=0:
        errline = "Pair failed"
        return res

    logger.debug("bl_pair 5")
    pr = parse_bluetoothctl(res,outline, errline)
    if res == 0:
        logger.debug("bl_pair 6")
        outobj = pr
    else:
        logger.debug("bl_pair 7")
        errobj = pr

    logger.debug("bl_pair 7")
    return res

# BL unpair
# bluetoothctl remove <dev>
def bl_unpair(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    res = bl_parse_url_and_check(arg)
    if res != 0:
        errline="Error: parsing BL URL"
        return res
    device = bl_parse_url_get_device(arg)
    # bluetoothctl remove <dev>
    args = ["remove", device]
    res = run_bluetoothctl(args)
    if res == 0:
        pr = parse_bluetoothctl(res,outline, errline)
        outobj = pr
        return 0
    errline = err.decode()
    return res

# bluetoothctl trust device
def bl_trust(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    device=''
    res = bl_parse_url_and_check(arg)
    if res != 0:
        errline="Error: parsing BL URL"
        return res
    device = bl_parse_url_get_device(arg)
    # bluetoothctl trust [dev]
    args = ["trust", device]
    res = run_bluetoothctl(args)
    if res == 0:
        pr = parse_bluetoothctl(res,outline, errline)
        outobj = pr
        return 0
    errline = err.decode()
    return res

# BL untrust
# bluetoothctl untrust device
def bl_untrust(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    res = bl_parse_url_and_check(arg)
    if res != 0:
        errline="Error: parsing BL URL"
        return res
    device = bl_parse_url_get_device(arg)
    # bluetoothctl untrust [dev]
    args = ["untrust", device]
    res = run_bluetoothctl(args)
    if res == 0:
        pr = parse_bluetoothctl(res,outline, errline)
        outobj = pr
        return 0
    errline = err.decode()
    return res


# /etc/bluetooth/rfcomm.conf
# rfcomm0 {
#     # Automatically bind the device at startup
#     bind yes;
#    # Bluetooth address of the device
#     device xx:xx:xx:xx:xx:xx;
#     # Channel
#     channel 1;
#     # Description of the connection
#     comment "LCP Bluetooth Printer";
# }
def bl_persist_rfcomm0_connection(device):
    global logger
    try:
        f = os.open("/etc/bluetooth/rfcomm.conf", os.O_RDWR | os.O_CREAT)
        os.write(f,b'rfcomm0 {\n')
        os.write(f,b'     bind yes;\n')
        devicestr = '     device '+device+';\n'
        os.write(f,devicestr.encode())
        os.write(f,b'     channel 1;\n')
        os.write(f,b'}\n')
        os.close(f)
    except IOError as e:
        logger.error("I/O error({0}): {1}".format(e.errno, e.strerror))
        return -1
    except:
        logger.error("Unexpected error:", sys.exc_info()[0])
        return -2
    return 0

def bl_remove_rfcomm0_connection(device):
    global logger
    try:
        os.remove("/etc/bluetooth/rfcomm.conf")
    except:
        return -1
    return 0


# bind
#root@var-som-mx6:~# rfcomm bind 0 CC:78:AB:68:54:B8 1
#
#root@var-som-mx6:~# rfcomm bind 0 CC:78:AB:68:54:B8 1
#Can't create device: Address already in use

# release
#root@var-som-mx6:~# rfcomm release CC:78:AB:68:54:B8 
#root@var-som-mx6:~# 
#root@var-som-mx6:~# rfcomm release CC:78:AB:68:54:B8 
#Can't release device: No such device

# BL connect
# bluetoothctl connect <dev>
def bl_connect(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    global fd
    res = bl_parse_url_and_check(arg)
    if res != 0:
        errline="Error: parsing BL URL"
        return res
    device = bl_parse_url_get_device(arg)

    logger.debug("bl_connect pair")
    res = bl_pair(arg)
    if res != 0:
        return res

    logger.debug("bl_connect connect")
    resp = run_subprocess_quit('/usr/bin/bluetoothctl', 'connect '+device)
    logger.debug(resp)
    if res != 0:
        errline = err.decode()   
        return res

    logger.debug("bl_connect binding /dev/rfcomm0")
    resp = run_subprocess_nocmd(['/usr/bin/rfcomm', 'bind', '0', device, '1'])
    logger.debug(resp)

    if res == 0:
        pr = parse_rfcomm(res,outline, errline)
        outobj = pr
    
        # Maybe we should check if fd is closed?
        logger.debug('Opening /dev/rfcomm0')
        try:
            fd = open_rfcomm('/dev/rfcomm0', os.O_RDONLY) # os.O_RDWR
            logger.debug('Opened /dev/rfcomm0 fd %d' % fd)
        except:
            logger.debug('Can not open /dev/rfcomm0')
    else:
        fd = -1
    bl_persist_rfcomm0_connection(device)
    errline = err.decode()
    return res

# BL disconnect
# bluetoothctl disconnect [dev]
def bl_disconnect(arg):
    global logger
    global outline
    global errline
    global outobj
    global errobj
    global fd
    res = bl_parse_url_and_check(arg)
    if res != 0:
        errline="Error: parsing BL URL"
        return res
    device = bl_parse_url_get_device(arg)

    logger.debug('Closing /dev/rfcomm0 fd %d' % fd)
    try:
        close_rfcomm(fd)
        logger.debug('Closed /dev/rfcomm0 fd %d' % fd)
        fd = -1
    except:
        logger.debug('Can not close /dev/rfcomm0')
        
    logger.debug('removing device')
    resp = run_subprocess_quit('/usr/bin/bluetoothctl', 'remove '+device)
    logger.debug(resp)
    if res != 0:
        errline = err.decode()   
        return res
    
    logger.debug('removing rfcomm0 connection')
    bl_remove_rfcomm0_connection(device)

    logger.debug('releasing /dev/rfcomm0')
    resp = run_subprocess_nocmd(['/usr/bin/rfcomm', 'release', '0', device, '1'])
    logger.debug(resp)
    if res == 0:
        pr = parse_rfcomm(res,outline, errline)
        outobj = pr
        return 0
    errline = err.decode()
    return res

# END of Bluetooth

def handle_bl(args):
    global logger
    try:
        if args.cmd == "start":
            return bl_start(args.arg)

        if args.cmd == "shutdown":
            return bl_shutdown(args.arg)

        if args.cmd == "configure":
            return bl_configure(args.arg)

        if args.cmd == "status":
            return bl_status(args.arg)
        
        if args.cmd == "diag":
            return bl_diag(args.arg)
        
        if args.cmd == "scan_on":
            return bl_scan_on(args.arg)

        if args.cmd == "scan_off":
            return bl_scan_off(args.arg)
        
        if args.cmd == "scan_results":
            return bl_scan_results(args.arg)

        if args.cmd == "connect":
            return bl_connect(args.arg)
        
        if args.cmd == "disconnect":
            return bl_disconnect(args.arg)

        if args.cmd == "pair":
            return bl_pair(args.arg)

        if args.cmd == "unpair":
            return bl_unpair(args.arg)
        
        if args.cmd == "trust":
            return bl_trust(args.arg)

        if args.cmd == "untrust":
            return bl_untrust(args.arg)
        
        # Controller commands
        if args.cmd == "discoverable_on":
            return bl_discoverable(True)

        if args.cmd == "discoverable_off":
            return bl_discoverable(False)

        if args.cmd == "pairable_on":
            return bl_pairable(True)

        if args.cmd == "pairable_off":
            return bl_pairable(False)

        if args.cmd == "power_on":
            return bl_power(True)

        if args.cmd == "power_off":
            return bl_power(False)

    except:
        return -2
    return -3



# usage and arguments
class WLMonParser(argparse.ArgumentParser):
    def __init__(self, *args, **kwargs):
        """Initialisation method for the parser class"""
        if 'my_keyword' in kwargs:
            # Do what needs to be done with kwargs['my_keyword']
            del kwargs['my_keyword'] # Since ArgumentParser won't recognize it

        argparse.ArgumentParser.__init__(self, *args, **kwargs)
    
    # override these 2 methods to silece printing errors
    def exit(self, status=0, message=None):
        pass

    def error(self, message):
        pass


# create the top-level parser
parser = WLMonParser(prog='WLMon',description='wireless control')
parser.usage = argparse.SUPPRESS
parser.add_argument('--wlhelp', action='store_true', help='wireless help')
subparsers = parser.add_subparsers(help='sub-command help')

# create the parser for the "wl" command
parser_a = subparsers.add_parser('wl', help='wl help')
parser_a.add_argument('cmd', type=str, help='command')
parser_a.add_argument('arg', type=str, help='argument')
parser_a.set_defaults(func=handle_wl)

# create the parser for the "wifi" command
parser_b = subparsers.add_parser('wifi', help='wifi help')
parser_b.add_argument('cmd', type=str, help='command')
parser_b.add_argument('arg', type=str, help='argument', nargs='+')
parser_b.set_defaults(func=handle_wifi)

# create the parser for the "bl" command
parser_c = subparsers.add_parser('bl', help='bl help')
parser_c.add_argument('cmd', type=str, help='command')
parser_c.add_argument('arg', type=str, help='argument')
parser_c.set_defaults(func=handle_bl)

def create_logger(log_level, syslog=None):
    logger_format = '%(name)s %(asctime)s %(levelname)-8s %(module)s:%(lineno)s %(funcName)-10s: %(message)s'

    logger = logging.getLogger(__name__)
    logger.setLevel(log_level)
    if distro  == "Ubuntu":
        fh = logging.FileHandler('WLMon.log')
    else:
        # on Register
        fh = RotatingFileHandler('/var/log/WLMon.log', maxBytes=1024*1024*10, backupCount=2)
    fh.setLevel(log_level)
    formatter = logging.Formatter(logger_format)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.debug("Logger created")
    
    return logger

def main():
    global logger
    global doexit
    global outline
    global errline
    global outobj
    global errobj

    # close inherited file descriptors other than std in,out and err
    os.closerange(3, 100)

    #args = parser.parse_args()
    #res = args.func(args)

    # Logging level
    # logLevel = "CRITICAL"
    logLevel="INFO"
    log_level = logging.CRITICAL

    if logLevel.upper() == "DEBUG":
        log_level = logging.DEBUG
    elif logLevel.upper() == "INFO":
        log_level = logging.INFO
    elif logLevel.upper() == "WARNING":
        log_level = logging.WARNING
    elif logLevel.upper() == "ERROR":
        log_level = logging.ERROR
    elif logLevel.upper() == "CRITICAL":
        log_level = logging.CRITICAL
    else:
        log_level = logging.WARNING

    logger = create_logger(log_level)

    #for line in sys.stdin:
    logger.debug("WLMon Main Running")

    for line in sys.stdin:
        ERROR=0
        ERRLINE=""
        res = 0
        outobj = {}
        errobj = {}
        outline =""
        errline =""
        logger.debug("line: " + line)
        #sys.stderr.write("<-"+line+"->")
        try:
            args = parser.parse_args(line.split())
        except:
            logger.error("ERROR:-6 parsing failed")
            ERROR=-6
            ERRLINE="parsing failed"
            #continue
        try:
            res = args.func(args)
        except:
            logger.error("ERROR:-5 line failed")
            ERROR=-5
            ERRLINE="line failed"
            #continue 

        # create reply 
        aline = " "
        try:
            if (args.cmd):
                aline += str(args.cmd + " ")
            if (args.arg):
                aline += str(args.arg + " ")
        except:
            pass

        if res == -4:
            logger.error("ERORR:-4 " + aline + " subcommand failed")
            ERROR=-4
            ERRLINE=aline + " subcommand failed"
            #continue

        if res == -3:
            logger.error("ERROR:-3 " + aline + " unknown")
            ERROR=-3
            ERRLINE=aline + " unknown"
            #continue

        if res == -2:
            logger.error("ERROR:-2 " + aline + " unimplemented")
            ERROR=-2
            ERRLINE=aline + " unimplemented"
            #continue

        if res == -1:
            logger.error("ERROR:-1 " + aline + " failed")
            ERROR=-1
            ERRLINE=aline + " failed"
            #continue
        
        # we have success or so we think
        
        outline = outline.strip("\n")
        errline = errline.strip("\n")
        cmdresponse = {}
        
        if ERROR == 0:
            # both success
            if res == 0:
                cmdresponse['OK'] = res
                cmdresponse['OU'] = outobj
            else:
                cmdresponse['ER'] = res
                cmdresponse['OU'] = {"errline":errline}
        else:
            cmdresponse['ER'] = ERROR
            cmdresponse['OU'] = {"errline":ERRLINE}

#        if err:
#            print("OK:-1 " + outline + " ERROR:1 " + errline)
#        else:
#            print("OK:0 " + outline + " ERROR:0 " + errline)

        #print(json.dumps(cmdresponse, indent=4)) # pretty
        #print(json.dumps(cmdresponse)) # one line
        cmdresponsejson = json.dumps(cmdresponse)
        sys.stdout.write(cmdresponsejson+"\n")
        sys.stdout.flush()

        if doexit:
            exit(0)

if __name__ == '__main__':
    main()

def test_me():
    ret = run_subprocess(["/sbin/ifconfig", "eth0"])
    res = ret[0]
    if res == 0:
        print("========")
        print(out.decode())
        print("--------")
        pr = parse_ifconfig(out.decode())
        print(pr['hardware_address'])

