#!/usr/bin/python3
# LAVA Controls RPI- Copyright 2019 Lucid Networks - Stephen Loeckle

import socket
import serial
import binascii
import logging
import socketserver
import struct
import os
import sys
import time
import json
import inspect
from time import time, sleep
import datetime
import collections
import configparser
import argparse
import pickle
import urllib.request
import traceback
import netifaces
import subprocess
import sqlite3
import platform
from multiprocessing import freeze_support, Process, Queue
from lavacontrolslibrpi import api, CoreDisplaySettings, DisplayInfo, fetchcmdDE, fetchcmdLAVA, Input, jobs, ping, sendlog, saveconfig, sendemail, sethdmimodes, Timeout, update_dict, UpdateApi, UpdateDatabase, Volume

PORT = 7142

class LogRecordStreamHandler(socketserver.StreamRequestHandler):
    def handle(self):
        while True:
            chunk = self.connection.recv(4)
            if len(chunk) < 4:
                break
            slen = struct.unpack('>L', chunk)[0]
            chunk = self.connection.recv(slen)
            while len(chunk) < slen:
                chunk = chunk + self.connection.recv(slen - len(chunk))
            obj = self.unPickle(chunk)
            record = logging.makeLogRecord(obj)
            self.handleLogRecord(record)

    def unPickle(self, data):
#        sortedresults = collections.OrderedDict(sorted(pickle.loads(data).items()))
#        for key,val in sortedresults.items():
#            print("{0} = {1}".format(key,val))
        return pickle.loads(data)

    def handleLogRecord(self, record):
        # if a name is specified, we use the named logger rather than the one
        # implied by the record.
        if self.server.logname is not None:
            name = self.server.logname
        else:
            name = record.name
        logger = logging.getLogger(name)
        # N.B. EVERY record gets logged. This is because Logger.handle
        # is normally called AFTER logger-level filtering. If you want
        # to do filtering, do it at the client end to save wasting
        # cycles and network bandwidth!
        logger.handle(record)

class LogRecordSocketReceiver(socketserver.ThreadingTCPServer):
    allow_reuse_address = True

    def __init__(self, host='localhost',
                 port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
                 handler=LogRecordStreamHandler):
        socketserver.ThreadingTCPServer.__init__(self, (host, port), handler)
        self.abort = 0
        self.timeout = 1
        self.logname = None

    def serve_until_stopped(self):
        import select
        abort = 0
        while not abort:
            rd, wr, ex = select.select([self.socket.fileno()],
                                       [], [],
                                       self.timeout)
            if rd:
                self.handle_request()
            abort = self.abort
        
