from htra_api import *
import time
from ctypes import byref
from datetime import datetime, timedelta

###打开设备###
Status = 0          #函数的返回。
Device = c_void_p() #当前设备的内存地址。
DevNum = c_int(0)   #指定设备号。

BootProfile = BootProfile_TypeDef() #启动配置结构体，包括物理接口、供电方式等。
BootInfo = BootInfo_TypeDef()       #启动信息结构体，包括设备信息、USB速率等。

BootProfile.DevicePowerSupply = DevicePowerSupply_TypeDef.USBPortAndPowerPort #使用USB数据端口及独立电源端口双供电
BootProfile.PhysicalInterface = PhysicalInterface_TypeDef.USB                 #使用USB接口进行数据传输。

#使用网口设备配置ETH接口
#BootProfile.PhysicalInterface = PhysicalInterface_TypeDef.ETH #使用网口进行数据传输
#BootProfile.ETH_IPVersion = IPVersion_TypeDef.IPv4
#BootProfile.ETH_RemotePort = 5000
#BootProfile.ETH_ReadTimeOut = 10000
#BootProfile.ETH_IPAddress[0] = 192
#BootProfile.ETH_IPAddress[1] = 168
#BootProfile.ETH_IPAddress[2] = 1
#BootProfile.ETH_IPAddress[3] = 100

Status = dll.Device_Open(pointer(Device),DevNum,pointer(BootProfile),pointer(BootInfo)) #打开设备。
if(Status == 0):
    print("Device is opened successfully")
else:
    print("Return other errors Status = {:d}".format(Status))

#设置GNSS外部天线
GNSSAntennaState = GNSSAntennaState_TypeDef() #启动GNSS天线信息。
GNSSAntennaState = GNSSAntennaState_TypeDef.GNSS_AntennaExternal

#设置GNSS天线状态
Status = dll.Device_SetGNSSAntennaState(pointer(Device), GNSSAntennaState) #下发GNSS天线相关配置。

# 等待 GNSS 锁定（每 1s 打印一次）
GNSSInfo = GNSSInfo_TypeDef()  # 当前天线配置下的 GNSS 信息
while True:
    Status = dll.Device_GetGNSSInfo_Realtime(pointer(Device), byref(GNSSInfo))
    # 0: 未锁定；1: 锁定
    print(f"GNSS_LockState: {int(GNSSInfo.GNSS_LockState)}")
    if GNSSInfo.GNSS_LockState == 1:
        break
    time.sleep(1)

# 配置并等待 DOCXO 锁定（若硬件支持）
HardwareVersion = BootInfo.DeviceInfo.HardwareVersion
OCXO_Enable = (HardwareVersion >> 10) & 0x3  
if OCXO_Enable:
    DOCXOWorkMode = DOCXOWorkMode_TypeDef.DOCXO_LockMode
    Status = dll.Device_SetDOCXOWorkMode(pointer(Device), DOCXOWorkMode)

    start_time = time.time()

    # 先获取一次状态
    Status = dll.Device_GetGNSSInfo_Realtime(pointer(Device), byref(GNSSInfo))

    if GNSSInfo.DOCXO_LockState:
        print()
        print(f"GNSS_LockState: {int(GNSSInfo.GNSS_LockState)}")
        print(f"DOCXO_LockState: {int(GNSSInfo.DOCXO_LockState)}")
    else:
        while True:
            Status = dll.Device_GetGNSSInfo_Realtime(pointer(Device), byref(GNSSInfo))

            # 两者都锁定则退出
            if GNSSInfo.DOCXO_LockState == 1 and GNSSInfo.GNSS_LockState == 1:
                print()
                print(f"GNSS_LockState: {int(GNSSInfo.GNSS_LockState)}")
                print(f"DOCXO_LockState: {int(GNSSInfo.DOCXO_LockState)}")
                print("DOCXO is locked!")
                break

            # 若 GNSS 未锁，强制 DOCXO 回到 Lock 模式
            if GNSSInfo.GNSS_LockState == 0:
                DOCXOWorkMode = DOCXOWorkMode_TypeDef.DOCXO_LockMode
                Status = dll.Device_SetDOCXOWorkMode(pointer(Device), DOCXOWorkMode)

            print(f"GNSS_LockState: {int(GNSSInfo.GNSS_LockState)}")
            print(f"DOCXO_LockState: {int(GNSSInfo.DOCXO_LockState)}")

            # 超时检查：600 s
            if time.time() - start_time > 600:
                print("The DOCXO is not locked, please check whether the cable connection is normal.")
                break

            time.sleep(1)



