### Libraries to load
import glob, matplotlib, pprint, requests, math, time, os
import pandas as pd
import numpy as np
from collections import Counter
from itertools import repeat
import matplotlib.pyplot as plt


### Loading the data
os.chdir = ("/home/pi/lora_gateway/util_pkt_logger")
docs = glob.glob(os.chdir + "/*.csv")

if len(docs) > 1:
    print("1. Reading " + str(len(docs)) + " csv files...")
    all_dat = []
    for doc in docs:
        all_dat.append(pd.read_csv(doc, encoding = 'unicode_escape'))

    every = pd.concat(all_dat)
    every.reset_index(inplace=True)
    every = every.dropna(subset=["UTC timestamp"])
    every = every.drop("index", axis=1)

elif len(docs) == 1:
    print("1. Reading one csv file...")
    print(docs)
    every = pd.read_csv(docs[0], encoding = 'unicode_escape')
    every.reset_index(inplace=True)
    every = every.dropna(subset=["UTC timestamp"])
    every = every.drop("index", axis=1)

elif len(docs) == 0:
    print("ERROR: No Data Files!")
    exit()


### Filtering the data for ABP
print("2. Filtering the data for ABP...")
info = every.payload
info = list(map(str, info)) # Convert payload to string. This is needed as some payloads only have integers.
devs = []
fcnts = []
mhdrs = []
fctrls = []
devaddr_revl = []
fcnt_revl = []
devaddr_len = 8

for i in info:
    devaddr = i[2:11].split("-")
    devaddr = "".join(devaddr)
    fcnt = i[13:17].split("-")
    fcnt = "".join(fcnt)
    fctrl = i[11:13].split("-")
    fctrl = "".join(fctrl)
    mhdr = i[:2].split("-")
    mhdr = "".join(mhdr)
    devs.append(devaddr)
    fcnts.append(fcnt)
    fctrls.append(fctrl)
    mhdrs.append(mhdr)

# reversing the little endian form of DevAddr and Fcnt
for row in devs:
    fixed = []
    for j in range(int(len(row)/2)):
        fixed.append(row[j*2:(j+1)*2])
    fixed.reverse()
    devaddr_rev = "".join(fixed)
    devaddr_revl.append(devaddr_rev)

for row in fcnts:
    fixed = []
    for j in range(int(len(row)/2)):
        fixed.append(row[j*2:(j+1)*2])
    fixed.reverse()
    fcnt_rev = "".join(fixed)
    fcnt_revl.append(fcnt_rev)

