''' 
    Python based example CLI Application interface control for vaunix LMS devies
    JA 01/10/2021    Initial Verison oF CLI control Interface for LDA devices
	RD 5/16/2024	 Changed to use open the device at the beginning and close it when done
					 along with other fixes
	RD 6/7/24		 Adapted LDA example for LMS example
    (c) 2020-2024 by Vaunix Technology Corporation
'''
from ctypes import *
import ctypes
import os
import time
import sys, getopt
import array as arr
from time import sleep

class CLI_Vaunix_LMS:
	def __init__(self, name="lmsApi"):
		self.vnx = cdll.LoadLibrary(os.path.join(os.path.abspath(os.path.dirname(__file__)), '/usr/lib/liblmshid.so'))
		self.vnx.fnLMS_Init()
		self.vnx.fnLMS_SetTestMode(False)
		num_devices = self.vnx.fnLMS_GetNumDevices()
		DeviceIDArray = c_int * 64
		self.device_list = DeviceIDArray()
		# fill the array with the ID's of the connected LMS devices
		# if any devices are present
		# print("DBG: num_devices = ", num_devices)
		if (self.vnx.fnLMS_GetDevInfo(self.device_list) == 0):
			print("No LMS devices found")

	def get_devicelist(self):
		return self.device_list

	def open_lmsdevice(self, deviceid):
		return self.vnx.fnLMS_InitDevice(deviceid)

	def close_lmsdevice(self, deviceid):
		return self.vnx.fnLMS_CloseDevice(deviceid)

	# Get device status word
	def get_devicestatus(self, deviceid):
		return self.vnx.fnLMS_GetDeviceStatus(deviceid)

	# return True if the device is ready
	def check_deviceready(self, deviceid):
		result = self.vnx.fnLMS_GetDeviceStatus(deviceid)
		if (result & 0x80000000): 
			return False
		else:
			return True

	# get PLL lock status
	def check_PLLstatus(self, deviceid):
		result = self.vnx.fnLMS_GetPLLLock(deviceid)
		# if we get an error then we report no PLL lock
		if (result & 0x80000000): 
			return False

		# the library function returns 1 for locked, 0 for unlocked
		# this just makes the integer value to logical value explicit
		if (result): 
			return True
		else:
			return False
    
	# Get Model Name
	def get_modelname(self, deviceid):
		self.data = ctypes.create_string_buffer(32)
		self.vnx.fnLMS_GetModelName(deviceid, ctypes.byref(self.data))
		return self.data.value

	# Ger Serial Number
	def get_serialnumber(self, deviceid):
		return self.vnx.fnLMS_GetSerialNumber(deviceid)

    # Get Library Software Version
	def get_libversion(self):
		return self.vnx.fnLMS_GetLibVersion()

	# Get Min.Frequency
	def get_minfrequency(self, deviceid):
		return self.vnx.fnLMS_GetMinFreq(deviceid)

	# Get Max. Frequency
	def get_maxfrequency(self, deviceid):
		return self.vnx.fnLMS_GetMaxFreq(deviceid)

	# Get Frequency
	def get_frequency(self, deviceid):
		return self.vnx.fnLMS_GetFrequency(deviceid)

	# Determine if the internal reference frequency is in use
	def get_usinginteralreference(self, deviceid):
		return self.vnx.fnLMS_GetUseInternalRef(deviceid)

	# Get Min. Output Power
	# this function shows one simplistic way to report an error, normally
	# actual application code should raise and manage exceptions
	def get_minpower(self, deviceid):
		self.vnx.fnLMS_GetPulseOnTime.restype = ctypes.c_int
		result = self.vnx.fnLMS_GetMinPwr(deviceid)
		if (result == 0x80030000): 
			return -203.0			# not possible for a real device, yet easy to decipher
		elif (result == 0x80020000):
			return -202.0
		else:
			return result/4.0

	# Get Max. Output Power
	def get_maxpower(self, deviceid):
		result = self.vnx.fnLMS_GetMaxPwr(deviceid)
		if (result == 0x80030000): 
			return -203.0			# not possible for a real device, yet easy to decipher
		elif (result == 0x80020000):
			return -202.0
		else:
			return result/4.0

	# Get Output Power Level (db units)
	# this is just an example of the high level language wrapper re-mapping error codes
	# an actual application would map the error codes that the library could return into
	# useful application related error codes, or raise an exception to trigger error handling
	# by the application level code
	def get_power(self, deviceid):
		result = self.vnx.fnLMS_GetPowerLevel(deviceid)
		if (result == 0x80030000): 
			return -203.0			# not possible for a real device, yet easy to decipher
		elif (result == 0x80020000):
			return -202.0
		else:
			return result/4.0

	# this function will return 1 for RF on, 0 for RF muted, and error codes if an error occurred
	def get_RFmuting(self, deviceid):
		return self.vnx.fnLMS_GetRF_On(deviceid)

	# Get Sweep Start (10Hz units)
	def get_sweepstart(self, deviceid):
		return self.vnx.fnLMS_GetStartFrequency(deviceid)
        
	# Get Sweep End (10Hz units)
	def get_sweepend(self, deviceid):
		return self.vnx.fnLMS_GetEndFrequency(deviceid)

	# Get Sweep time (1 millisecond units)
	def get_sweeptime(self, deviceid):
		return self.vnx.fnLMS_GetSweepTime(deviceid)

	# Get Pulse Mode Status
	def get_pulsemode(self, deviceid):
		return self.vnx.fnLMS_GetPulseMode(deviceid)

	# Determine if the LMS device has the fast pulse mode option
	def has_fastpulsemode(self, deviceid):
		return self.vnx.fnLMS_GetHasFastPulseMode(deviceid)

	# Determine if internal or external pulse modulation is enabled
	def get_usinginternalpulsemod(self, deviceid):
		return self.vnx.fnLMS_GetUseInternalPulseMod(deviceid)

	# Get Pulse On time (in seconds as a floating point value)
	def get_pulseontime(self, deviceid):
		self.vnx.fnLMS_GetPulseOnTime.restype = ctypes.c_float
		return self.vnx.fnLMS_GetPulseOnTime(deviceid)

	# Get Pulse Off time (in seconds as a floating point value)
	def get_pulseofftime(self, deviceid):
		self.vnx.fnLMS_GetPulseOffTime.restype = ctypes.c_float
		return self.vnx.fnLMS_GetPulseOffTime(deviceid)

	# Get sweep bi-directional mode of the device
	def get_sweepbidirectional(self, deviceid):
		return self.vnx.fnLMS_GetSweepbidirectionalmode(deviceid)

	# Get sweep mode of the device
	def get_sweepmode(self, deviceid):
		return self.vnx.fnLMS_GetSweepmode(deviceid)

	# Determine if the device supports chirps
	def has_chirp(self, deviceid):
		return self.vnx.fnLMS_GetHasChirpMode(deviceid)

	# Get the chirp length (in seconds as a floating point value)
	def get_chirplength(self, deviceid):
		self.vnx.fnLMS_GetChirpLength.restype = ctypes.c_float
		return self.vnx.fnLMS_GetChirpLength(deviceid)

	# Get the chirp repeat time (in seconds as a floating point value)
	def get_chirprepeattime(self, deviceid):
		self.vnx.fnLMS_GetChirpRepeatRate.restype = ctypes.c_float
		return self.vnx.fnLMS_GetChirpRepeatRate(deviceid)
            
    # Set Frequency (frequency is in 10Hz units)
	# the return value indicates the status, 0 for STATUS_OK
	def set_frequency(self, deviceid, frequency):
		return self.vnx.fnLMS_SetFrequency(deviceid, frequency)

	# Select Internal Frequency Reference
	def set_internalreference(self, deviceid, internal):
		self.vnx.fnLMS_SetUseInternalRef.argtypes = [c_int, c_bool]
		return self.vnx.fnLMS_SetUseInternalRef(deviceid, internal)

    # Set Output Power
	# The power argument is in .db, the library API uses 0.25 db integer units
	# the return value indicates the status, 0 for STATUS_OK
	def set_power(self, deviceid, power):
		return self.vnx.fnLMS_SetPowerLevel(deviceid, int(power * 4))

    # Set RF On/Off
	# the return value indicates the status, 0 for STATUS_OK
	def set_RFon(self, deviceid, on):
		self.vnx.fnLMS_SetRFOn.argtypes = [c_int, c_bool]
		return self.vnx.fnLMS_SetRFOn(deviceid, on)	

	# Set Sweep Start
	def set_sweepstart(self, deviceid, startfrequency):
		return self.vnx.fnLMS_SetStartFrequency(deviceid, startfrequency)

	# Set Sweep end
	def set_sweepend(self, deviceid, endfrequency):
		return self.vnx.fnLMS_SetEndFrequency(deviceid, endfrequency)

    # Set Sweep Time (in 1 ms units)
	def set_sweeptime(self, deviceid, sweeptime):
		return self.vnx.fnLMS_SetSweepTime(deviceid, sweeptime)

	# Select external hardware or internal sweep trigger
	def set_externalsweeptrigger(self, deviceid, external):
		self.vnx.fnLMS_SetUseExtSweepTrigger.argtypes = [c_int, c_bool]
		return self.vnx.fnLMS_SetUseExtSweepTrigger(deviceid, external)

    # Set Sweep direction  -- True: Upwards, False: Downwards
	# this setting should match the relationship of the start and end frequencies
	def set_sweepdirection(self, deviceid, upwards):
		self.vnx.fnLMS_SetSweepDirection.argtypes = [c_int, c_bool]		
		return self.vnx.fnLMS_SetSweepDirection(deviceid, upwards)

	# Set bidirectional sweep mode  -- True: Bi-directional (triangle shaped), False: Uni-directional (sawtooth)
	def set_sweepbidirectional(self, deviceid, bidir):
		self.vnx.fnLMS_SetSweepType.argtypes = [c_int, c_bool]		
		return self.vnx.fnLMS_SetSweepType(deviceid, bidir)

	# Set sweep mode  -- True: repeating sweep, False: one time sweep
	def set_sweeprepeatmode(self, deviceid, repeat):
		self.vnx.fnLMS_SetSweepMode.argtypes = [c_int, c_bool]		
		return self.vnx.fnLMS_SetSweepMode(deviceid, repeat)

	# Start sweep
	def startsweep(self, deviceid, go):
		self.vnx.fnLMS_StartSweep.argtypes = [c_int, c_bool]		
		return self.vnx.fnLMS_StartSweep(deviceid, go)

	# Set Chirp Mode
	def set_chirpmode(self, deviceid, mode):
		self.vnx.fnLMS_SetChirpMode.argtypes = [c_int, c_bool]		
		return self.vnx.fnLMS_SetChirpMode(deviceid, mode)

	# Set Chirp Length (length is in seconds as a floating point value)
	def set_chirplength(self, deviceid, length):
		self.vnx.fnLMS_SetChirpLength.argtypes = [c_int, c_float]
		return self.vnx.fnLMS_SetChirpLength(deviceid, length)

	# Set Chirp repeat rate (period is in seconds as a floating point value)
	def set_chirprepeat(self, deviceid, length):
		self.vnx.fnLMS_SetChirpRepeatRate.argtypes = [c_int, c_float]		
		return self.vnx.fnLMS_SetChirpRepeatRate(deviceid, length)

	# Select internal or external pulse modulation (True selects external HW pulse modulation)
	def set_useexternalpulsemod(self, deviceid, external):
		self.vnx.fnLMS_SetUseExternalPulseMod.argtypes = [c_int, c_bool]
		return self.vnx.fnLMS_SetUseExternalPulseMod(deviceid, external)

	# Set Pulse Modulation On/Off (on is True, off is False)
	def set_pulsemodulationon(self, deviceid, on):
		self.vnx.fnLMS_EnableInternalPulseMod.argtypes = [c_int, c_bool]
		return self.vnx.fnLMS_EnableInternalPulseMod(deviceid, on)

	# Set Fast Pulsed Output mode
	def set_pulsedoutput(self, deviceid, on):
		self.vnx.fnLMS_SetFastPulsedOutput.argtypes = [c_int, c_float, c_float, c_bool]
		return self.vnx.SetFastPulsedOutput(deviceid, pulseontime, pulsereptime, on)

	# save settings to LMS device   
	def set_savesettings(self, deviceid):
		return self.vnx.fnLMS_SaveSettings(deviceid)

	# ------------------------------------------------------------------------------------

	# Main Function call   
	def main(argv):
		deviceid = 0
		power = 0.0
		frequency = 0
		rfon = ''
		err_devnotrdy = 'Device not ready.. Please Check!'
		err_nodev = 'Specify a device (1,2,3, etc)'
		lmsobj = CLI_Vaunix_LMS()

		# gather up the set of device's we found so the user can pick one
		# the underlying array of deviceID values has one entry for each device found
		deviceIDlist = arr.array('i', lmsobj.get_devicelist())

		# process the user's command line
		try:
			opts, args = getopt.getopt(argv,"hSd:f:p:o:s:e:t:g:b:r:",["ddevice=","ffreq=","ppower=","oRF=", "sstart=", "eend=", "ttime=", "ggo=", "bbidir=", "Ssave", "rread="])
		except getopt.GetoptError as err:
			print(err)
			print ('lmscli.py argument error')
			sys.exit(2)
		for opt, arg in opts:
			if opt == '-h':
				print ('lmscli.py -d <deviceid> -f <frequency> -p <power> -o <RF on/off> -s <sweepstart> -e <sweepend> -t <sweeptime> -g <startsweep[0-Off,1-Once,2-Repeat]> -b <sweepbidirectional[0-Unidirectional,1-Bidirectional]> -S <savesetting> -r <read [all,PLL,frequency, power>')
				sys.exit()
			elif opt in ("-d", "--ddevice"):
				dev_select = int(arg)
				if (dev_select < 1):
					print(err_nodev)
					sys.exit()			
				print ('device:', dev_select)
				deviceid = deviceIDlist[dev_select - 1]
				if (lmsobj.open_lmsdevice(deviceid) != 0):
					print(err_devnotrdy)
					sys.exit()
				
			# set output power level
			elif opt in ("-p", "--ppower"):
				power = float(arg)
				print ('Power:', power)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						lmsobj.set_power(deviceid, power)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev)

			# set output frequency
			elif opt in ("-f", "--ffreq"):
				frequency = int(arg)
				print ('frequency (MHz):', frequency/100000.0)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						result = lmsobj.set_frequency(deviceid, frequency)
						print("set_frequency status: ", result)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev)                    

			# RF on/off
			elif opt in ("-o", "--oRF"):
				rfon = arg
				print ('RF:', rfon)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						if (rfon == "on"):
							lmsobj.set_RFon(deviceid, True)
						elif (rfon == "off"):
							lmsobj.set_RFon(deviceid, False)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev)  
                    
            # set sweep start frequency
			elif opt in ("-s", "--sstart"):
				sweepstart = int(arg)
				print ('sweepstart (MHz):', sweepstart/100000.0)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						lmsobj.set_sweepstart(deviceid, sweepstart)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev) 

			# set sweep end frequency
			elif opt in ("-e", "--eend"):
				sweepend = int(arg)
				print ('sweepend (MHz):', sweepend/100000.0)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						lmsobj.set_sweepend(deviceid, sweepend)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev) 

			# set sweep time
			elif opt in ("-t", "--ttime"):
				sweeptime = int(arg)
				print ('sweeptime:', sweeptime)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						lmsobj.set_sweeptime(deviceid, sweeptime)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev) 

			# Set sweep bi-directional
			elif opt in ("-b", "bbidir"):
				swpdir = arg
				print ('sweep bi-direction:', swpdir)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						lmsobj.set_sweepbidirectional(deviceid, swpdir)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev)
                    
			# Set sweep mode & start ramp
            # swpmode = 0 to stop, swpmode = 1 for once, swpmode = 2 for repeating sweeps
			elif opt in ("-g", "--ggo"):
				swpmode = int(arg)
				print ('sweep mode:', swpmode)
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						if swpmode == 0:
							lmsobj.startsweep(deviceid, False)
						elif swpmode == 1:
							lmsobj.set_sweeprepeatmode(deviceid, False)
							lmsobj.startsweep(deviceid, True)
						elif swpmode == 2:
							lmsobj.set_sweeprepeatmode(deviceid, True)
							lmsobj.startsweep(deviceid, True)
						else:
							print('Invalid sweep mode')
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev)


			# Save settings
			elif opt in ("-S", "Ssave="):
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						lmsobj.set_savesettings(deviceid)
					else:
						print (err_devnotrdy)
				else:
					print (err_nodev)

			elif opt in ("-r", "rread="):
				if(deviceid != 0):
					if lmsobj.check_deviceready(deviceid):
						if arg == 'PLL':
							print("PLL Locked:",lmsobj.check_PLLstatus(deviceid))							
						elif arg == 'frequency':
							print("Frequency(MHz):",(lmsobj.get_frequency(deviceid)/100000.0))
						elif arg == 'power':
							print("Output Power(dB):",lmsobj.get_power(deviceid))
							print("RF Output:",lmsobj.get_RFmuting(deviceid))
						# this is the 'all' case
						else:
							print()
							print("***************** Device Status **********")
							print("Frequency (MHz):",(lmsobj.get_frequency(deviceid)/100000.0))
							print("Output Power (dB):",lmsobj.get_power(deviceid))
							print("RF Output:",lmsobj.get_RFmuting(deviceid))
							print("PLL Locked:",lmsobj.check_PLLstatus(deviceid))
							print("Using Internal Reference Osc:", lmsobj.get_usinginteralreference(deviceid))
							print()
							print("Sweep Start (MHz):",(lmsobj.get_sweepstart(deviceid)/100000.0))
							print("Sweep End (MHz):",(lmsobj.get_sweepend(deviceid)/100000.0))
							print("Sweep Time (ms):",lmsobj.get_sweeptime(deviceid))
							print()
							print("***************** Device Information **********")     
							print("Model Name:",lmsobj.get_modelname(deviceid))
							print("Serial #:",lmsobj.get_serialnumber(deviceid))
							print("Library Version:", (lmsobj.get_libversion()/100.0))
							print("Min Frequency (MHz):",(lmsobj.get_minfrequency(deviceid)/100000.0))
							print("Max Frequency (MHz):",(lmsobj.get_maxfrequency(deviceid)/100000.0))
							print("Max Output Power (dB):",lmsobj.get_maxpower(deviceid))
							print("Min Output Power (dB):",lmsobj.get_minpower(deviceid))
							print()
					else:
						print (err_devnotrdy)
				else:
					print(err_nodev)

		# we are done our loop over options, close the device we selected
		if(deviceid != 0):
			if lmsobj.check_deviceready(deviceid):
				lmsobj.close_lmsdevice(deviceid)
				# print("LMS Device closed")
		else:             
			print(err_nodev)

if __name__ == '__main__':
	CLI_Vaunix_LMS.main(sys.argv[1:])