# 打印北京时间（GNSS 提供的是 UTC，这里 +8 小时）
print("Beijing Time:")
try:
    utc_dt = datetime(
        int(GNSSInfo.Year), int(GNSSInfo.month), int(GNSSInfo.day),
        int(GNSSInfo.hour), int(GNSSInfo.minute), int(GNSSInfo.second)
    )
    bj_dt = utc_dt + timedelta(hours=8)
    print(bj_dt.strftime("%Y/%m/%d %H:%M:%S"))
except Exception:
    # 若结构体时间域暂不可用，则退回直接 +8 的显示（可能会溢出到下一天）
    print(f"{GNSSInfo.Year}/{GNSSInfo.month}/{GNSSInfo.day} "
          f"{int(GNSSInfo.hour) + 8}:{GNSSInfo.minute}:{GNSSInfo.second}")

def deg_to_dms(deg: float) -> str:
    sign = "-" if deg < 0 else ""
    a = abs(float(deg))
    d = int(a)
    m_float = (a - d) * 60.0
    m = int(m_float)
    s = (m_float - m) * 60.0
    return f"{sign}{d}°{m}′{s:.2f}″"

# 打印经纬度、海拔
latitudeDMS_GN = deg_to_dms(GNSSInfo.latitude)
longitudeDMS_GN  = deg_to_dms(GNSSInfo.longitude)

print(f"Current Latitude ： {latitudeDMS_GN}")
print(f"Current Longitude ：{longitudeDMS_GN}")
print(f"Current altitude ： {int(GNSSInfo.altitude)}")

# 获取 GNSS_SatDate（卫星信噪比等）
GNSS_SatDate = GNSS_SatDate_TypeDef()
Status = dll.Device_GetGNSS_SatDate_Realtime(pointer(Device), byref(GNSS_SatDate))

print(f"Max C/N0 (used for position): {int(GNSS_SatDate.GNSS_SNR_UsePos.Max_SatxC_No)}")
print(f"Min C/N0 (used for position): {int(GNSS_SatDate.GNSS_SNR_UsePos.Min_SatxC_No)}")
print(f"Avg C/N0 (used for position): {int(GNSS_SatDate.GNSS_SNR_UsePos.Avg_SatxC_No)}")


###配置下发###
IQS_ProfileIn = IQS_Profile_TypeDef()  #IQS输入配置，包括起始频率、终止频率、RBW、参考电平等。
IQS_ProfileOut = IQS_Profile_TypeDef() #IQS输出配置。
StreamInfo = IQS_StreamInfo_TypeDef()  #当前配置下IQ数据信息，包括带宽,IQ单路采样率等。

dll.IQS_ProfileDeInit(pointer(Device),pointer(IQS_ProfileIn)) #初始化配置IQS模式下的相关参数。

IQS_ProfileIn.CenterFreq_Hz = 1e9                           #配置中心频率。
IQS_ProfileIn.RefLevel_dBm = 0                              #配置参考电平。
IQS_ProfileIn.DecimateFactor = 2                            #配置抽取倍数。
IQS_ProfileIn.DataFormat = DataFormat_TypeDef.Complex16bit  #配置IQ数据格式。
IQS_ProfileIn.TriggerSource = IQS_TriggerSource_TypeDef.Bus #配置触发源为内部总线触发。
IQS_ProfileIn.TriggerMode = TriggerMode_TypeDef.FixedPoints
IQS_ProfileIn.TriggerLength = 16242                         #配置单次触发采集的点数，仅当TriggerMode设置为FixedPoints时生效。
IQS_ProfileIn.NativeIQSampleRate_SPS=128e6