# Remove payloads shorter than the length of DevAddr, as these will not provide sufficient information to identify ABP devices
ttn_addr = []
all_addr = []
other_addr = []
network = []
activation = []
for addr in devaddr_revl:
    if len(addr) == devaddr_len:
        # Network prefix assignments to the World
        if addr[:2] == "26" or addr[:2] == "27":
            ttn_addr.append(addr) # potential DevAddr from TTN
            all_addr.append(addr)
            network.append("World: The Things Network")
            activation.append("ABP")
        elif addr[:2] == "04" or addr[:2] == "05":
            all_addr.append(addr)
            network.append("World: Actility")
            activation.append("ABP")
        elif addr[:2] == "10" or addr[:2] == "11":
            all_addr.append(addr)
            network.append("World: Orbiwise")
            activation.append("ABP")
        elif addr[:2] == "1a" or addr[:2] == "1b":
            all_addr.append(addr)
            network.append("World: SK Telecom")
            activation.append("ABP")
        elif addr[:2] == "1c" or addr[:2] == "1d":
            all_addr.append(addr)
            network.append("World: SagemCom")
            activation.append("ABP")
        elif addr[:2] == "24" or addr[:2] == "25":
            all_addr.append(addr)
            network.append("World: Kerlink")
            activation.append("ABP")
        elif addr[:2] == "2a" or addr[:2] == "2b":
            all_addr.append(addr)
            network.append("World: Cisco Systems")
            activation.append("ABP")
        elif addr[:2] == "2e" or addr[:2] == "2f":
            all_addr.append(addr)
            network.append("World: MultiTech Systems")
            activation.append("ABP")
        elif addr[:2] == "30" or addr[:2] == "31":
            all_addr.append(addr)
            network.append("World: Loriot")
            activation.append("ABP")
        elif addr[:2] == "32" or addr[:2] == "33":
            all_addr.append(addr)
            network.append("World: NNNCo")
            activation.append("ABP")
        elif addr[:2] == "34" or addr[:2] == "35":
            all_addr.append(addr)
            network.append("World: Flashnet")
            activation.append("ABP")
        elif addr[:2] == "36" or addr[:2] == "37":
            all_addr.append(addr)
            network.append("World: TrackNet")
            activation.append("ABP")
        elif addr[:2] == "38" or addr[:2] == "39":
            all_addr.append(addr)
            network.append("World: Lar.Tech")
            activation.append("ABP")
        elif addr[:2] == "3a" or addr[:2] == "3b":
            all_addr.append(addr)
            network.append("World: Swiss Led")
            activation.append("ABP")
        elif addr[:2] == "42" or addr[:2] == "43":
            all_addr.append(addr)
            network.append("World: Patavina Technologies")
            activation.append("ABP")
        elif addr[:2] == "48" or addr[:2] == "49":
            all_addr.append(addr)
            network.append("World: Gimasi")
            activation.append("ABP")
        elif addr[:2] == "4a" or addr[:2] == "4b":
            all_addr.append(addr)
            network.append("World: Talkpool")
            activation.append("ABP")
        elif addr[:2] == "4e" or addr[:2] == "4f":
            all_addr.append(addr)
            network.append("World: MCF88 SRL")
            activation.append("ABP")
        elif addr[:2] == "52" or addr[:2] == "53":
            all_addr.append(addr)
            network.append("World: GIoT")
            activation.append("ABP")
        elif addr[:2] == "54" or addr[:2] == "55":
            all_addr.append(addr)
            network.append("World: M2B Communications")
            activation.append("ABP")
        elif addr[:2] == "5a" or addr[:2] == "5b":
            all_addr.append(addr)
            network.append("World: Rai Way")
            activation.append("ABP")
        elif addr[:2] == "5c" or addr[:2] == "5d":
            all_addr.append(addr)
            network.append("World: Levikom")
            activation.append("ABP")
        elif addr[:2] == "60" or addr[:2] == "61":
            all_addr.append(addr)
            network.append("World: SoftBank")
            activation.append("ABP")
        elif addr[:2] == "62" or addr[:2] == "63":
            all_addr.append(addr)
            network.append("World: Inmarsat")
            activation.append("ABP")
        elif addr[:4] == "e006" or addr[:4] == "e007":
            all_addr.append(addr)
            network.append("World: IOTCAN")
            activation.append("ABP")
        elif addr[:4] == "e00a" or addr[:4] == "e00b":
            all_addr.append(addr)
            network.append("World: IoT Network AS")
            activation.append("ABP")
        elif addr[:4] == "e00e" or addr[:4] == "e00f":
            all_addr.append(addr)
            network.append("World: EDF")
            activation.append("ABP")
        elif addr[:4] == "e018" or addr[:4] == "e019":
            all_addr.append(addr)
            network.append("World: SenSys")
            activation.append("ABP")
        elif addr[:4] == "e01c" or addr[:4] == "e01d":
            all_addr.append(addr)
            network.append("World: Spark")
            activation.append("ABP")
        elif addr[:4] == "e020" or addr[:4] == "e021":
            all_addr.append(addr)
            network.append("World: Senet")
            activation.append("ABP")
        elif addr[:4] == "e026" or addr[:4] == "e027":
            all_addr.append(addr)
            network.append("World: Actility")
            activation.append("ABP")
        elif addr[:4] == "e02a" or addr[:4] == "e02b":
            all_addr.append(addr)
            network.append("World: Kerlink")
            activation.append("ABP")
        elif addr[:4] == "e02c" or addr[:4] == "e02d":
            all_addr.append(addr)
            network.append("World: Cisco")
            activation.append("ABP")
        elif addr[:4] == "e02e" or addr[:4] == "e02f":
            all_addr.append(addr)
            network.append("World: Schneider Electric")
            activation.append("ABP")
        elif addr[:4] == "e030" or addr[:4] == "e031":
            all_addr.append(addr)
            network.append("World: ZENNER")
            activation.append("ABP")
        elif addr[:4] == "e038" or addr[:4] == "e039":
            all_addr.append(addr)
            network.append("World: MachineQ/Comcast")
            activation.append("ABP")
        elif addr[:6] == "fc0004" or addr[:6] == "fc0005" or addr[:6] == "fc0006" or addr[:6] == "fc0007":
            all_addr.append(addr)
            network.append("World: Nordic Automation Systems")
            activation.append("ABP")
        elif addr[:6] == "fc0008" or addr[:6] == "fc0009" or addr[:6] == "fc000a" or addr[:6] == "fc000b":
            all_addr.append(addr)
            network.append("World: ResIOT")
            activation.append("ABP")
        elif addr[:6] == "fc000c" or addr[:6] == "fc000d" or addr[:6] == "fc000e" or addr[:6] == "fc000f":
            all_addr.append(addr)
            network.append("World: SYSDEV")
            activation.append("ABP")
        elif addr[:6] == "fc0020" or addr[:6] == "fc0021" or addr[:6] == "fc0022" or addr[:6] == "fc0023":
            all_addr.append(addr)
            network.append("World: Definium Technologies")
            activation.append("ABP")
        elif addr[:6] == "fc0030" or addr[:6] == "fc0031" or addr[:6] == "fc0032" or addr[:6] == "fc0033":
            all_addr.append(addr)
            network.append("World: nFore Technology")
            activation.append("ABP")
        elif addr[:6] == "fc0044" or addr[:6] == "fc0045" or addr[:6] == "fc0046" or addr[:6] == "fc0047":
            all_addr.append(addr)
            network.append("World: Digital Nordix AB (DNX)")
            activation.append("ABP")
        elif addr[:6] == "fc0058" or addr[:6] == "fc0059" or addr[:6] == "fc005a" or addr[:6] == "fc005b":
            all_addr.append(addr)
            network.append("World: Schneider Electric")
            activation.append("ABP")
        elif addr[:6] == "fc0060" or addr[:6] == "fc0061" or addr[:6] == "fc0062" or addr[:6] == "fc0063":
            all_addr.append(addr)
            network.append("World: ZENNER")
            activation.append("ABP")
        elif addr[:6] == "fc0068" or addr[:6] == "fc0069" or addr[:6] == "fc006a" or addr[:6] == "fc006b":
            all_addr.append(addr)
            network.append("World: REQUEA")
            activation.append("ABP")
        elif addr[:6] == "fc007c" or addr[:6] == "fc007d" or addr[:6] == "fc007e" or addr[:6] == "fc007f":
            all_addr.append(addr)
            network.append("World: mcf88 SRL")
            activation.append("ABP")
        elif addr[:6] == "fc0084" or addr[:6] == "fc0085" or addr[:6] == "fc0086" or addr[:6] == "fc0087":
            all_addr.append(addr)
            network.append("World: Hiber")
            activation.append("ABP")
        elif addr[:6] == "fc0098" or addr[:6] == "fc0099" or addr[:6] == "fc009a" or addr[:6] == "fc009b":
            all_addr.append(addr)
            network.append("World: Mirakonta")
            activation.append("ABP")
        
        # Network prefix assignments to Australia
        elif addr[:2] == "0a" or addr[:2] == "0b":
            all_addr.append(addr)
            network.append("Singapore Indonesia Australia Africa India: SingTel")
            activation.append("ABP")
        elif addr[:2] == "46" or addr[:2] == "47":
            all_addr.append(addr)
            network.append("Australia New Zealand: Ventia")
            activation.append("ABP")
        elif addr[:2] == "58" or addr[:2] == "59":
            all_addr.append(addr)
            network.append("Australia: Airlora")
            activation.append("ABP")
        
        # Neither
        else:
            all_addr.append(addr)
            other_addr.append(addr) # potential DevAddr from non-TTN network
            network.append("-")
            activation.append("-")
    else:
        all_addr.append("-")
        network.append("-")
        activation.append("-")
        


