#!/usr/bin/env python3
#############################################################################
################## BLE Direct controller for RPIEasy ########################
#############################################################################
#
# This controller is able to listen or send BLE messages to nearby devices
#
# Copyright (C) 2019 by Alexander Nagy - https://bitekmindenhol.blog.hu/
#
import controller
import misc
import rpieGlobals
import time
from rpieTime import *
import webserver
import commands
import Settings
from datetime import datetime
import lib.lib_p2pbuffer as p2pbuffer
from pybleno import *
from bluepy.btle import Peripheral
import os
import linux_os as OS
import base64

BLE_SERVICE_ID    = "5032505f-5250-4945-6173-795f424c455f"
BLE_RECEIVER_CHAR = "10000001-5250-4945-6173-795f424c455f"
BLE_INFO_CHAR     = "10000002-5250-4945-6173-795f424c455f"

class Controller(controller.ControllerProto):
 CONTROLLER_ID = 21
 CONTROLLER_NAME = "BLE Direct (EXPERIMENTAL)"

 def __init__(self,controllerindex):
  controller.ControllerProto.__init__(self,controllerindex)
  self.usesID = True
  self.onmsgcallbacksupported = False # use direct set_value() instead of generic callback to make sure that values setted anyway
  self.controllerport = 1
  self.bleserv = None
  self.bleclient = None
  self.timer30s = True
  self.duty = 0  # use 100%
  self.defaultdestination = ""
  self.defaultunit = 1
  self.lastsysinfo = 0
  self.sysinfoperiod = 1800 # seconds
  self.enablerec = True
  self.enablesend = False
  self.directsend = False

 def controller_init(self,enablecontroller=None):
  if enablecontroller != None:
   self.enabled = enablecontroller
  self.initialized = False
  if self.enabled:
   if int(Settings.Settings["Unit"])>0:
    self.controllerport = Settings.Settings["Unit"]
   self.lastsysinfo = 0
   if self.bleserv is not None:
    try:
     self.bleserv.stop()
     time.sleep(1)
    except:
     pass
   try:
    output = os.popen(OS.cmdline_rootcorrect("sudo systemctl stop bluetooth"))
    for l in output:
     pass
   except Exception as e:
    print(e)
   try:
    output = os.popen(OS.cmdline_rootcorrect("sudo hciconfig hci0 up"))
    for l in output:
     pass
   except Exception as e:
    print(e)
   try:
    self.bleclient = BLEClient(self.defaultdestination,self.duty)
    self.bleserv = BLEServer(Settings.Settings["Name"],self.pkt_receiver,self.getmode)
    if self.enablerec:
     self.bleserv.start()
    if self.enablesend==False:
     self.defaultdestination=""
    self.initialized = True
    time.sleep(1)
    misc.addLog(rpieGlobals.LOG_LEVEL_DEBUG,"BLE Direct initialized")
   except Exception as e:
    self.initialized = False
    misc.addLog(rpieGlobals.LOG_LEVEL_ERROR,"BLE Direct init error: "+str(e))
  else:
   if self.bleserv is not None:
    try:
     self.bleserv.stop()
    except:
     pass
  return True

 def controller_exit(self):
   if self.bleserv is not None:
    try:
     self.bleserv.stop()
    except:
     pass
 
 def webform_load(self):
  webserver.addFormNote("IP and Port parameter is not used!")
  webserver.addFormNote("<a href='https://github.com/enesbcs/ESPEasyRetro/blob/master/ESPEasyRetro/_C021.ino'>ESP32 reference controller</a>")

  webserver.addFormCheckBox("Enable Receiver Service","receiver",self.enablerec)
  webserver.addFormNote("Enable this for Gateway/Repeater unit, Disable if you only want to send data!")
  try:
   if self.bleserv is not None:
    webserver.addFormNote("Current Address: "+str(self.bleserv.getaddress()))
  except:
   pass
  webserver.addFormCheckBox("Enable Sending to Default Master Unit","sender",self.enablesend)
  webserver.addFormCheckBox("Enable Direct Sending to Units in P2P list","directsender",self.directsend)
  webserver.addFormNote("Please respect MASTER-SLAVE nature of BLE and do not create infinite loops!")
  webserver.addFormTextBox("Default BLE Master Unit address","masteraddress",self.defaultdestination,23)
  webserver.addFormNote("Enable bluetooth then <a href='blescanner'>scan RPIEasy BLE address</a> first.")
  webserver.addFormNumericBox("Default destination node index","defaultnode",self.defaultunit,0,255)
  webserver.addFormNote("Default node index for data sending, only used when Master Unit address is setted")
  return True

 def webform_save(self,params):
  try:
   self.enablerec = (webserver.arg("receiver",params)=="on")
   self.enablesend = (webserver.arg("sender",params)=="on")
   self.directsend = (webserver.arg("directsender",params)=="on")
   self.defaultdestination = str(webserver.arg("masteraddress",params)).strip()
   if len(self.defaultdestination)<13:
    self.defaultdestination = ""
   self.defaultunit = int(webserver.arg("defaultnode",params))
