import os
import sys
import string
import base64
import httplib

#configurables
IDIGI_ADDR    = "app.idigi.com"     #hostname of iDigi Server to Connect to
USERNAME      = "tkim"            #username on that account
PASSWORD      = "password"          #password on that account
XB_FW_VER     = "0x2182"            #XBee firmware version to upgrade to
FILENAME      = "XBP24-ZB_2182.ebl" #XBee file to use to do the upgrade
MAC_LIST      = "maclist.csv"       #List of devices to upgrade
USE_SKIP_LIST = 0                   #Read from a skip list and skip devices found in this list
ADD_SKIP_LIST = 1                   #Write to a skip list of devices successfully updated

#non-configurables
DEV_STATE_PREV_UPDATED_CON  = "Prev Updated Conn "
DEV_STATE_PREV_UPDATED_DISC = "Prev Updated Disc "
DEV_STATE_DISCONNECTED      = "Disconnected      "
DEV_STATE_UPDATED           = "Updated      Conn "
DEV_STATE_FAILED_UPDATE     = "Failed Update     "
FILES_REMOVED               = 1
FILES_NOT_REMOVED           = 0
STATE_SUCCESS               = 1
STATE_FAIL                  = 0

msg_part = "/ws/DeviceCore/?condition=devConnectwareId="
sci_header = "<sci_request version=\"1.0\"><send_message><targets><device id=\""
sci_header_refresh = "<sci_request version=\"1.0\"><send_message cache=\"false\"><targets><device id=\""
sci_end_targets = "\"/></targets>"
sci_tail = "</send_message></sci_request>"
rci_delete_file = "<rci_request version=\"1.1\"><do_command target=\"file_system\"><rm name=\"/WEB/" + FILENAME + "\"/></do_command></rci_request>"
rci_delete_file_mesh = "<rci_request version=\"1.1\"><do_command target=\"file_system\"><rm name=\"/WEB/mesh/" + FILENAME + "\"/></do_command></rci_request>"
rci_reboot = "<rci_request version=\"1.1\"><reboot/></rci_request>"
rci_query_zigbee = "<rci_request version=\"1.1\"><query_state><zigbee_state/></query_state></rci_request>"
rci_send_update_cmd = "<rci_request version=\"1.1\"><do_command target=\"zigbee\"><fw_update file=\"WEB/" + FILENAME + "\"/></do_command></rci_request>"
rci_upload_file_cmd = "" #actual command calculated below
lineoutput = []
targetList = []
total_count = 0
connected_count = 0
disconnected_count = 0
updated_count = 0
dpLastConnectTime = ""
debug_output = ""

#functions
def checkfw(refresh):
    
    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("POST", "/ws/sci")
    connection.putheader("Authorization", "Basic " + auth)
    connection.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
    
    if refresh == 1:
        scimsg = sci_header_refresh + devstr + sci_end_targets + rci_query_zigbee + sci_tail
    else:
        scimsg = sci_header + devstr + sci_end_targets + rci_query_zigbee + sci_tail
        
    connection.putheader("Content-length", "%d" % len(scimsg))    
    connection.endheaders()
    connection.send(scimsg)
    resp = connection.getresponse()
    respdata = resp.read()
    connection.close()
    
    retA = respdata.find("<firmware_version>")
    
    if retA >= 0:
        retB = respdata.find("</firmware_version>")
        retval = respdata[retA + 18:retB]
    else:
        retval = "Unknown"     
    
    return retval


def checkStatus(connected_count, disconnected_count):
    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("GET", msg_part + "'" + devstr + "'")
    connection.putheader("Authorization", "Basic " + auth)
    connection.endheaders()
    connection.send("")
    response = connection.getresponse()
    data = response.read()
    connection.close()
    
    retA = data.find("<dpLastKnownIp>")
    
    if retA > 0:
        retB = data.find("</dpLastKnownIp>")
        lastknownIP = data[retA + 15:retB]
    else:
        lastknownIP = "Unknown IP"
    
    retA = data.find("<dpConnectionStatus>")
    retB = data.find("</dpConnectionStatus>")
    dpConnectionStatusNum = data[retA + 20:retB]

    if dpConnectionStatusNum == "1":
        dpConnectionStatus = "Connected"
        disconnected_count = disconnected_count + 1 
    else:
        dpConnectionStatus = DEV_STATE_DISCONNECTED
        connected_count = connected_count + 1

    retA = data.find("<dpLastConnectTime>")
    
    if retA > 0:
        retB = data.find("</dpLastConnectTime>")
        dpLastConnectTimeUTC = data[retA + 19:retB]
        dpLastConnectTime = dpLastConnectTimeUTC[11:19] + " " + dpLastConnectTimeUTC[5:7] + "-" + dpLastConnectTimeUTC[8:10] + "-" + dpLastConnectTimeUTC[:4]
    else:
        dpLastConnectTime = "Never Connected    "
         
    return dpConnectionStatus, dpLastConnectTime, connected_count, disconnected_count, lastknownIP

def rci_cmd(rci_frame):
    status = STATE_FAIL
    output = ""
    
    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("POST", "/ws/sci")
    connection.putheader("Authorization", "Basic " + auth)
    connection.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
    scimsg = sci_header + devstr + sci_end_targets + rci_frame + sci_tail
    connection.putheader("Content-length", "%d" % len(scimsg))    
    connection.endheaders()
    connection.send(scimsg)
    resp = connection.getresponse()
    respdata = resp.read()
    connection.close()
    
    retA = respdata.find("<error")
    
    if retA >= 0: #error condition
        retB = respdata.find("</error>")
        output = respdata[retA:retB+8]
    else:
        status = STATE_SUCCESS
    
    return status, output