### Filtering the data for OTAA
print("3. Filtering the data for OTAA...")
info = every.payload
info = list(map(str, info))
deveuis = []
appeuis = []
deveui_revl = []
appeui_revl = []
deveui_len = 16
appeui_len = 16

for i in range(len(info)):
    if list(map(str, mhdrs))[i] == "00":
        deveui = info[i][20:38].split("-")
        deveui = "".join(deveui)
        appeui = info[i][2:20].split("-")
        appeui = "".join(appeui)
        deveuis.append(deveui)
        appeuis.append(appeui)
    else:
        deveuis.append("-")
        appeuis.append("-")

for row in deveuis: # reversing DevEUI's little endian form
    fixed = []
    for j in range(int(len(row)/2)):
        fixed.append(row[j*2:(j+1)*2])
    fixed.reverse()
    deveui_rev = "".join(fixed)
    deveui_revl.append(deveui_rev)

for row in appeuis: # reversing AppEUI's little endian form
    fixed = []
    for j in range(int(len(row)/2)):
        fixed.append(row[j*2:(j+1)*2])
    fixed.reverse()
    appeui_rev = "".join(fixed)
    appeui_revl.append(appeui_rev)

filt_deveui = [] # Remove payloads shorter than the length of DevAddr
filt_appeui = []
all_deveui = []
all_appeui = []
for deveui_s in deveui_revl:
    if len(deveui_s) == deveui_len:
        filt_deveui.append(deveui_s)
        all_deveui.append(deveui_s)
    else:
        all_deveui.append("-")

for appeui_s in appeui_revl:
    if len(appeui_s) == appeui_len:
        filt_appeui.append(appeui_s)
        all_appeui.append(appeui_s)
    else:
        all_appeui.append("-")
deveui_results = Counter(filt_deveui)
appeui_results = Counter(filt_appeui)



### Using API to obtain device manufacturer
print("4. Looking up device manufacturers...")
MAC_URL = "https://api.macvendors.com/v1/lookup/%s"
deveui_knowns = []
appeui_knowns = []
deveui_total = len(list(deveui_results.keys()))
appeui_total = len(list(appeui_results.keys()))

num = 1
starttime = time.time()
for eui in list(deveui_results.keys()):
    print("Looking up", num, "out of", deveui_total, "DevEUIs...")
    r = requests.get(MAC_URL % eui)
    if "data" in list(r.json().keys()):
        deveui_knowns.append([eui, r.json()])
    num += 1
    time.sleep(0.5)
    
num = 1
starttime = time.time()
for eui in list(appeui_results.keys()):
    print("Looking up", num, "out of", appeui_total, "AppEUIs...")
    r = requests.get(MAC_URL % eui)
    if "data" in list(r.json().keys()):
        appeui_knowns.append([eui, r.json()])
    num += 1
    time.sleep(0.5)
    


### Adding new columns
# Turn DevEUI and AppEUI knowns into dictionary
print("5. Adding new columns...")
deveui_knowns_dict = {}
appeui_knowns_dict = {}
for i in range(len(deveui_knowns)):
    deveui_knowns_dict[deveui_knowns[i][0]] = deveui_knowns[i][1]
for i in range(len(appeui_knowns)):
    appeui_knowns_dict[appeui_knowns[i][0]] = appeui_knowns[i][1]
    
# Get a list of devaddress and mic
devaddress = []
mic = []
for i in range(len(activation)):
    if activation[i] != "-":
        devaddress.append(devaddr_revl[i])
        if len("".join(info[i].split("-"))[-8:]) == 8:
            mic.append("".join(info[i].split("-"))[-8:])
        else:
            mic.append("-")
    else:
        devaddress.append(devaddr_revl[i])
        if len("".join(info[i].split("-"))[-8:]) == 8:
            mic.append("".join(info[i].split("-"))[-8:])
        else:
            mic.append("-")