#   self.controller_init()
  except Exception as e:
   misc.addLog(rpieGlobals.LOG_LEVEL_ERROR,"BLE parameter save: "+str(e))
  return True

 def nodesort(self,item):
  v = 0
  try:
   v = int(item["unitno"])
  except:
   v = 0
  return v

 def pkt_receiver(self,payload): # processing incoming packets
#  print(payload) # debug
  if self.enabled:
    dp = p2pbuffer.data_packet() 
    pbuf = list(payload)
    if pbuf[0]=='/' or pbuf[0]==47: # base64 encoded
     dp.buffer = base64.b64decode(payload.decode("utf-8"))
    else:               # otherwise plain data arrived
     dp.buffer = payload
    dp.decode()            # asking p2pbuffer library to decode it
#    print(dp.buffer) # debug
    if dp.pkgtype!=0:
        if dp.pkgtype==1: # info packet received
#         print(dp.infopacket)
         if int(dp.infopacket["unitno"]) == int(Settings.Settings["Unit"]): # skip own messages
          return False
         un = getunitordfromnum(dp.infopacket["unitno"]) # process incoming alive reports
         if un==-1:
          # CAPABILITIES byte: first bit 1 if able to send, second bit 1 if able to receive
          Settings.p2plist.append({"protocol":"BLE","unitno":dp.infopacket["unitno"],"name":dp.infopacket["name"],"build":dp.infopacket["build"],"type":dp.infopacket["type"],"mac":dp.infopacket["mac"],"lastseen":datetime.now(),"lastrssi":self.bleserv.getrssi(),"cap":dp.infopacket["cap"]})
          misc.addLog(rpieGlobals.LOG_LEVEL_INFO,"New BLE unit discovered: "+str(dp.infopacket["unitno"])+" "+str(dp.infopacket["name"]))
          Settings.p2plist.sort(reverse=False,key=self.nodesort)
         else:
          misc.addLog(rpieGlobals.LOG_LEVEL_DEBUG,"Unit alive: "+str(dp.infopacket["unitno"]))
          if Settings.p2plist[un]["type"]==0:
           Settings.p2plist[un]["name"] = dp.infopacket["name"]
           Settings.p2plist[un]["build"] = dp.infopacket["build"]
           Settings.p2plist[un]["type"] = dp.infopacket["type"]
           Settings.p2plist[un]["mac"] = dp.infopacket["mac"]
          Settings.p2plist[un]["cap"] = dp.infopacket["cap"]
          Settings.p2plist[un]["lastseen"] = datetime.now()
          Settings.p2plist[un]["lastrssi"] = self.bleserv.getrssi()

        elif dp.pkgtype==5:                          # process incoming data
          if int(dp.sensordata["sunit"])==int(Settings.Settings["Unit"]):
           return False
          un = getunitordfromnum(dp.sensordata["sunit"])
          if un>-1: # refresh lastseen data
           Settings.p2plist[un]["lastseen"] = datetime.now()
           Settings.p2plist[un]["lastrssi"] = self.bleserv.getrssi()
          else:
           Settings.p2plist.append({"protocol":"BLE","unitno":dp.sensordata["sunit"],"name":"","build":0,"type":0,"mac":"","lastseen":datetime.now(),"lastrssi":self.bleserv.getrssi(),"cap":1})

          if (int(Settings.Settings["Unit"])==int(dp.sensordata["dunit"])) or (0==int(dp.sensordata["dunit"])): # process only if we are the destination or broadcast
           ltaskindex = -1
           for x in range(0,len(Settings.Tasks)): # check if the sent IDX already exists?
             try:
              if (type(Settings.Tasks[x]) is not bool and Settings.Tasks[x]):
                if Settings.Tasks[x].controlleridx[self.controllerindex]==int(dp.sensordata["idx"]):
                 ltaskindex = x
                 break
             except Exception as e:
              print(e)
           dvaluecount = int(dp.sensordata["valuecount"])
           if rpieGlobals.VARS_PER_TASK<dvaluecount: # avoid possible buffer overflow
            dvaluecount = rpieGlobals.VARS_PER_TASK
           if ltaskindex < 0: # create new task if necessarry
            devtype = int(dp.sensordata["pluginid"])
            m = False
            try:
             for y in range(len(rpieGlobals.deviceselector)):
              if int(rpieGlobals.deviceselector[y][1]) == devtype:
               m = __import__(rpieGlobals.deviceselector[y][0])
               break
            except:
             m = False
            TempEvent = None
            if m:
             try: 
              TempEvent = m.Plugin(-1)
             except:
              TempEvent = None
            if True:
             ltaskindex = -1
             for x in range(0,len(Settings.Tasks)): # check if there are free TaskIndex slot exists
               try:
                if (type(Settings.Tasks[x]) is bool):
                 if Settings.Tasks[x]==False:
                  ltaskindex = x
                  break
               except:
                pass
             devtype = 33 # dummy device
             m = False
             try:
              for y in range(len(rpieGlobals.deviceselector)):
               if int(rpieGlobals.deviceselector[y][1]) == devtype:
                m = __import__(rpieGlobals.deviceselector[y][0])
                break
             except:
              m = False
             if m:
              if ltaskindex<0:
               ltaskindex = len(Settings.Tasks)
              try:
               Settings.Tasks[ltaskindex] = m.Plugin(ltaskindex)
              except:
               ltaskindex = len(Settings.Tasks)
               Settings.Tasks.append(m.Plugin(ltaskindex))  # add a new device
              Settings.Tasks[ltaskindex].plugin_init(True)
              Settings.Tasks[ltaskindex].remotefeed = True  # Mark that this task accepts incoming data updates!
              Settings.Tasks[ltaskindex].enabled  = True
              Settings.Tasks[ltaskindex].interval = 0
              Settings.Tasks[ltaskindex].senddataenabled[self.controllerindex]=True
              Settings.Tasks[ltaskindex].controlleridx[self.controllerindex]=int(dp.sensordata["idx"])
              if TempEvent is not None:
               Settings.Tasks[ltaskindex].taskname = TempEvent.PLUGIN_NAME.replace(" ","")
               for v in range(dvaluecount):
                Settings.Tasks[ltaskindex].valuenames[v] = TempEvent.valuenames[v]
               Settings.Tasks[ltaskindex].taskdevicepluginconfig[0] = TempEvent.vtype
               Settings.Tasks[ltaskindex].vtype = TempEvent.vtype
              else:
               Settings.Tasks[ltaskindex].taskname = Settings.Tasks[ltaskindex].PLUGIN_NAME.replace(" ","")
              Settings.Tasks[ltaskindex].valuecount = dvaluecount
              Settings.savetasks()
           if ltaskindex<0:
            return False
           misc.addLog(rpieGlobals.LOG_LEVEL_DEBUG,"Sensordata update arrived from unit "+str(dp.sensordata["sunit"])) # save received values
           if Settings.Tasks[ltaskindex].remotefeed:
            for v in range(dvaluecount):
             Settings.Tasks[ltaskindex].set_value(v+1,dp.sensordata["values"][v],False)
            Settings.Tasks[ltaskindex].plugin_senddata()
          elif (int(Settings.Settings["Unit"])!=int(dp.sensordata["dunit"])): # reroute if pkt is not for us
            if (self.defaultunit!=int(dp.sensordata["dunit"])) and (self.enablesend or self.directsend): # ... and not came from default target, and sending is enabled
             if self.directsend:
              un = getunitordfromnum(dp.sensordata["dunit"]) # try direct send only if instructed to do so
             else:
              un = -1
             self.bleclient.setdestination(self.defaultdestination)
             if un>-1:
              if (int(Settings.p2plist[un]["cap"]) & 2)==2: # try only if endpoint is able to receive
               self.bleclient.setdestination(Settings.p2plist[un]["mac"])
             success = self.bleclient.send(dp.buffer)
             if success==False and un>-1:
              self.bleclient.setdestination(self.defaultdestination)
              success = self.bleclient.send(dp.buffer)

        elif dp.pkgtype==7: # process incoming command
          if int(dp.cmdpacket["sunit"])==int(Settings.Settings["Unit"]):
           return False
          un = getunitordfromnum(dp.cmdpacket["sunit"])
          if un>-1: # refresh lastseen data
           Settings.p2plist[un]["lastseen"] = datetime.now()
           Settings.p2plist[un]["lastrssi"] = self.bleserv.getrssi()
          else:
           Settings.p2plist.append({"protocol":"BLE","unitno":dp.cmdpacket["sunit"],"name":"","build":0,"type":0,"mac":"","lastseen":datetime.now(),"lastrssi":self.bleserv.getrssi(),"cap":1})
          if (int(Settings.Settings["Unit"])==int(dp.cmdpacket["dunit"])) or (0==int(dp.cmdpacket["dunit"])): # process only if we are the destination or broadcast
           misc.addLog(rpieGlobals.LOG_LEVEL_INFO,"Command arrived from "+str(dp.cmdpacket["sunit"]))
