#!/usr/bin/env python2
#
#   Version 1.1
#
#   Copyright 2016-2018 Lee Djavaherian
#
#   This file is part of TrillSat.
#
#   TrillSat is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   TrillSat is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with TrillSat.  If not, see <http://www.gnu.org/licenses/>.
###################################################################################

import serial, sys, os, time
import posix_ipc                    #for interprocess communication
import multiprocessing, threading   #for multiprocessing and concurrency in different situations
import pigpio, mmap, struct         #needed for radio communication
import subprocess                   #to launch ax25_call
import binascii                     #for reading UV-5RA flash strings
import pexpect                      #for texting another bbs like itself
import math                         #for accelerometer trigonometric calculations
import string                       #helps with text cleanup

from xml.sax.saxutils import escape #to excape xml for sending to xmpp

sys.path.append('/trillsat/')
import trillsat_config          #configuration settings

##############################################################
### THIS IS PART OF THE RADIO PROCESS ########################
# Create virtual serial port object for UV-5RA communication usign pigpio
class fakeserial:
  def __init__(self, gpio, Fake_TX, Fake_RX): 
    self.Fake_TX = Fake_TX
    self.Fake_RX = Fake_RX
    self.gpio = gpio

  def read(self, bytes):
    readbuffer = ''
    while len(readbuffer) < bytes:
      (count, data) = self.gpio.bb_serial_read(self.Fake_RX)
      piece = ''
      if count:
        readbuffer += str(data)
    piece = readbuffer[:bytes]
    readbuffer = readbuffer[bytes:]
    return piece

  def write(self, mytext):
    self.gpio.wave_clear()
    self.gpio.wave_add_serial(self.Fake_TX, 9600, mytext, bb_bits=8)
    wid=self.gpio.wave_create()
    self.gpio.wave_send_once(wid)
    self.gpio.wave_delete(wid)
#############################################################
#### THIS IS PART OF THE RADIO PROCESS ######################
# Read a 64-byte block from the UV-5RA flash
def ReadRadio(uartF, mem_address):
 
 #mem_address must be big endian, 2-byte value
 uartF.write("\x53" + struct.pack('>H', mem_address) + "\x40")
 command = uartF.read(1)
 which_block = uartF.read(3)
 
 if command + which_block != "\x58" + which_block:
   print("READ - Wrong command response from radio.")
   raise Exception("READ - Wrong command response from radio.")
 
 block = uartF.read(64)
 if len(block) != 64:
   print("READ - Wrong block size from radio (should be 64 bytes).")
   raise Exception("READ - Wrong block size from radio (should be 64 bytes).")
 
 #send and receive ACK
 uartF.write("\x06")
 if uartF.read(1) != "\x06":
   print("READ - No final ACK from radio.")
   raise Exception("READ - No final ACK from radio.")

 return block
############################################################
#### THIS IS PART OF THE RADIO PROCESS ######################
# Write a block to the UV-5RA flash (size can vary, can be as small as 16 bytes)
def WriteRadio(uartF, mem_address, data):

   #mem_address must be a big endian, 2 byte value and length must be <= 255
   uartF.write("\x58" + struct.pack('>H', mem_address) + chr(len(data)) + data)
   time.sleep(0.05)
   if uartF.read(1) != "\x06":
     print("WRITE - No final ACK from radio.")
     raise Exception("WRITE - No final ACK from radio.")

###################################################################
#### THIS IS A SEPARATE RADIO PROCESS MANAGED BY THREAD 4? ########
#Program the UV-5RA clone-mode flash memory (this just changes the frequency for now)
def ProgramRadio(freq, gpio, Fake_TX, Fake_RX, RTS):

 try:

      ########  INITIAL SETUP ###############
    
      status = gpio.bb_serial_read_open(Fake_RX,9600,8)
      uartF = fakeserial(gpio, Fake_TX, Fake_RX)

      #I already have a memory dump from earlier, so this 
      #is just a load from file and a write:    

      ###### OPEN 64-BYTE SAVED FILE CONTAINING FREQ BYTES #############
      #This was a file saved earlier from the 3840 block (64 bytes)

      with open('/trillsat/trillsat_radio_memsmall', "r+b") as f:
        mm = mmap.mmap(f.fileno(), 0)  # this can write directly to the file

        #The Python 3 version of mmap works differently
        #So make the changes below if using Python 3

        #for Python 3, change str(ord(char)) to str(char)
        print(' '.join(str(ord(char)) for char in mm[8:15]))

        #break apart each letter in string
        #get digit from string, skipping decimal point (.)
        #turn digit into an integer
        #get byte that it would represent if that integer was its ascii value
        #then write the byte
     
        mm.seek(8) #This is an offset from 3840, so we are starting write at 3848

        ######### WRITE NEW FREQ BYTES ##########

        #for Python 3, change chr(1) to 1 
        mm.write_byte(chr(int(freq[0:1])))
        mm.write_byte(chr(int(freq[1:2])))
        mm.write_byte(chr(int(freq[2:3])))
        #--- skip decimal point ---
        mm.write_byte(chr(int(freq[4:5])))
        mm.write_byte(chr(int(freq[5:6])))
        mm.write_byte(chr(int(freq[6:7])))
        mm.write_byte(chr(0))
        mm.write_byte(chr(0))
     
        #for Python 3, change str(ord(char)) to str(char)
        print(' '.join(str(ord(char)) for char in mm[8:15]))
        mm.close()

      #this extra reading from file is redundant - fix later
      mapfile = file('/trillsat/trillsat_radio_memsmall', "rb")
      data = mapfile.read()
      mapfile.close()
  
      ######## UPLOAD NEW FREQ TO RADIO #####################

      print("Changing frequency to: " + freq)
  
      uartF.write(trillsat_config.radio_id) #send radio_id to radio
      if uartF.read(1) != "\x06":     #see if radio sends back an ACK
        print("WRITE - No initial ACK from radio.")
        raise Exception("WRITE - No initial ACK from radio.")
      uartF.write("\x02")             #send Start-Of-Text byte to radio
      id = uartF.read(8)              #read ident string from radio
      print "ID string: " + binascii.hexlify(id)
      uartF.write("\x06")             #send initial ACK to radio
      time.sleep(1)
      if uartF.read(1) != "\x06":     #see if radio sends back an ACK
        print("WRITE - No second ACK from radio.")
        raise Exception("WRITE - No second ACK from radio.")
 
      #write blocks can be as small as 16 bytes instead of 64
      WriteRadio(uartF, 3840, data[0:16]) #this only writes the first 16 of the 64 byte file
 
      time.sleep(7) # allow time to reboot

  

      ####### CHECK RADIO TO CONFIRM FREQ #####################

      print("Downloading radio memory block to confirm change...")  

      uartF.write(trillsat_config.radio_id)  #send radio_id to radio
      if uartF.read(1) != "\x06":      #see if radio sends back an ACK
        print("READ - No initial ACK from radio.")
        raise Exception("READ - No initial ACK from radio.")
      uartF.write("\x02")              #send Start-Of-Text byte to radio
      id = uartF.read(8)               #read ident string from radio
      print "ID string: " + binascii.hexlify(id)
      uartF.write("\x06")              #send ACK to radio
      time.sleep(1)
      if uartF.read(1) != "\x06":      #see if radio sends back an ACK
        print ("READ - No second ACK from radio.")
        raise Exception("READ - No second ACK from radio.")
      data = ""

      #use this to backup complete memory to file
      #then use "hexdump -C trillsat_radio_fulldump" to view file and look at bytes
      #change radio memory, then do a diff on the 2 files to see where they differ
      #need to do something special since diff has problems with this...
      #for i in range(0, 6144, 64): #get all 6144 bytes in 64 byte blocks
        #data += _read_block(ser, i)
      #mapfile = file('/trillsat/trillsat_radio_fulldump', "wb")
      #mapfile.write(data)
      #mapfile.close()

      #reading does not need to occur for changing frequencies
      #since memory can be stored in a file
      #direct write is extremely fast

      #freq seems to be at 3848 (60th block, 3840)
      data = ReadRadio(uartF, 3840)

      #display hex dump of 8x8 (64-byte block) with address in decimal
      #(decimal addresses are easier for me to remember)
      hex = binascii.hexlify(data)
      address = 3840
      count = 0
      string = ''
      for chars in hex:
        count += 1
        if count % 2 == 0 :
          string += chars + ' ' #space between bytes
        else:
          string += chars
        if count == 16:
           count = 0
           print (str(address) + ': ' + string) #print a line
           address += 8
           string = ''  

      #use this if you need to write a new memory file
      #mapfile = file('/trillsat/trillsat_radio_memsmall', "wb")
      #mapfile.write(data)
      #mapfile.close()


      #now check bytes on data read from radio
      time.sleep(8) #allow time to reboot

      freq_bytestring = chr(int(freq[0:1])) + chr(int(freq[1:2])) + chr(int(freq[2:3])) + chr(int(freq[4:5])) + chr(int(freq[5:6])) + chr(int(freq[6:7])) + chr(0) + chr(0)
      if data[8:16] == freq_bytestring:
         print("Radio check successfully matched.")
      else:
         print("Radio check did not match.")
         sys.exit(1) # failed check

      

      #####################  DONE #######################

      gpio.bb_serial_read_close(Fake_RX)  
   
      print("RADIO_FREQ: done")
      print("Radio is now set to: " + freq + " Mhz")

 except Exception as e:
    gpio.bb_serial_read_close(Fake_RX)
    #set to 0 (no error) for testing, normally this should be set to 1
    sys.exit(0) # error in radio communication (perhaps use different error code?) 
    print(e)