# Create a dataframe for OTAA
dev_matcheui = []
app_matcheui = []
dev_manufeui = []
app_manufeui = []
manumac = []
for i in deveui_knowns:
    dev_matcheui.append(i[0])
    dev_manufeui.append(i[1].get("data").get("organization_name"))
    manumac.append(i[1].get("data").get("assignment"))

for i in appeui_knowns:
    app_matcheui.append(i[0])
    app_manufeui.append(i[1].get("data").get("organization_name"))

#manufdf = pd.DataFrame({"DevEUI": dev_matcheui, "DevManufacturer": dev_manufeui, "MAC_prefix": manumac,
#                        "AppEUI": app_matcheui, "AppManufacturer": app_manufeui})

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])
deveui_res = dictfilt(deveui_results, dev_matcheui)
appeui_res = dictfilt(appeui_results, app_matcheui)

# Update the new columns' values
dev_manufid = []
app_manufid = []
for i in range(len(all_deveui)):
    #if int(every["size"][i]) == 23 and str(mhdrs[i]) == "00":
    if (all_deveui[i] in dev_matcheui or all_appeui[i] in app_matcheui) and str(mhdrs[i]) == "00":
        activation[i] = "OTAA"
        network[i] = "-"
        devaddress[i] = all_deveui[i]
        if all_deveui[i] in dev_matcheui and all_appeui[i] in app_matcheui:
            dev_manufid.append(deveui_knowns_dict[all_deveui[i]].get("data").get("organization_name").replace(',', ''))
            app_manufid.append(appeui_knowns_dict[all_appeui[i]].get("data").get("organization_name").replace(',', ''))
        elif all_deveui[i] in dev_matcheui and all_appeui[i] not in app_matcheui:
            dev_manufid.append(deveui_knowns_dict[all_deveui[i]].get("data").get("organization_name").replace(',', ''))
            app_manufid.append("-")
        elif all_deveui[i] not in dev_matcheui and all_appeui[i] in app_matcheui:
            dev_manufid.append("-")
            app_manufid.append(appeui_knowns_dict[all_appeui[i]].get("data").get("organization_name").replace(',', ''))
        else:
            dev_manufid.append("-")
            app_manufid.append("-")
    elif int(every["size"][i]) == 23 and str(mhdrs[i]) == "00":
        activation[i] = "OTAA"
        network[i] = "-"
        devaddress[i] = all_deveui[i]
        if all_deveui[i] in dev_matcheui and all_appeui[i] in app_matcheui:
            dev_manufid.append(deveui_knowns_dict[all_deveui[i]].get("data").get("organization_name").replace(',', ''))
            app_manufid.append(appeui_knowns_dict[all_appeui[i]].get("data").get("organization_name").replace(',', ''))
        elif all_deveui[i] in dev_matcheui and all_appeui[i] not in app_matcheui:
            dev_manufid.append(deveui_knowns_dict[all_deveui[i]].get("data").get("organization_name").replace(',', ''))
            app_manufid.append("-")
        elif all_deveui[i] not in dev_matcheui and all_appeui[i] in app_matcheui:
            dev_manufid.append("-")
            app_manufid.append(appeui_knowns_dict[all_appeui[i]].get("data").get("organization_name").replace(',', ''))
        else:
            dev_manufid.append("-")
            app_manufid.append("-")
    else:
        dev_manufid.append("-")
        app_manufid.append("-")

all_fcnt = []
all_fctrl = []
all_mhdr = []
final_appeui = []
for i in range(len(activation)):
    if activation[i] == "ABP":
        final_appeui.append("-")
        all_mhdr.append(mhdrs[i])
        all_fcnt.append(fcnt_revl[i])
        all_fctrl.append(fctrls[i])
    elif activation[i] == "-":
        final_appeui.append("-")
        all_fcnt.append(fcnt_revl[i])
        all_fctrl.append(fctrls[i])
        all_mhdr.append(mhdrs[i])        
    elif activation[i] == "OTAA":
        final_appeui.append(all_appeui[i])
        all_fcnt.append("-")
        all_fctrl.append("-")
        all_mhdr.append(mhdrs[i])

# Converting MHDR (hex) to binary and getting message type
mtype_raw = []
for hexdec in all_mhdr:
    if hexdec == "-":
        mtype_raw.append("-")
    elif hexdec == "na":
        mtype_raw.append("-")
    elif hexdec == "9.":
        mtype_raw.append("-")
    else:
        scale = 16 ## equals to hexadecimal
        num_of_bits = 8
        mtype_raw.append(str(bin(int(hexdec, scale))[2:].zfill(num_of_bits)[:3]))

mtype_desc = []
for mtype in mtype_raw:
    if mtype == "000":
        mtype_desc.append("Join-request")
    elif mtype == "001":
        mtype_desc.append("Join-accept")
    elif mtype == "010":
        mtype_desc.append("Unconfirmed Data Up")
    elif mtype == "011":
        mtype_desc.append("Unconfirmed Data Down")
    elif mtype == "100":
        mtype_desc.append("Confirmed Data Up")
    elif mtype == "101":
        mtype_desc.append("Confirmed Data Down")
    elif mtype == "110":
        mtype_desc.append("Rejoin-request")
    elif mtype == "111":
        mtype_desc.append("Proprietary")
    else:
        mtype_desc.append("-")
        
