from _sx126x import *

from sys import implementation

if implementation.name == 'micropython':
    from machine import SPI, Pin
    from utime import sleep_ms, sleep_us, ticks_ms, ticks_us, ticks_diff

if implementation.name == 'circuitpython':
    import digitalio
    import busio
    from time import sleep, monotonic_ns

    _MS_PER_NS = const(1000000)
    _US_PER_NS = const(1000)
    _MS_PER_S = const(1000)
    _TICKS_MAX = const(536870911)
    _TICKS_PERIOD = const(536870912)
    _TICKS_HALFPERIOD = const(268435456)

    def sleep_ms(ms):
        sleep(ms/1000)

    def sleep_us(us):
        sleep(us/1000000)

    def ticks_ms():
        return (monotonic_ns() // _MS_PER_NS) & _TICKS_MAX

    def ticks_us():
       return (monotonic_ns() // _US_PER_NS) & _TICKS_MAX

    def ticks_diff(end, start):
        diff = (end - start) & _TICKS_MAX
        diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD
        return diff

class SX126X:

    def __init__(self, spi_bus, clk, mosi, miso, cs, irq, rst, gpio):
        self._irq = irq
        if implementation.name == 'micropython':
          try:
              self.spi = SPI(spi_bus, mode=SPI.MASTER, baudrate=2000000, pins=(clk, mosi, miso))        # Pycom variant uPy
          except:
              self.spi = SPI(spi_bus, baudrate=2000000, sck=Pin(clk), mosi=Pin(mosi), miso=Pin(miso))   # Generic variant uPy
          self.cs = Pin(cs, mode=Pin.OUT)
          self.irq = Pin(irq, mode=Pin.IN)
          self.rst = Pin(rst, mode=Pin.OUT)
          self.gpio = Pin(gpio, mode=Pin.IN)

        if implementation.name == 'circuitpython':
          self.spi = busio.SPI(clk, MOSI=mosi, MISO=miso)
          while not self.spi.try_lock():
              pass
          self.spi.configure(baudrate=2000000, phase=0, polarity=0, bits=8)
          self.spi.unlock()
          self.cs = digitalio.DigitalInOut(cs)
          self.cs.switch_to_output(value=True)
          self.irq = digitalio.DigitalInOut(irq)
          self.irq.switch_to_input()
          self.rst = digitalio.DigitalInOut(rst)
          self.rst.switch_to_output(value=True)
          self.gpio = digitalio.DigitalInOut(gpio)
          self.gpio.switch_to_input()

        self._bwKhz = 0
        self._sf = 0
        self._bw = 0
        self._cr = 0
        self._ldro = 0
        self._crcType = 0
        self._preambleLength = 0
        self._tcxoDelay = 0
        self._headerType = 0
        self._implicitLen = 0
        self._txIq = 0
        self._rxIq = 0
        self._invertIQ = 0
        self._ldroAuto = True

        self._br = 0
        self._freqDev = 0
        self._rxBw = 0
        self._rxBwKhz = 0
        self._pulseShape = 0
        self._crcTypeFSK = 0
        self._preambleLengthFSK = 0
        self._addrComp = 0
        self._syncWordLength = 0
        self._whitening = 0
        self._packetType = 0
        self._dataRate = 0
        self._packetLength = 0
        self._preambleDetectorLength = 0

    def begin(self, bw, sf, cr, syncWord, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO=False, txIq=False, rxIq=False):
        self._bwKhz = bw
        self._sf = sf

        self._bw = SX126X_LORA_BW_125_0
        self._cr = SX126X_LORA_CR_4_7
        self._ldro = 0x00
        self._crcType = SX126X_LORA_CRC_ON
        self._preambleLength = preambleLength
        self._tcxoDelay = 0
        self._headerType = SX126X_LORA_HEADER_EXPLICIT
        self._implicitLen = 0xFF

        self._txIq = txIq
        self._rxIq = rxIq
        self._invertIQ = SX126X_LORA_IQ_STANDARD

        state = self.reset()
        ASSERT(state)

        state = self.standby()
        ASSERT(state)
        
        if tcxoVoltage > 0.0:
            state = self.setTCXO(tcxoVoltage)
            ASSERT(state)

        state = self.config(SX126X_PACKET_TYPE_LORA)
        ASSERT(state)
        
        if useRegulatorLDO:
            state = self.setRegulatorLDO()
        else:
            state = self.setRegulatorDCDC()
        ASSERT(state)

        state = self.setSpreadingFactor(sf)
        ASSERT(state)

        state = self.setBandwidth(bw)
        ASSERT(state)

        state = self.setCodingRate(cr)
        ASSERT(state)

        state = self.setSyncWord(syncWord)
        ASSERT(state)

        state = self.setCurrentLimit(currentLimit)
        ASSERT(state)

        state = self.setPreambleLength(preambleLength)
        ASSERT(state)

        state = self.setDio2AsRfSwitch(True)
        ASSERT(state)

        return state

    def beginFSK(self, br, freqDev, rxBw, currentLimit, preambleLength, dataShaping, preambleDetectorLength, tcxoVoltage, useRegulatorLDO=False):
        self._br = 21333
        self._freqDev = 52428
        self._rxBw = SX126X_GFSK_RX_BW_156_2
        self._rxBwKhz = 156.2
        self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_5
        self._crcTypeFSK = SX126X_GFSK_CRC_2_BYTE_INV
        self._preambleLengthFSK = preambleLength
        self._addrComp = SX126X_GFSK_ADDRESS_FILT_OFF
        self._preambleDetectorLength = preambleDetectorLength

        state = self.reset()
        ASSERT(state)

        state = self.standby()
        ASSERT(state)
        
        if tcxoVoltage > 0.0:
            state = self.setTCXO(tcxoVoltage)
            ASSERT(state)

        state = self.config(SX126X_PACKET_TYPE_GFSK)
        ASSERT(state)
        
        if useRegulatorLDO:
            state = self.setRegulatorLDO()
        else:
            state = self.setRegulatorDCDC()
        ASSERT(state)

        state = self.setBitRate(br)
        ASSERT(state)

        state = self.setFrequencyDeviation(freqDev)
        ASSERT(state)

        state = self.setRxBandwidth(rxBw)
        ASSERT(state)

        state = self.setCurrentLimit(currentLimit)
        ASSERT(state)

        state = self.setDataShaping(dataShaping)
        ASSERT(state)

        state = self.setPreambleLength(preambleLength)
        ASSERT(state)

        sync = [0x2D, 0x01]
        state = self.setSyncWord(sync, 2)
        ASSERT(state)

        state = self.setWhitening(True, 0x0100)
        ASSERT(state)

        state = self.variablePacketLengthMode(SX126X_MAX_PACKET_LENGTH)
        ASSERT(state)

        state = self.setDio2AsRfSwitch(True)
        ASSERT(state)

        return state

    def reset(self, verify=True):
        if implementation.name == 'micropython':
          self.rst.value(1)
          sleep_us(150)
          self.rst.value(0)
          sleep_us(150)
          self.rst.value(1)
          sleep_us(150)

        if implementation.name == 'circuitpython':
          self.rst.value = True
          sleep_us(150)
          self.rst.value = False
          sleep_us(150)
          self.rst.value = True
          sleep_us(150)

        if not verify:
            return ERR_NONE

        start = ticks_ms()
        while True:
            state = self.standby()
            if state == ERR_NONE:
                return ERR_NONE
            if abs(ticks_diff(start, ticks_ms())) >= 3000:
                return state
            sleep_ms(10)

    def transmit(self, data, len_, addr=0):
        state = self.standby()
        ASSERT(state)

        if len_ > SX126X_MAX_PACKET_LENGTH:
            return ERR_PACKET_TOO_LONG

        timeout = 0

        modem = self.getPacketType()
        if modem == SX126X_PACKET_TYPE_LORA:
            timeout = int((self.getTimeOnAir(len_) * 3) / 2)

        elif modem == SX126X_PACKET_TYPE_GFSK:
            timeout = int(self.getTimeOnAir(len_) * 5)

        else:
            return ERR_UNKNOWN

        state = self.startTransmit(data, len_, addr)
        ASSERT(state)

        start = ticks_us()
        if implementation.name == 'micropython':
            while not self.irq.value():
                yield_()
                if abs(ticks_diff(start, ticks_us())) > timeout:
                    self.clearIrqStatus()
                    self.standby()
                    return ERR_TX_TIMEOUT
        if implementation.name == 'circuitpython':
            while not self.irq.value:
                yield_()
                if abs(ticks_diff(start, ticks_us())) > timeout:
                    self.clearIrqStatus()
                    self.standby()
                    return ERR_TX_TIMEOUT                     

        elapsed = abs(ticks_diff(start, ticks_us()))

        self._dataRate = (len_*8.0)/(float(elapsed)/1000000.0)

        state = self.clearIrqStatus()
        ASSERT(state)

        state = self.standby()

        return state

    def receive(self, data, len_, timeout_en, timeout_ms):
        state = self.standby()
        ASSERT(state)

        timeout = 0

        modem = self.getPacketType()
        if modem == SX126X_PACKET_TYPE_LORA:
            symbolLength = float(1 << self._sf) / float(self._bwKhz)
            timeout = int(symbolLength * 100.0 * 1000.0)
        elif modem == SX126X_PACKET_TYPE_GFSK:
            maxLen = len_
            if len_ == 0:
                maxLen = 0xFF
            brBps = (float(SX126X_CRYSTAL_FREQ) * 1000000.0 * 32.0) / float(self._br)
            timeout = int(((maxLen * 8.0) / brBps) * 1000000.0 * 5.0)
        else:
            return ERR_UNKNOWN

        if timeout_ms == 0:
            pass
        else:
            timeout = timeout_ms * 1000

        if timeout_en:
            timeoutValue = int(float(timeout) / 15.625)
        else:
            timeoutValue = SX126X_RX_TIMEOUT_NONE
            
        state = self.startReceive(timeoutValue)
        ASSERT(state)

        start = ticks_us()
        if implementation.name == 'micropython':
            while not self.irq.value():
                yield_()
                if timeout_en:
                    if abs(ticks_diff(start, ticks_us())) > timeout:
                        self.fixImplicitTimeout()
                        self.clearIrqStatus()
                        self.standby()
                        return ERR_RX_TIMEOUT
        if implementation.name == 'circuitpython':
            while not self.irq.value:
                yield_()
                if timeout_en:
                    if abs(ticks_diff(start, ticks_us())) > timeout:
                        self.fixImplicitTimeout()
                        self.clearIrqStatus()
                        self.standby()
                        return ERR_RX_TIMEOUT

        if self._headerType == SX126X_LORA_HEADER_IMPLICIT and self.getPacketType() == SX126X_PACKET_TYPE_LORA:
            state = self.fixImplicitTimeout()
            ASSERT(state)

        return self.readData(data, len_)

    def transmitDirect(self, frf=0):
        state = ERR_NONE
        if frf != 0:
            state = self.setRfFrequency(frf)
        ASSERT(state)

        data = [SX126X_CMD_NOP]
        return self.SPIwriteCommand([SX126X_CMD_SET_TX_CONTINUOUS_WAVE], 1, data, 1)

    def receiveDirect(self):
        return ERR_UNKNOWN

    def scanChannel(self):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM

        state = self.standby()
        ASSERT(state)

        state = self.setDioIrqParams(SX126X_IRQ_CAD_DETECTED | SX126X_IRQ_CAD_DONE, SX126X_IRQ_CAD_DETECTED | SX126X_IRQ_CAD_DONE)
        ASSERT(state)

        state = self.clearIrqStatus()
        ASSERT(state)

        state = self.setCad()
        ASSERT(state)

        if implementation.name == 'micropython':
            while not self.irq.value():
                yield_()
        if implementation.name == 'circuitpython':
            while not self.irq.value:
                yield_()
        

        cadResult = self.getIrqStatus()
        if cadResult & SX126X_IRQ_CAD_DETECTED:
            self.clearIrqStatus()
            return LORA_DETECTED
        elif cadResult & SX126X_IRQ_CAD_DONE:
            self.clearIrqStatus()
            return CHANNEL_FREE

        return ERR_UNKNOWN

    def sleep(self, retainConfig=True):
        sleepMode = [SX126X_SLEEP_START_WARM | SX126X_SLEEP_RTC_OFF]
        if not retainConfig:
            sleepMode = [SX126X_SLEEP_START_COLD | SX126X_SLEEP_RTC_OFF]
        state = self.SPIwriteCommand([SX126X_CMD_SET_SLEEP], 1, sleepMode, 1, False)

        sleep_us(500)

        return state

    def standby(self, mode=SX126X_STANDBY_RC):
        data = [mode]
        return self.SPIwriteCommand([SX126X_CMD_SET_STANDBY], 1, data, 1)

    def setDio1Action(self, func):
        try:
            self.irq.callback(trigger=Pin.IRQ_RISING, handler=func)     # Pycom variant uPy
        except:
            self.irq.irq(trigger=Pin.IRQ_RISING, handler=func)          # Generic variant uPy

    def clearDio1Action(self):
        if implementation.name == 'micropython':
          self.irq = Pin(self._irq, mode=Pin.IN)

        if implementation.name == 'circuitpython':
          self.irq.deinit()
          self.irq = digitalio.DigitalInOut(self._irq)
          self.irq.switch_to_input()

    def startTransmit(self, data, len_, addr=0):
        if len_ > SX126X_MAX_PACKET_LENGTH:
            return ERR_PACKET_TOO_LONG
                
        if self._addrComp != SX126X_GFSK_ADDRESS_FILT_OFF and len_ > (SX126X_MAX_PACKET_LENGTH - 1):
            return ERR_PACKET_TOO_LONG
                
        state = ERR_NONE
        modem = self.getPacketType()
        if modem == SX126X_PACKET_TYPE_LORA:
            if self._txIq:
                self._invertIQ = SX126X_LORA_IQ_INVERTED
            else:
                self._invertIQ = SX126X_LORA_IQ_STANDARD
                
            if self._headerType == SX126X_LORA_HEADER_IMPLICIT:
                if len_ != self._implicitLen:
                    return ERR_INVALID_PACKET_LENGTH
                
            state = self.setPacketParams(self._preambleLength, self._crcType, len_, self._headerType, self._invertIQ)
        elif modem == SX126X_PACKET_TYPE_GFSK:
            if self._packetType == SX126X_GFSK_PACKET_FIXED:
                if len_ != self._packetLength:
                    return ERR_INVALID_PACKET_LENGTH
                
            state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, len_, self._preambleDetectorLength)
        else:
            return ERR_UNKNOWN
        ASSERT(state)
        
        state = self.setDioIrqParams(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT, SX126X_IRQ_TX_DONE)
        ASSERT(state)
        
        state = self.setBufferBaseAddress()
        ASSERT(state)
        
        state = self.writeBuffer(data, len_)
        ASSERT(state)
        
        state = self.clearIrqStatus()
        ASSERT(state)
        
        state = self.fixSensitivity()
        ASSERT(state)
        
        state = self.setTx(SX126X_TX_TIMEOUT_NONE)
        ASSERT(state)
        
        if implementation.name == 'micropython':
          while self.gpio.value():
              yield_()

        if implementation.name == 'circuitpython':
          while self.gpio.value:
              yield_()

        return state
		
    def startReceive(self, timeout=SX126X_RX_TIMEOUT_INF):
        state = ERR_NONE
        modem = self.getPacketType()
        if modem == SX126X_PACKET_TYPE_LORA:
            if self._rxIq:
                self._invertIQ = SX126X_LORA_IQ_INVERTED
            else:
                self._invertIQ = SX126X_LORA_IQ_STANDARD
                
            state = self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ)
        elif modem == SX126X_PACKET_TYPE_GFSK:
            state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)
        else:
            return ERR_UNKNOWN
        ASSERT(state)
        
        state = self.startReceiveCommon()
        ASSERT(state)
        
        state = self.setRx(timeout)
        
        return state
            
    def startReceiveDutyCycle(self, rxPeriod, sleepPeriod):
        transitionTime = int(self._tcxoDelay + 1000)
        sleepPeriod -= transitionTime
        
        rxPeriodRaw = int((rxPeriod * 8) / 125)
        sleepPeriodRaw = int((sleepPeriod * 8) / 125)
        
        if rxPeriodRaw & 0xFF000000 or rxPeriodRaw == 0:
            return ERR_INVALID_RX_PERIOD
                
        if sleepPeriodRaw & 0xFF000000 or sleepPeriodRaw == 0:
            return ERR_INVALID_SLEEP_PERIOD
                
        state = self.startReceiveCommon()
        ASSERT(state)
        
        data = [int((rxPeriodRaw >> 16) & 0xFF), int((rxPeriodRaw >> 8) & 0xFF), int(rxPeriodRaw & 0xFF),
                int((sleepPeriodRaw >> 16) & 0xFF),int((sleepPeriodRaw >> 8) & 0xFF),int(sleepPeriodRaw & 0xFF)]
        return self.SPIwriteCommand([SX126X_CMD_SET_RX_DUTY_CYCLE], 1, data, 6)
            
    def startReceiveDutyCycleAuto(self, senderPreambleLength=0, minSymbols=8):
        if senderPreambleLength == 0:
            senderPreambleLength = self._preambleLength
                
        sleepSymbols = int(senderPreambleLength - 2 * minSymbols)
        
        if (2 * minSymbols) > senderPreambleLength:
            return self.startReceive()
                
        symbolLength = int(((10*1000) << self._sf) / (10 * self._bwKhz))
        sleepPeriod = symbolLength * sleepSymbols
        
        wakePeriod = int(max((symbolLength * (senderPreambleLength + 1) - (sleepPeriod - 1000)) / 2, symbolLength * (minSymbols + 1)))
        
        if sleepPeriod < (self._tcxoDelay + 1016):
            return self.startReceive()
                
        return self.startReceiveDutyCycle(wakePeriod, sleepPeriod)
            
    def startReceiveCommon(self):
        state = self.setDioIrqParams(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR | SX126X_IRQ_HEADER_ERR, SX126X_IRQ_RX_DONE)
        ASSERT(state)
        
        state = self.setBufferBaseAddress()
        ASSERT(state)
        
        state = self.clearIrqStatus()

        modem = self.getPacketType()
        if modem == SX126X_PACKET_TYPE_LORA:
            state = self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ)
        elif modem == SX126X_PACKET_TYPE_GFSK:
            state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType)
        else:
            return ERR_UNKNOWN
                
        return state
            
    def readData(self, data, len_):
        state = self.standby()
        ASSERT(state)
        
        irq = self.getIrqStatus()
        crcState = ERR_NONE
        if irq & SX126X_IRQ_CRC_ERR or irq & SX126X_IRQ_HEADER_ERR:
            crcState = ERR_CRC_MISMATCH
                
        length = len_
        if len_ == SX126X_MAX_PACKET_LENGTH:
            length = self.getPacketLength()
                
        state = self.readBuffer(data, length)
        ASSERT(state)
        
        state = self.clearIrqStatus()
        
        ASSERT(crcState)
        
        return state
            
    def setBandwidth(self, bw):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM
                
        if not ((bw > 0) and (bw < 510)):
            return ERR_INVALID_BANDWIDTH
                
        bw_div2 = int(bw / 2 + 0.01)
        switch = {3: SX126X_LORA_BW_7_8,
                  5: SX126X_LORA_BW_10_4,
                  7: SX126X_LORA_BW_15_6,
                  10: SX126X_LORA_BW_20_8,
                  15: SX126X_LORA_BW_31_25,
                  20: SX126X_LORA_BW_41_7,
                  31: SX126X_LORA_BW_62_5,
                  62: SX126X_LORA_BW_125_0,
                  125: SX126X_LORA_BW_250_0,
                  250: SX126X_LORA_BW_500_0}
        try:
            self._bw = switch[bw_div2]
        except:
            return ERR_INVALID_BANDWIDTH

        self._bwKhz = bw
        return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro)

    def setSpreadingFactor(self, sf):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM

        if not ((sf >= 5) and (sf <= 12)):
            return ERR_INVALID_SPREADING_FACTOR

        self._sf = sf
        return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro)

    def setCodingRate(self, cr):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM

        if not ((cr >= 5) and (cr <= 8)):
            return ERR_INVALID_CODING_RATE

        self._cr = cr - 4
        return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro)

    def setSyncWord(self, syncWord, *args):
        if self.getPacketType() == SX126X_PACKET_TYPE_LORA:
            if len(args) > 0:
                controlBits = args[0]
            else:
                controlBits = 0x44
            data = [int((syncWord & 0xF0) | ((controlBits & 0xF0) >> 4)), int(((syncWord & 0x0F) << 4) | (controlBits & 0x0F))]
            return self.writeRegister(SX126X_REG_LORA_SYNC_WORD_MSB, data, 2)

        elif self.getPacketType() == SX126X_PACKET_TYPE_GFSK:
            len_ = args[0]
            if len_ > 8:
                return ERR_INVALID_SYNC_WORD

            state = self.writeRegister(SX126X_REG_SYNC_WORD_0, syncWord, len_)
            ASSERT(state)

            self._syncWordLength = len_ * 8
            state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)

            return state

        else:
            return ERR_WRONG_MODEM

    def setCurrentLimit(self, currentLimit):
        if not ((currentLimit >= 0) and (currentLimit <= 140)):
            return ERR_INVALID_CURRENT_LIMIT

        rawLimit = [int(currentLimit / 2.5)]

        return self.writeRegister(SX126X_REG_OCP_CONFIGURATION, rawLimit, 1)

    def getCurrentLimit(self):
        ocp = bytearray(1)
        ocp_mv = memoryview(ocp)
        self.readRegister(SX126X_REG_OCP_CONFIGURATION, ocp_mv, 1)

        return float(ocp[0]) * 2.5

    def setPreambleLength(self, preambleLength):
        modem = self.getPacketType()
        if modem == SX126X_PACKET_TYPE_LORA:
            self._preambleLength = preambleLength
            return self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ)
        elif modem == SX126X_PACKET_TYPE_GFSK:
            self._preambleLengthFSK = preambleLength
            return self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)

        return ERR_UNKNOWN

    def setFrequencyDeviation(self, freqDev):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        if not (freqDev <= 200.0):
            return ERR_INVALID_FREQUENCY_DEVIATION

        freqDevRaw = int(((freqDev * 1000.0) * float(1 << 25)) / (SX126X_CRYSTAL_FREQ * 1000000.0))

        self._freqDev = freqDevRaw
        return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev)

    def setBitRate(self, br):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        if not ((br >= 0.6) and (br <= 300.0)):
            return ERR_INVALID_BIT_RATE

        brRaw = int((SX126X_CRYSTAL_FREQ * 1000000.0 * 32.0) / (br * 1000.0))

        self._br = brRaw

        return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev)

    def setRxBandwidth(self, rxBw):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        self._rxBwKhz = rxBw

        if abs(rxBw - 4.8) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_4_8
        elif abs(rxBw - 5.8) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_5_8
        elif abs(rxBw - 7.3) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_7_3
        elif abs(rxBw - 9.7) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_9_7
        elif abs(rxBw - 11.7) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_11_7
        elif abs(rxBw - 14.6) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_14_6
        elif abs(rxBw - 19.5) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_19_5
        elif abs(rxBw - 23.4) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_23_4
        elif abs(rxBw - 29.3) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_29_3
        elif abs(rxBw - 39.0) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_39_0
        elif abs(rxBw - 46.9) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_46_9
        elif abs(rxBw - 58.6) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_58_6
        elif abs(rxBw - 78.2) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_78_2
        elif abs(rxBw - 93.8) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_93_8
        elif abs(rxBw - 117.3) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_117_3
        elif abs(rxBw - 156.2) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_156_2
        elif abs(rxBw - 187.2) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_187_2
        elif abs(rxBw - 234.3) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_234_3
        elif abs(rxBw - 312.0) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_312_0
        elif abs(rxBw - 373.6) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_373_6
        elif abs(rxBw - 467.0) <= 0.001:
            self._rxBw = SX126X_GFSK_RX_BW_467_0
        else:
            return ERR_INVALID_RX_BANDWIDTH

        return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev)

    def setDataShaping(self, sh):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        sh *= 10.0
        if abs(sh - 0.0) <= 0.001:
            self._pulseShape = SX126X_GFSK_FILTER_NONE
        elif abs(sh - 3.0) <= 0.001:
            self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_3
        elif abs(sh - 5.0) <= 0.001:
            self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_5
        elif abs(sh - 7.0) <= 0.001:
            self._pulseShape = SX126X_GFSK_FILTER_GAUSS_0_7
        elif abs(sh - 10.0) <= 0.001:
            self._pulseShape = SX126X_GFSK_FILTER_GAUSS_1
        else:
            return ERR_INVALID_DATA_SHAPING

        return self.setModulationParamsFSK(self._br, self._pulseShape, self._rxBw, self._freqDev)

    def setSyncBits(self, syncWord, bitsLen):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        if bitsLen > 0x40:
            return ERR_INVALID_SYNC_WORD

        bytesLen = int(bitsLen / 8)
        if (bitsLen % 8) != 0:
            bytesLen += 1

        state = self.writeRegister(SX126X_REG_SYNC_WORD_0, syncWord, bytesLen)
        ASSERT(state)

        self._syncWordLength = bitsLen
        state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)

        return state

    def setNodeAddress(self, nodeAddr):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        self._addrComp = SX126X_GFSK_ADDRESS_FILT_NODE

        state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)
        ASSERT(state)

        state = self.writeRegister(SX126X_REG_NODE_ADDRESS, [nodeAddr], 1)

        return state

    def setBroadcastAddress(self, broadAddr):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        self._addrComp = SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST
        state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)
        ASSERT(state)

        state = self.writeRegister(SX126X_REG_BROADCAST_ADDRESS, [broadAddr], 1)

        return state

    def disableAddressFiltering(self):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        self._addrComp = SX126X_GFSK_ADDRESS_FILT_OFF
        return self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)

    def setCRC(self, len_, initial=0x1D0F, polynomial=0x1021, inverted=True):
        modem = self.getPacketType()

        if modem == SX126X_PACKET_TYPE_GFSK:
            if len_ == 0:
                self._crcTypeFSK = SX126X_GFSK_CRC_OFF
            elif len_ == 1:
                if inverted:
                    self._crcTypeFSK = SX126X_GFSK_CRC_1_BYTE_INV
                else:
                    self._crcTypeFSK = SX126X_GFSK_CRC_1_BYTE
            elif len_ == 2:
                if inverted:
                    self._crcTypeFSK = SX126X_GFSK_CRC_2_BYTE_INV
                else:
                    self._crcTypeFSK = SX126X_GFSK_CRC_2_BYTE
            else:
                return ERR_INVALID_CRC_CONFIGURATION

            state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)
            ASSERT(state)

            data = [int((initial >> 8) & 0xFF), int(initial & 0xFF)]
            state = self.writeRegister(SX126X_REG_CRC_INITIAL_MSB, data, 2)
            ASSERT(state)

            data[0] = int((polynomial >> 8) & 0xFF)
            data[1] = int(polynomial & 0xFF)
            state = self.writeRegister(SX126X_REG_CRC_POLYNOMIAL_MSB, data, 2)

            return state

        elif modem == SX126X_PACKET_TYPE_LORA:

            if len_:
                self._crcType = SX126X_LORA_CRC_ON
            else:
                self._crcType = SX126X_LORA_CRC_OFF

            return self.setPacketParams(self._preambleLength, self._crcType, self._implicitLen, self._headerType, self._invertIQ)

        return ERR_UNKNOWN

    def setWhitening(self, enabled, initial=0x0100):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        state = ERR_NONE
        if enabled != True:
            self._whitening = SX126X_GFSK_WHITENING_OFF

            state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)
            ASSERT(state)
        else:
            self._whitening = SX126X_GFSK_WHITENING_ON
            
            data = bytearray(1)
            data_mv = memoryview(data)
            state = self.readRegister(SX126X_REG_WHITENING_INITIAL_MSB, data_mv, 1)
            ASSERT(state)
            data2 = [(data[0] & 0xFE) | int((initial >> 8) & 0x01), int(initial & 0xFF)]
            state = self.writeRegister(SX126X_REG_WHITENING_INITIAL_MSB, data2, 2)
            ASSERT(state)

            state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, self._packetType, self._packetLength, self._preambleDetectorLength)
            ASSERT(state)
        return state

    def getDataRate(self):
        return self._dataRate

    def getRSSI(self):
        packetStatus = self.getPacketStatus()
        rssiPkt = int(packetStatus & 0xFF)
        return -1.0 * rssiPkt/2.0

    def getSNR(self):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM

        packetStatus = self.getPacketStatus()
        snrPkt = int((packetStatus >> 8) & 0xFF)
        if snrPkt < 128:
            return snrPkt/4.0
        else:
            return (snrPkt - 256)/4.0

    def getPacketLength(self, update=True):
        rxBufStatus = bytearray(2)
        rxBufStatus_mv = memoryview(rxBufStatus)
        self.SPIreadCommand([SX126X_CMD_GET_RX_BUFFER_STATUS], 1, rxBufStatus_mv, 2)
        return rxBufStatus[0]

    def fixedPacketLengthMode(self, len_=SX126X_MAX_PACKET_LENGTH):
        return self.setPacketMode(SX126X_GFSK_PACKET_FIXED, len_)

    def variablePacketLengthMode(self, maxLen=SX126X_MAX_PACKET_LENGTH):
        return self.setPacketMode(SX126X_GFSK_PACKET_VARIABLE, maxLen)

    def getTimeOnAir(self, len_):
        if self.getPacketType() == SX126X_PACKET_TYPE_LORA:
            symbolLength_us = int(((1000 * 10) << self._sf) / (self._bwKhz * 10))
            sfCoeff1_x4 = 17
            sfCoeff2 = 8
            if self._sf == 5 or self._sf == 6:
                sfCoeff1_x4 = 25
                sfCoeff2 = 0
            sfDivisor = 4*self._sf
            if symbolLength_us >= 16000:
                sfDivisor = 4*(self._sf - 2)
            bitsPerCrc = 16
            N_symbol_header = 20 if self._headerType == SX126X_LORA_HEADER_EXPLICIT else 0

            bitCount = int(8 * len_ + self._crcType * bitsPerCrc - 4 * self._sf  + sfCoeff2 + N_symbol_header)
            if bitCount < 0:
                bitCount = 0

            nPreCodedSymbols = int((bitCount + (sfDivisor - 1)) / sfDivisor)

            nSymbol_x4 = int((self._preambleLength + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * (self._cr + 4) * 4)

            return int((symbolLength_us * nSymbol_x4) / 4)
        else:
            return int((len_ * 8 * self._br) / (SX126X_CRYSTAL_FREQ * 32))

    def implicitHeader(self, len_):
        return self.setHeaderType(SX126X_LORA_HEADER_IMPLICIT, len_)

    def explicitHeader(self):
        return self.setHeaderType(SX126X_LORA_HEADER_EXPLICIT)

    def setRegulatorLDO(self):
        return self.setRegulatorMode(SX126X_REGULATOR_LDO)

    def setRegulatorDCDC(self):
        return self.setRegulatorMode(SX126X_REGULATOR_DC_DC)

    def setEncoding(self, encoding):
        return self.setWhitening(encoding)

    def forceLDRO(self, enable):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM

        self._ldroAuto = False
        self._ldro = enable
        return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro)

    def autoLDRO(self):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM

        self._ldroAuto = True
        return self.setModulationParams(self._sf, self._bw, self._cr, self._ldro)

    def setTCXO(self, voltage, delay=5000):
        self.standby()

        if self.getDeviceErrors() & SX126X_XOSC_START_ERR:
            self.clearDeviceErrors()

        if abs(voltage - 0.0) <= 0.001:
            return self.reset()

        data = [0,0,0,0]
        if abs(voltage - 1.6) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_1_6
        elif abs(voltage - 1.7) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_1_7
        elif abs(voltage - 1.8) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_1_8
        elif abs(voltage - 2.2) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_2_2
        elif abs(voltage - 2.4) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_2_4
        elif abs(voltage - 2.7) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_2_7
        elif abs(voltage - 3.0) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_3_0
        elif abs(voltage - 3.3) <= 0.001:
            data[0] = SX126X_DIO3_OUTPUT_3_3
        else:
            return ERR_INVALID_TCXO_VOLTAGE

        delayValue = int(float(delay) / 15.625)
        data[1] = int((delayValue >> 16) & 0xFF)
        data[2] = int((delayValue >> 8) & 0xFF)
        data[3] = int(delayValue & 0xFF)

        self._tcxoDelay = delay

        return self.SPIwriteCommand([SX126X_CMD_SET_DIO3_AS_TCXO_CTRL], 1, data, 4)

    def setDio2AsRfSwitch(self, enable=True):
        data = [0]
        if enable:
            data = [SX126X_DIO2_AS_RF_SWITCH]
        else:
            data = [SX126X_DIO2_AS_IRQ]
        return self.SPIwriteCommand([SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL], 1, data, 1)

    def setTx(self, timeout=0):
        data = [int((timeout >> 16) & 0xFF), int((timeout >> 8) & 0xFF), int(timeout & 0xFF)]
        return self.SPIwriteCommand([SX126X_CMD_SET_TX], 1, data, 3)

    def setRx(self, timeout):
        data = [int((timeout >> 16) & 0xFF), int((timeout >> 8) & 0xFF), int(timeout & 0xFF)]
        return self.SPIwriteCommand([SX126X_CMD_SET_RX], 1, data, 3)

    def setCad(self):
        return self.SPIwriteCommand([SX126X_CMD_SET_CAD], 1, [], 0)

    def setPaConfig(self, paDutyCycle, deviceSel, hpMax=SX126X_PA_CONFIG_HP_MAX, paLut=SX126X_PA_CONFIG_PA_LUT):
        data = [paDutyCycle, hpMax, deviceSel, paLut]
        return self.SPIwriteCommand([SX126X_CMD_SET_PA_CONFIG], 1, data, 4)

    def writeRegister(self, addr, data, numBytes):
        cmd = [SX126X_CMD_WRITE_REGISTER, int((addr >> 8) & 0xFF), int(addr & 0xFF)]
        state = self.SPIwriteCommand(cmd, 3, data, numBytes)
        return state

    def readRegister(self, addr, data, numBytes):
        cmd = [SX126X_CMD_READ_REGISTER, int((addr >> 8) & 0xFF), int(addr & 0xFF)]
        return self.SPItransfer(cmd, 3, False, [], data, numBytes, True)

    def writeBuffer(self, data, numBytes, offset=0x00):
        cmd = [SX126X_CMD_WRITE_BUFFER, offset]
        state = self.SPIwriteCommand(cmd, 2, data, numBytes)

        return state

    def readBuffer(self, data, numBytes):
        cmd = [SX126X_CMD_READ_BUFFER, SX126X_CMD_NOP]
        state = self.SPIreadCommand(cmd, 2, data, numBytes)

        return state

    def setDioIrqParams(self, irqMask, dio1Mask, dio2Mask=SX126X_IRQ_NONE, dio3Mask=SX126X_IRQ_NONE):
        data = [int((irqMask >> 8) & 0xFF), int(irqMask & 0xFF),
                int((dio1Mask >> 8) & 0xFF), int(dio1Mask & 0xFF),
                int((dio2Mask >> 8) & 0xFF), int(dio2Mask & 0xFF),
                int((dio3Mask >> 8) & 0xFF), int(dio3Mask & 0xFF)]
        return self.SPIwriteCommand([SX126X_CMD_SET_DIO_IRQ_PARAMS], 1, data, 8)

    def getIrqStatus(self):
        data = bytearray(2)
        data_mv = memoryview(data)
        self.SPIreadCommand([SX126X_CMD_GET_IRQ_STATUS], 1, data_mv, 2)
        return int((data[0] << 8) | data[1])

    def clearIrqStatus(self, clearIrqParams=SX126X_IRQ_ALL):
        data = [int((clearIrqParams >> 8) & 0xFF), int(clearIrqParams & 0xFF)]
        return self.SPIwriteCommand([SX126X_CMD_CLEAR_IRQ_STATUS], 1, data, 2)

    def setRfFrequency(self, frf):
        data = [int((frf >> 24) & 0xFF),
                int((frf >> 16) & 0xFF),
                int((frf >> 8) & 0xFF),
                int(frf & 0xFF)]
        return self.SPIwriteCommand([SX126X_CMD_SET_RF_FREQUENCY], 1, data, 4)

    def calibrateImage(self, data):
        return self.SPIwriteCommand([SX126X_CMD_CALIBRATE_IMAGE], 1, data, 2)

    def getPacketType(self):
        data = bytearray([0xFF])
        data_mv = memoryview(data)
        self.SPIreadCommand([SX126X_CMD_GET_PACKET_TYPE], 1, data_mv, 1)
        return data[0]

    def setTxParams(self, power, rampTime=SX126X_PA_RAMP_200U):
        if power < 0:
            power += 256
        data = [power, rampTime]
        return self.SPIwriteCommand([SX126X_CMD_SET_TX_PARAMS], 1, data, 2)

    def setPacketMode(self, mode, len_):
        if self.getPacketType() != SX126X_PACKET_TYPE_GFSK:
            return ERR_WRONG_MODEM

        state = self.setPacketParamsFSK(self._preambleLengthFSK, self._crcTypeFSK, self._syncWordLength, self._addrComp, self._whitening, mode, len_, self._preambleDetectorLength)
        ASSERT(state)

        self._packetType = mode
        self._packetLength = len_
        return state

    def setHeaderType(self, headerType, len_=0xFF):
        if self.getPacketType() != SX126X_PACKET_TYPE_LORA:
            return ERR_WRONG_MODEM

        state = self.setPacketParams(self._preambleLength, self._crcType, len_, headerType, self._invertIQ)
        ASSERT(state)

        self._headerType = headerType
        self._implicitLen = len_

        return state

    def setModulationParams(self, sf, bw, cr, ldro):
        if self._ldroAuto:
            symbolLength = float((1 << self._sf)) / float(self._bwKhz)
            if symbolLength >= 16.0:
                self._ldro = SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON
            else:
                self._ldro = SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF
        else:
            self._ldro = ldro

        data = [sf, bw, cr, self._ldro]
        return self.SPIwriteCommand([SX126X_CMD_SET_MODULATION_PARAMS], 1, data, 4)

    def setModulationParamsFSK(self, br, pulseShape, rxBw, freqDev):
        data = [int((br >> 16) & 0xFF), int((br >> 8) & 0xFF), int(br & 0xFF),
                pulseShape, rxBw,
                int((freqDev >> 16) & 0xFF), int((freqDev >> 8) & 0xFF), int(freqDev & 0xFF)]
        return self.SPIwriteCommand([SX126X_CMD_SET_MODULATION_PARAMS], 1, data, 8)

    def setPacketParams(self, preambleLength, crcType, payloadLength, headerType, invertIQ=SX126X_LORA_IQ_STANDARD):
        state = self.fixInvertedIQ(invertIQ)
        ASSERT(state)
        data = [int((preambleLength >> 8) & 0xFF), int(preambleLength & 0xFF),
                headerType, payloadLength, crcType, invertIQ]
        return self.SPIwriteCommand([SX126X_CMD_SET_PACKET_PARAMS], 1, data, 6)

    def setPacketParamsFSK(self, preambleLength, crcType, syncWordLength, addrComp, whitening, packetType=SX126X_GFSK_PACKET_VARIABLE, payloadLength=0xFF, preambleDetectorLength=SX126X_GFSK_PREAMBLE_DETECT_16):
        data = [int((preambleLength >> 8) & 0xFF), int(preambleLength & 0xFF),
                preambleDetectorLength, syncWordLength, addrComp,
                packetType, payloadLength, crcType, whitening]
        return self.SPIwriteCommand([SX126X_CMD_SET_PACKET_PARAMS], 1, data, 9)

    def setBufferBaseAddress(self, txBaseAddress=0x00, rxBaseAddress=0x00):
        data = [txBaseAddress, rxBaseAddress]
        return self.SPIwriteCommand([SX126X_CMD_SET_BUFFER_BASE_ADDRESS], 1, data, 2)

    def setRegulatorMode(self, mode):
        data = [mode]
        return self.SPIwriteCommand([SX126X_CMD_SET_REGULATOR_MODE], 1, data, 1)

    def getStatus(self):
        data = bytearray(1)
        data_mv = memoryview(data)
        self.SPIreadCommand([SX126X_CMD_GET_STATUS], 1, data_mv, 1)
        return data[0]

    def getPacketStatus(self):
        data = bytearray(3)
        data_mv = memoryview(data)
        self.SPIreadCommand([SX126X_CMD_GET_PACKET_STATUS], 1, data_mv, 3)
        return (data[0] << 16) | (data[1] << 8) | data[2]

    def getDeviceErrors(self):
        data = bytearray(2)
        data_mv = memoryview(data)
        self.SPIreadCommand([SX126X_CMD_GET_DEVICE_ERRORS], 1, data_mv, 2)
        opError = ((data[0] & 0xFF) << 8) & data[1]
        return opError

    def clearDeviceErrors(self):
        data = [SX126X_CMD_NOP, SX126X_CMD_NOP]
        return self.SPIwriteCommand([SX126X_CMD_CLEAR_DEVICE_ERRORS], 1, data, 2)

    def setFrequencyRaw(self, freq):
        frf = int((freq * (1 << SX126X_DIV_EXPONENT)) / SX126X_CRYSTAL_FREQ)
        return self.setRfFrequency(frf)

    def fixSensitivity(self):
        sensitivityConfig = bytearray(1)
        sensitivityConfig_mv = memoryview(sensitivityConfig)
        state = self.readRegister(SX126X_REG_SENSITIVITY_CONFIG, sensitivityConfig_mv, 1)
        ASSERT(state)

        if self.getPacketType() == SX126X_PACKET_TYPE_LORA and abs(self._bwKhz - 500.0) <= 0.001:
            sensitivityConfig_mv[0] &= 0xFB
        else:
            sensitivityConfig_mv[0] |= 0x04
        return self.writeRegister(SX126X_REG_SENSITIVITY_CONFIG, sensitivityConfig, 1)

    def fixPaClamping(self):
        clampConfig = bytearray(1)
        clampConfig_mv = memoryview(clampConfig)
        state = self.readRegister(SX126X_REG_TX_CLAMP_CONFIG, clampConfig_mv, 1)
        ASSERT(state)

        clampConfig_mv[0] |= 0x1E
        return self.writeRegister(SX126X_REG_TX_CLAMP_CONFIG, clampConfig, 1)

    def fixImplicitTimeout(self):
        if not (self._headerType == SX126X_LORA_HEADER_IMPLICIT and self.getPacketType() == SX126X_PACKET_TYPE_LORA):
            return ERR_WRONG_MODEM

        rtcStop = [0x00]
        state = self.writeRegister(SX126X_REG_RTC_STOP, rtcStop, 1)
        ASSERT(state)

        rtcEvent = bytearray(1)
        rtcEvent_mv = memoryview(rtcEvent)
        state = self.readRegister(SX126X_REG_RTC_EVENT, rtcEvent_mv, 1)
        ASSERT(state)

        rtcEvent_mv[0] |= 0x02
        return self.writeRegister(SX126X_REG_RTC_EVENT, rtcEvent, 1)

    def fixInvertedIQ(self, iqConfig):
        iqConfigCurrent = bytearray(1)
        iqConfigCurrent_mv = memoryview(iqConfigCurrent)
        state = self.readRegister(SX126X_REG_IQ_CONFIG, iqConfigCurrent_mv, 1)
        ASSERT(state)

        if iqConfig == SX126X_LORA_IQ_STANDARD:
            iqConfigCurrent_mv[0] &= 0xFB
        else:
            iqConfigCurrent_mv[0] |= 0x04

        return self.writeRegister(SX126X_REG_IQ_CONFIG, iqConfigCurrent, 1)

    def config(self, modem):
        state = self.setBufferBaseAddress()
        ASSERT(state)

        data = [0,0,0,0,0,0,0]
        data[0] = modem
        state = self.SPIwriteCommand([SX126X_CMD_SET_PACKET_TYPE], 1, data, 1)
        ASSERT(state)

        data[0] = SX126X_RX_TX_FALLBACK_MODE_STDBY_RC
        state = self.SPIwriteCommand([SX126X_CMD_SET_RX_TX_FALLBACK_MODE], 1, data, 1)
        ASSERT(state)

        data[0] = SX126X_CAD_ON_8_SYMB
        data[1] = self._sf + 13
        data[2] = 10
        data[3] = SX126X_CAD_GOTO_STDBY
        data[4] = 0x00
        data[5] = 0x00
        data[6] = 0x00
        state = self.SPIwriteCommand([SX126X_CMD_SET_CAD_PARAMS], 1, data, 7)
        ASSERT(state)

        state = self.clearIrqStatus()
        state |= self.setDioIrqParams(SX126X_IRQ_NONE, SX126X_IRQ_NONE)
        ASSERT(state)

        data[0] = SX126X_CALIBRATE_ALL
        state = self.SPIwriteCommand([SX126X_CMD_CALIBRATE], 1, data, 1)
        ASSERT(state)

        sleep_ms(5)

        if implementation.name == 'micropython':
          while self.gpio.value():
              yield_()

        if implementation.name == 'circuitpython':
          while self.gpio.value:
              yield_()

        return ERR_NONE

    def SPIwriteCommand(self, cmd, cmdLen, data, numBytes, waitForBusy=True):
        return self.SPItransfer(cmd, cmdLen, True, data, [], numBytes, waitForBusy)

    def SPIreadCommand(self, cmd, cmdLen, data, numBytes, waitForBusy=True):
        return self.SPItransfer(cmd, cmdLen, False, [], data, numBytes, waitForBusy)

    def SPItransfer(self, cmd, cmdLen, write, dataOut, dataIn, numBytes, waitForBusy, timeout=5000):
        if implementation.name == 'micropython':
          self.cs.value(0)

          start = ticks_ms()
          while self.gpio.value():
              yield_()
              if abs(ticks_diff(start, ticks_ms())) >= timeout:
                  self.cs.value(1)
                  return ERR_SPI_CMD_TIMEOUT

          for i in range(cmdLen):
              self.spi.write(bytes([cmd[i]]))

        if implementation.name == 'circuitpython':
          while not self.spi.try_lock():
              pass
          self.cs.value = False

          start = ticks_ms()
          while self.gpio.value:
              yield_()
              if abs(ticks_diff(start, ticks_ms())) >= timeout:
                  self.cs.value = True
                  self.spi.unlock()
                  return ERR_SPI_CMD_TIMEOUT

          for i in range(cmdLen):
              self.spi.write(bytes([cmd[i]]))

          in_ = bytearray(1)

        status = 0

        if write:
            for i in range(numBytes):
                if implementation.name == 'micropython':
                    try:
                        in_ = self.spi.read(1, dataOut[i])
                    except:
                        in_ = self.spi.read(1, write=dataOut[i])

                if implementation.name == 'circuitpython':
                  self.spi.write_readinto(bytes([dataOut[i]]), in_)

                if (in_[0] & 0b00001110) == SX126X_STATUS_CMD_TIMEOUT or\
                   (in_[0] & 0b00001110) == SX126X_STATUS_CMD_INVALID or\
                   (in_[0] & 0b00001110) == SX126X_STATUS_CMD_FAILED:
                    status = in_[0] & 0b00001110
                    break
                elif (in_[0] == 0x00) or (in_[0] == 0xFF):
                    status = SX126X_STATUS_SPI_FAILED
                    break
        else:
            if implementation.name == 'micropython':
                try:
                    in_ = self.spi.read(1, SX126X_CMD_NOP)
                except:
                    in_ = self.spi.read(1, write=SX126X_CMD_NOP)

            if implementation.name == 'circuitpython':
              self.spi.readinto(in_)

            if (in_[0] & 0b00001110) == SX126X_STATUS_CMD_TIMEOUT or\
               (in_[0] & 0b00001110) == SX126X_STATUS_CMD_INVALID or\
               (in_[0] & 0b00001110) == SX126X_STATUS_CMD_FAILED:
                status = in_[0] & 0b00001110
            elif (in_[0] == 0x00) or (in_[0] == 0xFF):
                status = SX126X_STATUS_SPI_FAILED
            else:
                if implementation.name == 'micropython':
                    for i in range(numBytes):
                        try:
                            dataIn[i] = self.spi.read(1, SX126X_CMD_NOP)[0]
                        except:
                            dataIn[i] = self.spi.read(1, write=SX126X_CMD_NOP)[0]

                if implementation.name == 'circuitpython':
                  for i in range(numBytes):
                      self.spi.readinto(in_)
                      dataIn[i] = in_[0]

        if implementation.name == 'micropython':
          self.cs.value(1)

        if implementation.name == 'circuitpython':
          self.cs.value = True
          self.spi.unlock()

        if waitForBusy:
            sleep_us(1)
            start = ticks_ms()
            if implementation.name == 'micropython':
              while self.gpio.value():
                  yield_()
                  if abs(ticks_diff(start, ticks_ms())) >= timeout:
                      status =  SX126X_STATUS_CMD_TIMEOUT
                      break

            if implementation.name == 'circuitpython':
              while self.gpio.value:
                  yield_()
                  if abs(ticks_diff(start, ticks_ms())) >= timeout:
                      status =  SX126X_STATUS_CMD_TIMEOUT
                      break

        switch = {SX126X_STATUS_CMD_TIMEOUT: ERR_SPI_CMD_TIMEOUT,
                  SX126X_STATUS_CMD_INVALID: ERR_SPI_CMD_INVALID,
                  SX126X_STATUS_CMD_FAILED: ERR_SPI_CMD_FAILED,
                  SX126X_STATUS_SPI_FAILED: ERR_CHIP_NOT_FOUND}
        try:
            return switch[status]
        except:
            return ERR_NONE