#           print(dp.cmdpacket["cmdline"]) # DEBUG
           commands.doExecuteCommand(dp.cmdpacket["cmdline"],True)


 def senddata(self,idx,sensortype,value,userssi=-1,usebattery=-1,tasknum=-1,changedvalue=-1): # called by plugin
  if self.enabled and self.initialized:
#   print(idx,value) # debug
   if int(idx)>0:
    if Settings.Tasks[tasknum].remotefeed==False or Settings.Tasks[tasknum].remotefeed==-1:  # do not republish received values
     dp2 = p2pbuffer.data_packet()
     dp2.sensordata["sunit"] = Settings.Settings["Unit"]
     dp2.sensordata["dunit"] = self.defaultunit
     dp2.sensordata["idx"] = idx
     if tasknum>-1:
      dp2.sensordata["pluginid"] = Settings.Tasks[tasknum].pluginid
     else:
      dp2.sensordata["pluginid"] = 33
     dp2.sensordata["valuecount"] = Settings.Tasks[tasknum].valuecount
     for u in range(Settings.Tasks[tasknum].valuecount):
      try:
       dp2.sensordata["values"][u] = Settings.Tasks[tasknum].uservar[u]
      except:
       dp2.sensordata["values"].append(Settings.Tasks[tasknum].uservar[u])
     dp2.encode(5)