# Converting fcnt (hex) to decimal       
int_fcnt = []
for fcnt_hex in all_fcnt:
    if str(fcnt_hex) == "-":
        int_fcnt.append("-")
    elif str(fcnt_hex) == "":
        int_fcnt.append("-")
    else:
        dec = int(fcnt_hex, 16)
        int_fcnt.append(dec)

# Correcting the time
yr = []
month = []
day = []
hrs = []
mins = []
secs = []
for i in list(every["UTC timestamp"]):
    #split for date and time
    date_split = i.split(" ")
    time_split = date_split[1].split(":")
    
    if int(date_split[0].split("-")[0]) == 2013:
        y = int(date_split[0].split("-")[0]) + 0
        if len(str(y)) == 1:
            yr.append("0"+str(y))
        else:
            yr.append(str(y))
        m = int(date_split[0].split("-")[1]) - 0
        if len(str(m)) == 1:
            month.append("0"+str(m))
        else:
            month.append(str(m))
        d = int(date_split[0].split("-")[2]) - 0
        if len(str(d)) == 1:
            day.append("0"+str(d))
        else:
            day.append(str(d))
        h = int(time_split[0]) + 0
        if len(str(h)) == 1:
            hrs.append("0"+str(h))
        else:
            hrs.append(str(h))
        m = int(time_split[1]) + 0
        if len(str(m)) == 1:
            mins.append("0"+str(m))
        else:
            mins.append(str(m))
        secs.append(time_split[2])
    
    elif int(date_split[0].split("-")[0]) == 2014:
        y = int(date_split[0].split("-")[0]) + 0
        if len(str(y)) == 1:
            yr.append("0"+str(y))
        else:
            yr.append(str(y))
        m = int(date_split[0].split("-")[1]) + 0
        if len(str(m)) == 1:
            month.append("0"+str(m))
        else:
            month.append(str(m))
        d = int(date_split[0].split("-")[2]) + 0
        if len(str(d)) == 1:
            day.append("0"+str(d))
        else:
            day.append(str(d))
        h = int(time_split[0]) + 0
        if len(str(h)) == 1:
            hrs.append("0"+str(h))
        else:
            hrs.append(str(h))
        m = int(time_split[1]) + 0
        if len(str(m)) == 1:
            mins.append("0"+str(m))
        else:
            mins.append(str(m))
        secs.append(time_split[2])
   
    else:
        yr.append(date_split[0].split("-")[0])
        month.append(date_split[0].split("-")[1])
        day.append(date_split[0].split("-")[2])
        h = int(time_split[0]) + 0
        if len(str(h)) == 1:
            hrs.append("0"+str(h))
        else:
            hrs.append(str(h))
        mins.append(time_split[1])
        secs.append(time_split[2])

yr = list(map(str, yr))
month = list(map(str, month))
day = list(map(str, day))
hrs = list(map(str, hrs))
mins = list(map(str, mins))
secs = list(map(str, secs))

# correcting cases where mins >= 60 and hours >= 24
for i in range(len(mins)):
    if int(mins[i]) >= 60:
        h = int(hrs[i])
        h += 1
        if len(str(h)) == 1:
            hrs[i] = "0"+str(h)
        else:
            hrs[i] = str(h)
        m = int(mins[i])
        m -= 60
        if len(str(m)) == 1:
            mins[i] = "0"+str(m)
        else:
            mins[i] = str(m)

for i in range(len(hrs)):
    if int(hrs[i]) >= 24:
        d = int(day[i])
        d += 1
        if len(str(d)) == 1:
            day[i] = "0"+str(d)
        else:
            day[i] = str(d)
        h = int(hrs[i])
        h -= 24
        if len(str(h)) == 1:
            hrs[i] = "0"+str(h)
        else:
            hrs[i] = str(h)

# correcting cases where day is > possible value within a certain month
for i in range(len(day)):
    if int(day[i]) > 30:
        if month[i] in ["4", "6", "9", "11"]:
            m = int(month[i])
            m += 1
            if len(str(m)) == 1:
                month[i] = "0"+str(m)
            else:
                month[i] = str(m)
            d = int(day[i])
            d -= 30
            if len(str(d)) == 1:
                day[i] = "0"+str(d)
            else:
                day[i] = str(d)
    elif int(day[i]) > 28:
        if month[i] in ["2"]:
            if int(yr[i])%4 == 0 and int(day[i]) > 29:
                m = int(month[i])
                m += 1
                if len(str(m)) == 1:
                    month[i] = "0"+str(m)
                else:
                    month[i] = str(m)
                d = int(day[i])
                d -= 29
                if len(str(d)) == 1:
                    day[i] = "0"+str(d)
                else:
                    day[i] = str(d)
            elif int(yr[i])%4 != 0:
                m = int(month[i])
                m += 1
                if len(str(m)) == 1:
                    month[i] = "0"+str(m)
                else:
                    month[i] = str(m)
                d = int(day[i])
                d -= 28
                if len(str(d)) == 1:
                    day[i] = "0"+str(d)
                else:
                    day[i] = str(d)

# correcting cases where year needs to be changed
for i in range(len(month)):
    if int(month[i]) == 12 and int(day[i]) > 31:
        y = int(yr[i])
        y += 1
        yr[i] = str(y)
        m = 1
        if len(str(m)) == 1:
            month[i] = "0"+str(m)
        else:
            month[i] = str(m)
        d = 1
        if len(str(d)) == 1:
            day[i] = "0"+str(d)
        else:
            day[i] = str(d)