##################################################################
#### THIS IS PART OF THREAD 3 BUT CAN BE LAUNCHED BY THREAD 1 ##
#Change the frequency of the UV-5RA
## use locks before launching
def SetRadio(freq):

   global gpio
   global frequency
   global gpio_setup_lock
   global update_status_lock
   global uart0

   if float(freq) < 144.1 or float(freq) > 148:
     #need better error checking to check for length and decimal point
     uart0_write("Frequency out of range!")
   else:
     ProgramRadio_process = multiprocessing.Process(target=ProgramRadio, args=(freq, gpio, trillsat_config.Fake_TX, trillsat_config.Fake_RX, trillsat_config.RTS,))

     frequency = 'setting...'
     update_status_lock.acquire()
     UpdateStatus()
     update_status_lock.release()
     
     gpio_setup_lock.acquire()
     gpio_setup("PROGRAM")
     gpio_setup_lock.release()
     ProgramRadio_process.start()
     
     ProgramRadio_process.join(20) #don't wait longer than 20 seconds to finish
     if ProgramRadio_process.is_alive():
       
       ProgramRadio_process.terminate()     
       #pigpiod uses more CPU cycles and can lock GPIO if calling process killed
       gpio.stop()
       os.system('/usr/bin/killall pigpiod')      
       time.sleep(3)
       os.system('/usr/bin/pigpiod')
       time.sleep(3)

       gpio = pigpio.pi()
       gpio_setup_lock.acquire()
       uart0_write("RADIO: STUCK")

       return ("RADIO: STUCK")
     elif ProgramRadio_process.exitcode == 1:
       uart0_write("RADIO: FAILED CHECK")
       return ("RADIO: FAILED CHECK") 
     else:
       print ("exit code: " + str(ProgramRadio_process.exitcode))
       frequency = freq
       UpdateStatus()
       uart0_write("RADIO: FINISHED")  
       return ("RADIO: FINISHED")

     gpio_setup_lock.acquire()
     gpio_setup("PACKET")
     gpio_setup_lock.release()

#####################################################################
##### THIS IS PART OF THREAD 1 THAT STARTS A TIMER FOR THREAD 3 #####
#Timer object for starting the APRS beacon
class BeaconTimer: #had to wrap threading.Timer in class to get it to self-repeat
  def __init__(self, seconds):
    self.seconds = seconds

  def __call__(self):
    SendBeacon()
    self.timer_running = None  #timer thread should be ending here, so set flag
    self.start() #start it up again...

  def start(self):
    self.timer_thread = threading.Timer(self.seconds, self)
    self.timer_thread.start()
    self.timer_running = True #timer thread has just been started

  def cancel(self):
    #check to make sure you only try to cancel a timer thread if running
    #since there is a fraction of a second before the next thread is started
    if self.timer_running is not None:
      self.timer_thread.cancel()
##############################################################
######  THIS IS THREAD 3 STARTED BY TIMER ###################
#Send the APRS beacon (even while AX.25 sessions are still in progress)
def SendBeacon():
 
  global frequency
  global call_list
  global Beacon
  global Virtual_PTT
  global gpio
  global uart0
  global beacon_command

  for callsign in call_list:
    bbs_queue_send(callsign, 'MESSAGE --------------------')
    bbs_queue_send(callsign, 'MESSAGE Please wait 20 seconds for the system to send out its periodic APRS beacon.  Please do not send me any text or disconnect until you hear back from me.  If you do not receive a response from me within 20 seconds, then consider the frequency clear and disconnect.  Please wait...')

  ######## PTT DISABLED ###########
  Virtual_PTT.cancel() #first, break PTT to silently remove control from Direwolf
  gpio.write(trillsat_config.RTS,1) #make sure the actual PTT pin wasn't on at that exact moment (or it could get stuck on)
  print("PTT canceled")
  #time.sleep(20)

  #this stops any unexpected bbs packet transmissions from AX.25 kernel code when on APRS freq
  #ideally an RNS AX.25 frame would be sent to all connected BBS clients and the AX.25 code 
  #would be paused, but I can't find a simple way to do this, and AX.25 timeouts are long enough to allow a quick switch to APRS
 
  uart0_write("Attempting to write to radio.")

  radio_finished = SetRadio(trillsat_config.aprs_freq) #this is blocking for 20 seconds max
  if radio_finished == 'RADIO: FINISHED':

     ######## PTT ENABLED ########### (or you can handle this manually if you want)
     Virtual_PTT = gpio.callback(trillsat_config.Virtual_RTS, pigpio.EITHER_EDGE, pin_link)

     print("PTT enabled")
     #time.sleep(20)

     ######## SEND OUT APRS BEACON ######
     #os.system(beacon_command)
     uart0_write("Sending beacon " + beacon_command)

     ######## PTT DISABLED ###########
     Virtual_PTT.cancel()
     gpio.write(trillsat_config.RTS,1)

     print("PTT canceled again...")
     #time.sleep(20)

     radio_finished = SetRadio(trillsat_config.packet_freq)
     if radio_finished == 'RADIO: FINISHED':
        ######## PTT DISABLED ###########
        Virtual_PTT = gpio.callback(trillsat_config.Virtual_RTS, pigpio.EITHER_EDGE, pin_link)

        print("PTT enabled again")
        #time.sleep(20)

        for callsign in call_list:
           bbs_queue_send(callsign, 'MESSAGE I am back!  Thanks for waiting.  You may now continue where you left off.')
           bbs_queue_send(callsign, 'MESSAGE --------------------')
     else:
        print ("PACKET FAILED: " + radio_finished)
        ######### PACKET FAILED #########
        #If no success, returning back to packet, take system off the air (perhaps send APRS down beacon, too?)
        #perhaps create a special function for ota, since it is used frequently
        uart0_write("Radio has failed to return from beacon.  Taking system OFF the air.")
        #os.system('/usr/bin/killall ax25d')
        #need to also kill any running bbs processes (make sure process name is listed (instead of python) so it is easy to do this
        Beacon.cancel()
        on_the_air = ""
        
  else:
    
    ########## APRS FAILED #########
    print("Radio has failed to go to APRS: " + radio_finished)
    uart0_write("Radio has failed to go to APRS.  Aborting further beacon attempts.")
    Beacon.cancel() #disable APRS (but there needs to be a way to turn it back on easily)
    on_the_air = "" #perhaps leave this on - fix it later
    for callsign in call_list:
       bbs_queue_send(callsign, 'MESSAGE I am back!  I was not able to send that APRS beacon, but thanks for waiting.  You may now continue (p for menu).')