Status = dll.IQS_Configuration(pointer(Device),pointer(IQS_ProfileIn),pointer(IQS_ProfileOut),pointer(StreamInfo))  #下发IQS模式的相关配置。

if(Status == 0):
    print("configuration delivery succeeded")
else:
    print("IQS_Configuration call is incorrect Status = {:d}".format(Status))

###获取数据###
IQ_Data = (c_int16 * (StreamInfo.StreamSamples*2))() #存放IQ数据
AlternIQPacket = c_int16_p()                         #指向IQ数据(库函数)临时存放地址的指针
ScaleToV = c_float()                                 #幅度还原成电压的缩放因子
TriggerInfo = IQS_TriggerInfo_TypeDef()              #迹线信息结构体
MeasAuxInfo = MeasAuxInfo_TypeDef()                  #测量辅助信息结构体

try:
    while(True):
        Status = dll.IQS_BusTriggerStart(pointer(Device)) #调用IQS_BusTriggerStart触发设备。若触发源为外部触发，则不需要调用此函数。
        for i in range(0,StreamInfo.PacketCount):
            dll.IQS_GetIQStream(pointer(Device),pointer(AlternIQPacket),pointer(ScaleToV),pointer(TriggerInfo),pointer(MeasAuxInfo)) #获取IQ数据包、触发信息、I路数据最大值及最大值数组下标
            if(i == StreamInfo.PacketCount - 1 and StreamInfo.StreamSamples % StreamInfo.PacketSamples != 0):                        #可能最后一包不满一整包（16242个点）；所以只需要循环不满一包的点数
                IQ_Data[(i*StreamInfo.PacketSamples*2):] = AlternIQPacket[0:(2*(StreamInfo.StreamSamples % StreamInfo.PacketSamples))]
                break
            else:
                IQ_Data[(i*StreamInfo.PacketSamples*2):((i+1)*StreamInfo.PacketSamples*2)] = AlternIQPacket[0:(StreamInfo.PacketSamples*2)]

        time_stamp = float(MeasAuxInfo.AbsoluteTimeStamp)
        hour_var   = c_int16()
        minute_var = c_int16()
        second_var = c_int16()
        year_var   = c_int16()
        month_var  = c_int16()
        day_var    = c_int16()

        Status = dll.Device_AnysisGNSSTime(
            c_double(time_stamp),
            byref(hour_var), byref(minute_var), byref(second_var),
            byref(year_var), byref(month_var), byref(day_var)
        )

        try:
            utc_dt = datetime(int(year_var.value), int(month_var.value), int(day_var.value),
                            int(hour_var.value), int(minute_var.value), int(second_var.value))
            bj_dt  = utc_dt + timedelta(hours=8)
            print(bj_dt.strftime("%Y/%m/%d %H:%M:%S"))
        except Exception:
            print(f"{year_var.value}/{month_var.value}/{day_var.value} "
                f"{hour_var.value + 8}:{minute_var.value}:{second_var.value}")

        def deg_to_dms(deg: float) -> str:
            sign = "-" if deg < 0 else ""
            a = abs(float(deg))
            d = int(a)
            m_float = (a - d) * 60.0
            m = int(m_float)
            s = (m_float - m) * 60.0
            return f"{sign}{d}°{m}′{s:.2f}″"
        
        lon = float(MeasAuxInfo.Longitude)
        lat = float(MeasAuxInfo.Latitude)

        longitudeDMS = deg_to_dms(lon)
        latitudeDMS  = deg_to_dms(lat)

        print("Current Longitude ：", longitudeDMS)
        print("Current Latitude ： ", latitudeDMS)
        
except KeyboardInterrupt:
    print("Stopped by user with Ctrl+C")
finally:
    dll.Device_Close(pointer(Device))
    print("Device closed.")