# concatenating the fixed date time
date = []
aus_time = []

for i in range(len(yr)):
    date.append("-".join([yr[i], month[i], day[i]]))
    aus_time.append(":".join([hrs[i], mins[i], secs[i]]))

every["date"] = date
every["time"] = aus_time
every["activation"] = activation
every["network"] = network
every["DevEUI or DevAddr"] = devaddress
every["AppEUI"] = final_appeui
every["fctrl"] = all_fctrl
every["fcnt"] = int_fcnt
every["mhdr"] = all_mhdr
every["mtype"] = mtype_raw
every["mtype_desc"] = mtype_desc
every["MIC"] = mic
every["deveui_manufacturer"] = dev_manufid
every["appeui_manufacturer"] = app_manufid

every = every[['UTC timestamp', 'date', 'time', 'us count', 'frequency', 'RF chain', 'RX chain','status',
               'bandwidth', 'datarate', 'coderate', 'RSSI', 'SNR', 'size', 'DevEUI or DevAddr',
               'AppEUI', 'fctrl', 'fcnt', 'mhdr', 'mtype', 'mtype_desc', 'MIC', 'activation',
               'network', 'deveui_manufacturer', 'appeui_manufacturer', 'payload']]

filtered = every.copy()
no_fcnt = filtered.loc[(filtered.fcnt == "-") & (filtered.activation != "OTAA")].index
filtered = filtered.drop(no_fcnt)



### Calculating the time difference
# Function for truncating decimals without rounding to 3 dp
print("6. Calculating the time difference in seconds...")
time_sec = []
timeutc = list(pd.to_datetime(filtered["UTC timestamp"]))
for i in range(len(timeutc)):
    time_sec.append(pd.Timedelta(timeutc[i]-timeutc[0]).total_seconds())

# Add a new column to the dataframe
filtered["sec_diff"] = time_sec



### Algorithm for identifying unique devices
# Split it from the original dataframe by DevAddr or DevEUI
print("7. Identifying unique devices...")
l_freq = []
l_devaddr915 = []
l_devaddr923 = []
all_freq = []
filtered = filtered.loc[filtered.status == "CRC_OK"]

# ISM band
fr915 = [916800000, 917000000, 917200000, 917400000, 917600000, 917800000, 918000000, 91820000]
fr923 = [922000000, 922100000, 922200000, 922400000, 922600000, 922800000, 923000000, 923200000, 923400000]

freq915 = filtered.loc[filtered.frequency.isin(fr915)]
if freq915.shape[0] > 0:
    freq915 = filtered.loc[filtered.frequency.isin(fr915)].index
    filtered.loc[freq915, "Freq_Plan"] = "915band"
    all_freq.append(freq915)

freq923 = filtered.loc[filtered.frequency.isin(fr923)]
if freq923.shape[0] > 0:
    freq923 = filtered.loc[filtered.frequency.isin(fr923)].index
    filtered.loc[freq923, "Freq_Plan"] = "923band"
    all_freq.append(freq923)

if len(all_freq) == 0:
    print("ERROR: No valid LoRaWAN packets!")
    exit()

if (freq915.shape[0] and freq923.shape[0]) > 0:
    for a, b in filtered.groupby("Freq_Plan", sort=False):
        l_freq.append(b.sort_values(by=["sec_diff"]))
    #915
    for a, b in l_freq[0].groupby("DevEUI or DevAddr", sort=False):
        l_devaddr915.append(b.sort_values(by=["sec_diff"]))
    #923
    for a, b in l_freq[1].groupby("DevEUI or DevAddr", sort=False):
        l_devaddr923.append(b.sort_values(by=["sec_diff"]))

elif freq915.shape[0] == 0:
    #923
    for a, b in filtered.groupby("DevEUI or DevAddr", sort=False):
        l_devaddr923.append(b.sort_values(by=["sec_diff"]))

elif freq923.shape[0] == 0:
    #915
    for a, b in filtered.groupby("DevEUI or DevAddr", sort=False):
        l_devaddr915.append(b.sort_values(by=["sec_diff"]))


### Counting the number of devices
print("8. Counting the number of devices...")
def truncate(f):
    if f == "-":
        return "-"
    else:
        return math.floor(f * 10 ** 3) / 10 ** 3

def truncate3(f):
    if f == "-":
        return "-"
    else:
        return math.floor(f * 10 ** 3) / 10 ** 3

def truncate0(f):
    if f == "-":
        return "-"
    else:
        return math.floor(f * 10 ** 0) / 10 ** 0

dev_num915 = []
rate915 = []
dev_num923 = []
rate923 = []
num = 1
    