##############################################################
############# ANY THREAD #####################################
def UpdateStatus():
  #Update the status display (XMPP presence) and APRS beacon information

  #lock if possibility of multiple threads use it
  #don't use in another process        
 
  global statusbar
  global chatmode
  global call_list
  global on_the_air
  global frequency
  global beacon_command
  global gpio
  global huckleberry
  global sawyer
  global roll
  global pitch

  
  #Array numbers for Huckleberry for reference:
  #Solar_Charge_Switch I2C_Array[5]
  #Solar_Charge_Mode   I2C_Array[6]
  #Solar_Power         I2C_Array[7]
  #Boost_51            I2C_Array[8]
  #Boost_52            I2C_Array[9]
  #Boost_71            I2C_Array[10]
  #Boost_72            I2C_Array[11]
  #Charger_Relay       I2C_Array[12]
  #Batt1_Voltage_High  I2C_Array[13]
  #Batt1_Voltage_Low   I2C_Array[14]
  #Batt2_Voltage_High  I2C_Array[15]
  #Batt2_Voltage_Low   I2C_Array[16]
  #Batt_Mode           I2C_Array[17]

  
  #Read the states of the two ATtinys (Huckleberry and Sawyer)
  (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 20)
  (I2C_Length_Sawyer, I2C_Array_Sawyer) = gpio.i2c_read_device(sawyer, 20)

  #Read the accelerometer
  ReadAccelerometer()

  if I2C_Array[0] == 254:
  	 Huck_Just_Started = 'h'
  else:
  	 Huck_Just_Started = '' 

  if I2C_Array[17] == 11:
  	 Batt_Mode = 'S'
  else:
  	 Batt_Mode = 'P' 

  if I2C_Array[12] == 1:
  	 Charger_Relay = '1'
  else:
  	 Charger_Relay = '2' 

  if I2C_Array[6] == 1:
  	 Solar_Charge_Mode = '~'
  else:
  	 Solar_Charge_Mode = '' 

  if I2C_Array[5] == 1:
  	 Solar_Charge_Switch = '^'
  else:
  	 Solar_Charge_Switch = '' 
  	 
  if I2C_Array[7] == 1:
  	 Solar_Power = '*'
  else:
  	 Solar_Power = '' 

  if I2C_Array[8] == 1:
  	 Boost_51 = '5.1'
  else:
  	 Boost_51 = '' 

  if I2C_Array[9] == 1:
  	 Boost_52 = '5.2'
  else:
  	 Boost_52 = '' 

  if I2C_Array[10] == 1:
  	 Boost_71 = '7.1'
  else:
  	 Boost_71 = '' 

  if I2C_Array[11] == 1:
  	 Boost_72 = '7.2'
  else:
  	 Boost_72 = '' 
  	 
  if I2C_Array_Sawyer[6] == 12:
  	 Phase_of_Day = 'N'
  elif I2C_Array_Sawyer[6] == 3:
    Phase_of_Day = 'SR' 
  elif I2C_Array_Sawyer[6] == 9:
    Phase_of_Day = 'SS'
  elif I2C_Array_Sawyer[6] == 1:
    Phase_of_Day = 'M'
  elif I2C_Array_Sawyer[6] == 11:
    Phase_of_Day = 'A'
  elif I2C_Array_Sawyer[6] == 6:
    Phase_of_Day = 'Night'
  else:
  	 Phase_of_Day = '' 

  if os.path.exists(trillsat_config.message_file):
    you_have_mail = 'M'
  else:
    you_have_mail = ''

  if on_the_air == "ota":
    upstatus = '(' + str(len(call_list)) + ' users online)'
  else:
    upstatus = 'DOWN'

  # get "zulu time" for APRS
  day = time.strftime("%d", time.gmtime())
  hour = time.strftime("%H", time.gmtime())
  minute = time.strftime("%M", time.gmtime())

  lday = time.strftime("%d", time.localtime())
  lhour = time.strftime("%H", time.localtime())
  lminute = time.strftime("%M", time.localtime())

  #Construction of APRS status report beacon (callsign, time, PBBS symbol, freq, # of users online or DOWN, software type experimental)
  #Note that this includes time, but not position--the "day + hour + minute + 'z'" section can be replaced by the 6 character Maidenhead location instead.
  beacon_command = '/usr/bin/beacon -c ' + trillsat_config.stationcall + '-d WIDE2-1 -s 1 >' + day + hour + minute + 'z/] Experimental PBBS - 1200 baud on ' + trillsat_config.packet_freq + ' ' + upstatus


  #Voltage is in the range from 34 (3.4v) to 43 (4.3v)
  #By subtracting 34, this gives a value of 0 to 9
  #If batt reaches 3.3v, Huckleberry should emergency shutdown
  #Lithium-ion surprisingly scales well for single digit 0-9 measurement

  Batt1_Voltage = ((I2C_Array[13] << 8) + I2C_Array[14])/1000 - 34
  Batt2_Voltage = ((I2C_Array[15] << 8) + I2C_Array[16])/1000 - 34

  #Now construct the status line
  newstatus = str(Batt1_Voltage) + str(Batt2_Voltage) + ' '

  if Huck_Just_Started != '':
     newstatus += Huck_Just_Started + ' '
  if roll != '':
     newstatus += str(roll) + ' '
  if pitch != '':
     newstatus += str(pitch) + ' '
  if Phase_of_Day != '':
     newstatus += Phase_of_Day + ' '
  if Solar_Charge_Mode != '':
     newstatus += Solar_Charge_Mode
  if Charger_Relay != '':
     newstatus += Charger_Relay
  if Solar_Power != '':
     newstatus += Solar_Charge_Switch
  if Batt_Mode != '':
     newstatus += Solar_Power + ' '
  if Batt_Mode != '':
     newstatus += Batt_Mode + ' '
  if Boost_51 != '':
     newstatus += Boost_51 + ' '
  if Boost_52 != '':
     newstatus += Boost_52 + ' '
  if Boost_71 != '':
     newstatus += Boost_71 + ' '
  if Boost_72 != '':
     newstatus += Boost_72 + ' '
  if you_have_mail != '':
     newstatus += you_have_mail + ' '
  if on_the_air != '':
     newstatus += on_the_air + ' '
  if call_list != []:
     newstatus += ', '.join(call_list) + ' '
  if chatmode != '':
     newstatus += chatmode + ' '
  if frequency != '':
     newstatus += frequency + ' '
  newstatus += lday + ' ' + lhour + ':' + lminute

  if newstatus != statusbar:
    statusbar = newstatus

    # THIS SENDS STATUS UPDATES DIRECTLY TO ESP
    uart0_write('STATUS: ' + newstatus)
    print ("newstatus: " + newstatus)

######### THIS IS THREAD 2 ######################################## 
#This just gets a serial string\n (blocking) from the ESP8266 and sends it to bot_queue
def GetXMPP(uart0):

 try:
   xmpp_message = ''
   command = []
   while xmpp_message != "term":
    serialchar = ''
    xmpp_message = ''
    while serialchar != "\n":
      xmpp_message += serialchar
      serialchar = uart0.read()

    #add line below if Python 3 (check to see if it works)
    if sys.version_info[0] >= 3:
      xmpp_message = xmpp_message.decode("utf-8")

    if xmpp_message == "term":
       print("GetXMPP is terminating...  Telling main thread to terminate")
       bot_queue_send('TERMINATE')
       break

    print ('RECEIVED FROM ESP: ' + xmpp_message)
    bot_queue_send('ESP: ' + xmpp_message)
 except Exception as e:
    print (e)

###############################################################
#This writes text to the ESP8266
#any thread can call this, but don't use for other processes
def uart0_write(text):
  global uart0
  global uart0_lock

  #VERY STRANGE PROBLEM
  #Must put at least a 1 second pause between writes to ESP
  #or it will miss a message - don't know if this is ESP, Xabber, or threading issue
  #putting a pause in my XMPP code, however did not resolve it, but this did
  #length of pause needed seems to be related to amount of text sent--can go less if smaller
  #don't exceed 256 bytes in writes or it seems to malfunction/reset ESP
  #add code later to break into chunks

  time.sleep(1)

  uart0_lock.acquire()
  uart0.write(escape(text + "\n"))
  uart0_lock.release()  

################################################################
#This writes text to the bot_queue (to be read by other trillsat_bot processes)
#any thread can call this, but don't use for other processes
def bot_queue_send(text):

  global bot_queue
  global bot_queue_lock

  bot_queue_lock.acquire()
  bot_queue.send(text)
  bot_queue_lock.release()

################################################################
#any thread can call this, but don't use for other processes
#This writes test to the bbs_queue (to be ready by trillsat_bbs)
def bbs_queue_send(callsign, text):
 try:
  global bbs_queue
  global bbs_queue_lock

  bbs_queue_lock.acquire()
  #print('bbs_queue_' + callsign + '.send("' + trillsat_config.botpassword + ' ' + callsign + ' ' + text + '")')
  exec('bbs_queue_' + callsign + '.send("' + trillsat_config.botpassword + ' ' + callsign + ' ' + text + '")')
  bbs_queue_lock.release()
 except Exception as e:
  print(e)