#     print(dp2.buffer) # debug
     if self.directsend:
      un = getunitordfromnum(self.defaultunit) # try direct send only if instructed to do so
     else:
      un = -1
     self.bleclient.setdestination(self.defaultdestination)
     if un>-1:
      if (int(Settings.p2plist[un]["cap"]) & 2)==2: # try only if endpoint is able to receive
       self.bleclient.setdestination(Settings.p2plist[un]["mac"])
#       print("a2:",Settings.p2plist[un]["mac"])
     success = self.bleclient.send(dp2.buffer)
     if success==False and un>-1:
#      print("retry",success,un) # debug
      self.bleclient.setdestination(self.defaultdestination)
      success = self.bleclient.send(dp2.buffer)
     return success

 def sendcommand(self,unitno,commandstr):
     dpc = p2pbuffer.data_packet()
     dpc.cmdpacket["sunit"] = Settings.Settings["Unit"]
     dpc.cmdpacket["dunit"] = unitno
     dpc.cmdpacket["cmdline"] = commandstr
     dpc.encode(7)
     un = getunitordfromnum(unitno) # try direct send anyway
     if un==-1:
      self.bleclient.setdestination(self.defaultdestination)
     else:
      self.bleclient.setdestination(Settings.p2plist[un]["mac"])
     success = self.bleclient.send(dpc.buffer)
     if success==False and un>-1:
      self.bleclient.setdestination(self.defaultdestination)
      success = self.bleclient.send(dpc.buffer)
     return success

 def timer_thirty_second(self):
  if self.enabled and self.initialized:
   if self.defaultdestination!="" and ((time.time()-self.lastsysinfo) >self.sysinfoperiod):
    dp = p2pbuffer.data_packet()
    try:
     dp.infopacket["mac"] = self.bleserv.getaddress()
    except Exception as e:
     dp.infopacket["mac"] = "00:00:00:00:00:00"
    dp.infopacket["unitno"] = int(Settings.Settings["Unit"])
    dp.infopacket["build"] = int(rpieGlobals.BUILD)
    dp.infopacket["name"] = Settings.Settings["Name"]
    dp.infopacket["type"] = int(rpieGlobals.NODE_TYPE_ID_RPI_EASY_STD)
    # CAPABILITIES byte: first bit 1 if able to send, second bit 1 if able to receive
    dp.infopacket["cap"] = self.getmode()
    dp.encode(1)
    self.bleclient.setdestination(self.defaultdestination)
    misc.addLog(rpieGlobals.LOG_LEVEL_DEBUG,"Sending infopacket")
    success = False
    if self.bleclient.connect():
        success = self.bleclient.writepayload(dp.buffer)
        reply = self.bleclient.readpayload() # read remote infos
        self.bleclient.disconnect()
        if reply:
#         print("repl",reply) # debug
         self.pkt_receiver(reply) # handle received infos
    self.lastsysinfo = time.time()
#    if success:
#     self.lastsysinfo = time.time()
#    else:
#     self.lastsysinfo = (time.time()-self.sysinfoperiod)+10 # retry in 10sec
  return True

 def getmode(self):
    wm = 0
    if self.enablesend or self.directsend:
     wm = 1
    if self.enablerec:
     wm += 2
    return wm

class InfoCharacteristic(Characteristic):
    def __init__(self,addressfunc=None,modefunc=None):
        Characteristic.__init__(self, {
            'uuid': BLE_INFO_CHAR,
            'properties': ['read'],
            'descriptors': [
                    Descriptor({
                        "uuid" : "2901",
                        "value" : array.array('B',[73, 110, 102, 111, 32, 112, 97, 99, 107, 101, 116])}
                    )],
            'value': None
          })
        self._value = []
        self.addressfunc=addressfunc
        self.modefunc=modefunc

    def onReadRequest(self, offset, callback):
      dp = p2pbuffer.data_packet()
      try:
       dp.infopacket["mac"] = self.addressfunc()
      except Exception as e:
       dp.infopacket["mac"] = "00:00:00:00:00:00"
      dp.infopacket["unitno"] = int(Settings.Settings["Unit"])
      dp.infopacket["build"] = int(rpieGlobals.BUILD)
      dp.infopacket["name"] = Settings.Settings["Name"]
      dp.infopacket["type"] = int(rpieGlobals.NODE_TYPE_ID_RPI_EASY_STD)
      # CAPABILITIES byte: first bit 1 if able to send, second bit 1 if able to receive
      dp.infopacket["cap"] = self.modefunc()
      dp.encode(1)
#      data = array.array('B',[0]*64)
      data = list(base64.b64encode(dp.buffer[offset:]))
#      print(offset,data[offset:])
      callback(Characteristic.RESULT_SUCCESS, data)

class ReceiverCharacteristic(Characteristic):
    def __init__(self, updateValueCallback=None):
        Characteristic.__init__(self, {
            'uuid': BLE_RECEIVER_CHAR,
            'properties': ['write'],
            'descriptors': [
                    Descriptor({
                        "uuid" : '2901',
                        "value" : array.array('B',[80, 50, 80, 32, 109, 101, 115, 115, 97, 103, 101, 32, 113, 117, 101, 117, 101])}
                    )],
            'value': None
          })
        self._value = []
        self._updateValueCallback = updateValueCallback

    def onWriteRequest(self, data, offset, withoutResponse, callback):
        self._value = data
#        print('EchoCharacteristic - %s - onWriteRequest: value = %s' % (self['uuid'], [hex(c) for c in self._value]))
        if self._updateValueCallback:
            self._updateValueCallback(self._value)
        callback(Characteristic.RESULT_SUCCESS)

class RPIBLEService(BlenoPrimaryService):
    def __init__(self,s_updateValueCallback=None,nameval="",addrfunc=None,mfunc=None):
        BlenoPrimaryService.__init__(self, {
          'uuid': BLE_SERVICE_ID,
          'characteristics': [
              ReceiverCharacteristic(updateValueCallback=s_updateValueCallback),
              InfoCharacteristic(addressfunc=addrfunc,modefunc=mfunc)
          ]})