# Identifying unique devices
l_index_915 = []
if len(l_devaddr915) > 0:
    for i in l_devaddr915:   
        sec_df = list(map(float, i["sec_diff"]))
        sec_df = [x - sec_df[0] for x in sec_df]
        sec_df = list(map(truncate, sec_df))
            
        devaddr_df = list(i["DevEUI or DevAddr"])
        activation_df = list(i["activation"])
           
        # OTAA case
        if activation_df[0] == "OTAA":
            l_index_915.extend(i.index)
            for j in range(len(devaddr_df)):
                if j >= 1:
                    rate915.append(truncate3(float(sec_df[j]) - float(sec_df[j-1])))
                else:
                    rate915.append("-")
            dev_num915.extend(repeat(num, len(devaddr_df)))
            num += 1
            
        # ABP case
        # Single packet case
        elif i.shape[0] == 1 and activation_df[0] != "OTAA":
            l_index_915.extend(i.index)
            dev_num915.append(num)
            rate915.append("-")
            num += 1
        
        # 2 packets or more
        elif i.shape[0] > 1 and activation_df[0] == "ABP":
            dev_num915.extend(repeat(num, i.shape[0]))
            #j = i.sort_values(by=["date", "time"]) # sort by date and time
            l_index_915.extend(i.index)
            fcnt_df = list(map(int, i["fcnt"]))
            i_rate = []
            rate915.append("-")
            
            for k in range(len(fcnt_df)): # +1 to start from 2nd row
                if 1 <= k < len(fcnt_df): # make sure it doesn't exceed the row index limit
                    if fcnt_df[k] != fcnt_df[k-1]:
                        i_rate.append(truncate3(((sec_df[k] - sec_df[k-1])/(fcnt_df[k] - fcnt_df[k-1]))))
                    else:
                        i_rate.append(truncate3((sec_df[k] - sec_df[k-1])))
            trunc_i_rate = []
            i_rate2 = list(map(np.round, i_rate))
            i_rate_set = list(dict.fromkeys(i_rate2)) # list of the set intervals to look up for assigning devnumber
            # Remove rates that are within +-1 rate of each other
            for m in range(len(i_rate_set)):
                if m == 0:
                    trunc_i_rate.append(i_rate_set[m])
                elif m >= 1:
                    if (i_rate_set[m] > all([x+1 for x in trunc_i_rate])) or (i_rate_set[m] < all([x-1 for x in trunc_i_rate])):
                        trunc_i_rate.append(i_rate_set[m])                 
                
            for element in i_rate:
                for i_devnum in range(len(trunc_i_rate)):
                    if (trunc_i_rate[i_devnum] - 1) <= float(element) <= (trunc_i_rate[i_devnum] + 1):
                        break
                    else:
                        continue
                rate915.append(element)
            num += 1
        
        # For neither OTAA and ABP activation and more than 1 packet
        elif i.shape[0] > 1 and activation_df[0] == "-":
            dev_num915.extend(repeat(num, i.shape[0]))
            #j = i.sort_values(by=["date", "time"]) # sort by date and time
            l_index_915.extend(i.index)
            fcnt_df = list(i["fcnt"])
            if any(i["fcnt"] == "-"):
                for k in range(len(fcnt_df)): # +1 to start from 2nd row
                    if 1 <= k < len(fcnt_df): # make sure it doesn't exceed the row index limit
                        rate915.append(truncate3((sec_df[k] - sec_df[k-1])))
            else:
                fcnt_df = list(map(int, i["fcnt"]))
                i_rate = []
                rate915.append("-")

                for k in range(len(fcnt_df)): # +1 to start from 2nd row
                    if 1 <= k < len(fcnt_df): # make sure it doesn't exceed the row index limit
                        if fcnt_df[k] != fcnt_df[k-1]:
                            i_rate.append(truncate3(((sec_df[k] - sec_df[k-1])/(fcnt_df[k] - fcnt_df[k-1]))))
                        else:
                            i_rate.append(truncate3((sec_df[k] - sec_df[k-1])))
                trunc_i_rate = []
                i_rate2 = list(map(np.round, i_rate))
                i_rate_set = list(dict.fromkeys(i_rate2)) # list of the set intervals to look up for assigning devnumber
                # Remove rates that are within +-1 rate of each other
                for m in range(len(i_rate_set)):
                    if m == 0:
                        trunc_i_rate.append(i_rate_set[m])
                    elif m >= 1:
                        if (i_rate_set[m] > all([x+1 for x in trunc_i_rate])) or (i_rate_set[m] < all([x-1 for x in trunc_i_rate])):
                            trunc_i_rate.append(i_rate_set[m])                   

                for element in i_rate:
                    for i_devnum in range(len(trunc_i_rate)):
                        if (trunc_i_rate[i_devnum] - 1) <= float(element) <= (trunc_i_rate[i_devnum] + 1):
                            break
                        else:
                            continue
                    rate915.append(element)
            num += 1
    