def startupdisplaystatus(s):
    gotthefunc = inspect.stack()[0][3]
    msg = ('Entered {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    ts = time()
    cmd = '01 30 2A 30 43 30 36 02 30 32 33 45 03 1A 0D'
    cmd = bytes.fromhex(cmd)
    s.write(cmd)
    s.flushInput()
    response = s.read(27)
    hexresponse = binascii.b2a_hex(response).decode()
    if hexresponse == '01303041443132023030303233453030303036343030303103700d':
        displayid = 1
    elif hexresponse == '01303042443132023030303233453030303036343030303203700d':
        displayid = 2
    elif hexresponse == '01303043443132023030303233453030303036343030303303700d':
        displayid = 3
    elif hexresponse == '01303044443132023030303233453030303036343030303403700d':
        displayid = 4
    elif hexresponse == '01303045443132023030303233453030303036343030303503700d':
        displayid = 5
    elif hexresponse == '01303046443132023030303233453030303036343030303603700d':
        displayid = 6
    elif hexresponse == '01303047443132023030303233453030303036343030303703700d':
        displayid = 7
    elif hexresponse == '01303048443132023030303233453030303036343030303803700d':
        displayid = 8
    elif hexresponse == '01303049443132023030303233453030303036343030303903700d':
        displayid = 9
    elif hexresponse == '0130304a4431320230303032334530303030363430303041030b0d':
        displayid = 10
    elif hexresponse == '0130304b443132023030303233453030303036343030304203090d':
        displayid = 11
    elif hexresponse == '0130304c4431320230303032334530303030363430303043030f0d':
        displayid = 12
    else:
        displayid = 1

    output = {}
    output['D{0}'.format(displayid)] = {}
    output['D{0}'.format(displayid)]['ID'] = displayid
    dispconn = DisplayInfo(displayid,s)
    output['D{0}'.format(displayid)]['MODEL'] = dispconn.getmodel()
    output['D{0}'.format(displayid)]['SERIAL'] = dispconn.getserial()
    output['D{0}'.format(displayid)]['F1'],output['D{0}'.format(displayid)]['F2'],output['D{0}'.format(displayid)]['F3'],output['D{0}'.format(displayid)]['F4'] = dispconn.getfirmware()
    output['D{0}'.format(displayid)]['TEMP1'],output['D{0}'.format(displayid)]['TEMP2'],output['D{0}'.format(displayid)]['TEMP3'],output['D{0}'.format(displayid)]['TEMP4'] = dispconn.gettemperature()
    inputconn = Input(displayid,s)
    output['D{0}'.format(displayid)]['INPUT'] = inputconn.getinput()
    coreconn = CoreDisplaySettings(displayid,s)
    output['D{0}'.format(displayid)]['PWR'] = coreconn.getpower()
    volconn = Volume(displayid,s)
    output['D{0}'.format(displayid)]['VOL'] = volconn.getvolume()
    output['D{0}'.format(displayid)]['ST'] = 'Last process time: {0}'.format(time() - ts)
    msg = ('Exited {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    return(output)

def setdefaults(sysinfo, displayid, s):
    gotthefunc = inspect.stack()[0][3]
    msg = ('Entered {} function with options {}'.format(gotthefunc, displayid))
    sendlog(gotthefunc, msg, 10)
    ts = time()
    output = {}
    output['D{0}'.format(displayid)] = {}
    output['D{0}'.format(displayid)]['SERIAL'] = sysinfo['D{0}'.format(displayid)]['SERIAL']
    coreconn = CoreDisplaySettings(displayid, s)
    output['D{0}'.format(displayid)]['OSD'] = coreconn.setosd(0)
    output['D{0}'.format(displayid)]['FUNCNAME'] = gotthefunc
    output['D{0}'.format(displayid)]['FUNCTIME'] = 'Last process time: {0}'.format(time() - ts)
    msg = ('Exited {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    return(output)

def changeinput(sysinfo, newinput, s, displayid=999):
    gotthefunc = inspect.stack()[0][3]
    msg = ('Entered {} function with options {} {}'.format(gotthefunc, displayid, newinput))
    sendlog(gotthefunc, msg, 10)
    if displayid == 999:
        displayid = sysinfo['SYSTEM']['TARGET']
        msg = ('CHANGEINPUT: Fetching displayid from sysinfo target: {}'.format(displayid))
        sendlog(gotthefunc, msg, 10)
    else:
        ip = sysinfo['D{0}'.format(displayid)]['IP']
        msg = ('CHANGEINPUT: Fetching displayid from arguments passed to function: {}'.format(displayid))
        sendlog(gotthefunc, msg, 10)
    ts = time()
    output = {}
    output['D{0}'.format(displayid)] = {}
    output['D{0}'.format(displayid)]['SERIAL'] = sysinfo['D{0}'.format(displayid)]['SERIAL']
    output['D{0}'.format(displayid)]['LASTINPUT'] = sysinfo['D{0}'.format(displayid)]['INPUT']
    returninputstatus = Input(displayid,s)
    output['D{0}'.format(displayid)]['INPUT']=returninputstatus.setinput(newinput)
##  Need to store hdmi mode (or any other input) states
    if input == 'hdmi':
        returninputsettings = sethdmimodes(displayid, s)
        
    apidata = {}
    apidata['id'] = displayid
    apidata['lava_id'] = sysinfo['D{0}'.format(displayid)]['lava_id']
    apidata['input'] = output['D{0}'.format(displayid)]['INPUT']
    au = UpdateApi(sysinfo)
    au.display(apidata)
    
    output['D{0}'.format(displayid)]['SAVED']=0
    returnsaved = saveconfig(displayid, s)
    output['D{0}'.format(displayid)]['SAVED']=returnsaved
    output['D{0}'.format(displayid)]['CHANGEINPUTTIME'] = 'Last process time: {0}'.format(time() - ts)
    msg = ('Exited {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    UpdateDatabase(sysinfo, output)
    return(output)

def changepower(sysinfo, displayid, power, s):
    gotthefunc = inspect.stack()[0][3]
    msg = ('Entered {} function with options {} {}'.format(gotthefunc, displayid, power))
    sendlog(gotthefunc, msg, 10)
    ts = time()
    output = {}
    output['D{0}'.format(displayid)] = {}
    coreconn = CoreDisplaySettings(displayid,s)
    output['D{0}'.format(displayid)]['PWR'] = coreconn.setpower(power)
    
    apidata = {}
    apidata['id'] = displayid
    apidata['lava_id'] = sysinfo['D{0}'.format(displayid)]['lava_id']
    apidata['powermode'] = output['D{0}'.format(displayid)]['PWR']
    au = UpdateApi(sysinfo)
    au.displayUpdate(apidata)
    
    output['D{0}'.format(displayid)]['FUNCNAME'] = gotthefunc
    output['D{0}'.format(displayid)]['FUNCTIME'] = 'Last process time: {0}'.format(time() - ts)
    msg = ('Exited {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    return(output)

def changevolume(sysinfo,s,displayid=0,resetvolume=0):
    gotthefunc = inspect.stack()[0][3]
    if isinstance( sysinfo['SYSTEM']['TARGET'], int):
        if sysinfo['SYSTEM']['TARGET'] == 0 or not sysinfo['SYSTEM']['TARGET']:
            displayid = 1
        else:
            displayid = sysinfo['SYSTEM']['TARGET']
    else:
        displayid = 1
    if resetvolume == 0:
        vol = sysinfo['SYSTEM']['COMMAND'].split("_")
    else:
        vol = resetvolume
    msg = ('Entered {} function with options {} {}'.format(gotthefunc, displayid, vol[1]))
    sendlog(gotthefunc, msg, 10)
    ts = time()
    output = {}
    output['D{0}'.format(displayid)] = {}
    try:
        # with Timeout(3):
            returnvolumestatus = Volume(displayid, s)
            output['D{0}'.format(sysinfo['SYSTEM']['TARGET'])]['VOL'] = returnvolumestatus.setvolume(int(vol[1]))
    except Timeout.Timeout as e:
        pass
        msg = ('Change Volume Timeout')
        sendlog(gotthefunc, msg, 40)
        output['D{0}'.format(sysinfo['SYSTEM']['TARGET'])]['VOL'] = sysinfo['D{0}'.format(sysinfo['SYSTEM']['TARGET'])]['VOL']
    except:
        pass
        msg = ('Error in {0}'.format(gotthefunc))
        sendlog(gotthefunc, msg, 40)
        err = traceback.format_exc()
        msg = ('Error in {0} Detail: {1}'.format(gotthefunc,err))
        sendlog(gotthefunc, msg, 10)
        output['D{0}'.format(sysinfo['SYSTEM']['TARGET'])]['VOL'] = sysinfo['D{0}'.format(sysinfo['SYSTEM']['TARGET'])]['VOL']
    returnsaved = saveconfig(displayid, s)
    msg = ('Exited {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    return(output)
    
def logserver(logdir):
    gotthefunc = inspect.stack()[0][3]
    msg = ('Entered {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    logging.basicConfig(format='%(asctime)s %(processName)-10s %(name)s %(levelname)-8s %(message)s',handlers=[logging.handlers.RotatingFileHandler('{}/lavacontrols-rpi.log'.format(logdir), mode='a', maxBytes=10000*1024, backupCount=30, encoding=None, delay=False)])
    tcpserver = LogRecordSocketReceiver()
    tcpserver.serve_until_stopped()
    msg = ('Exited {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    
def hardware(sysinfo,s):
    gotthefunc = inspect.stack()[0][3]
    msg = ('Entered {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
    csloop = 0
    while True:
        conn = sqlite3.connect(sysinfo['SYSTEM']['DBFILE'])
        c = conn.cursor()
        displaylist = None
        try:
            # with Timeout(3):
                olddict = sysinfo.copy()
        except Timeout.Timeout as e:
            pass
            msg = ('CSLoop Start Class Timeout')
            sendlog(gotthefunc, msg, 40)
        except:
            pass
            msg = ('Error 1 in {0}'.format(gotthefunc))
            sendlog(gotthefunc, msg, 40)
            err = traceback.format_exc()
            msg = ('Error 1 Detail: {0}'.format(err))
            sendlog(gotthefunc, msg, 10)
        try:
            # with Timeout(3):
                if 'lava' in sysinfo['SYSTEM']['PLATFORM'].lower():
                    cmd = fetchcmdLAVA(sysinfo)
                elif sysinfo['SYSTEM']['PLATFORM'].lower() == 'de':
                    cmd = fetchcmdDE(sysinfo)
        except Timeout.Timeout as e:
            pass
            msg = ('CSLoop cmd = fetchcmd Class Timeout')
            sendlog(gotthefunc, msg, 40)
        except:
            pass
            msg = ('Error 2 in {0}'.format(gotthefunc))
            sendlog(gotthefunc, msg, 40)
            err = traceback.format_exc()
            msg = ('Error 2 Detail: {0}'.format(err))
            sendlog(gotthefunc, msg, 10)
        # Merge results into master sysinfo dictionary
        msg = ('Entering update_dict function'.format(gotthefunc))
        sendlog(gotthefunc, msg, 10)
        try:
            # with Timeout(3):
                update_dict(sysinfo, cmd)
        except Timeout.Timeout as e:
            pass
            msg = ('CSLoop update_dict Class Timeout')
            sendlog(gotthefunc, msg, 40)
        except:
            pass
            msg = ('Error 3 in {0}'.format(gotthefunc))
            sendlog(gotthefunc, msg, 40)
            err = traceback.format_exc()
            msg = ('Error 3 Detail: {0}'.format(err))
            sendlog(gotthefunc, msg, 10)
        msg = ('Exited update_dict function'.format(gotthefunc))
        sendlog(gotthefunc, msg, 10)
        cmdresponse = {}
        if sysinfo['SYSTEM']['COMMAND'] != 'null' or sysinfo['SYSTEM']['COMMAND'] is not None:
            if 'input' in sysinfo['SYSTEM']['COMMAND']:
                input = sysinfo['SYSTEM']['COMMAND'].split('_')
                try:
                    # with Timeout(10):
                        cmdresponse = changeinput(sysinfo, input[1], s)
                except Timeout.Timeout as e:
                    pass
                    msg = ('CSLoop changeinput {} Class Timeout'.format(input[1]))
                    sendlog(gotthefunc, msg, 40)
                except:
                    pass
                    msg = ('Error 5 in {0}'.format(gotthefunc))
                    sendlog(gotthefunc, msg, 40)
                    err = traceback.format_exc()
                    msg = ('Error 5 Detail: {0}'.format(err))
                    sendlog(gotthefunc, msg, 10)
                update_dict(sysinfo, cmdresponse)
            if 'power' in sysinfo['SYSTEM']['COMMAND']:
                power = sysinfo['SYSTEM']['COMMAND'].split('_')
                try:
                    # with Timeout(10):
                        if power[1] == 'off':
                            cmdresponse = changepower(sysinfo, sysinfo['SYSTEM']['TARGET'], 0, s)
                        if power[1] == 'on':
                            cmdresponse = changepower(sysinfo, sysinfo['SYSTEM']['TARGET'], 1, s)
                except Timeout.Timeout as e:
                    pass
                    msg = ('CSLoop changeinput {} Class Timeout'.format(input[1]))
                    sendlog(gotthefunc, msg, 40)
                except:
                    pass
                    msg = ('Error 5 in {0}'.format(gotthefunc))
                    sendlog(gotthefunc, msg, 40)
                    err = traceback.format_exc()
                    msg = ('Error 5 Detail: {0}'.format(err))
                    sendlog(gotthefunc, msg, 10)
                update_dict(sysinfo, cmdresponse)
            if sysinfo['SYSTEM']['COMMAND'].startswith('volume'):
                vol = sysinfo['SYSTEM']['COMMAND'].split("_")
                try:
                    # with Timeout(10):
                        cmdresponse = changevolume(sysinfo)
                except Timeout.Timeout as e:
                    pass
                    msg = ('CSLoop changevolume Class Timeout')
                    sendlog(gotthefunc, msg, 40)
                except:
                    pass
                    msg = ('Error 17 in {0}'.format(gotthefunc))
                    sendlog(gotthefunc, msg, 40)
                    err = traceback.format_exc()
                    msg = ('Error 17 Detail: {0}'.format(err))
                    sendlog(gotthefunc, msg, 10)
                update_dict(sysinfo, cmdresponse)
                msg = cmdresponse
                sendlog(gotthefunc, msg, 10)
            if sysinfo['SYSTEM']['COMMAND'] == 'nexp_reboot':
                msg = 'Reboot function disabled'
                sendlog(gotthefunc, msg, 10)
#                controller_reboot() 
        else:
            msg = "Command is null. Pass."
            sendlog(gotthefunc, msg, 10)
            pass
            
        sleep(int(sysinfo['SYSTEM']['INTERVAL']))
        conn.commit()
        conn.close()
            
def main():
    gotthefunc = inspect.stack()[0][3]
    msg = ('Entered {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)

    parser = argparse.ArgumentParser(description='LAVA Controls')
    parser.add_argument('--console', action="store_true", default=False, dest='consolelogs', help = 'Output logs to console')
    cliargs = parser.parse_args()
    
    timeout = 10

    # Build master systemwide sysinfo dictionary
    sysinfo = {}
    sysinfo['SYSTEM'] = {}
    sysinfo['WALL'] = {}
    sysinfo['SYSTEM']['LASTHEADDATE'] = None
    
    if platform.system() == 'Linux':
        configdir = '/etc/lavacontrols-rpi'
        logdir = '/var/log/lavacontrols-rpi'
        dbdir = '/var/lib/lavacontrols-rpi'
    elif platform.system() == 'Windows':
        configdir = 'c:/lavacontrols-rpi/conf'
        logdir = 'c:/lavacontrols-rpi/logs'
        dbdir = 'c:/lavacontrols-rpi/db'
    sysinfo['SYSTEM']['CONFIGFILE'] = configdir + '/lavacontrols-rpi.conf'
    sysinfo['SYSTEM']['DBFILE'] = dbdir + '/lavacontrols-rpi.db'
    
    conn = sqlite3.connect(sysinfo['SYSTEM']['DBFILE'])
    c = conn.cursor()
    
    mainworkers = []
    
    mainworker = Process(target=logserver, args=(logdir,))
    mainworkers.append(mainworker)
    mainworker.start()
    
    sleep(2)
    
    logger = logging.getLogger('main')
    sock = logging.handlers.SocketHandler('localhost', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
    logger.addHandler(sock)
    if cliargs.consolelogs:
        consolelog = logging.StreamHandler(sys.stdout)
        logger.addHandler(consolelog)
    logger.setLevel(logging.DEBUG)

    msg = 'Log test'
    sendlog(gotthefunc, msg, 10)
        
    msg = ('Logworker started. Waiting 10 seconds for network to come alive.')
    sleep(5)

    # Read config
    config = configparser.ConfigParser()
    try:
        config.read(sysinfo['SYSTEM']['CONFIGFILE'])
    except configparser.Error as e:
        msg = ('Unable to read configuration file {}! Halting!'.format(sysinfo['SYSTEM']['CONFIGFILE']))
        sendlog(gotthefunc, msg, 40)
        sys.exit()
    except:
        msg = ('Error in {0} Reading Config! Halting!'.format(gotthefunc))
        sendlog(gotthefunc, msg, 40)
        err = traceback.format_exc()
        msg = ('Error Detail: {0}'.format(err))
        sendlog(gotthefunc, msg, 10)
        sys.exit()
    else:
        msg = ('CONFIG: Loaded config file')
        sendlog(gotthefunc, msg, 10)
    #get platform type, whether new LAVA Controls API or legacy display engine
    try:
        sysinfo['SYSTEM']['PLATFORM'] = config.get('system', 'platform')
    except (configparser.NoOptionError, configparser.InterpolationMissingOptionError):
        msg = ('No platform type in configuration file! Halting!')
        sendlog(gotthefunc, msg, 40)
        sys.exit()
    except configparser.Error as e:
        msg = ('Config read entry system platform type error! Halting!')
        sendlog(gotthefunc, msg, 40)
        sys.exit()
    except:
        msg = ('Error in {0} Reading Config! Halting!'.format(gotthefunc))
        sendlog(gotthefunc, msg, 40)
        err = traceback.format_exc()
        msg = ('Error Detail: {0}'.format(err))
        sendlog(gotthefunc, msg, 10)
        sys.exit()
    else:
        msg = ('CONFIG: Loaded platform type')
        sendlog(gotthefunc, msg, 10)
    if 'lava' in sysinfo['SYSTEM']['PLATFORM'].lower():
        # Load token key
        try:
            sysinfo['SYSTEM']['TOKEN'] = config.get('system', 'token')
        except (configparser.NoOptionError, configparser.InterpolationMissingOptionError):
            msg = ('No token in configuration file! Halting!')
            sendlog(gotthefunc, msg, 40)
            sys.exit()
        except configparser.Error as e:
            msg = ('Config read entry system token error! Halting!')
            sendlog(gotthefunc, msg, 40)
            sys.exit()
        except:
            msg = ('Error in {0} Reading Config! Halting!'.format(gotthefunc))
            sendlog(gotthefunc, msg, 40)
            err = traceback.format_exc()
            msg = ('Error Detail: {0}'.format(err))
            sendlog(gotthefunc, msg, 10)
            sys.exit()
        else:
            msg = ('CONFIG: Loaded authorization token')
            sendlog(gotthefunc, msg, 10)
    elif sysinfo['SYSTEM']['PLATFORM'].lower() == 'de':
        # Load license key
        try:
            sysinfo['SYSTEM']['LICENSEKEY'] = config.get('system', 'licensekey')
        except (configparser.NoOptionError, configparser.InterpolationMissingOptionError):
            msg = ('No license key in configuration file! Halting!')
            sendlog(gotthefunc, msg, 40)
            sys.exit()
        except configparser.Error as e:
            msg = ('Config read entry system license key error! Halting!')
            sendlog(gotthefunc, msg, 40)
            sys.exit()
        except:
            msg = ('Error in {0} Reading Config! Halting!'.format(gotthefunc))
            sendlog(gotthefunc, msg, 40)
            err = traceback.format_exc()
            msg = ('Error Detail: {0}'.format(err))
            sendlog(gotthefunc, msg, 10)
            sys.exit()
        else:
            msg = ('CONFIG: Loaded license key')
            sendlog(gotthefunc, msg, 10)
    else:
        msg = ('Unknown platform type! Halting!')
        sendlog(gotthefunc, msg, 40)
        sys.exit()
    try:
        sysinfo['SYSTEM']['INTERVAL'] = config.get('system', 'interval')
    except (configparser.NoOptionError, configparser.InterpolationMissingOptionError):
        msg = ('No interval specified in configuration file. Defaulting to 1!')
        sendlog(gotthefunc, msg, 40)
        sysinfo['SYSTEM']['INTERVAL'] = '1'
        pass
    except configparser.Error as e:
        sysinfo['SYSTEM']['INTERVAL'] = '1'
    except:
        msg = ('Error in {0} Reading Config! Halting!'.format(gotthefunc))
        sendlog(gotthefunc, msg, 40)
        err = traceback.format_exc()
        msg = ('Error Detail: {0}'.format(err))
        sendlog(gotthefunc, msg, 10)    
        sysinfo['SYSTEM']['INTERVAL'] = '1'
    else:
        msg = ('CONFIG: Got interval')
        sendlog(gotthefunc, msg, 10)
    try:
        sysinfo['SYSTEM']['LOGLEVEL'] = config.get('system', 'loglevel')
    except (configparser.NoOptionError, configparser.InterpolationMissingOptionError):
        msg = ('No logging type specified in configuration file. Defaulting to INFO!')
        sendlog(gotthefunc, msg, 40)
        sysinfo['SYSTEM']['LOGLEVEL'] = '20'
        pass
    except configparser.Error as e:
        msg = ('Config read entry system loglevel error! Halting!')
        sendlog(gotthefunc, msg, 40)
        sys.exit()
    except:
        msg = ('Error in {0} Reading Config! Halting!'.format(gotthefunc))
        sendlog(gotthefunc, msg, 40)
        err = traceback.format_exc()
        msg = ('Error Detail: {0}'.format(err))
        sendlog(gotthefunc, msg, 10)    
        sys.exit()
    else:
        msg = ('CONFIG: Got logging level')
        sendlog(gotthefunc, msg, 10)
    try:
        sysinfo['SYSTEM']['HTTPPROXY'] = config.get('system', 'proxy')
    except (configparser.NoOptionError, configparser.InterpolationMissingOptionError):
        msg = ('No proxy specified in configuration file. Defaulting to none')
        sendlog(gotthefunc, msg, 40)
        pass
    except configparser.Error as e:
        msg = ('Config read entry system proxy error. Moving on.')
        sendlog(gotthefunc, msg, 40)
        pass
    except:
        msg = ('Error in {0} Reading Config! Moving on.'.format(gotthefunc))
        sendlog(gotthefunc, msg, 40)
        err = traceback.format_exc()
        msg = ('Error Detail: {0}'.format(err))
        sendlog(gotthefunc, msg, 10)
        pass
    else:
        msg = ('CONFIG: Got proxy {}'.format(sysinfo['SYSTEM']['HTTPPROXY']))
        sendlog(gotthefunc, msg, 10)

    try:
        sysinfo['SYSTEM']['SERIALPORT'] = config.get('serial', 'port')
    except (configparser.NoOptionError, configparser.InterpolationMissingOptionError):
        msg = ('No serial port specified in configuration file. Defaulting to /dev/ttyUSB0')
        sendlog(gotthefunc, msg, 40)
        sysinfo['SYSTEM']['SERIALPORT'] = '/dev/ttyUSB0'
        pass
    except configparser.Error as e:
        msg = ('Config read entry system serial port error! Halting!')
        sendlog(gotthefunc, msg, 40)
        sys.exit()
    except:
        msg = ('Error in {0} Reading Config! Halting!'.format(gotthefunc))
        sendlog(gotthefunc, msg, 40)
        err = traceback.format_exc()
        msg = ('Error Detail: {0}'.format(err))
        sendlog(gotthefunc, msg, 10)
        sys.exit()

    if sysinfo['SYSTEM']['LOGLEVEL'] == '10':
        logger.setLevel(logging.DEBUG)
    if sysinfo['SYSTEM']['LOGLEVEL'] == '20':
        logger.setLevel(logging.INFO)
    if sysinfo['SYSTEM']['LOGLEVEL'] == '30':
        logger.setLevel(logging.WARNING)
    if sysinfo['SYSTEM']['LOGLEVEL'] == '40':
        logger.setLevel(logging.ERROR)
    if sysinfo['SYSTEM']['LOGLEVEL'] == '50':
        logger.setLevel(logging.CRITICAL)

    tsmain = time()
    
    if 'HTTPPROXY' in sysinfo['SYSTEM']:
        if len(sysinfo['SYSTEM']['HTTPPROXY']) > 0:
            os.environ["http_proxy"] = sysinfo['SYSTEM']['HTTPPROXY']
            os.environ["https_proxy"] = sysinfo['SYSTEM']['HTTPPROXY']

    # Test network connection
    msg = 'Gathering network information'
    sendlog(gotthefunc, msg, 10)
    gateways = {}
    msg = "Waiting for network to come up"
    sendlog(gotthefunc, msg, 10)
    while 'default' not in gateways or not defaultgateway:
        try:
            gateways = netifaces.gateways()
        except:
            pass
            msg = ("Exception in {}!:".format(gotthefunc))
            sendlog(gotthefunc, msg, 40)
            err = traceback.format_exc()
            msg = ('Error in {0} Detail: {1}'.format(gotthefunc,err))
            sendlog(gotthefunc, msg, 10)
        try:
            defaultgateway = gateways['default'][netifaces.AF_INET][0]
        except:
            pass
            defaultgateway = None
            msg = ('Network not ready in {0}.'.format(gotthefunc))
            sendlog(gotthefunc, msg, 10)
            sleep(1)
        else:
            msg = 'Obtained default gateway ip address'
            sendlog(gotthefunc, msg, 20)
    msg = 'Attempting to ping default gateway'
    sendlog(gotthefunc, msg, 10)
    while True:
        isup = ping(defaultgateway)
        if isup:
            msg = 'Default gateway {} is alive.'.format(defaultgateway)
            sendlog(gotthefunc, msg, 10)
            break
        else:
            msg = 'Default gateway {} is down. Looping.'.format(defaultgateway)
            sendlog(gotthefunc, msg, 10)

    sendlog(gotthefunc, msg, 10)
    socketnum = 0
    serialport = serial.Serial(sysinfo['SYSTEM']['SERIALPORT'], baudrate=9600, bytesize=8, parity=serial.PARITY_NONE, stopbits=1, timeout=5)
    serialport.isOpen()
    startupresults = startupdisplaystatus(serialport)

    msg = ('Took {}'.format(time() - tsmain))
    sendlog(gotthefunc, msg, 10)
    # Merge startup results into master sysinfo dictionary
    
    # Let's set some display defaults, like OSD Off

    tsmain = time()

#    sortedresults = collections.OrderedDict(sorted(startupresults.items()))
#    for key,val in sortedresults.items():
    msg = startupresults
    sendlog(gotthefunc, msg, 10)
    
    for key,val in startupresults.items():
        msg = ("{0} = {1}".format(key,val))
        sendlog(gotthefunc, msg, 10)
        try:
            c.execute('SELECT id, lava_id FROM displays WHERE serial = ?', (val['SERIAL'],))
        except:
            err = traceback.format_exc()
            msg = ('Error Detail: {0}'.format(err))
            sendlog(gotthefunc, msg, 10) 
            pass  
        row = c.fetchone()
        if row is None:
            dbsuccess = 0
            while dbsuccess == 0:
                try:
                    c.execute('INSERT into displays (model, serial, f1, f2, f3, f4, id, power_state, input, volume) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', (val['MODEL'], val['SERIAL'], val['F1'], val['F2'], val['F3'], val['F4'], val['ID'], val['PWR'], val['INPUT'], val['VOL']))
                except sqlite3.OperationalError:
                    msg = ('{}: Database locked. Trying again.',format(gotthefunc))
                    sendlog(gotthefunc, msg, 10)
                    pass
                    sleep(0.5)
                except:
                    msg = ('Startup display entry insert for {} failed. Pass.'.format(key))
                    sendlog(gotthefunc, msg, 10)
                    err = traceback.format_exc()
                    msg = ('Error Detail: {0}'.format(err))
                    sendlog(gotthefunc, msg, 10)   
                    pass
                    sleep(0.5)
                else:
                    msg = ('Startup display entry insert for {} successful.'.format(key))
                    sendlog(gotthefunc, msg, 10)
                    dbsuccess = 1
        else:
            dbsuccess = 0
            while dbsuccess == 0:
                try:
                    c.execute('UPDATE displays SET f1 = ?, f2 = ?, f3 = ?, f4 = ?, id = ?, power_state = ?, input = ?, volume = ? WHERE serial = ?', (val['F1'], val['F2'], val['F3'], val['F4'], val['ID'], val['PWR'], val['INPUT'], val['VOL'], val['SERIAL']))
                except sqlite3.OperationalError:
                    msg = ('{}: Database locked. Trying again.',format(gotthefunc))
                    sendlog(gotthefunc, msg, 10)
                    pass
                    sleep(0.5)
                except:
                    err = traceback.format_exc()
                    msg = ('Error Detail: {0}'.format(err))
                    sendlog(gotthefunc, msg, 10)
                    pass
                    sleep(0.5)
                else:
                    msg = ('Startup display entry update for {} successful.'.format(key))
                    sendlog(gotthefunc, msg, 10)
                    dbsuccess = 1
        try:
            startupresults['D{0}'.format(row[0])]['lava_id'] = row[1]
        except:
            pass
    conn.commit()
    conn.close()
    update_dict(sysinfo, startupresults)
    msg = ('Took {}'.format(time() - tsmain))
    sendlog(gotthefunc, msg, 10)
    setdefaultresults = setdefaults(sysinfo, 1, serialport)
    update_dict(sysinfo, setdefaultresults)

    laststate = {}
    sysinfo['SYSTEM']['LASTUPDATED'] = 0
    sysinfo['SYSTEM']['COMMAND'] = None
    sysinfo['SYSTEM']['BASEINPUT'] = None

    print(sysinfo)
    # Begin cloud server query for data
    test = 0
    mainworker = Process(target=api, args=(sysinfo,))
    mainworkers.append(mainworker)
    mainworker.start()
    mainworker = Process(target=hardware, args=(sysinfo,serialport))
    mainworkers.append(mainworker)
    mainworker.start()
    mainworker = Process(target=jobs, args=(sysinfo,serialport))
    mainworkers.append(mainworker)
    mainworker.start()
    for w in mainworkers:
        w.join()
        
    msg = ('Exited {} function'.format(gotthefunc))
    sendlog(gotthefunc, msg, 10)
#    logworker.join()


if __name__ == '__main__':
    freeze_support()
    main()