def updatefw():
    status = DEV_STATE_FAILED_UPDATE
    
    retval, output = rci_cmd(rci_upload_file_cmd)
    
    if retval != STATE_SUCCESS:
        return status, output
    
    retval, output = rci_cmd(rci_send_update_cmd)

    if retval != STATE_SUCCESS:
        return status, output
    
    output = checkfw(1)
    
    if output == XB_FW_VER:
        status = DEV_STATE_UPDATED
        
    return status, output


def removeFiles():
    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("POST", "/ws/sci")
    connection.putheader("Authorization", "Basic " + auth)
    connection.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
    scimsg = sci_header + devstr + sci_end_targets + rci_delete_file + sci_tail
    connection.putheader("Content-length", "%d" % len(scimsg))    
    connection.endheaders()
    connection.send(scimsg)
    resp1 = connection.getresponse()
    resp1data = resp1.read()
    connection.close()
    
    connection = httplib.HTTPConnection(IDIGI_ADDR)
    connection.putrequest("POST", "/ws/sci")
    connection.putheader("Authorization", "Basic " + auth)
    connection.putheader("Content-type", "text/xml; charset=\"UTF-8\"")
    scimsg = sci_header + devstr + sci_end_targets + rci_delete_file_mesh + sci_tail
    connection.putheader("Content-length", "%d" % len(scimsg))    
    connection.endheaders()
    connection.send(scimsg)
    resp2 = connection.getresponse()
    resp2data = resp2.read()
    connection.close()
    
    if resp1data.find("error") < 0 or resp2data.find("error") < 0:
        return FILES_REMOVED

    return FILES_NOT_REMOVED


#Start Main#
csvfh = open(MAC_LIST, 'r')
csvString = csvfh.read()
csvList = csvString.split('\n')

for devmac in csvList:
   mac = devmac.strip()
   appendme = ''.join(mac.split(":"))
   if string.find(appendme, "#") < 0 and len(appendme) > 0:
       targetList.append("00000000-00000000-00409DFF-FF" + appendme[6:]) #TODO should probably convert using whole MAC instead of assuming first 6 chars

auth = base64.encodestring("%s:%s" % (USERNAME, PASSWORD))[:-1]

if USE_SKIP_LIST == 1 or ADD_SKIP_LIST == 1:
    import skiplist
    skipList = list(skiplist.skiplist)
    

hFile = open(FILENAME, "rb")
fileData = hFile.read()
hFile.close()
tmpImg = base64.encodestring(fileData)

rci_upload_file_cmd = "<rci_request version=\"1.1\"><do_command target=\"file_system\"><put_file encode=\"base64\" name=\"/WEB/"+ FILENAME + \
                        "\"><data encode=\"base64\">" + tmpImg[:len(tmpImg)-1] + "</data></put_file></do_command></rci_request>"

print "Starting Main Loop"

for devstr in targetList:
    if USE_SKIP_LIST == 1:
        mac = "00409D" + devstr[29:] #TODO should probably convert using whole MAC instead of assuming first 6 chars
        if mac in skipList:
            print "Skipping " + devstr
            pass

    debug_output = ""
    
    fwver = checkfw(0)#check firmware version of XBee module
    modstatus, dpLastConnectTime, connected_count, disconnected_count, lastknownIP = checkStatus(connected_count, disconnected_count)#check connection status

    if modstatus != DEV_STATE_DISCONNECTED and fwver != XB_FW_VER:
        fwupdate, debug_output = updatefw()#attempt to update XBee Module
        
        if fwupdate == DEV_STATE_UPDATED:
            dev_status = DEV_STATE_UPDATED
        else:
            dev_status = DEV_STATE_FAILED_UPDATE

    elif modstatus != DEV_STATE_DISCONNECTED and fwver == XB_FW_VER:
        dev_status = DEV_STATE_PREV_UPDATED_CON
            
    else: #modstatus == DEV_STATE_DISCONNECTED
        dev_status = DEV_STATE_DISCONNECTED

    retval = removeFiles()#delete files as needed

    if retval == FILES_REMOVED:
        removed_files = "Yes"
        r_status, r_output = rci_cmd(rci_reboot) #reboot device if we successfully removed files
    else:
        removed_files = "No"
        
    total_count = total_count + 1

    if ADD_SKIP_LIST == 1:
        if dev_status == DEV_STATE_PREV_UPDATED_CON or dev_status == DEV_STATE_PREV_UPDATED_DISC or dev_status == DEV_STATE_UPDATED:
            mac = "00409D" + devstr[29:] #TODO should probably convert using whole MAC instead of assuming first 6 chars
            if mac not in skipList: 
                skipList.append(mac)

    t_output = devstr + " IP:" + lastknownIP + "\tStatus: " + dev_status + " LC: " + dpLastConnectTime + "\tRemoved Files: " + removed_files + " " + debug_output
    print t_output
    t_output = devstr + "," + lastknownIP + "," + dev_status + "," + dpLastConnectTime + "," + removed_files
    lineoutput.append(t_output) 
    

fh = open("output.csv", "w")
for line in lineoutput:
    fh.write(line)
fh.close()

if ADD_SKIP_LIST == 1:
    fh = open("skiplist.py", 'w')
    fh.write("skiplist = [\r\n")
    for mac in skipList:
        linetoadd = ""
        linetoadd = "\"%s\",\n" % mac
        fh.write(linetoadd)
    fh.write("]\n")
    fh.close()

print "\nComplete[" + str(total_count) + "]:" 
print "[Connected:" + str(connected_count) + "] [Disconnected:" + str(disconnected_count) + "]"
print "[Updated:" + str(updated_count)