l_index_923 = []
if len(l_devaddr923) > 0:
    for i in l_devaddr923:   
        sec_df = list(map(float, i["sec_diff"]))
        sec_df = [x - sec_df[0] for x in sec_df]
        sec_df = list(map(truncate, sec_df))
            
        devaddr_df = list(i["DevEUI or DevAddr"])
        activation_df = list(i["activation"])
           
        # OTAA case
        if activation_df[0] == "OTAA":
            l_index_923.extend(i.index)
            for j in range(len(devaddr_df)):
                if j >= 1:
                    rate923.append(truncate3(float(sec_df[j]) - float(sec_df[j-1])))
                else:
                    rate923.append("-")
            dev_num923.extend(repeat(num, len(devaddr_df)))
            num += 1
            
        # ABP case
        # Single packet case
        elif i.shape[0] == 1 and activation_df[0] != "OTAA":
            l_index_923.extend(i.index)
            dev_num923.append(num)
            rate923.append("-")
            num += 1
        
        # 2 packets or more
        elif i.shape[0] > 1 and activation_df[0] == "ABP":
            dev_num923.extend(repeat(num, i.shape[0]))
            #j = i.sort_values(by=["date", "time"]) # sort by date and time
            l_index_923.extend(i.index)
            fcnt_df = list(map(int, i["fcnt"]))
            i_rate = []
            rate923.append("-")
            
            for k in range(len(fcnt_df)): # +1 to start from 2nd row
                if 1 <= k < len(fcnt_df): # make sure it doesn't exceed the row index limit
                    if fcnt_df[k] != fcnt_df[k-1]:
                        i_rate.append(truncate3(((sec_df[k] - sec_df[k-1])/(fcnt_df[k] - fcnt_df[k-1]))))
                    else:
                        i_rate.append(truncate3((sec_df[k] - sec_df[k-1])))
            trunc_i_rate = []
            i_rate2 = list(map(np.round, i_rate))
            i_rate_set = list(dict.fromkeys(i_rate2)) # list of the set intervals to look up for assigning devnumber
            # Remove rates that are within +-1 rate of each other
            for m in range(len(i_rate_set)):
                if m == 0:
                    trunc_i_rate.append(i_rate_set[m])
                elif m >= 1:
                    if (i_rate_set[m] > all([x+1 for x in trunc_i_rate])) or (i_rate_set[m] < all([x-1 for x in trunc_i_rate])):
                        trunc_i_rate.append(i_rate_set[m])                 
                
            for element in i_rate:
                for i_devnum in range(len(trunc_i_rate)):
                    if (trunc_i_rate[i_devnum] - 1) <= float(element) <= (trunc_i_rate[i_devnum] + 1):
                        break
                    else:
                        continue
                rate923.append(element)      
            num += 1
        
        # For neither OTAA and ABP activation and more than 1 packet
        elif i.shape[0] > 1 and activation_df[0] == "-":
            dev_num923.extend(repeat(num, i.shape[0]))
            #j = i.sort_values(by=["date", "time"]) # sort by date and time
            l_index_923.extend(i.index)
            fcnt_df = list(i["fcnt"])
            if any(i["fcnt"] == "-"):
                for k in range(len(fcnt_df)): # +1 to start from 2nd row
                    if 1 <= k < len(fcnt_df): # make sure it doesn't exceed the row index limit
                        rate923.append(truncate3((sec_df[k] - sec_df[k-1])))
            else:
                fcnt_df = list(map(int, i["fcnt"]))
                i_rate = []
                rate923.append("-")

                for k in range(len(fcnt_df)): # +1 to start from 2nd row
                    if 1 <= k < len(fcnt_df): # make sure it doesn't exceed the row index limit
                        if fcnt_df[k] != fcnt_df[k-1]:
                            i_rate.append(truncate3(((sec_df[k] - sec_df[k-1])/(fcnt_df[k] - fcnt_df[k-1]))))
                        else:
                            i_rate.append(truncate3((sec_df[k] - sec_df[k-1])))
                trunc_i_rate = []
                i_rate2 = list(map(np.round, i_rate))
                i_rate_set = list(dict.fromkeys(i_rate2)) # list of the set intervals to look up for assigning devnumber
                # Remove rates that are within +-1 rate of each other
                for m in range(len(i_rate_set)):
                    if m == 0:
                        trunc_i_rate.append(i_rate_set[m])
                    elif m >= 1:
                        if (i_rate_set[m] > all([x+1 for x in trunc_i_rate])) or (i_rate_set[m] < all([x-1 for x in trunc_i_rate])):
                            trunc_i_rate.append(i_rate_set[m])                   

                for element in i_rate:
                    for i_devnum in range(len(trunc_i_rate)):
                        if (trunc_i_rate[i_devnum] - 1) <= float(element) <= (trunc_i_rate[i_devnum] + 1):
                            break
                        else:
                            continue
                    rate923.append(element)
            num += 1
    


### Sort the derived device number, interval, rate by their corresponding index (to match it with original dataframe)
print("9. Sorting the device numbers by index...")
def sortFirst(val): 
    return val[0]

all_l_indx = []
rate = []
dev_num = []

if len(l_devaddr915) > 0:
    all_list915 = list(zip(l_index_915, rate915, dev_num915))
    all_list915.sort(key = sortFirst)

    for i in all_list915:
        all_l_indx.append(i[0])
        rate.append(i[1])
        dev_num.append(i[2])

elif len(l_devaddr) > 0:
    all_list923 = list(zip(l_index_923, rate923, dev_num923))
    all_list923.sort(key = sortFirst)
    
    for i in all_list923:
        all_l_indx.append(i[0])
        rate.append(i[1])
        dev_num.append(i[2])
    
all_list = list(zip(all_l_indx, rate, dev_num))
all_list.sort(key = sortFirst)


### Adding the corrected ordered tx_interval and dev_number to the dataframe
print("10. Adding the transmission interval column...")
l_rate = []
l_dev_num = []
for i in all_list:
    if i[1] != "-":
        l_rate.append(float(i[1]))
        l_dev_num.append(int(i[2]))
    else:
        l_rate.append(i[1])
        l_dev_num.append(int(i[2]))
    
filtered["tx_interval"] = l_rate
filtered["dev_number"] = l_dev_num



### Save as csv
print("11. Saving as csv...")
filtered.to_csv(pd.datetime.today().strftime('%y%m%d_%H%M%S_') + "LoRadar_data.csv", index=False)
print("Data saved!")