class BLEServer():
    def __init__(self, name="",receiverfunc=None,pmodefunc=None):
        self.receiverfunc=receiverfunc
        self.name    = name
        self.bleno   = None
        self.service = None
        self.initialized = False
        self.address = ""
        self.modefunc=pmodefunc

    def start(self):
        self.address = ""
        self.bleno = Bleno()
        self.service = RPIBLEService(self.receiverfunc,self.name,self.getaddress,self.modefunc)
        self.bleno.on('stateChange', self.onStateChange)
        self.bleno.on('advertisingStart', self.onAdvertisingStart)
        self.bleno.start()
        self.initialized = True

    def getrssi(self):
        return self.bleno.rssi

    def getaddress(self):
        if self.bleno is None:
         self.bleno = Bleno()
        if self.bleno is not None:
         if self.address == "":
          try:
            if self.bleno.address is not None and self.bleno.address != "" and self.bleno.address != "unknown":
             self.address = self.bleno.address
            else:
             self.bleno._bindings._hci.init()
             self.bleno._bindings._hci.readBdAddr() # force read BT address cmd
             time.sleep(0.5)
             self.address = self.bleno._bindings._hci.address
            aa = self.address.split(":")
            self.address = ""
            for l in reversed(range(len(aa))):
             self.address += aa[l]+":"
            self.address = self.address[:-1]
          except Exception as e:
            print(e)
         return self.address
        else:
         return ""

    def onStateChange(self,state):
#      print('on -> stateChange: ' + state);
      if (state == 'poweredOn'):
       self.bleno.startAdvertising(self.name, [BLE_SERVICE_ID]);
       misc.addLog(rpieGlobals.LOG_LEVEL_INFO,"BLE Service started at: "+str(self.getaddress()))
      else:
       self.bleno.stopAdvertising();

    def onAdvertisingStart(self,error):
#     print('on -> advertisingStart: ' + ('error ' + error if error else 'success'));
     if not error:
        def on_setServiceError(error):
          if error:
             misc.addLog(rpieGlobals.LOG_LEVEL_ERROR,"BLE service start: "+str(error))
#            print('setServices: %s'  % ('error ' + error if error else 'success'))
        self.bleno.setServices([
            self.service
        ], on_setServiceError)
     else:
       misc.addLog(rpieGlobals.LOG_LEVEL_ERROR,"BLE advertising result: "+str(error))

    def stop(self):
     self.bleno.stopAdvertising()
     self.bleno.disconnect()

class BLEClient():
    def __init__(self,address="",duty=0):
        self.address=address
        self.tx_active = False
        self.tx_start  = 0
        self.tx_end    = 0
        self.nexttransmit = 0
        self.duty=duty
        self.service = None
        self.periph  = None

    def setdestination(self,address):
        self.address=address.lower().strip() # only lower case address is supported by BluePy!!

    def connect(self):
       if self.tx_active or self.address=="":
        return False
       self.tx_start = millis()
       if self.tx_start<self.nexttransmit:
        print("Next possible transmit ",self.nexttransmit)
        return False
       self.tx_active = True
       try:
        self.periph = Peripheral(self.address)
       except Exception as e: 
#        print("connect error",e)
        self.tx_active = False
        self.tx_end = millis()
        return False
       try:
        self.service = self.periph.getServiceByUUID(BLE_SERVICE_ID)
       except Exception as e:
#        print("service error ",e)
        self.tx_active = False
        self.tx_end = millis()
        return False
       return True

    def writepayload(self,apayload):
       try:
        ch = self.service.getCharacteristics(BLE_RECEIVER_CHAR)[0]
#        print(ch,apayload,len(apayload))
        ch.write(base64.b64encode(apayload), True)
       except Exception as e:
#        print("write error ",e)
        return False
       return True

    def readpayload(self):
       payload = []
       try:
        ch = self.service.getCharacteristics(BLE_INFO_CHAR)[0]
        payload = ch.read()
        payload = base64.b64decode(payload.decode("utf-8"))
       except Exception as e:
#        print("read error ",e)
        pass
       return payload

    def disconnect(self):
       try:
        self.periph.disconnect()
       except:
        return False
       self.tx_active = False
       self.tx_end = millis()
       if self.duty>0:
        self.nexttransmit = ((self.tx_end-self.tx_start)*self.duty)+self.tx_end
       return True

    def send(self,spayload):
       if self.connect():
        self.writepayload(spayload)
        self.disconnect()

# Helper functions

def getunitordfromnum(unitno):
  for n in range(len(Settings.p2plist)):
   if int(Settings.p2plist[n]["unitno"]) == int(unitno) and str(Settings.p2plist[n]["protocol"]) == "BLE":
    return n
  return -1