###################################################################
####### PART OF THREAD 1 #######################################
#Parse the commands from ESP8266 and process them
#Some are user commands, some are from the ESP8266 bot
def ProcessESP(esp_message):

 try:

  global call_list
  global chatmode
  global xmppstatus
  global on_the_air
  global frequency
  global md
  global update_status_lock
  global chatcall
  global uart0
  global gpio
  global sawyer
  global huckleberry
  global hucktempoffset
  global sawtempoffset

  if chatmode == '':


    ######### BE SURE TO MOVE MUCH OF THESE TO INDIVIDUAL FUNCTIONS LATER, TO SIMPLIFY #######

    print("uart0 message is: " + esp_message)
    command = esp_message.split(' ')
    if command[0] == "Help" or command[0] == "help":
    	#could not run at 115200 baud or corruption occurred on long writes, use 9600 or 19200
    	#break into small chunks < 256 bytes or it will overload and reset ESP
    	#pause between them or it will get missed and cause ESP to malfunction for some reason
    	#perhaps add code to uart0_write to automatically break into chunks
    	uart0_write("Pi Command List (probably obsolete):\r" +
    	"hi: Echo greeting.\r" +
    	"beacon: Send beacon.\r" +
    	"ota: Toggle on-the-air mode.\r" +
    	"c: Toggle chat mode.\r" +
    	"r: Read messages.\r" +
    	"d: Delete all messages.\r" +
    	"away: Set away mode.\r" +
    	"gettime: Display the time.\r" +
    	"m: Send mail.\r")  
    	uart0_write("text message: Send text message.\r" +
    	"settime: Set the time using format example (2004-02-29  16:21:42)\r" +
    	"shutdown: Shut down the Pi OS\r" +
    	"piwifi [on|off]: Enable/disable the Pi wifi adapter.\r")
    	uart0_write("pibootstrap [on|off] (20): Enable/disable the ability for the Pi to control its own power switch, but ESP can override.\r"    	
    	"ldo [on|off] (19): Enable/disable the ESP power regulator.\r" +
    	"light [on|off] (26): Enable/disable the LED light, but ESP can override.\r")
     	uart0_write("voltage (30,101,110,120): Display Huckleberry Vcc, Batt1, Batt2, and Ground voltages.\r" +
    	"cds (142): Display the two CdS LDR/photocell voltages.\r" +
    	"solar (152): Display the crude solar charger USB voltage.\r" +
    	"hall (143): Display the digital values of the two capstan hall sensors.\r")
    	uart0_write("gyrohall (144): Display the digital values of the three gyro hall sensors.\r" +
    	"gyro (222): Test the gyro motor.\r" +
    	"[--|-+|++|+-] (109,108,9,8): Drive the capstan H-Bridge without PWM.\r")
    	uart0_write("ramp [1|0] acceleration [1|0] (88): Test PWM ramp up capstan motor, 1=locked antiphase, 0=sign magnitude, use small values for accel, finish with direction 1=up, 0=down.\r" +
    	"pc (89): Clear PWM and turn capstan motor off.\r")
    	uart0_write("s (11): Series mode.\r"
    	"p (1): Parallel mode.\r" +
    	"charge [1|2] (22): Set solar charger relay to batt 1 or 2.\r")
    	uart0_write("chargeswitch [on|off] (44): Enable/disable solar charge previously set by charger relay.\r" +
    	"[5.1|5.2|7.2|7.2] [on|off] (51,52,71,72): Enable/disable 5 or 7.4 volt boost converter for that battery.\r" +
    	"qi [on|off] (91): Enable/disable qi wireless charger.\r")
    	uart0_write("radio [on|off] (23): Enable/disable UV-5RA radio power.\r" +
    	"chargemode [on|off] (157): Enable/Disable charge mode.")   
    	 	 
    elif command[0] == "Hi" or command[0] == "hi":
       uart0_write("hello there from trillsat_bot on Pi")
    elif command[0] == "Beacon" or command[0] == "beacon":
       uart0_write("you picked beacon")
       SendBeacon()
    elif esp_message == "pi send sleep":
       uart0_write("esp sleep")
    elif command[0] == "ota":
       if on_the_air == "":          
          on_the_air = "ota"
          uart0_write("Radio is now ON the air.  BBS and APRS beacons enabled.")
          #os.system('/usr/bin/ax25d') -- this doesn't really work without ports attached
          Beacon.start()          
       else:      
          on_the_air = ""    
          #os.system('/usr/bin/killall ax25d')
          Beacon.cancel()
          if call_list == []:
             uart0_write("BBS is now OFF the air.  APRS beacons disabled.") #later, add a mode to allow APRS to send messages that BBS is down
          else:
             uart0_write("BBS will no longer allow connections.  APRS beacons disabled.  However, there is still at least 1 person connected.")
          time.sleep(.5)
          
    elif command[0] == "C" or command[0] == "c":
       uart0_write("Chat mode has been enabled for " + command[1])
       #use code so that bbs knows it is from bot and not a bbs user
       bbs_queue_send(command[1], 'CHAT_ACCEPTED')
       #put error checking if firstargument is missing
       chatcall = command[1]
       chatmode = 'chat'
    elif command[0] == "R" or command[0] == "r":
       try:
         with open(trillsat_config.message_file, "r") as msgfile:
           msg_piece = ''
           for msg_line in msgfile:
              if msg_line == "--------------------\n":
                 uart0_write(msg_piece)
                 msg_piece = ''
              else:
                 msg_piece += msg_line                
       except FileNotFoundError:
         uart0.write("You have no messages.")
    elif command[0] == "D" or command[0] == "d":
       os.remove(trillsat_config.message_file)
       uart0_write("Deleted all messages.")
       UpdateStatus()
    elif command[0] == "Away" or command[0] == "away":
       xmppstatus = 'away'
    elif command[0] == 'PRESENCE:':
       xmppstatus = command[1]
       print("presence is " + command[1])
    #ESP has set Pi
    elif command[0] == 'SET_TIME:':
       os.system("/usr/bin/date --set @" + command[1])
    #manually tell ESP to set the Pi time (put in timer, and disable timed)
    elif command[0] == "Gettime" or command[0] == "gettime": 
       uart0_write("GET_TIME: ")   
    elif command[0] == "F" or command[0] == "f":
       SetRadio(command[1], "write")
    elif command[0] == "M" or command[0] == "m":
       timestamp = time.strftime("%a, %d %b %Y %H:%M:%S CST", time.localtime())
       msgfile = open(trillsat_config.maildir + command[1], "w")          
       msgfile.write('Date: ' + timestamp + "\nFrom: " + trillsat_config.stationcall + "\n" + " ".join(command[2:]) + "\n")
       msgfile.close()
       uart0_write("Ok, mail has been sent to " + command[1])
    #manually set time on Pi and ESP
    elif command[0] == "Settime" or command[0] == "settime": 
       os.system("/usr/bin/date --set " + command[1])
       uart0_write("SET_TIME: " + str(time.time()))
    elif command[0] == 'Shutdown' or command[0] == 'shutdown':
       #make sure it is in parallel mode first
       #also add code later to make sure gyro is off to prevent attiny damage before switching to parallel
       gpio.i2c_write_device(huckleberry, [1])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Switched to parallel mode.  Shutting down...")
          os.system('/usr/bin/shutdown now')
    elif command[0] == 'piwifi' or command[0] == 'Piwifi':
       if command[1] == 'on':
          os.system('/usr/bin/rfkill unblock 0')
       else:
          os.system('/usr/bin/rfkill block 0')
       uart0_write("Pi received command Piwifi on/off to turn wifi card " + command[1] + " .")
    elif command[0] == 'pibootstrap' or command[0] == 'Pibootstap':
       if command[1] == 'on':
          gpio.write(20,1)
          gpio.set_mode(20, pigpio.OUTPUT)
          gpio.write(20,1)
          #drive high 3.3 volts to keep itself on, may coexist with ESP sending 3.3v signal
          #just make sure esp never forces a low, but uses pi reset line to shutdown instead
       else:
          gpio.set_mode(20, pigpio.INPUT)
          #relies on pulldown to keep transistor off
          #don't feed 3.3v power line directly into transistor input!! only use ESP output line
       uart0_write("Pi received command Pibootsrap on/off to turn boostrap mode " + command[1] + " .")
    elif command[0] == 'ldo' or command[0] == 'Ldo':
       if command[1] == 'on':
          gpio.i2c_write_device(huckleberry, [99,1])
       else:
          gpio.i2c_write_device(huckleberry, [99,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 99 on/off to turn " + command[1] + " the LDO power for the Huzzah ESP8266.")
          #note that you won't get a response if you turn LDO off, since Huzzah (and xmpp) goes offline
    elif command[0] == 'light' or command[0] == 'Light':
       if command[1] == 'on':
          gpio.set_mode(26, pigpio.INPUT)
       else:
          gpio.write(26,0)
          gpio.set_mode(26, pigpio.OUTPUT)
          gpio.write(26,0)
          #light only comes on if hall and huckleberry are not sinking
          #don't drive pin high or it will overcurrent through transistor!!
       uart0_write("Pi received command Light on/off to turn white LEDs " + command[1] + " .")

    #these are the most accurate sensors, absolute and accurate to +-.5 C
    elif command[0] == 'Dtemp' or command[0] == 'dtemp':
       
       #unreliable
       #due to a python/shell and/or cpu/pigpio interaction, had to use cron to run shell 1-wire script
       #it corrupts/errors when run from python, even if it spawns a nohup process for some reason
       probefile = open('/tmp/trillsat_therm', 'r')
       probelines = probefile.readlines()
       probefile.close()

       uart0_write("The DS18B20+ chip reports " +  str(round(float(probelines[0]) * .0018 + 32.0,1)) + " " + str(round(float(probelines[1]) * .0018 + 32.0,1)) + " " + str(round(float(probelines[2]) * .0018 + 32.0,1)) + " " + str(round(float(probelines[3]) * .0018 + 32.0,1)) + " " + " degrees Fahrenheit.")

    #this is has ok accuracy of about 1C, but is not guaranteed and is relative and has to be calibrated against the DS18B20+ when trillsat_bot loads
    elif command[0] == 'Listemp' or command[0] == 'listemp':
       ReadLisTemp()
       uart0_write("The LIS3DH chip reports " + str(listemp) + " degrees Fahrenheit.")

    elif command[0] == 'Voltage' or command[0] == 'voltage':
      	 #get vcc value using 1.1v reference
         uart0_write("Reading voltages...")
         I2C_Command = 1
         while I2C_Command != 255:
           gpio.i2c_write_device(huckleberry, [30]) # huckleberry vcc voltage
           time.sleep(1)
           (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 3)
           time.sleep(1)
           I2C_Command = I2C_Array[0]
         vcc = ((I2C_Array[2] << 8) + I2C_Array[1])/10000.0
         I2C_Command = 1
         while I2C_Command != 255:
           gpio.i2c_write_device(huckleberry, [110]) # batt1 voltage
           time.sleep(1)
           (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 3)
           time.sleep(1)
           I2C_Command = I2C_Array[0]
         batt1 = ((I2C_Array[2] << 8) + I2C_Array[1])/10000.0
         I2C_Command = 1
         while I2C_Command != 255:
           gpio.i2c_write_device(huckleberry, [120]) # batt2 voltage
           time.sleep(1)
           (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 3)
           time.sleep(1)
           I2C_Command = I2C_Array[0]
         batt2 = ((I2C_Array[2] << 8) + I2C_Array[1])/10000.0
         uart0_write("Huckleberry received Voltage commands 30, 110, 120.\r" +
         "Huckleberry Vcc: " + str(vcc) +
         "\rBatt 1: " + str(batt1) + 
         "\rBatt 2: " + str(batt2) )
         #.461545894
    #The ATtiny CPU's have the worst accuracy of +-10 C, are relative and also has to be calibrated against DS18B20+ on trillsat_bot load
    #there is a factory set gain coefficient in the signature table, but it won't improve things much if non-linear, so I didn't use
    elif command[0] == 'Tinytemp' or command[0] == 'tinytemp':
         uart0_write("Reading temp probe on Huckleberry...")
         I2C_Command = 1
         while I2C_Command != 255:
           gpio.i2c_write_device(huckleberry, [70]) # huckleberry temp
           time.sleep(1)
           (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 3)
           time.sleep(1)
           I2C_Command = I2C_Array[0]
         #converts to Fahrenheit
         hucktemp = ((I2C_Array[2] << 8) + I2C_Array[1] + hucktempoffset) * 1.8 + 32
         I2C_Command = 1
         uart0_write("Huckleberry temp: " + str(round(hucktemp,1)))
         uart0_write("Reading temp probe on Sawyer...")
         while I2C_Command != 255:
           gpio.i2c_write_device(sawyer, [70]) # sawyer temp
           time.sleep(1)
           (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 3)
           time.sleep(1)
           I2C_Command = I2C_Array[0]
         #roughly estimates temperature +- 10C based on datasheet of 1C per bit, then converts to Fahrenheit
         sawtemp = ((I2C_Array[2] << 8) + I2C_Array[1] + sawtempoffset) * 1.8 + 32
         uart0_write("Sawyer temp: " + str(round(sawtemp, 1)))

    elif command[0] == 'Pitemp' or command[0] == 'pitemp':
         uart0_write("Pi GPU/CPU Temp: " + str(float(subprocess.check_output(['cat', '/sys/class/thermal/thermal_zone0/temp'])) * .0018 + 32.0))

    #On-the-fly tweaking of LIS3DH accelerometer click detection parameters:

    #click direction 1 is x only, 4 is y only, 16 is z only (add for combos)
    elif command[0] == 'Cfg' or command[0] == 'cfg':
       gpio.i2c_write_byte_data(lis, CLICK_CFG, int(command[1]))
       uart0_write("(1 is x, 4 is y, 16 is z) Set LIS3DH CLICK_CFG to " + command[1])
    #threshold
    elif command[0] == 'Ths' or command[0] == 'ths':
       gpio.i2c_write_byte_data(lis, CLICK_THS, int(command[1]))
       uart0_write("Set LIS3DH CLICK_THS to " + command[1])

    #time limit, latency, and window
    elif command[0] == 'Limit' or command[0] == 'limit':
       gpio.i2c_write_byte_data(lis, TIME_LIMIT, int(command[1]))
       uart0_write("Set LIS3DH TIME_LIMIT to " + command[1])
    elif command[0] == 'Latency' or command[0] == 'latency':
       gpio.i2c_write_byte_data(lis, TIME_LATENCY, int(command[1]))
       uart0_write("Set LIS3DH TIME_LATENCY to " + command[1])
    elif command[0] == 'Window' or command[0] == 'window':
       gpio.i2c_write_byte_data(lis, TIME_WINDOW, int(command[1]))
       uart0_write("Set LIS3DH TIME_WINDOW to " + command[1])

    elif command[0] == 'Ref' or command[0] == 'ref':
       #enable high pass filter with normal mode and click detection
       gpio.i2c_write_byte_data(lis, CTRL_REG2, 0b00111100)
 
       #read reference to delete dc value from axis
       lis_hpfilter_ref = gpio.i2c_read_byte_data(lis, REFERENCE)

       #enable high pass filter with reference mode and click detection
       gpio.i2c_write_byte_data(lis, CTRL_REG2, 0b01111100)

       #now write reference value obtained earlier (it is in twos complement)
       gpio.i2c_write_byte_data(lis, REFERENCE, int(lis_hpfilter_ref))

       uart0_write("LIS3DH REFERENCE register has been set.")

    elif command[0] == 'Cds' or command[0] == 'cds':
         uart0_write("Reading Sawyer CdS cells..." )
         I2C_Command = 1
         while I2C_Command != 255:
           gpio.i2c_write_device(sawyer, [142]) # CdS 1
           time.sleep(1)
           (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 3)
           time.sleep(1)
           I2C_Command = I2C_Array[0]
         cds1 = ((I2C_Array[2] << 8) + I2C_Array[1])
         I2C_Command = 1
         while I2C_Command != 255:
           gpio.i2c_write_device(sawyer, [242]) # CdS 2
           time.sleep(1)
           (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 3)
           time.sleep(1)
           I2C_Command = I2C_Array[0]
         cds2 = ((I2C_Array[2] << 8) + I2C_Array[1])
         uart0_write("Sawyer received CdS commands 142, 242.  CdS 1: " + str(cds1) + " CdS 2: " + str(cds2) )
    elif command[0] == 'Solar' or command[0] == 'solar':
       uart0_write("Reading solar charger voltage...")
       I2C_Command = 1
       while I2C_Command != 255:
         gpio.i2c_write_device(huckleberry, [152])
         time.sleep(1)
         (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 3)
         time.sleep(1)
         I2C_Command = I2C_Array[0]
       uart0_write("Solar charger voltage is: " + str(((I2C_Array[2] << 8) + I2C_Array[1])/10000.0) )
    elif command[0] == 'Hall' or command[0] == 'hall':
       gpio.i2c_write_device(sawyer, [143])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 3)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received Hall command 143.  Hall 1: " + str(I2C_Array[1]) + " Hall 2 (miso line): " + str(I2C_Array[2]) )
    elif command[0] == 'Gyrohall' or command[0] == 'gyrohall':
       gpio.i2c_write_device(sawyer, [144])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 4)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received Gyro Hall command 144.  HU: " + str(I2C_Array[1]) + " HV: " + str(I2C_Array[2]) + " HW: " + str(I2C_Array[3]) )
    elif command[0] == 'Gyrostop' or command[0] == 'gyrostop':
       gpio.i2c_write_device(sawyer, [147])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received Gyro Stop command 147." )   
    elif command[0] == 'Bldctest' or command[0] == 'bldctest':
       gpio.i2c_write_device(sawyer, [223, int(command[1])])
       time.sleep(3)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer performed BLDC test." )   
    elif command[0] == 'Gyro' or command[0] == 'gyro':
       #first force a turn on series to prevent chip damage
       gpio.i2c_write_device(huckleberry, [11])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         #now go ahead and activate gyro
         uart0_write("Enabled series mode and performing gyro test...")
         I2C_Command = 1
         while I2C_Command != 255:
            gpio.i2c_write_device(sawyer, [222,int(command[1])])
            time.sleep(1)
            (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
            time.sleep(1)
            I2C_Command = I2C_Array[0]
         uart0_write("Gyro test complete.")
    elif command[0] == 'Gyrorpm' or command[0] == 'gyrorpm':
       uart0_write("Reading gyro RPM...")
       I2C_Command = 1
       while I2C_Command != 255:
         gpio.i2c_write_device(sawyer, [56])
         time.sleep(1)
         (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 3)
         time.sleep(1)
         I2C_Command = I2C_Array[0]
       uart0_write("Gyro RPM is: " + str((I2C_Array[2] << 8) + I2C_Array[1]) )


    #THUMP haptic-related commands
    elif command[0] == 'Pulse' or command[0] == 'pulse':
       gpio.i2c_write_device(sawyer, [112])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer performed brief haptic motor pulse.")
    elif command[0] == 'Pulsemode' or command[0] == 'pulsemode':
       gpio.i2c_write_device(sawyer, [113,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
         uart0_write("Set haptic/led pulse mode on sawyer.")
       gpio.i2c_write_device(huckleberry, [113,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         uart0_write("Set haptic/led pulse mode on huckleberry.")
    elif command[0] == 'Thump' or command[0] == 'thump':
       gpio.i2c_write_device(sawyer, [114,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
         uart0_write("Set thump interrupt on sawyer.")
       gpio.i2c_write_device(huckleberry, [114,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         uart0_write("Set thump interrupt on huckleberry.")
    elif command[0] == 'Code' or command[0] == 'code':
       gpio.i2c_write_device(huckleberry, [115,ord(command[1][0]),ord(command[1][1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 3)
       if I2C_Array[0] == 255:
          uart0_write("Thump access code on huckleberry is set to: " + chr(I2C_Array[1]) + chr(I2C_Array[2]))
       gpio.i2c_write_device(sawyer, [115,ord(command[1][0]),ord(command[1][1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 3)
       if I2C_Array[0] == 255:
         uart0_write("Thump access code on sawyer is set to: " + chr(I2C_Array[1])  + chr(I2C_Array[2]))


    elif command[0] == '--':
       gpio.i2c_write_device(sawyer, [109])
       gpio.i2c_write_device(sawyer, [108])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received command --.")  
    elif command[0] == '-+':
       gpio.i2c_write_device(sawyer, [109])
       gpio.i2c_write_device(sawyer, [8])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received command -+.")  
    elif command[0] == '++':
       gpio.i2c_write_device(sawyer, [9])
       gpio.i2c_write_device(sawyer, [8])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received command ++.")  
    elif command[0] == '+-':
       gpio.i2c_write_device(sawyer, [9])
       gpio.i2c_write_device(sawyer, [108])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received command +-.")           
    elif command[0] == 'Ramp' or command[0] == 'ramp':
       #first force a turn on series
       gpio.i2c_write_device(huckleberry, [11])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         #now go ahead and activate h-bridge
         uart0_write("Enabled series mode and activating h-bridge...")
       I2C_Command = 1
       while I2C_Command != 255:
          gpio.i2c_write_device(sawyer, [88,int(command[1]),int(command[2]),int(command[3]),int(command[4]), int(command[5])])
          time.sleep(1)
          (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
          time.sleep(1)
          I2C_Command = I2C_Array[0]
       uart0_write("H-Bridge move complete.")        
    elif command[0] == 'Rotate' or command[0] == 'rotate':
       #first force a turn on series
       gpio.i2c_write_device(huckleberry, [11])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         #now go ahead and activate h-bridge
         uart0_write("Enabled series mode and activating h-bridge...")
       I2C_Command = 1
       while I2C_Command != 255:
          #convert 360 degrees to 256 degrees for 8-bit compatibility first
          gpio.i2c_write_device(sawyer, [12,int(command[1]),int(round(.711 * int(command[2]),0))])
          time.sleep(1)
          (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
          time.sleep(1)
          I2C_Command = I2C_Array[0]
       uart0_write("Planetary mass rotation complete.") 
    elif command[0] == 'Park' or command[0] == 'park':
       #first force a turn on series
       gpio.i2c_write_device(huckleberry, [11])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         #now go ahead and activate h-bridge
         uart0_write("Enabled series mode and activating h-bridge...")
       I2C_Command = 1
       while I2C_Command != 255:
          gpio.i2c_write_device(sawyer, [90])
          time.sleep(1)
          (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
          time.sleep(1)
          I2C_Command = I2C_Array[0]
       uart0_write("Park complete.") 
    elif command[0] == 'Orbit' or command[0] == 'orbit':
       #first force a turn on series
       gpio.i2c_write_device(huckleberry, [11])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         #now go ahead and activate h-bridge
         uart0_write("Enabled series mode and activating h-bridge...")
       I2C_Command = 1
       while I2C_Command != 255:
          #two params: [direction][# of orbits]
          gpio.i2c_write_device(sawyer, [92,int(command[1]),int(command[2])])
          time.sleep(1)
          (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
          time.sleep(1)
          I2C_Command = I2C_Array[0]
       uart0_write("Orbit complete.") 
    elif command[0] == 'Pc' or command[0] == 'pc':
       gpio.i2c_write_device(sawyer, [89])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received pwm clear Pc command 89.") 
    elif command[0] == 'Rmorse' or command[0] == 'rmorse':
       gpio.i2c_write_device(huckleberry, [170])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 2)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received Morse letter: " + chr(I2C_Array[1] + 65))
       time.sleep(1)
       gpio.i2c_write_device(sawyer, [170])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 2)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer received Morse letter: " + chr(I2C_Array[1] + 65))
    elif command[0] == 'Hucklight' or command[0] == 'hucklight':
       gpio.i2c_write_device(huckleberry, [183,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command to turn on/off LED spotlight on MISO line.")
    elif command[0] == 'S' or command[0] == 's':
       gpio.i2c_write_device(huckleberry, [11])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received series command 11.")
    elif command[0] == 'P' or command[0] == 'p':
       gpio.i2c_write_device(huckleberry, [1])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received parallel command 1.")
    elif command[0] == 'Charge' or command[0] == 'charge':
       if command[1] == '1':
         gpio.i2c_write_device(huckleberry, [22,1])
       else:
         gpio.i2c_write_device(huckleberry, [22,2])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
         uart0_write("Huckleberry received command 22 to charge Batt " + command[1])
    elif command[0] == 'Chargeswitch' or command[0] == 'chargeswitch':
       if command[1] == 'on':
         gpio.i2c_write_device(huckleberry, [44,1])
       else:
         gpio.i2c_write_device(huckleberry, [44,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 44 to turn " + command[1] + " solar charge switch.")
    elif command[0] == 'Chargemode' or command[0] == 'chargemode':
       if command[1] == 'on':
         gpio.i2c_write_device(huckleberry, [157,1])
       else:
         gpio.i2c_write_device(huckleberry, [157,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 157 to turn " + command[1] + " charge mode.")
    elif command[0] == '5.1':
       if command[1] == 'on':
          gpio.i2c_write_device(huckleberry, [51,1])
       else:
          gpio.i2c_write_device(huckleberry, [51,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 51 on/off to turn " + command[1] + "Batt 1 5v boost.")
    elif command[0] == '5.2':
       if command[1] == 'on':
          gpio.i2c_write_device(huckleberry, [52,1])
       else:
         gpio.i2c_write_device(huckleberry, [52,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 52 on/off to turn " + command[1] + "Batt 2 5v boost.")
    elif command[0] == '7.1':
       if command[1] == 'on':
          gpio.i2c_write_device(huckleberry, [71,1])
       else:
          gpio.i2c_write_device(huckleberry, [71,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 71 on/off to turn " + command[1] + "Batt 1 7.4v boost.")
    elif command[0] == '7.2':
       if command[1] == 'on':
          gpio.i2c_write_device(huckleberry, [72,1])
       else:
          gpio.i2c_write_device(huckleberry, [72,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 72 on/off to turn " + command[1] + "Batt 2 7.4v boost.")
    elif command[0] == 'Qi' or command[0] == 'qi':
       if command[1] == 'on':
          gpio.i2c_write_device(huckleberry, [91,1])
       else:
          gpio.i2c_write_device(huckleberry, [91,0])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry received command 91 on/off to turn " + command[1] + "qi wireless charger.")
    elif command[0] == 'Radio' or command[0] == 'radio':
       if command[1] == 'on':
          gpio.set_mode(23, pigpio.OUTPUT)
          gpio.write(23,1)
       else:
          gpio.set_mode(23, pigpio.OUTPUT)
          gpio.write(23,0)
       uart0_write("Turned " + command[1] + " UV-5RA radio.")
    elif command[0] == 'Huckctc' or command[0] == 'huckctc':
       gpio.i2c_write_device(huckleberry, [55,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 1)
       if I2C_Array[0] == 255:
          uart0_write("Huckleberry set CTC OCR0A value to " + command[1] + ".")
    elif command[0] == 'Sawctc' or command[0] == 'sawctc':
       gpio.i2c_write_device(sawyer, [55,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer set CTC OCR0A value to " + command[1] + ".")
    elif command[0] == 'Gyrosetrpm' or command[0] == 'gyrosetrpm':
       gpio.i2c_write_device(sawyer, [57,int(command[1])])
       time.sleep(1)
       (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 1)
       if I2C_Array[0] == 255:
          uart0_write("Sawyer set gyro rpm to value " + command[1] + ".")
    elif command[0] == "Text" or command[0] == 'text':
       uart0_write("Sending text.  Please wait up to 40 seconds to complete.")
       child = pexpect.spawn('/usr/bin/call -r -h 1 ' + command[1])
       try:
         child.expect("What is your text? .*", timeout=20)
         uart0_write("received response")
         #print("received first response, sending text")
         child.write('REMOTETEXT: ' + ' '.join(command[2:]) + "\r")
         child.expect("Your text has been received..*", timeout=20)
         uart0_write("Ok, your text has been sent to " + command[1])
       except:
         uart0_write("Could not send text.")
    else:
       uart0_write("Unknown command.  You sent: " + esp_message)

  else:
    ###### CHAT MODE #################
    if esp_message == 'endchat':
      chatmode = ''
      bbs_queue_send(chatcall, 'CHAT_ENDED')
      uart0_write("CHAT ENDED FOR " + chatcall)
    else:
      bbs_queue_send(chatcall, 'CHAT_LINE Station Operator said: ' + esp_message)

 except Exception as e:
   print (e)

##############################################################
######## THIS IS A PIGPIO CALLBACK ###########################
#this requires pigpiod to be running
def pin_link(Virtual_RTS, level, tick):
   global gpio
   gpio.write(trillsat_config.RTS,level)

######################################################################
#Setup GPIO states for the UV-5RA data interface for packet mode and program mode
#can be called from any thread, but use locking if potential for other threads exist.
#don't call from another process.
def gpio_setup(type): 

  global gpio

  #note that pigpio will not use the uart circuitry and may not have the high-impedance protection
  #like the pi built in UART
  #in PACKET mode RTS is active (OUTPUT) and TX should be input (high impedance)
  #in program mode, RTS should be input (high impedance) and TX should be output
  #be sure to also disable pull up/pull downs on GPIO when in floating
  #9, 10, 14, 15, 17, 27 are pulled low for example

  if type == 'PACKET':
   gpio.set_mode(trillsat_config.Fake_TX, pigpio.INPUT) #PIN 9 Set to floating so it doesn't interfere
   gpio.set_pull_up_down(trillsat_config.Fake_TX, pigpio.PUD_OFF)
   gpio.set_mode(trillsat_config.RTS, pigpio.OUTPUT) #PIN 17
   gpio.write(trillsat_config.RTS,1) #Start high so PTT doesn't go off
   gpio.set_mode(trillsat_config.Virtual_RTS, pigpio.OUTPUT) #PIN 4, since it is Type 1,2,3 safe
   gpio.write(trillsat_config.Virtual_RTS,1) #This makes RTS go high which prevents PTT
  elif type == 'PROGRAM':
   gpio.set_mode(trillsat_config.RTS, pigpio.INPUT) #make floating so it doesn't interfere with programming
   gpio.set_pull_up_down(trillsat_config.RTS, pigpio.PUD_OFF)
   gpio.set_mode(trillsat_config.Fake_TX, pigpio.OUTPUT)
   gpio.write(trillsat_config.Fake_TX,1) # keep high so PTT is not activated
   gpio.set_mode(trillsat_config.Fake_RX, pigpio.INPUT)
  elif type == 'EXIT':
   gpio.set_mode(trillsat_config.RTS, pigpio.INPUT) #make floating so it doesn't interfere with programming
   gpio.set_pull_up_down(trillsat_config.RTS, pigpio.PUD_OFF)
   gpio.set_mode(trillsat_config.Fake_TX, pigpio.INPUT)
   gpio.set_pull_up_down(trillsat_config.Fake_TX, pigpio.PUD_OFF)
   gpio.set_mode(trillsat_config.Fake_RX, pigpio.INPUT)
   gpio.set_pull_up_down(trillsat_config.Fake_RX, pigpio.PUD_OFF)
   gpio.set_mode(trillsat_config.Virtual_RTS, pigpio.INPUT) 
   gpio.set_pull_up_down(trillsat_config.Virtual_RTS, pigpio.PUD_OFF)

######################################################################
#Read LIS3DH accelerometer data and compute pitch and roll
def ReadAccelerometer(): 

   global roll
   global pitch

   xlval = gpio.i2c_read_byte_data(lis, OUT_X_L)
   xhval = gpio.i2c_read_byte_data(lis, OUT_X_H)
   ylval = gpio.i2c_read_byte_data(lis, OUT_Y_L)
   yhval = gpio.i2c_read_byte_data(lis, OUT_Y_H)
   zlval = gpio.i2c_read_byte_data(lis, OUT_Z_L)
   zhval = gpio.i2c_read_byte_data(lis, OUT_Z_H)

   # first combine high and low bytes (since it is a 12-bit value)
   # then correct for the 12-bit left justifiction by shifting to the right 4 bits
   # then finally perform a two's complement on that 12-bit number
   # note the binary values below have 12 and 13 bits to match the 12-bit value
   
   xm = (xhval << 8) + xlval
   xm = xm >> 4
   if (0b100000000000 & xm):
     xm = xm - 0b1000000000000

   ym = (yhval << 8) + ylval
   ym = ym >> 4
   if (0b100000000000 & ym):
     ym = ym - 0b1000000000000

   zm = (zhval << 8) + zlval
   zm = zm >> 4
   if (0b100000000000 & zm):
     zm = zm - 0b1000000000000

   #in actual use flip z-axis, since it was mounted inverted
   #roll/pitch equation flips x 180 degrees if inverted
   #zm = -zm

   #print ("x, y, z accelerometer: ", xm, ym, zm)

   pitch = math.atan2(ym, zm) * 180.0/math.pi
   roll = math.atan2((-xm), math.sqrt(ym ** 2 + zm ** 2)) * 180.0/math.pi

   roll  = round(roll, 1)
   pitch = round(pitch, 1)

##################################
# Read LIS3DH temperature
def ReadLisTemp(): 

   global listemp
   global listempoffset

   #temp adc is 10 bits max, even though accel is set to 12 bits
   listempl = gpio.i2c_read_byte_data(lis, OUT_ADC3_L)
   listemph = gpio.i2c_read_byte_data(lis, OUT_ADC3_H)
   
   listemp = (listemph << 8) + listempl
  
   #remove 10-bit left alignment
   listemp = listemp >> 6
   
   #10-bit two's complement (10 and 11 bit binaries below)
   if (0b1000000000 & listemp):
     listemp = listemp - 0b10000000000

   #now convert to Fahrenheit
   listemp = listemp * 1.8 + 32.0

   #now add offset, round to 1 decimal - some precision is lost since I convert to Fahrenheit first
   listemp = round(listemp + listempoffset,1)


#####################################################################################
###################### MAIN THREAD 1 ################################################

## Note: there is the slim possibility that the system will disable PTT at the exact 
## moment Direwolf is transmitting, keeping pin 17 down (in live PTT), so it is a good
## idea to also set pin 17 high at the same time

try:
  #use global variables for program state, since threads can get to them
  statusbar = 'Pi '
  call_list = []
  chatcall = ''
  chatmode = ''
  radio_done = 0
  xmppstatus = ''
  on_the_air = ''
  frequency = trillsat_config.packet_freq
  md = 'internal' # call mode
  beacon_command = ''
  roll = ''
  pitch = ''
  listemp = ''
  listempoffset = 0.0
  hucktempoffset = 0.0
  sawtempoffset = 0.0

  try:
   
   #note that trillsat_get_therm.sh must be running and creating a file every minute
   #or trillsat_bot.py will fail to load
 
   #Launch pigpio daemon
   os.system('/usr/bin/pigpiod')
   time.sleep(3)
   gpio = pigpio.pi()

   #I2C connect to the two ATtinys and the LIS3DH
   sawyer = gpio.i2c_open(1,0x51) 
   huckleberry = gpio.i2c_open(1,0x52)
   lis = gpio.i2c_open(1,0x18)

   # Registers from LIS3DH Datasheet (excluding reserved values that should not be touched)
   STATUS_REG_AUX = 0x07
   OUT_ADC1_L     = 0x08
   OUT_ADC1_H     = 0x09
   OUT_ADC2_L     = 0x0A
   OUT_ADC2_H     = 0x0B
   OUT_ADC3_L     = 0x0C
   OUT_ADC3_H     = 0x0D
   WHO_AM_I       = 0x0F #should return decimal 51 if working
   TEMP_CFG_REG   = 0x1F
   CTRL_REG1      = 0x20 
   CTRL_REG2      = 0x21
   CTRL_REG3      = 0x22
   CTRL_REG4      = 0x23 
   CTRL_REG5      = 0x24
   CTRL_REG6      = 0x25
   REFERENCE      = 0x26
   STATUS_REG     = 0x27
   OUT_X_L        = 0x28
   OUT_X_H        = 0x29
   OUT_Y_L        = 0x2A
   OUT_Y_H        = 0x2B
   OUT_Z_L        = 0x2C
   OUT_Z_H        = 0x2D
   FIFO_CTRL_REG  = 0x2E
   FIFO_SRC_REG   = 0x2F
   INT1_CFG       = 0x30
   INT1_SRC       = 0x31
   INT1_THS       = 0x32
   INT1_DURATION  = 0x33
   INT2_CFG       = 0x34
   INT2_SRC       = 0x35
   INT2_THS       = 0x36
   INT2_DURATION  = 0x37
   CLICK_CFG      = 0x38
   CLICK_SRC      = 0x39
   CLICK_THS      = 0x3A
   TIME_LIMIT     = 0x3B
   TIME_LATENCY   = 0x3C
   TIME_WINDOW    = 0x3D
   ACT_THIS       = 0x3E
   ACT_DUR        = 0x3F
   
   #whowho = gpio.i2c_read_byte_data(lis, WHO_AM_I)
   #print ("Who am I, from LIS3DH (should return 51): ", whowho)  
   
   #Initialize the LIS3DH 
   #first, reboot it to clear any bad memory
   gpio.i2c_write_byte_data(lis, CTRL_REG5, 0b10000000)
   time.sleep(1)

   #400 Hz, low-power mode disabled, z,y,x axes enabled
   gpio.i2c_write_byte_data(lis, CTRL_REG1, 0b01110111)

   #enable high pass filter with normal mode and click detection
   gpio.i2c_write_byte_data(lis, CTRL_REG2, 0b00111100)
 
   #read reference to delete dc value from axis
   lis_hpfilter_ref = gpio.i2c_read_byte_data(lis, REFERENCE)

   #enable high pass filter with reference mode and click detection
   gpio.i2c_write_byte_data(lis, CTRL_REG2, 0b01111100)

   #now write reference value obtained earlier (it is in twos complement)
   gpio.i2c_write_byte_data(lis, REFERENCE, int(lis_hpfilter_ref))

   #disable continous update, little endian, max +-4g scale, max 12-bit hi-res, no self-test, spi 4-wire (not used) 
   gpio.i2c_write_byte_data(lis, CTRL_REG4, 0b10011000)
   #enable ADC and temp sensor
   gpio.i2c_write_byte_data(lis, TEMP_CFG_REG, 0b11000000)

   #enable click interrupt
   gpio.i2c_write_byte_data(lis, CTRL_REG3, 0b10000000)

   #invert the interrupt polarity (to match test key)
   gpio.i2c_write_byte_data(lis, CTRL_REG6, 0b00000010)

   #enable single-click detection for 
   #gpio.i2c_write_byte_data(lis, CLICK_SRC, 0b00010000)

   #the following LIS3DH click parameters can be tweaked on the fly using XMPP commands:

   #configure single click interrupt in X direction
   gpio.i2c_write_byte_data(lis, CLICK_CFG, 0b00000001)
   
   #configure single click threshold (unsigned, lower 7 bits)
   #128 max threshold, set to 0 for testing
   gpio.i2c_write_byte_data(lis, CLICK_THS, 8)

   #configure click time limit x/400 = milliseconds (101 = 127.5 ms)
   gpio.i2c_write_byte_data(lis, TIME_LIMIT, 120)

   #configure click time latency x/400 = milliseconds (21 = 52.5 ms)
   #the latency determines the length of the interrupt pulse
   #set it longer than debounce, but keep it short so not to confuse dahs
   gpio.i2c_write_byte_data(lis, TIME_LATENCY, 75)

   #configure click time window x/400 = milliseconds (100 = 250 ms)
   gpio.i2c_write_byte_data(lis, TIME_WINDOW, 128)
   

   #uses 73 uA in this mode
   #512 would be 1g, could max up to 2048g in positive or negative directions (since +- 4g scale)
   #in ITSO orientation, Trillsat should show close to 512 on z-axis
   #since accelerometer thinks it is accelerating upward at 1g
   #resolution of 4096/360 degrees, gives a resolution of around .1 degree accuracy

   ##############################
   #get offset for listemp, calibrate the LIS3DH temperature to one of the DS18B20+ probes
   probefile = open('/tmp/trillsat_therm', 'r')
   probelines = probefile.readlines()
   probefile.close()
   ReadLisTemp() #on first run to fetch listemp, the offset is 0
   #now compare the two and get the offset
   listempoffset = float(probelines[2]) * .0018 + 32.0 - listemp
   ################################
   #get offset for ATtiny temp sensors, calibrate to the same DS18B20+ probe
   print("Reading temp probe on Huckleberry to calibrate offset...")
   I2C_Command = 1
   while I2C_Command != 255:
      gpio.i2c_write_device(huckleberry, [70]) # huckleberry temp
      time.sleep(1)
      (I2C_Length, I2C_Array) = gpio.i2c_read_device(huckleberry, 3)
      time.sleep(1)
      I2C_Command = I2C_Array[0]
      hucktempoffset = float(probelines[2]) / 1000 - ((I2C_Array[2] << 8) + I2C_Array[1])
   print("Reading temp probe on Sawyer to calibrate offset...")
   I2C_Command = 1
   while I2C_Command != 255:
      gpio.i2c_write_device(sawyer, [70]) # sawyer temp
      time.sleep(1)
      (I2C_Length, I2C_Array) = gpio.i2c_read_device(sawyer, 3)
      time.sleep(1)
      I2C_Command = I2C_Array[0]
      sawtempoffset = float(probelines[2]) / 1000 - ((I2C_Array[2] << 8) + I2C_Array[1])
   #####################################

   print (str(hucktempoffset), str(sawtempoffset))

   gpio_setup("PACKET")
   #create a fake PTT pin to give to Direwolf, future pin changes will be synchronized
   Virtual_PTT = gpio.callback(trillsat_config.Virtual_RTS, pigpio.EITHER_EDGE, pin_link) 
   
   bot_queue = posix_ipc.MessageQueue('/bot_queue',posix_ipc.O_CREAT)
   
   bot_queue_lock = threading.Lock()
   bbs_queue_lock = threading.Lock()
   uart0_lock = threading.Lock()
   gpio_setup_lock = threading.Lock()
   update_status_lock = threading.Lock()

   #process locks can also be used if Radio needs access to something
   pLock = multiprocessing.Lock()
   
   Beacon = BeaconTimer(60.0) #set for every 1 minutes
   #text the ota command to start beacon

   #timeout=None for serial ensures blocking mode to save CPU
   #use 9600 or possibly 19200 max, 115200 loses data on long texts
   uart0 = serial.Serial(port=trillsat_config.serial, baudrate=9600, timeout=None)

   #can't daemonize, since they have to launch sub-threads
   GetXMPP_thread = threading.Thread(target=GetXMPP, args=(uart0, ))
   GetXMPP_thread.start()

  except Exception as e:
    print("Error starting threads: " + str(e))

  update_status_lock.acquire()
  UpdateStatus()
  update_status_lock.release()

  uart0_write("Please set time.\r(Example: 2004-02-29  16:21:42")

  #Pi bootstraps itself on startup so if ESP goes down it won't turn off
  gpio.write(20,1)
  gpio.set_mode(20, pigpio.OUTPUT)
  gpio.write(20,1)

  #Make sure ESP GPIO 15 is grounded to stay out of boot mode
  gpio.write(25,0)
  gpio.set_mode(25, pigpio.OUTPUT)
  gpio.write(25,0)


  while 1:
   
    command = []

  

    ## this loop only runs after a message is received
    #  need to add an UpdateStatus/Enviromental monitor thread via timer to poll ATtiny inputs
    # or wait for ATtinys to send interrupt
    message, _ = bot_queue.receive()
    if message == "TERMINATE":
      print("terminating the timer thread inside the beacon...")
      Beacon.cancel()
      print("main thread terminating...")
      break
    command = message.split(' ')

    if command[0] == 'CALL_START:':
        call_list.append(command[1])
        print(command[1] + " has connected")
        #dynamically connect to posix message queue based on remote bbs connection
        exec('bbs_queue_' + command[1] + ' = posix_ipc.MessageQueue("/bbs_queue_' + command[1] + '")')

    elif command[0] == 'CALL_END:':
        call_list.remove(command[1])
        exec('bbs_queue_' + command[1] + '.close()')

    elif command[0] == 'CHAT_REQUEST:':
     if xmppstatus == 'away':
         bbs_queue_send(command[1], 'CHAT_DENIED_AWAY')

     elif chatmode == 'chat':
         bbs_queue_send(command[1], 'CHAT_DENIED_IN_CHAT')
         uart0_write(message)

     else:
        uart0_write(message)
 
    elif command[0] == 'ESP:':      #COMMANDS FROM ESP GET PROCESSED
      ProcessESP(" ".join(command[1:]))

    elif command[0] == 'REMOTETEXT:': #REMOTE TEXT HAS BEEN RECEIVED
      uart0_write(" ".join(command[1:]))

    #ANY MESSAGE IN bot_queue THAT IS NOT A COMMAND GETS FORWARDED TO ESP
    #how do I ensure that BBS users can't sent commands into bot_queue?  CHECK!!!
    else:
      uart0_write(message)
    
    update_status_lock.acquire()
    UpdateStatus()
    update_status_lock.release()


#########################################
####### EXIT CLEANUP ####################

finally:
  uart0.write("STATUS: ESP\n") # clean this up later to retain even more status
  xmppstatus = 'away'
  uart0.close()
  bot_queue.close() 
  posix_ipc.unlink_message_queue('/bot_queue')
  gpio.i2c_close(sawyer)
  gpio.i2c_close(huckleberry)
  gpio.i2c_close(lis)
  gpio_setup("EXIT")
  gpio.stop()
  os.system('/usr/bin/killall pigpiod')  
 
  sys.exit()

