diff --git a/communications/ax5043_manager/ax5043_driver.py b/communications/ax5043_manager/ax5043_driver.py index fb2db9a2..71fe3d42 100644 --- a/communications/ax5043_manager/ax5043_driver.py +++ b/communications/ax5043_manager/ax5043_driver.py @@ -1,329 +1,14 @@ -from enum import IntEnum, unique -import time - - -@unique -class Reg(IntEnum): - SILICONREVISION = 0x000 - SCRATCH = 0x001 - PWRMODE = 0x002 - POWSTAT = 0x003 - POWSTICKYSTAT = 0x004 - POWIRQMASK = 0x005 - IRQMASK1 = 0x006 - IRQMASK0 = 0x007 - RADIOEVENTMASK1 = 0x008 - RADIOEVENTMASK0 = 0x009 - IRQINVERSION1 = 0x00A - IRQINVERSION0 = 0x00B - IRQREQUEST1 = 0x00C - IRQREQUEST0 = 0x00D - RADIOEVENTREQ1 = 0x00E - RADIOEVENTREQ0 = 0x00F - MODULATION = 0x010 - ENCODING = 0x011 - FRAMING = 0x012 - CRCINIT3 = 0x014 - CRCINIT2 = 0x015 - CRCINIT1 = 0x016 - CRCINIT0 = 0x017 - FEC = 0x018 - FECSYNC = 0x019 - FECSTATUS = 0x01A - RADIOSTATE = 0x01C - XTALSTATUS = 0x01D - PINSTATE = 0x020 - PINFUNCSYSCLK = 0x021 - PINFUNCDCLK = 0x022 - PINFUNCDATA = 0x023 - PINFUNCIRQ = 0x024 - PINFUNCANTSEL = 0x025 - PINFUNCPWRAMP = 0x026 - PWRAMP = 0x027 - FIFOSTAT = 0x028 - FIFODATA = 0x029 - FIFOCOUNT1 = 0x02A - FIFOCOUNT0 = 0x02B - FIFOFREE1 = 0x02C - FIFOFREE0 = 0x02D - FIFOTHRESH1 = 0x02E - FIFOTHRESH0 = 0x02F - PLLLOOP = 0x030 - PLLCPI = 0x031 - PLLVCODIV = 0x032 - PLLRANGINGA = 0x033 - FREQA3 = 0x034 - FREQA2 = 0x035 - FREQA1 = 0x036 - FREQA0 = 0x037 - PLLLOOPBOOST = 0x038 - PLLCPIBOOST = 0x039 - PLLRANGINGB = 0x03B - FREQB3 = 0x03C - FREQB2 = 0x03D - FREQB1 = 0x03E - FREQB0 = 0x03F - RSSI = 0x040 - BGNDRSSI = 0x041 - DIVERSITY = 0x042 - AGCCOUNTER = 0x043 - TRKDATARATE2 = 0x045 - TRKDATARATE1 = 0x046 - TRKDATARATE0 = 0x047 - TRKAMPL1 = 0x048 - TRKAMPL0 = 0x049 - TRKPHASE1 = 0x04A - TRKPHASE0 = 0x04B - TRKRFFREQ2 = 0x04D - TRKRFFREQ1 = 0x04E - TRKRFFREQ0 = 0x04F - TRKFREQ1 = 0x050 - TRKFREQ0 = 0x051 - TRKFSKDEMOD1 = 0x052 - TRKFSKDEMOD0 = 0x053 - TRKAFSKDEMOD1 = 0x054 - TRKAFSKDEMOD0 = 0x055 - TIMER2 = 0x059 - TIMER1 = 0x05A - TIMER0 = 0x05B - WAKEUPTIMER1 = 0x068 - WAKEUPTIMER0 = 0x069 - WAKEUP1 = 0x06A - WAKEUP0 = 0x06B - WAKEUPFREQ1 = 0x06C - WAKEUPFREQ0 = 0x06D - WAKEUPXOEARLY = 0x06E - IFFREQ1 = 0x100 - IFFREQ0 = 0x101 - DECIMATION = 0x102 - RXDATARATE2 = 0x103 - RXDATARATE1 = 0x104 - RXDATARATE0 = 0x105 - MAXDROFFSET2 = 0x106 - MAXDROFFSET1 = 0x107 - MAXDROFFSET0 = 0x108 - MAXRFOFFSET2 = 0x109 - MAXRFOFFSET1 = 0x10A - MAXRFOFFSET0 = 0x10B - FSKDMAX1 = 0x10C - FSKDMAX0 = 0x10D - FSKDMIN1 = 0x10E - FSKDMIN0 = 0x10F - AFSKSPACE1 = 0x110 - AFSKSPACE0 = 0x111 - AFSKMARK1 = 0x112 - AFSKMARK0 = 0x113 - AFSKCTRL = 0x114 - AMPLFILTER = 0x115 - FREQUENCYLEAK = 0x116 - RXPARAMSETS = 0x117 - RXPARAMCURSET = 0x118 - AGCGAIN0 = 0x120 - AGCTARGET0 = 0x121 - AGCAHYST0 = 0x122 - AGCMINMAX0 = 0x123 - TIMEGAIN0 = 0x124 - DRGAIN0 = 0x125 - PHASEGAIN0 = 0x126 - FREQUENCYGAINA0 = 0x127 - FREQUENCYGAINB0 = 0x128 - FREQUENCYGAINC0 = 0x129 - FREQUENCYGAIND0 = 0x12A - AMPLITUDEGAIN0 = 0x12B - FREQDEV10 = 0x12C - FREQDEV00 = 0x12D - FOURFSK0 = 0x12E - BBOFFSRES0 = 0x12F - AGCGAIN1 = 0x130 - AGCTARGET1 = 0x131 - AGCAHYST1 = 0x132 - AGCMINMAX1 = 0x133 - TIMEGAIN1 = 0x134 - DRGAIN1 = 0x135 - PHASEGAIN1 = 0x136 - FREQUENCYGAINA1 = 0x137 - FREQUENCYGAINB1 = 0x138 - FREQUENCYGAINC1 = 0x139 - FREQUENCYGAIND1 = 0x13A - AMPLITUDEGAIN1 = 0x13B - FREQDEV11 = 0x13C - FREQDEV01 = 0x13D - FOURFSK1 = 0x13E - BBOFFSRES1 = 0x13F - AGCGAIN2 = 0x140 - AGCTARGET2 = 0x141 - AGCAHYST2 = 0x142 - AGCMINMAX2 = 0x143 - TIMEGAIN2 = 0x144 - DRGAIN2 = 0x145 - PHASEGAIN2 = 0x146 - FREQUENCYGAINA2 = 0x147 - FREQUENCYGAINB2 = 0x148 - FREQUENCYGAINC2 = 0x149 - FREQUENCYGAIND2 = 0x14A - AMPLITUDEGAIN2 = 0x14B - FREQDEV12 = 0x14C - FREQDEV02 = 0x14D - FOURFSK2 = 0x14E - BBOFFSRES2 = 0x14F - AGCGAIN3 = 0x150 - AGCTARGET3 = 0x151 - AGCAHYST3 = 0x152 - AGCMINMAX3 = 0x153 - TIMEGAIN3 = 0x154 - DRGAIN3 = 0x155 - PHASEGAIN3 = 0x156 - FREQUENCYGAINA3 = 0x157 - FREQUENCYGAINB3 = 0x158 - FREQUENCYGAINC3 = 0x159 - FREQUENCYGAIND3 = 0x15A - AMPLITUDEGAIN3 = 0x15B - FREQDEV13 = 0x15C - FREQDEV03 = 0x15D - FOURFSK3 = 0x15E - BBOFFSRES3 = 0x15F - MODCFGF = 0x160 - FSKDEV2 = 0x161 - FSKDEV1 = 0x162 - FSKDEV0 = 0x163 - MODCFGA = 0x164 - TXRATE2 = 0x165 - TXRATE1 = 0x166 - TXRATE0 = 0x167 - TXPWRCOEFFA1 = 0x168 - TXPWRCOEFFA0 = 0x169 - TXPWRCOEFFB1 = 0x16A - TXPWRCOEFFB0 = 0x16B - TXPWRCOEFFC1 = 0x16C - TXPWRCOEFFC0 = 0x16D - TXPWRCOEFFD1 = 0x16E - TXPWRCOEFFD0 = 0x16F - TXPWRCOEFFE1 = 0x170 - TXPWRCOEFFE0 = 0x171 - PLLVCOI = 0x180 - PLLVCOIR = 0x181 - PLLLOCKDET = 0x182 - PLLRNGCLK = 0x183 - XTALCAP = 0x184 - BBTUNE = 0x188 - BBOFFSCAP = 0x189 - PKTADDRCFG = 0x200 - PKTLENCFG = 0x201 - PKTLENOFFSET = 0x202 - PKTMAXLEN = 0x203 - PKTADDR3 = 0x204 - PKTADDR2 = 0x205 - PKTADDR1 = 0x206 - PKTADDR0 = 0x207 - PKTADDRMASK3 = 0x208 - PKTADDRMASK2 = 0x209 - PKTADDRMASK1 = 0x20A - PKTADDRMASK0 = 0x20B - MATCH0PAT3 = 0x210 - MATCH0PAT2 = 0x211 - MATCH0PAT1 = 0x212 - MATCH0PAT0 = 0x213 - MATCH0LEN = 0x214 - MATCH0MIN = 0x215 - MATCH0MAX = 0x216 - MATCH1PAT1 = 0x218 - MATCH1PAT0 = 0x219 - MATCH1LEN = 0x21C - MATCH1MIN = 0x21D - MATCH1MAX = 0x21E - TMGTXBOOST = 0x220 - TMGTXSETTLE = 0x221 - TMGRXBOOST = 0x223 - TMGRXSETTLE = 0x224 - TMGRXOFFSACQ = 0x225 - TMGRXCOARSEAGC = 0x226 - TMGRXAGC = 0x227 - TMGRXRSSI = 0x228 - TMGRXPREAMBLE1 = 0x229 - TMGRXPREAMBLE2 = 0x22A - TMGRXPREAMBLE3 = 0x22B - RSSIREFERENCE = 0x22C - RSSIABSTHR = 0x22D - BGNDRSSIGAIN = 0x22E - BGNDRSSITHR = 0x22F - PKTCHUNKSIZE = 0x230 - PKTMISCFLAGS = 0x231 - PKTSTOREFLAGS = 0x232 - PKTACCEPTFLAGS = 0x233 - GPADCCTRL = 0x300 - GPADCPERIOD = 0x301 - GPADC13VALUE1 = 0x308 - GPADC13VALUE0 = 0x309 - LPOSCCONFIG = 0x310 - LPOSCSTATUS = 0x311 - LPOSCKFILT1 = 0x312 - LPOSCKFILT0 = 0x313 - LPOSCREF1 = 0x314 - LPOSCREF0 = 0x315 - LPOSCFREQ1 = 0x316 - LPOSCFREQ0 = 0x317 - LPOSCPER1 = 0x318 - LPOSCPER0 = 0x319 - DACVALUE1 = 0x330 - DACVALUE0 = 0x331 - DACCONFIG = 0x332 - TUNE_F00 = 0xF00 - POWCTRL1 = 0xF08 - TUNE_F0C = 0xF0C - REF = 0xF0D - XTALOSC = 0xF10 - XTALAMPL = 0xF11 - TUNE_F18 = 0xF18 - TUNE_F1C = 0xF1C - TUNE_F21 = 0xF21 - TUNE_F22 = 0xF22 - TUNE_F23 = 0xF23 - TUNE_F26 = 0xF26 - TUNE_F30 = 0xF30 - TUNE_F31 = 0xF31 - TUNE_F32 = 0xF32 - TUNE_F33 = 0xF33 - TUNE_F34 = 0xF34 - TUNE_F35 = 0xF35 - TUNE_F44 = 0xF44 - MODCFGP = 0xF5F - TUNE_F72 = 0xF72 - - -@unique -class Pwrmode(IntEnum): - POWERDOWN = 0x0 - DEEPSLEEP = 0x1 - STANDBY = 0x5 - FIFOON = 0x7 - SYNTHRX = 0x8 - FULLRX = 0x9 - WORRX = 0xB - SYNTHTX = 0xC - FULLTX = 0xD - - -class Bits(IntEnum): - # POWSTAT - SVMODEM = 0x08 - # XTALSTATUS - XTAL_RUN = 0x01 - # PLLRANGINGA - RNG_START = 0x10 - RNGERR = 0x20 - - -@unique -class Fifocmd(IntEnum): - CLEAR_DATA_FLAGS = 0x03 - COMMIT = 0x04 +from communications.ax5043_manager.ax5043_regs import Reg +from adafruit_bus_device.spi_device import SPIDevice +from typing import Dict class Chunk: @staticmethod def from_bytes(buf): chunk_size = Chunk.check_length(buf) - if not chunk_size: return (None, buf) + if not chunk_size: + return (None, buf) rem = buf[chunk_size:] if buf[0] == 0x31: @@ -341,7 +26,7 @@ def from_bytes(buf): elif buf[0] == 0x75: return (Antrssi3Chunk(buf[1], buf[2], buf[3]), rem) elif buf[0] == 0xE1: - length = buf[1] + # length = buf[1] return (DataChunk(buf[2], buf[3:chunk_size]), rem) else: return (UnknownChunk(buf[0:chunk_size]), rem) @@ -365,7 +50,7 @@ def check_length(buf): length = buf[1] return (length + 2) * (len(buf) >= length + 2) else: - raise RuntimeError('Invalid top bits: %02X' % top) + raise RuntimeError("Invalid top bits: %02X" % top) @staticmethod def signed_byte(b): @@ -428,17 +113,18 @@ def __init__(self, buf): # Note: CE0 is used as CS pin by Linux system calls (and is NOT held between # Python calls, even in the same context), so all reads must use write_readinto. class Ax5043: - def __init__(self, bus): + def __init__(self, bus: SPIDevice): self._bus = bus - def execute(self, cmds): + def execute(self, cmds: Dict[Reg, int]): last_addr = -2 addr_wvals = None for addr, value in sorted(cmds.items()): if addr - last_addr != 1: # Write accumulated contiguous bytes if addr_wvals is not None: - with self._bus as spi: spi.write(addr_wvals) + with self._bus as spi: + spi.write(addr_wvals) # Initialize next write buffer if addr < 0x70: @@ -451,7 +137,8 @@ def execute(self, cmds): # Write accumulated contiguous bytes if addr_wvals is not None: - with self._bus as spi: spi.write(addr_wvals) + with self._bus as spi: + spi.write(addr_wvals) # Enhancements: return status bits? def read(self, addr): @@ -486,7 +173,8 @@ def write_fifo(self, values): # Writing to FIFODATA does not auto-advance the SPI address pointer addr_wvals = bytearray([0x80 | Reg.FIFODATA]) + values rvals = bytearray(len(addr_wvals)) - with self._bus as spi: spi.write_readinto(addr_wvals, rvals) + with self._bus as spi: + spi.write_readinto(addr_wvals, rvals) # TODO: Check FIFO status in rvals def write_fifo_data(self, data): @@ -496,5 +184,14 @@ def write_fifo_data(self, data): def read_fifo(self, count): addr_wvals = bytearray([Reg.FIFODATA]) + bytearray(count) rvals = bytearray(len(addr_wvals)) - with self._bus as spi: spi.write_readinto(addr_wvals, rvals) + with self._bus as spi: + spi.write_readinto(addr_wvals, rvals) return rvals[1:] + + def reg_dump(self) -> Dict[str, int]: + """Read and return all registers on the AX5043""" + return { + reg.name: self.read(reg.value) + for reg in Reg + if self.read(reg.value) is not None + } diff --git a/communications/ax5043_manager/ax5043_manager.py b/communications/ax5043_manager/ax5043_manager.py index 8808821c..d37aed52 100644 --- a/communications/ax5043_manager/ax5043_manager.py +++ b/communications/ax5043_manager/ax5043_manager.py @@ -1,10 +1,12 @@ import logging import queue import time -from communications.ax5043_manager.ax5043_driver import Reg, Pwrmode, Bits, Fifocmd, Chunk, DataChunk +from communications.ax5043_manager.ax5043_driver import Chunk, DataChunk, Ax5043 +from communications.ax5043_manager.ax5043_regs import Reg, Pwrmode, Bits, Fifocmd + class Manager: - def __init__(self, driver): + def __init__(self, driver: Ax5043): self.driver = driver self.tx_enabled = False self.rx_enabled = False @@ -12,295 +14,325 @@ def __init__(self, driver): self.inbox = queue.Queue() # TODO: consider SimpleQueue (requires Python 3.7) self.outbox = queue.Queue() # TODO: metrics - self.state = Manager.Initializing(self) + self.state = Initializing(self) # TODO: try-except? self.state.enter() def transition(self, new_state): - logging.debug('Transitioning from %s to %s', - self.state.__class__.__name__, new_state.__class__.__name__) + logging.debug( + "Transitioning from %s to %s", + self.state.__class__.__name__, + new_state.__class__.__name__, + ) try: self.state.exit() self.state = new_state except Exception as e: - logging.error('Exception exiting state %s: %s', - self.state.__class__.__name__, e) - self.state = Manager.Error(self, e) + logging.error( + "Exception exiting state %s: %s", self.state.__class__.__name__, e + ) + self.state = Error(self, e) try: self.state.enter() except Exception as e: - logging.error('Exception entering state %s: %s', - self.state.__class__.__name__, e) - self.state = Manager.Error(self, e) + logging.error( + "Exception entering state %s: %s", self.state.__class__.__name__, e + ) + self.state = Error(self, e) # Any exceptions raised by Error.enter() will not be caught self.state.enter() return def dispatch(self): if self.reset_requested: - logging.info('Resetting') - self.transition(Manager.Initializing(self)) + logging.info("Resetting") + self.transition(Initializing(self)) else: try: self.state.dispatch() except Exception as e: - logging.error('Exception dispatching state %s: %s', - self.state.__class__.__name__, e) + logging.error( + "Exception dispatching state %s: %s", + self.state.__class__.__name__, + e, + ) def enable_pa(self, enabled): # TODO: enable power amp - if enabled: logging.info('Enabling PA') - else: logging.info('Disabling PA') + if enabled: + logging.info("Enabling PA") + else: + logging.info("Disabling PA") def should_transmit(self): return self.tx_enabled and not self.inbox.empty() def is_faulted(self): - return isinstance(self.state, Manager.Error) + return isinstance(self.state, Error) def poll(self, reg, mask, target=None, timeout=0.1): - if target is None: target = mask + if target is None: + target = mask start_time = time.monotonic() val = self.driver.read(reg) while (val & mask) != target: val = self.driver.read(reg) dt = time.monotonic() - start_time if dt > timeout: - raise RuntimeError('Timeout (%s > %s) polling for register %02X, ' - 'mask %02X, to reach %02X (last value was %02X)' - % (dt, timeout, reg, mask, target, val)) + raise RuntimeError( + "Timeout (%s > %s) polling for register %02X, " + "mask %02X, to reach %02X (last value was %02X)" + % (dt, timeout, reg, mask, target, val) + ) return val def post(self): rev = self.driver.read(Reg.SILICONREVISION) if rev != 0x51: - logging.error('Expected rev 0x51, but got %02X' % rev) + logging.error("Expected rev 0x51, but got %02X" % rev) return False s = self.driver.read(Reg.SCRATCH) if s != 0xC5: - logging.error('Expected initial scratch to be 0xC5, but got %02X' % s) + logging.error("Expected initial scratch to be 0xC5, but got %02X" % s) return False self.driver.execute({Reg.SCRATCH: 0x3A}) s = self.driver.read(Reg.SCRATCH) if s != 0x3A: - logging.error('Expected scratch to be set to 0x3A, but got %02X' % s) + logging.error("Expected scratch to be set to 0x3A, but got %02X" % s) return False return True - class State: - def __init__(self, mgr): - assert mgr is not None - self.mgr = mgr - def enter(self): pass - def exit(self): pass - def dispatch(self): pass +class State: + def __init__(self, mgr: Manager): + assert mgr is not None + self.mgr = mgr + + def enter(self): + pass + + def exit(self): + pass + + def dispatch(self): + pass + + +class Initializing(State): + def __init__(self, mgr): + super().__init__(mgr) + + def enter(self): + logging.info("Initializing") + self.mgr.driver.reset() + if not self.mgr.post(): + raise RuntimeError("POST failed") + logging.info("POST passed") + + def dispatch(self): + self.mgr.driver.execute(setup_cmds) + self.mgr.driver.execute(datarate_cmds) + self.mgr.transition(Autoranging(self.mgr)) - class Initializing(State): - def __init__(self, mgr): - super().__init__(mgr) - def enter(self): - logging.info('Initializing') - self.mgr.driver.reset() - if not self.mgr.post(): - raise RuntimeError('POST failed') - logging.info('POST passed') +class Autoranging(State): + MAX_POLL_COUNT = 10 - def dispatch(self): - self.mgr.driver.execute(setup_cmds) - self.mgr.driver.execute(datarate_cmds) - self.mgr.transition(Manager.Autoranging(self.mgr)) + def __init__(self, mgr): + super().__init__(mgr) + self.started = False + self.poll_count = 0 - class Autoranging(State): - MAX_POLL_COUNT = 10 + def enter(self): + logging.info("Autoranging") + self.mgr.driver.set_pwrmode(Pwrmode.STANDBY) - def __init__(self, mgr): - super().__init__(mgr) - self.started = False - self.poll_count = 0 + # Wait until crystal oscillator is ready + # Note: This pattern is only appropriate if we expect that the XTAL + # settling time could be either shorter or longer than a control + # cycle and we don't want to wait to start the autoranging process + # in the former case. + try: + self.mgr.poll(Reg.XTALSTATUS, Bits.XTAL_RUN) + except Exception as e: + logging.warning(e) + return - def enter(self): - logging.info('Autoranging') - self.mgr.driver.set_pwrmode(Pwrmode.STANDBY) + # Set RNG_START + self.mgr.driver.execute({Reg.PLLRANGINGA: Bits.RNG_START | 0x08}) + self.started = True + def dispatch(self): + if not self.started: # Wait until crystal oscillator is ready - # Note: This pattern is only appropriate if we expect that the XTAL - # settling time could be either shorter or longer than a control - # cycle and we don't want to wait to start the autoranging process - # in the former case. try: self.mgr.poll(Reg.XTALSTATUS, Bits.XTAL_RUN) except Exception as e: - logging.warning(e) + logging.error(e, exc_info=True) + self.mgr.transition(Error(self.mgr, "Timeout waiting for XTAL_RUN")) return - # Set RNG_START self.mgr.driver.execute({Reg.PLLRANGINGA: Bits.RNG_START | 0x08}) self.started = True - def dispatch(self): - if not self.started: - # Wait until crystal oscillator is ready - try: - self.mgr.poll(Reg.XTALSTATUS, Bits.XTAL_RUN) - except Exception as e: - self.mgr.transition(Manager.Error(self.mgr, 'Timeout waiting for XTAL_RUN')) - return - # Set RNG_START - self.mgr.driver.execute({Reg.PLLRANGINGA: Bits.RNG_START | 0x08}) - self.started = True - - # Wait for auto-ranging to terminate - pllranging = self.mgr.driver.read(Reg.PLLRANGINGA) - if pllranging & Bits.RNGERR: - self.mgr.transition(Manager.Error(self.mgr, 'RNGERR')) - elif not (pllranging & Bits.RNG_START): - self.mgr.transition(Manager.Idle(self.mgr)) - else: - self.poll_count += 1 - if self.poll_count > MAX_POLL_COUNT: - self.mgr.transition(Manager.Error(self.mgr, 'RNG_START')) + # Wait for auto-ranging to terminate + pllranging = self.mgr.driver.read(Reg.PLLRANGINGA) + if pllranging & Bits.RNGERR: + self.mgr.transition(Error(self.mgr, "RNGERR")) + elif not (pllranging & Bits.RNG_START): + self.mgr.transition(Idle(self.mgr)) + else: + self.poll_count += 1 + if self.poll_count > self.MAX_POLL_COUNT: + self.mgr.transition(Error(self.mgr, "RNG_START")) - class Idle(State): - def __init__(self, mgr): - super().__init__(mgr) - def enter(self): - self.mgr.driver.set_pwrmode(Pwrmode.POWERDOWN) +class Idle(State): + def __init__(self, mgr): + super().__init__(mgr) + + def enter(self): + self.mgr.driver.set_pwrmode(Pwrmode.POWERDOWN) + + def dispatch(self): + if self.mgr.should_transmit(): + msg = self.mgr.inbox.get() + self.mgr.transition(Transmitting(self.mgr, msg)) + elif self.mgr.rx_enabled: + self.mgr.transition(Receiving(self.mgr)) + + +class Transmitting(State): + def __init__(self, mgr, msg): + super().__init__(mgr) + self.msg = msg + + def enter(self): + assert self.mgr.tx_enabled + logging.info("Transmitting %d bytes", len(self.msg)) + drv = self.mgr.driver + # Write to PWRMODE to avoid errata + drv.set_pwrmode(Pwrmode.FIFOON) + # Set Tx-specific configs + drv.execute({Reg.PLLVCODIV: 0x24, Reg.TUNE_F18: 0x06}) + drv.set_pwrmode(Pwrmode.FULLTX) + self.mgr.enable_pa(True) + # Before writing to the FIFO, wait for voltage regulator to finish starting up + self.mgr.poll(Reg.POWSTAT, Bits.SVMODEM) + # Write to FIFO + # Preamble (repeat raw 0xAA to generate 272 alternating bits) + drv.write_fifo(bytearray([0x62, 0x38, 0x21, 0xAA])) + # Sync word (undocumented command to write 0xCCAACCAA) + drv.write_fifo(bytearray([0xA1, 0x18, 0xCC, 0xAA, 0xCC, 0xAA])) + # Data (no framing, full packet) + # First byte must be length of message (including itself) + drv.write_fifo_data(bytearray([len(self.msg) + 1]) + self.msg) + # Wait until crystal oscillator is running + self.mgr.poll(Reg.XTALSTATUS, Bits.XTAL_RUN) + # Commit FIFO + drv.execute({Reg.FIFOSTAT: Fifocmd.COMMIT}) + # TODO: msg larger than fifo - def dispatch(self): + def dispatch(self): + if not self.mgr.tx_enabled: + # TODO: abort notice? + logging.warn("Transmission aborted") + self.mgr.transition(Idle(self.mgr)) + # Wait until transmission is done (TODO: 0-timeout) + # radiostate = self.mgr.driver.poll(Reg.RADIOSTATE, 0xFF, 0) + radiostate = self.mgr.driver.read(Reg.RADIOSTATE) + if radiostate == 0: if self.mgr.should_transmit(): - msg = self.mgr.inbox.get() - self.mgr.transition(Manager.Transmitting(self.mgr, msg)) + # No transmission to avoid powering down PA on exit + self.msg = self.mgr.inbox.get() + self.enter() elif self.mgr.rx_enabled: - self.mgr.transition(Manager.Receiving(self.mgr)) - - class Transmitting(State): - def __init__(self, mgr, msg): - super().__init__(mgr) - self.msg = msg - - def enter(self): - assert self.mgr.tx_enabled - logging.info('Transmitting %d bytes', len(self.msg)) - drv = self.mgr.driver - # Write to PWRMODE to avoid errata - drv.set_pwrmode(Pwrmode.FIFOON) - # Set Tx-specific configs - drv.execute({Reg.PLLVCODIV: 0x24, Reg.TUNE_F18: 0x06}) - drv.set_pwrmode(Pwrmode.FULLTX) - self.mgr.enable_pa(True) - # Before writing to the FIFO, wait for voltage regulator to finish starting up - self.mgr.poll(Reg.POWSTAT, Bits.SVMODEM) - # Write to FIFO - # Preamble (repeat raw 0xAA to generate 272 alternating bits) - drv.write_fifo(bytearray([0x62, 0x38, 0x21, 0xAA])) - # Sync word (undocumented command to write 0xCCAACCAA) - drv.write_fifo(bytearray([0xA1, 0x18, 0xCC, 0xAA, 0xCC, 0xAA])) - # Data (no framing, full packet) - # First byte must be length of message (including itself) - drv.write_fifo_data(bytearray([len(self.msg) + 1]) + self.msg) - # Wait until crystal oscillator is running - self.mgr.poll(Reg.XTALSTATUS, Bits.XTAL_RUN) - # Commit FIFO - drv.execute({Reg.FIFOSTAT: Fifocmd.COMMIT}) - # TODO: msg larger than fifo - - def dispatch(self): - if not self.mgr.tx_enabled: - # TODO: abort notice? - logging.warn('Transmission aborted') - self.mgr.transition(Manager.Idle(self.mgr)) - # Wait until transmission is done (TODO: 0-timeout) - #radiostate = self.mgr.driver.poll(Reg.RADIOSTATE, 0xFF, 0) - radiostate = self.mgr.driver.read(Reg.RADIOSTATE) - if radiostate == 0: - if self.mgr.should_transmit(): - # No transmission to avoid powering down PA on exit - self.msg = self.mgr.inbox.get() - self.enter() - elif self.mgr.rx_enabled: - self.mgr.transition(Manager.Receiving(self.mgr)) - else: - self.mgr.transition(Manager.Idle(self.mgr)) - - def exit(self): - self.mgr.enable_pa(False) - self.mgr.driver.set_pwrmode(Pwrmode.STANDBY) - - class Receiving(State): - def __init__(self, mgr): - super().__init__(mgr) - self.data = bytearray() - # If true, FIFO should not be drained prior to being cleared when - # exiting this state. - self.bad_fifo = False - - def enter(self): - assert self.mgr.rx_enabled - # Write to PWRMODE to avoid errata - self.mgr.driver.set_pwrmode(Pwrmode.FIFOON) - # Set Rx-specific configs (TODO: why are these different from Tx?) - self.mgr.driver.execute({Reg.PLLVCODIV: 0x25, Reg.TUNE_F18: 0x02}) - self.mgr.driver.set_pwrmode(Pwrmode.FULLRX) - - def dispatch(self): - # TODO: poll radiostate for whether currently receiving? - if self.mgr.should_transmit(): - msg = self.mgr.inbox.get() - self.mgr.transition(Manager.Transmitting(self.mgr, msg)) - elif not self.mgr.rx_enabled: - self.mgr.transition(Manager.Idle(self.mgr)) + self.mgr.transition(Receiving(self.mgr)) else: - self.drain_fifo() - - def exit(self): - self.mgr.driver.set_pwrmode(Pwrmode.STANDBY) - if not self.bad_fifo: - self.drain_fifo() - self.mgr.driver.execute({Reg.FIFOSTAT: Fifocmd.CLEAR_DATA_FLAGS}) - - def drain_fifo(self): - fifocount = self.mgr.driver.read_16(Reg.FIFOCOUNT1) - if fifocount > 0: - self.data += self.mgr.driver.read_fifo(fifocount) - while len(self.data) > 0: - try: - (chunk, self.data) = Chunk.from_bytes(self.data) - except Exception as e: - logging.error(e) - self.bad_fifo = True - self.mgr.transition(Manager.Receiving(self.mgr)) - return - logging.debug('Parsed chunk %s', chunk) - # TODO: metadata - if isinstance(chunk, DataChunk): - # TODO: multipart - logging.info('Received %d bytes', len(chunk.data)) - assert len(chunk.data) > 0 - # First byte is packet length (including itself) - if chunk.data[0] == len(chunk.data): - self.mgr.outbox.put(chunk.data[1:]) - else: - logging.error('First byte (%d) does not match length (%d)', chunk.data[0], len(chunk.data)) - # TODO: What next? - elif chunk is None: - # TODO: abort if not making progress - break - - class Error(State): - def __init__(self, mgr, err): - super().__init__(mgr) - self.err = err - - def enter(self): - logging.error(self.err) - self.mgr.driver.set_pwrmode(Pwrmode.POWERDOWN) - # This should be handled by the exit actions of any states that use - # the PA, but assert here as a fallback. - self.mgr.enable_pa(False) + self.mgr.transition(Idle(self.mgr)) + + def exit(self): + self.mgr.enable_pa(False) + self.mgr.driver.set_pwrmode(Pwrmode.STANDBY) + + +class Receiving(State): + def __init__(self, mgr): + super().__init__(mgr) + self.data = bytearray() + # If true, FIFO should not be drained prior to being cleared when + # exiting this state. + self.bad_fifo = False + + def enter(self): + assert self.mgr.rx_enabled + # Write to PWRMODE to avoid errata + self.mgr.driver.set_pwrmode(Pwrmode.FIFOON) + # Set Rx-specific configs (TODO: why are these different from Tx?) + self.mgr.driver.execute({Reg.PLLVCODIV: 0x25, Reg.TUNE_F18: 0x02}) + self.mgr.driver.set_pwrmode(Pwrmode.FULLRX) + + def dispatch(self): + # TODO: poll radiostate for whether currently receiving? + if self.mgr.should_transmit(): + msg = self.mgr.inbox.get() + self.mgr.transition(Transmitting(self.mgr, msg)) + elif not self.mgr.rx_enabled: + self.mgr.transition(Idle(self.mgr)) + else: + self.drain_fifo() + + def exit(self): + self.mgr.driver.set_pwrmode(Pwrmode.STANDBY) + if not self.bad_fifo: + self.drain_fifo() + self.mgr.driver.execute({Reg.FIFOSTAT: Fifocmd.CLEAR_DATA_FLAGS}) + + def drain_fifo(self): + fifocount = self.mgr.driver.read_16(Reg.FIFOCOUNT1) + if fifocount > 0: + self.data += self.mgr.driver.read_fifo(fifocount) + while len(self.data) > 0: + try: + (chunk, self.data) = Chunk.from_bytes(self.data) + except Exception as e: + logging.error(e) + self.bad_fifo = True + self.mgr.transition(Receiving(self.mgr)) + return + logging.debug("Parsed chunk %s", chunk) + # TODO: metadata + if isinstance(chunk, DataChunk): + # TODO: multipart + logging.info("Received %d bytes", len(chunk.data)) + assert len(chunk.data) > 0 + # First byte is packet length (including itself) + if chunk.data[0] == len(chunk.data): + self.mgr.outbox.put(chunk.data[1:]) + else: + logging.error( + "First byte (%d) does not match length (%d)", + chunk.data[0], + len(chunk.data), + ) + # TODO: What next? + elif chunk is None: + # TODO: abort if not making progress + break + + +class Error(State): + def __init__(self, mgr, err): + super().__init__(mgr) + self.err = err + + def enter(self): + logging.error(self.err) + self.mgr.driver.set_pwrmode(Pwrmode.POWERDOWN) + # This should be handled by the exit actions of any states that use + # the PA, but assert here as a fallback. + self.mgr.enable_pa(False) # Configuration for Cislunar Explorers @@ -430,3 +462,161 @@ def enter(self): Reg.TXRATE0: 0xAF, Reg.TUNE_F35: 0x12, } + + +# +# AX5043_SPI.c +# UHF_Transceiver +# +# Registers Created by Filipe Pereira on 5/26/16. +# Ported over to Python3 by Tobias Fischer on 2021-11-22 + +psk125_reg_settings = { + Reg.MODULATION: 0x04, # PSK + Reg.ENCODING: 0x03, # Diff and INV (NRZI Enconding) + Reg.FRAMING: 0x06, # Raw, pattern match ,no CRC-32 + Reg.PINFUNCSYSCLK: 0x01, # Output 1 + Reg.PINFUNCDCLK: 0x01, # Output 1 + Reg.PINFUNCDATA: 0x01, # Output 1 + Reg.PINFUNCANTSEL: 0x01, # Output 1 + Reg.PINFUNCPWRAMP: 0x07, # Output Power Amp control + Reg.WAKEUPXOEARLY: 0x01, # Nb of LPOSC cycles by which the XTAL Osc is woken up before the receiver + Reg.IFFREQ1: 0x01, # IF Freq (Computed by RadioLab) + Reg.IFFREQ0: 0x11, # IF Freq (Computed by RadioLab) + Reg.DECIMATION: 0x1E, # Filter decimation factor + Reg.RXDATARATE2: 0x00, # Receiver data rate + Reg.RXDATARATE1: 0x50, # Receiver data rate + Reg.RXDATARATE0: 0x00, # Receiver data rate + Reg.MAXDROFFSET2: 0x00, # maximum possible data rate offset + Reg.MAXDROFFSET1: 0x00, # maximum possible data rate offset + Reg.MAXDROFFSET0: 0x00, # maximum possible data rate offset + Reg.MAXRFOFFSET2: 0x80, # maximum frequency offset + Reg.MAXRFOFFSET1: 0x00, # maximum frequency offset + Reg.MAXRFOFFSET0: 0x00, # maximum frequency offset + Reg.AMPLFILTER: 0x00, # 3dB corner frequency of the Amplitude (Magnitude) Lowpass Filter + Reg.RXPARAMSETS: 0xF4, # Receiver Parameter Set Indirection + Reg.AGCGAIN0: 0xD6, # AGC Speed + Reg.AGCTARGET0: 0x84, # AGC Target + Reg.TIMEGAIN0: 0xA9, # Timing Gain + Reg.DRGAIN0: 0xA3, # Data Rate Gain + Reg.PHASEGAIN0: 0xC3, # Filter Index, Phase Gain + Reg.FREQUENCYGAINA0: 0x46, # Frequency Gain A + Reg.FREQUENCYGAINB0: 0x0A, # Frequency Gain B + Reg.FREQUENCYGAINC0: 0x1F, # Frequency Gain C + Reg.FREQUENCYGAIND0: 0x1F, # Frequency Gain D + Reg.AMPLITUDEGAIN0: 0x06, # Amplitude Gain + Reg.FREQDEV10: 0x00, # Receiver Frequency Deviation + Reg.FREQDEV00: 0x00, # Receiver Frequency Deviation + Reg.BBOFFSRES0: 0x00, # Baseband Offset Compensation Resistors + Reg.AGCGAIN1: 0xD6, # AGC Speed + Reg.AGCTARGET1: 0x84, # AGC Target + Reg.AGCAHYST1: 0x00, # AGC Digital Threshold Range + Reg.AGCMINMAX1: 0x00, # AGC Digital Minimum/Maximum Set Points + Reg.TIMEGAIN1: 0xA7, # Timing Gain + Reg.DRGAIN1: 0xA2, # Data Rate Gain + Reg.PHASEGAIN1: 0xC3, # Filter Index, Phase Gain + Reg.FREQUENCYGAINA1: 0x46, # Frequency Gain A + Reg.FREQUENCYGAINB1: 0x0A, # Frequency Gain B + Reg.FREQUENCYGAINC1: 0x1F, # Frequency Gain C + Reg.FREQUENCYGAIND1: 0x1F, # Frequency Gain D + Reg.AMPLITUDEGAIN1: 0x06, # Amplitude Gain + Reg.FREQDEV11: 0x00, # Rx freq deviation + Reg.FREQDEV01: 0x00, # Rx freq deviation + Reg.FOURFSK1: 0x16, # Four FSK control + Reg.BBOFFSRES1: 0x00, # BB offset compensation resistors + Reg.AGCGAIN3: 0xFF, # AGC Speed + Reg.AGCTARGET3: 0x84, # AGC Target + Reg.AGCAHYST3: 0x00, # AGC Digital Threshold Range + Reg.AGCMINMAX3: 0x00, # AGC Digital Minimum/Maximum Set Points + Reg.TIMEGAIN3: 0xA6, # Timing Gain + Reg.DRGAIN3: 0xA1, # Data Rate Gain + Reg.PHASEGAIN3: 0xC3, # Filter Index, Phase Gain + Reg.FREQUENCYGAINA3: 0x46, # Frequency Gain A + Reg.FREQUENCYGAINB3: 0x0A, # Frequency Gain B + Reg.FREQUENCYGAINC3: 0x1F, # Frequency Gain C + Reg.FREQUENCYGAIND3: 0x1F, # Frequency Gain D + Reg.AMPLITUDEGAIN3: 0x06, # Amplitude Gain + Reg.FREQDEV13: 0x00, # Rx freq deviation + Reg.FREQDEV03: 0x00, # Rx freq deviation + Reg.FOURFSK3: 0x16, # Four FSK control + Reg.BBOFFSRES3: 0x00, # BB offset compensation resistors + Reg.FSKDEV2: 0x00, # FSK freq deviation + Reg.FSKDEV1: 0x00, # FSK freq deviation + Reg.FSKDEV0: 0x00, # FSK freq deviation + Reg.MODCFGA: 0x06, # Amplitude shaping mode + Reg.TXRATE2: 0x00, # TX bitrate + Reg.TXRATE1: 0x06, # TX bitrate + Reg.TXRATE0: 0xD4, # TX bitrate + # Reg.TXPWRCOEFFB1 :0x04,#TX predistortion ... 6.5dBm + # Reg.TXPWRCOEFFB0 :0x54,#TX predistortion + # Reg.TXPWRCOEFFB1: 0x02, # TX predistortion ... 0dBm + # Reg.TXPWRCOEFFB0: 0x07, # TX predistortion + Reg.TXPWRCOEFFB1: 0x0F, # TX predistortion ... 15dBm + Reg.TXPWRCOEFFB0: 0xFF, # TX predistortion + Reg.PLLVCOI: 0x98, # PLL parameters + Reg.PLLRNGCLK: 0x05, # PLL parameters + Reg.BBTUNE: 0x0F, # Baseband tuning + Reg.BBOFFSCAP: 0x77, # Baseband gain offset compensation + Reg.PKTADDRCFG: 0x01, # Packet addr config + Reg.PKTLENCFG: 0x80, # Packet length config + Reg.PKTLENOFFSET: 0x00, # Packet length offset + Reg.PKTMAXLEN: 0xC8, # Packet max length + Reg.MATCH0PAT3: 0xAA, # Pattern match + Reg.MATCH0PAT2: 0xCC, # Pattern match + Reg.MATCH0PAT1: 0xAA, # Pattern match + Reg.MATCH0PAT0: 0xCC, # Pattern match + Reg.MATCH0LEN: 0x1F, # Pattern length + Reg.MATCH0MAX: 0x1F, # Max match + Reg.MATCH1PAT1: 0x55, # Pattern match + Reg.MATCH1PAT0: 0x55, # Pattern match + Reg.MATCH1LEN: 0x8A, # Pattern length + Reg.MATCH1MAX: 0x0A, # Max match + Reg.TMGTXBOOST: 0x5B, # Boost time + Reg.TMGTXSETTLE: 0x3E, # Settling time + Reg.TMGRXBOOST: 0x5B, # Boost time + Reg.TMGRXSETTLE: 0x3E, # Settling time + Reg.TMGRXOFFSACQ: 0x00, # Baseband offset acq + Reg.TMGRXCOARSEAGC: 0x9C, # Coarse AGC time + Reg.TMGRXRSSI: 0x03, # RSSI settling time + Reg.TMGRXPREAMBLE2: 0x35, # Preamble 2 timeout + Reg.RSSIABSTHR: 0xE0, # RSSI abs. threshold + Reg.BGNDRSSITHR: 0x00, # Background RSSI rel. threshold + Reg.PKTCHUNKSIZE: 0x0D, # Packet chunk size + Reg.PKTACCEPTFLAGS: 0x1C, # Packet ctrl accept flags + Reg.DACVALUE1: 0x00, # DAC value + Reg.DACVALUE0: 0x00, # DAC value + Reg.DACCONFIG: 0x00, # DAC config + Reg.REF: 0x03, + Reg.XTALOSC: 0x04, + Reg.XTALAMPL: 0x00, + Reg.TUNE_F1C: 0x07, # Tuning registers + Reg.TUNE_F21: 0x68, # Tuning registers + Reg.TUNE_F22: 0xFF, # Tuning registers + Reg.TUNE_F23: 0x84, # Tuning registers + Reg.TUNE_F26: 0x98, # Tuning registers + Reg.TUNE_F34: 0x28, # Tuning registers + Reg.TUNE_F35: 0x11, # Tuning registers + Reg.TUNE_F44: 0x25, # Tuning registers + Reg.MODCFGP: 0xE1, + # Register TX/RX + Reg.PLLLOOP: 0x0B, + Reg.PLLCPI: 0x10, + Reg.PLLVCODIV: 0x24, + Reg.XTALCAP: 0x00, + Reg.TUNE_F00: 0x0F, + Reg.TUNE_F18: 0x06, + # RXcont settings + Reg.TMGRXAGC: 0x00, # Receiver AGC settling time + Reg.TMGRXPREAMBLE1: 0x00, # Receiver preamble 1 timeout + Reg.PKTMISCFLAGS: 0x00, # Packet misc flags + # Set Frequency (437.5 MHz) + Reg.FREQA0: 0x55, # Output Frequency + Reg.FREQA1: 0x55, # Output Frequency + Reg.FREQA2: 0x1D, # Output Frequency + Reg.FREQA3: 0x09, # Output Frequency + # + Reg.RSSIREFERENCE: 0x00, # RSSI offset + Reg.PKTSTOREFLAGS: 0x54, # Packet store flags + # Additional registers + Reg.MODCFGF: 0x00, # Freq shaping mode +} diff --git a/communications/ax5043_manager/ax5043_regs.py b/communications/ax5043_manager/ax5043_regs.py new file mode 100644 index 00000000..dcfa49c2 --- /dev/null +++ b/communications/ax5043_manager/ax5043_regs.py @@ -0,0 +1,318 @@ +from enum import IntEnum, unique + + +@unique +class Reg(IntEnum): + SILICONREVISION = 0x000 + SCRATCH = 0x001 + PWRMODE = 0x002 + POWSTAT = 0x003 + POWSTICKYSTAT = 0x004 + POWIRQMASK = 0x005 + IRQMASK1 = 0x006 + IRQMASK0 = 0x007 + RADIOEVENTMASK1 = 0x008 + RADIOEVENTMASK0 = 0x009 + IRQINVERSION1 = 0x00A + IRQINVERSION0 = 0x00B + IRQREQUEST1 = 0x00C + IRQREQUEST0 = 0x00D + RADIOEVENTREQ1 = 0x00E + RADIOEVENTREQ0 = 0x00F + MODULATION = 0x010 + ENCODING = 0x011 + FRAMING = 0x012 + CRCINIT3 = 0x014 + CRCINIT2 = 0x015 + CRCINIT1 = 0x016 + CRCINIT0 = 0x017 + FEC = 0x018 + FECSYNC = 0x019 + FECSTATUS = 0x01A + RADIOSTATE = 0x01C + XTALSTATUS = 0x01D + PINSTATE = 0x020 + PINFUNCSYSCLK = 0x021 + PINFUNCDCLK = 0x022 + PINFUNCDATA = 0x023 + PINFUNCIRQ = 0x024 + PINFUNCANTSEL = 0x025 + PINFUNCPWRAMP = 0x026 + PWRAMP = 0x027 + FIFOSTAT = 0x028 + FIFODATA = 0x029 + FIFOCOUNT1 = 0x02A + FIFOCOUNT0 = 0x02B + FIFOFREE1 = 0x02C + FIFOFREE0 = 0x02D + FIFOTHRESH1 = 0x02E + FIFOTHRESH0 = 0x02F + PLLLOOP = 0x030 + PLLCPI = 0x031 + PLLVCODIV = 0x032 + PLLRANGINGA = 0x033 + FREQA3 = 0x034 + FREQA2 = 0x035 + FREQA1 = 0x036 + FREQA0 = 0x037 + PLLLOOPBOOST = 0x038 + PLLCPIBOOST = 0x039 + PLLRANGINGB = 0x03B + FREQB3 = 0x03C + FREQB2 = 0x03D + FREQB1 = 0x03E + FREQB0 = 0x03F + RSSI = 0x040 + BGNDRSSI = 0x041 + DIVERSITY = 0x042 + AGCCOUNTER = 0x043 + TRKDATARATE2 = 0x045 + TRKDATARATE1 = 0x046 + TRKDATARATE0 = 0x047 + TRKAMPL1 = 0x048 + TRKAMPL0 = 0x049 + TRKPHASE1 = 0x04A + TRKPHASE0 = 0x04B + TRKRFFREQ2 = 0x04D + TRKRFFREQ1 = 0x04E + TRKRFFREQ0 = 0x04F + TRKFREQ1 = 0x050 + TRKFREQ0 = 0x051 + TRKFSKDEMOD1 = 0x052 + TRKFSKDEMOD0 = 0x053 + TRKAFSKDEMOD1 = 0x054 + TRKAFSKDEMOD0 = 0x055 + TIMER2 = 0x059 + TIMER1 = 0x05A + TIMER0 = 0x05B + WAKEUPTIMER1 = 0x068 + WAKEUPTIMER0 = 0x069 + WAKEUP1 = 0x06A + WAKEUP0 = 0x06B + WAKEUPFREQ1 = 0x06C + WAKEUPFREQ0 = 0x06D + WAKEUPXOEARLY = 0x06E + IFFREQ1 = 0x100 + IFFREQ0 = 0x101 + DECIMATION = 0x102 + RXDATARATE2 = 0x103 + RXDATARATE1 = 0x104 + RXDATARATE0 = 0x105 + MAXDROFFSET2 = 0x106 + MAXDROFFSET1 = 0x107 + MAXDROFFSET0 = 0x108 + MAXRFOFFSET2 = 0x109 + MAXRFOFFSET1 = 0x10A + MAXRFOFFSET0 = 0x10B + FSKDMAX1 = 0x10C + FSKDMAX0 = 0x10D + FSKDMIN1 = 0x10E + FSKDMIN0 = 0x10F + AFSKSPACE1 = 0x110 + AFSKSPACE0 = 0x111 + AFSKMARK1 = 0x112 + AFSKMARK0 = 0x113 + AFSKCTRL = 0x114 + AMPLFILTER = 0x115 + FREQUENCYLEAK = 0x116 + RXPARAMSETS = 0x117 + RXPARAMCURSET = 0x118 + AGCGAIN0 = 0x120 + AGCTARGET0 = 0x121 + AGCAHYST0 = 0x122 + AGCMINMAX0 = 0x123 + TIMEGAIN0 = 0x124 + DRGAIN0 = 0x125 + PHASEGAIN0 = 0x126 + FREQUENCYGAINA0 = 0x127 + FREQUENCYGAINB0 = 0x128 + FREQUENCYGAINC0 = 0x129 + FREQUENCYGAIND0 = 0x12A + AMPLITUDEGAIN0 = 0x12B + FREQDEV10 = 0x12C + FREQDEV00 = 0x12D + FOURFSK0 = 0x12E + BBOFFSRES0 = 0x12F + AGCGAIN1 = 0x130 + AGCTARGET1 = 0x131 + AGCAHYST1 = 0x132 + AGCMINMAX1 = 0x133 + TIMEGAIN1 = 0x134 + DRGAIN1 = 0x135 + PHASEGAIN1 = 0x136 + FREQUENCYGAINA1 = 0x137 + FREQUENCYGAINB1 = 0x138 + FREQUENCYGAINC1 = 0x139 + FREQUENCYGAIND1 = 0x13A + AMPLITUDEGAIN1 = 0x13B + FREQDEV11 = 0x13C + FREQDEV01 = 0x13D + FOURFSK1 = 0x13E + BBOFFSRES1 = 0x13F + AGCGAIN2 = 0x140 + AGCTARGET2 = 0x141 + AGCAHYST2 = 0x142 + AGCMINMAX2 = 0x143 + TIMEGAIN2 = 0x144 + DRGAIN2 = 0x145 + PHASEGAIN2 = 0x146 + FREQUENCYGAINA2 = 0x147 + FREQUENCYGAINB2 = 0x148 + FREQUENCYGAINC2 = 0x149 + FREQUENCYGAIND2 = 0x14A + AMPLITUDEGAIN2 = 0x14B + FREQDEV12 = 0x14C + FREQDEV02 = 0x14D + FOURFSK2 = 0x14E + BBOFFSRES2 = 0x14F + AGCGAIN3 = 0x150 + AGCTARGET3 = 0x151 + AGCAHYST3 = 0x152 + AGCMINMAX3 = 0x153 + TIMEGAIN3 = 0x154 + DRGAIN3 = 0x155 + PHASEGAIN3 = 0x156 + FREQUENCYGAINA3 = 0x157 + FREQUENCYGAINB3 = 0x158 + FREQUENCYGAINC3 = 0x159 + FREQUENCYGAIND3 = 0x15A + AMPLITUDEGAIN3 = 0x15B + FREQDEV13 = 0x15C + FREQDEV03 = 0x15D + FOURFSK3 = 0x15E + BBOFFSRES3 = 0x15F + MODCFGF = 0x160 + FSKDEV2 = 0x161 + FSKDEV1 = 0x162 + FSKDEV0 = 0x163 + MODCFGA = 0x164 + TXRATE2 = 0x165 + TXRATE1 = 0x166 + TXRATE0 = 0x167 + TXPWRCOEFFA1 = 0x168 + TXPWRCOEFFA0 = 0x169 + TXPWRCOEFFB1 = 0x16A + TXPWRCOEFFB0 = 0x16B + TXPWRCOEFFC1 = 0x16C + TXPWRCOEFFC0 = 0x16D + TXPWRCOEFFD1 = 0x16E + TXPWRCOEFFD0 = 0x16F + TXPWRCOEFFE1 = 0x170 + TXPWRCOEFFE0 = 0x171 + PLLVCOI = 0x180 + PLLVCOIR = 0x181 + PLLLOCKDET = 0x182 + PLLRNGCLK = 0x183 + XTALCAP = 0x184 + BBTUNE = 0x188 + BBOFFSCAP = 0x189 + PKTADDRCFG = 0x200 + PKTLENCFG = 0x201 + PKTLENOFFSET = 0x202 + PKTMAXLEN = 0x203 + PKTADDR3 = 0x204 + PKTADDR2 = 0x205 + PKTADDR1 = 0x206 + PKTADDR0 = 0x207 + PKTADDRMASK3 = 0x208 + PKTADDRMASK2 = 0x209 + PKTADDRMASK1 = 0x20A + PKTADDRMASK0 = 0x20B + MATCH0PAT3 = 0x210 + MATCH0PAT2 = 0x211 + MATCH0PAT1 = 0x212 + MATCH0PAT0 = 0x213 + MATCH0LEN = 0x214 + MATCH0MIN = 0x215 + MATCH0MAX = 0x216 + MATCH1PAT1 = 0x218 + MATCH1PAT0 = 0x219 + MATCH1LEN = 0x21C + MATCH1MIN = 0x21D + MATCH1MAX = 0x21E + TMGTXBOOST = 0x220 + TMGTXSETTLE = 0x221 + TMGRXBOOST = 0x223 + TMGRXSETTLE = 0x224 + TMGRXOFFSACQ = 0x225 + TMGRXCOARSEAGC = 0x226 + TMGRXAGC = 0x227 + TMGRXRSSI = 0x228 + TMGRXPREAMBLE1 = 0x229 + TMGRXPREAMBLE2 = 0x22A + TMGRXPREAMBLE3 = 0x22B + RSSIREFERENCE = 0x22C + RSSIABSTHR = 0x22D + BGNDRSSIGAIN = 0x22E + BGNDRSSITHR = 0x22F + PKTCHUNKSIZE = 0x230 + PKTMISCFLAGS = 0x231 + PKTSTOREFLAGS = 0x232 + PKTACCEPTFLAGS = 0x233 + GPADCCTRL = 0x300 + GPADCPERIOD = 0x301 + GPADC13VALUE1 = 0x308 + GPADC13VALUE0 = 0x309 + LPOSCCONFIG = 0x310 + LPOSCSTATUS = 0x311 + LPOSCKFILT1 = 0x312 + LPOSCKFILT0 = 0x313 + LPOSCREF1 = 0x314 + LPOSCREF0 = 0x315 + LPOSCFREQ1 = 0x316 + LPOSCFREQ0 = 0x317 + LPOSCPER1 = 0x318 + LPOSCPER0 = 0x319 + DACVALUE1 = 0x330 + DACVALUE0 = 0x331 + DACCONFIG = 0x332 + TUNE_F00 = 0xF00 + POWCTRL1 = 0xF08 + TUNE_F0C = 0xF0C + REF = 0xF0D + XTALOSC = 0xF10 + XTALAMPL = 0xF11 + TUNE_F18 = 0xF18 + TUNE_F1C = 0xF1C + TUNE_F21 = 0xF21 + TUNE_F22 = 0xF22 + TUNE_F23 = 0xF23 + TUNE_F26 = 0xF26 + TUNE_F30 = 0xF30 + TUNE_F31 = 0xF31 + TUNE_F32 = 0xF32 + TUNE_F33 = 0xF33 + TUNE_F34 = 0xF34 + TUNE_F35 = 0xF35 + TUNE_F44 = 0xF44 + MODCFGP = 0xF5F + TUNE_F72 = 0xF72 + + +@unique +class Pwrmode(IntEnum): + POWERDOWN = 0x0 + DEEPSLEEP = 0x1 + STANDBY = 0x5 + FIFOON = 0x7 + SYNTHRX = 0x8 + FULLRX = 0x9 + WORRX = 0xB + SYNTHTX = 0xC + FULLTX = 0xD + + +class Bits(IntEnum): + # POWSTAT + SVMODEM = 0x08 + # XTALSTATUS + XTAL_RUN = 0x01 + # PLLRANGINGA + RNG_START = 0x10 + RNGERR = 0x20 + + +@unique +class Fifocmd(IntEnum): + CLEAR_DATA_FLAGS = 0x03 + COMMIT = 0x04 diff --git a/communications/ax5043_manager/mock_ax5043_driver.py b/communications/ax5043_manager/mock_ax5043_driver.py index c2e20c3e..dcb7f8d5 100644 --- a/communications/ax5043_manager/mock_ax5043_driver.py +++ b/communications/ax5043_manager/mock_ax5043_driver.py @@ -1,15 +1,20 @@ -from communications.ax5043_manager.ax5043_driver import Ax5043 +from typing import Dict +from communications.ax5043_manager.ax5043_driver import Ax5043, Reg # Ideas: # * Writes replace read_defaults # * FIFO utilities + + class MockAx5043(Ax5043): def __init__(self): super().__init__(None) self.read_defaults = rst_values.copy() self.read_queue = {} - def execute(self, cmds): pass + def execute(self, cmds: Dict[Reg, int]): + for register, value in cmds.items(): + self.read_defaults[register] = value def read(self, addr): if addr in self.read_queue: @@ -17,39 +22,45 @@ def read(self, addr): else: return self.read_defaults[addr] - def read_16(self, addr): return 0x0000 - def read_fifo(self, count): return bytearray(count) - def write_fifo(self, values): pass + def read_16(self, addr): + return 0x0000 + + def read_fifo(self, count): + return bytearray(count) + + def write_fifo(self, values): + pass + rst_values = { 0x000: 0x51, - 0x001: 0x7d, + 0x001: 0xC5, # changed from 0x7d to pass unit tests 0x002: 0x60, - 0x003: 0xf7, + 0x003: 0xF7, 0x004: 0x77, 0x005: 0x00, 0x006: 0x00, 0x007: 0x00, 0x008: 0x00, 0x009: 0x00, - 0x00a: 0x00, - 0x00b: 0x00, - 0x00c: 0x11, - 0x00d: 0x2a, - 0x00e: 0x00, - 0x00f: 0x00, + 0x00A: 0x00, + 0x00B: 0x00, + 0x00C: 0x11, + 0x00D: 0x2A, + 0x00E: 0x00, + 0x00F: 0x00, 0x010: 0x08, 0x011: 0x02, 0x012: 0x00, - 0x014: 0xff, - 0x015: 0xff, - 0x016: 0xff, - 0x017: 0xff, + 0x014: 0xFF, + 0x015: 0xFF, + 0x016: 0xFF, + 0x017: 0xFF, 0x018: 0x00, 0x019: 0x62, - 0x01a: 0x00, - 0x01c: 0x00, - 0x01d: 0x01, + 0x01A: 0x00, + 0x01C: 0x00, + 0x01D: 0x01, 0x020: 0x00, 0x021: 0x08, 0x022: 0x04, @@ -60,12 +71,12 @@ def write_fifo(self, values): pass 0x027: 0x00, 0x028: 0x00, 0x029: 0x00, - 0x02a: 0x00, - 0x02b: 0x00, - 0x02c: 0x00, - 0x02d: 0x00, - 0x02e: 0x00, - 0x02f: 0x00, + 0x02A: 0x00, + 0x02B: 0x00, + 0x02C: 0x00, + 0x02D: 0x00, + 0x02E: 0x00, + 0x02F: 0x00, 0x030: 0x00, 0x031: 0x00, 0x032: 0x00, @@ -76,11 +87,11 @@ def write_fifo(self, values): pass 0x037: 0x00, 0x038: 0x00, 0x039: 0x00, - 0x03b: 0x08, - 0x03c: 0x39, - 0x03d: 0x34, - 0x03e: 0xcc, - 0x03f: 0xcd, + 0x03B: 0x08, + 0x03C: 0x39, + 0x03D: 0x34, + 0x03E: 0xCC, + 0x03F: 0xCD, 0x040: 0x00, 0x041: 0x00, 0x042: 0x00, @@ -90,11 +101,11 @@ def write_fifo(self, values): pass 0x047: 0x00, 0x048: 0x00, 0x049: 0x00, - 0x04a: 0x00, - 0x04b: 0x00, - 0x04d: 0x00, - 0x04e: 0x00, - 0x04f: 0x00, + 0x04A: 0x00, + 0x04B: 0x00, + 0x04D: 0x00, + 0x04E: 0x00, + 0x04F: 0x00, 0x050: 0x00, 0x051: 0x00, 0x052: 0x00, @@ -102,31 +113,31 @@ def write_fifo(self, values): pass 0x054: 0x00, 0x055: 0x00, 0x059: 0x00, - 0x05a: 0x00, - 0x05b: 0x00, + 0x05A: 0x00, + 0x05B: 0x00, 0x068: 0x00, 0x069: 0x00, - 0x06a: 0x00, - 0x06b: 0x00, - 0x06c: 0x00, - 0x06d: 0x00, - 0x06e: 0x00, + 0x06A: 0x00, + 0x06B: 0x00, + 0x06C: 0x00, + 0x06D: 0x00, + 0x06E: 0x00, 0x100: 0x13, 0x101: 0x27, - 0x102: 0x0d, + 0x102: 0x0D, 0x103: 0x00, - 0x104: 0x3d, - 0x105: 0x8a, + 0x104: 0x3D, + 0x105: 0x8A, 0x106: 0x00, 0x107: 0x00, - 0x108: 0x9e, + 0x108: 0x9E, 0x109: 0x00, - 0x10a: 0x16, - 0x10b: 0x87, - 0x10c: 0x00, - 0x10d: 0x80, - 0x10e: 0xff, - 0x10f: 0x80, + 0x10A: 0x16, + 0x10B: 0x87, + 0x10C: 0x00, + 0x10D: 0x80, + 0x10E: 0xFF, + 0x10F: 0x80, 0x110: 0x00, 0x111: 0x40, 0x112: 0x00, @@ -136,86 +147,86 @@ def write_fifo(self, values): pass 0x116: 0x00, 0x117: 0x00, 0x118: 0x00, - 0x120: 0xb4, + 0x120: 0xB4, 0x121: 0x76, 0x122: 0x00, 0x123: 0x00, - 0x124: 0xf8, - 0x125: 0xf2, - 0x126: 0xc3, - 0x127: 0x0f, - 0x128: 0x1f, - 0x129: 0x0a, - 0x12a: 0x0a, - 0x12b: 0x46, - 0x12c: 0x00, - 0x12d: 0x20, - 0x12e: 0x16, - 0x12f: 0x88, - 0x130: 0xb4, + 0x124: 0xF8, + 0x125: 0xF2, + 0x126: 0xC3, + 0x127: 0x0F, + 0x128: 0x1F, + 0x129: 0x0A, + 0x12A: 0x0A, + 0x12B: 0x46, + 0x12C: 0x00, + 0x12D: 0x20, + 0x12E: 0x16, + 0x12F: 0x88, + 0x130: 0xB4, 0x131: 0x76, 0x132: 0x00, 0x133: 0x00, - 0x134: 0xf6, - 0x135: 0xf1, - 0x136: 0xc3, - 0x137: 0x0f, - 0x138: 0x1f, - 0x139: 0x0b, - 0x13a: 0x0b, - 0x13b: 0x46, - 0x13c: 0x00, - 0x13d: 0x20, - 0x13e: 0x18, - 0x13f: 0x88, - 0x140: 0xff, + 0x134: 0xF6, + 0x135: 0xF1, + 0x136: 0xC3, + 0x137: 0x0F, + 0x138: 0x1F, + 0x139: 0x0B, + 0x13A: 0x0B, + 0x13B: 0x46, + 0x13C: 0x00, + 0x13D: 0x20, + 0x13E: 0x18, + 0x13F: 0x88, + 0x140: 0xFF, 0x141: 0x76, 0x142: 0x00, 0x143: 0x00, - 0x144: 0xf5, - 0x145: 0xf0, - 0x146: 0xc3, - 0x147: 0x0f, - 0x148: 0x1f, - 0x149: 0x0d, - 0x14a: 0x0d, - 0x14b: 0x46, - 0x14c: 0x00, - 0x14d: 0x20, - 0x14e: 0x1a, - 0x14f: 0x88, - 0x150: 0xff, + 0x144: 0xF5, + 0x145: 0xF0, + 0x146: 0xC3, + 0x147: 0x0F, + 0x148: 0x1F, + 0x149: 0x0D, + 0x14A: 0x0D, + 0x14B: 0x46, + 0x14C: 0x00, + 0x14D: 0x20, + 0x14E: 0x1A, + 0x14F: 0x88, + 0x150: 0xFF, 0x151: 0x76, 0x152: 0x00, 0x153: 0x00, - 0x154: 0xf5, - 0x155: 0xf0, - 0x156: 0xc3, - 0x157: 0x0f, - 0x158: 0x1f, - 0x159: 0x0d, - 0x15a: 0x0d, - 0x15b: 0x46, - 0x15c: 0x00, - 0x15d: 0x20, - 0x15e: 0x1a, - 0x15f: 0x88, + 0x154: 0xF5, + 0x155: 0xF0, + 0x156: 0xC3, + 0x157: 0x0F, + 0x158: 0x1F, + 0x159: 0x0D, + 0x15A: 0x0D, + 0x15B: 0x46, + 0x15C: 0x00, + 0x15D: 0x20, + 0x15E: 0x1A, + 0x15F: 0x88, 0x160: 0x00, 0x161: 0x00, - 0x162: 0x0a, - 0x163: 0x3d, + 0x162: 0x0A, + 0x163: 0x3D, 0x164: 0x05, 0x165: 0x00, 0x166: 0x28, - 0x167: 0xf6, + 0x167: 0xF6, 0x168: 0x00, 0x169: 0x00, - 0x16a: 0x0f, - 0x16b: 0xff, - 0x16c: 0x00, - 0x16d: 0x00, - 0x16e: 0x00, - 0x16f: 0x00, + 0x16A: 0x0F, + 0x16B: 0xFF, + 0x16C: 0x00, + 0x16D: 0x00, + 0x16E: 0x00, + 0x16F: 0x00, 0x170: 0x00, 0x171: 0x00, 0x180: 0x12, @@ -235,22 +246,22 @@ def write_fifo(self, values): pass 0x207: 0x00, 0x208: 0x00, 0x209: 0x00, - 0x20a: 0x00, - 0x20b: 0x00, + 0x20A: 0x00, + 0x20B: 0x00, 0x210: 0x00, 0x211: 0x00, 0x212: 0x00, 0x213: 0x00, 0x214: 0x00, 0x215: 0x00, - 0x216: 0x1f, + 0x216: 0x1F, 0x218: 0x00, 0x219: 0x00, - 0x21c: 0x00, - 0x21d: 0x00, - 0x21e: 0x0f, + 0x21C: 0x00, + 0x21D: 0x00, + 0x21E: 0x0F, 0x220: 0x32, - 0x221: 0x0a, + 0x221: 0x0A, 0x223: 0x32, 0x224: 0x14, 0x225: 0x73, @@ -258,26 +269,26 @@ def write_fifo(self, values): pass 0x227: 0x00, 0x228: 0x00, 0x229: 0x00, - 0x22a: 0x00, - 0x22b: 0x00, - 0x22c: 0x00, - 0x22d: 0x00, - 0x22e: 0x00, - 0x22f: 0x00, + 0x22A: 0x00, + 0x22B: 0x00, + 0x22C: 0x00, + 0x22D: 0x00, + 0x22E: 0x00, + 0x22F: 0x00, 0x230: 0x00, 0x231: 0x00, 0x232: 0x00, 0x233: 0x00, 0x300: 0x00, - 0x301: 0x3f, + 0x301: 0x3F, 0x308: 0x00, 0x309: 0x00, 0x310: 0x00, 0x311: 0x00, 0x312: 0x20, - 0x313: 0xc4, + 0x313: 0xC4, 0x314: 0x61, - 0x315: 0xa8, + 0x315: 0xA8, 0x316: 0x00, 0x317: 0x00, 0x318: 0x00, @@ -285,25 +296,25 @@ def write_fifo(self, values): pass 0x330: 0x00, 0x331: 0x00, 0x332: 0x00, - 0xf00: 0x00, - 0xf08: 0x04, - 0xf0c: 0x00, - 0xf0d: 0x04, - 0xf10: 0x04, - 0xf11: 0x80, - 0xf18: 0x06, - 0xf1c: 0x04, - 0xf21: 0x20, - 0xf22: 0x54, - 0xf23: 0x50, - 0xf26: 0x88, - 0xf30: 0x3f, - 0xf31: 0xf0, - 0xf32: 0x3f, - 0xf33: 0xf0, - 0xf34: 0x0f, - 0xf35: 0x10, - 0xf44: 0x25, - 0xf5f: 0xe7, - 0xf72: 0x00 + 0xF00: 0x00, + 0xF08: 0x04, + 0xF0C: 0x00, + 0xF0D: 0x04, + 0xF10: 0x04, + 0xF11: 0x80, + 0xF18: 0x06, + 0xF1C: 0x04, + 0xF21: 0x20, + 0xF22: 0x54, + 0xF23: 0x50, + 0xF26: 0x88, + 0xF30: 0x3F, + 0xF31: 0xF0, + 0xF32: 0x3F, + 0xF33: 0xF0, + 0xF34: 0x0F, + 0xF35: 0x10, + 0xF44: 0x25, + 0xF5F: 0xE7, + 0xF72: 0x00, } diff --git a/communications/ax5043_manager/post.py b/communications/ax5043_manager/post.py index cdf1c651..f5e74e45 100644 --- a/communications/ax5043_manager/post.py +++ b/communications/ax5043_manager/post.py @@ -1,5 +1,4 @@ import logging -import time import board import busio from adafruit_bus_device.spi_device import SPIDevice diff --git a/communications/ax5043_manager/test_ax5043_driver.py b/communications/ax5043_manager/test_ax5043_driver.py index 1aef75c5..d4bd3a97 100644 --- a/communications/ax5043_manager/test_ax5043_driver.py +++ b/communications/ax5043_manager/test_ax5043_driver.py @@ -1,12 +1,30 @@ import unittest from communications.ax5043_manager.ax5043_driver import * +from communications.ax5043_manager.mock_ax5043_driver import MockAx5043, rst_values + class TestChunk(unittest.TestCase): def test_from_bytes(self): - buf = bytearray([0x55, 0x80, 0x80, - 0xE1, 0x05, 0x03, 0xCA, 0xFE, 0xBA, 0xBE, - 0x31, 0xBD, - 0x73, 0x00, 0x00, 0x00]) + buf = bytearray( + [ + 0x55, + 0x80, + 0x80, + 0xE1, + 0x05, + 0x03, + 0xCA, + 0xFE, + 0xBA, + 0xBE, + 0x31, + 0xBD, + 0x73, + 0x00, + 0x00, + 0x00, + ] + ) (c, buf) = Chunk.from_bytes(buf) self.assertTrue(isinstance(c, Antrssi2Chunk)) @@ -26,6 +44,12 @@ def test_from_bytes(self): self.assertTrue(isinstance(c, RffreqoffsChunk)) self.assertEqual(c.rffreqoffs, 0) + def test_reg_dump(self): + mock_radio = MockAx5043() + reg_dict = mock_radio.reg_dump() + fixed_rst_vals = {reg.name: rst_values[reg.value] for reg in Reg} + self.assertCountEqual(reg_dict, fixed_rst_vals) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/communications/ax5043_manager/test_ax5043_manager.py b/communications/ax5043_manager/test_ax5043_manager.py index 88a79521..2ba7d0fd 100644 --- a/communications/ax5043_manager/test_ax5043_manager.py +++ b/communications/ax5043_manager/test_ax5043_manager.py @@ -1,8 +1,15 @@ import unittest import logging -from communications.ax5043_manager.ax5043_manager import Manager +from communications.ax5043_manager.ax5043_manager import ( + Initializing, + Manager, + Autoranging, + Idle, + Transmitting, + Receiving, +) from communications.ax5043_manager.mock_ax5043_driver import MockAx5043 -from communications.ax5043_manager.ax5043_driver import Reg, Bits +from communications.ax5043_manager.ax5043_regs import Reg, Bits class TestManager(unittest.TestCase): @@ -10,7 +17,7 @@ def test_autorange_slow_xtal(self): mock = MockAx5043() mgr = Manager(mock) mgr.dispatch() - self.assertTrue(isinstance(mgr.state, Manager.Autoranging)) + self.assertIsInstance(mgr.state, Autoranging) mock.read_queue[Reg.XTALSTATUS] = Bits.XTAL_RUN mgr.dispatch() self.assertFalse(mgr.is_faulted()) @@ -20,18 +27,21 @@ def test_tx(self): mock.read_defaults[Reg.XTALSTATUS] = Bits.XTAL_RUN mock.read_defaults[Reg.POWSTAT] |= Bits.SVMODEM mgr = Manager(mock) + self.assertIsInstance(mgr.state, Initializing) # Transition to autoranging mgr.dispatch() + self.assertIsInstance(mgr.state, Autoranging) # Transition to idle mgr.dispatch() # Stay in idle when tx_enabled=False mgr.inbox.put(bytearray([0xCA, 0xFE, 0xBA, 0xBE])) mgr.dispatch() - self.assertTrue(isinstance(mgr.state, Manager.Idle)) + self.assertIsInstance(mgr.state, Idle) # Transition to transmitting mgr.tx_enabled = True mgr.dispatch() - self.assertTrue(isinstance(mgr.state, Manager.Transmitting)) + self.assertIsInstance(mgr.state, Transmitting) + mgr.dispatch() self.assertFalse(mgr.is_faulted()) @@ -47,11 +57,11 @@ def test_rx(self): # Transition to receiving mgr.rx_enabled = True mgr.dispatch() - self.assertTrue(isinstance(mgr.state, Manager.Receiving)) + self.assertTrue(isinstance(mgr.state, Receiving)) mgr.dispatch() self.assertFalse(mgr.is_faulted()) -if __name__ == '__main__': +if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) unittest.main() diff --git a/communications/command_definitions.py b/communications/command_definitions.py index e9d60490..86d85307 100644 --- a/communications/command_definitions.py +++ b/communications/command_definitions.py @@ -945,6 +945,22 @@ def _method( logging.warning("Can't talk to Gom P31u") +class ax5043_reg_dump(Command): + id = consts.CommandEnum.RegDump + uplink_args = [] + downlink_telem = [] + + def _method(self, parent: MainSatelliteThread, **kwargs) -> None: + registers = parent.radio.driver.reg_dump() + # unpack and stack: + output_string = "" + + for reg_name, reg_value in registers.items(): + output_string += f"{reg_name}:\t{hex(reg_value)}\n" + + logging.info(f"AX5043 Register dump:\n{output_string}") + + COMMAND_LIST = [ get_gom_conf2(), get_gom_conf1(), @@ -999,6 +1015,7 @@ def _method( CommandSwitch(), AttitudeAdjustmentSwitch(), run_opnav(), + ax5043_reg_dump(), ] diff --git a/communications/satellite_radio.py b/communications/satellite_radio.py index f0e85553..49de4410 100644 --- a/communications/satellite_radio.py +++ b/communications/satellite_radio.py @@ -1,33 +1,37 @@ import logging import time +from typing import Optional from adafruit_blinka.agnostic import board_id from utils.constants import ZERO_WORD, ONE_WORD -if board_id and board_id != 'GENERIC_LINUX_PC': +if board_id and board_id != "GENERIC_LINUX_PC": import board import busio - from adafruit_bus_device.spi_device import SPIDevice + +from adafruit_bus_device.spi_device import SPIDevice from communications.ax5043_manager.ax5043_driver import Ax5043 from communications.ax5043_manager.ax5043_manager import Manager from bitstring import BitArray -from time import time +from time import time, sleep -class Radio: +class Radio: def __init__(self): - self.driver = Ax5043(SPIDevice(busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO))) + self.driver = Ax5043( + SPIDevice(busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)) + ) self.mgr = Manager(self.driver) self.last_transmit_time = time() # Monitor radio health, request reset if faulted def monitorHealth(self): if self.mgr.is_faulted(): - logging.error('Radio manager faulted') + logging.error("Radio manager faulted") self.mgr.reset_requested = True return None @@ -49,7 +53,7 @@ def transmit(self, signal: bytes): self.mgr.tx_enabled = True inflatedSignal = self.bit_inflation(signal, ZERO_WORD, ONE_WORD) - print('Inflated Bytes: ' + str(inflatedSignal)) + print("Inflated Bytes: " + str(inflatedSignal)) self.mgr.inbox.put(inflatedSignal) cycles = 0 @@ -63,8 +67,9 @@ def transmit(self, signal: bytes): # After 5s, break for clean shutdown # (TODO: use interrupt handler to ensure clean shutdown when killed, # or monitor transmitting state and exit when complete) - if cycles >= 10: break - time.sleep(1) + if cycles >= 10: + break + sleep(1) self.mgr.tx_enabled = False self.mgr.rx_enabled = False @@ -72,18 +77,27 @@ def transmit(self, signal: bytes): self.last_transmit_time = time() - def bit_inflation(self, downlink: bytes, zero_word: bytes, one_word: bytes) -> bytearray: + def bit_inflation( + self, downlink: bytes, zero_word: bytes, one_word: bytes + ) -> bytearray: # Convert bytes to bits downlinkBitString = BitArray(bytes=downlink).bin - inflatedByteArray = bytearray('', encoding='utf-8') + inflatedByteArray = bytearray("", encoding="utf-8") # Add two bytes for every bit corresponding to the appropriate word for bit in downlinkBitString: - if bit == '0': + if bit == "0": inflatedByteArray += zero_word else: inflatedByteArray += one_word return inflatedByteArray + + +class MockRadio(Radio): + def __init__(self) -> None: + self.manager: Optional[Manager] = None + self.driver: Optional[Ax5043] = None + self.last_transmit_time = time() diff --git a/pyrightconfig.json b/pyrightconfig.json index 84bf28f3..78bb560c 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -10,8 +10,8 @@ "OpticalNavigation/core/camera.py", "OpticalNavigation/core/const.py", "OpticalNavigation/core/preprocess.py", - "communications/ax5043_manager", "communications/comms_driver.py", + "communications/ax5043_manager/test_ax5043_driver.py", "HITL-tests/comms_hardware_transmit.py", "HITL-tests/comms_security_test.py", "HITL-tests/file_transmitter.py", @@ -27,7 +27,9 @@ "OpticalNavigation/core/find_with_blobs.py", "OpticalNavigation/core/find_with_kmeans.py" ], - "extraPaths": ["OpticalNavigation/"], + "extraPaths": [ + "OpticalNavigation/" + ], "pythonVersion": "3.7", "pythonPlatform": "Linux", "reportUnboundVariable": "warning", diff --git a/tests/command_test.py b/tests/command_test.py index c70b2e2b..d031cdd0 100644 --- a/tests/command_test.py +++ b/tests/command_test.py @@ -3,6 +3,8 @@ from main import MainSatelliteThread from communications.command_handler import CommandHandler import utils.parameters as params +from communications.ax5043_manager.mock_ax5043_driver import MockAx5043 +from communications.satellite_radio import MockRadio class CommandTest(unittest.TestCase): @@ -58,6 +60,13 @@ def test_manual_fm_commands(self): self.sat.flight_mode.flight_mode_id, mode.value ) # verify flight mode + def test_radio_register_dump(self): + self.sat.radio = MockRadio() + self.sat.radio.driver = MockAx5043() + radio_command = self.ground_station.pack_command(CommandEnum.RegDump) + self.sat.command_queue.put(radio_command) + self.sat.execute_commands() # satellite executes commands + if __name__ == "__main__": unittest.main() diff --git a/utils/constants.py b/utils/constants.py index 7d493742..d088554f 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -18,7 +18,7 @@ SURRENDER_LOCAL_DIR = cast("str", config.get("SURRENDER_LOCAL_DIR")) -FOR_FLIGHT = config["FOR_FLIGHT"] == "1" +FOR_FLIGHT = config.get("FOR_FLIGHT", "NO") == "1" LOG = config["LOG"] == "1" # SQL Stuff @@ -363,6 +363,7 @@ class CommandEnum(IntEnum): SeparationTest = 55 LongString = 56 + RegDump = 57 IgnoreLowBatt = 60 CeaseComms = 170