diff --git a/software/glasgow/applet/all.py b/software/glasgow/applet/all.py index 928f25de4..98debc136 100644 --- a/software/glasgow/applet/all.py +++ b/software/glasgow/applet/all.py @@ -50,3 +50,5 @@ from .video.ws2812_output import VideoWS2812OutputApplet from .radio.nrf24l01 import RadioNRF24L01Applet +from .radio.sx1272 import RadioSX1272Applet +from .radio.lora import LoRaWANApplet diff --git a/software/glasgow/applet/radio/lora/__init__.py b/software/glasgow/applet/radio/lora/__init__.py new file mode 100644 index 000000000..f1b4bf742 --- /dev/null +++ b/software/glasgow/applet/radio/lora/__init__.py @@ -0,0 +1,190 @@ +import math +import asyncio +import logging +import argparse +from time import sleep +from nmigen.compat import * + +from ....support.logging import * +from ....support.endpoint import * +from ....support.bits import * +from ....arch.sx1272 import regs_common +from ....arch.sx1272 import regs_xxk +from ....arch.sx1272 import regs_lora +from ....arch.sx1272.apis import * +from ....protocol.lora import * +from ...interface.spi_master import SPIMasterSubtarget, SPIMasterInterface +from ... import * +from ..sx1272 import RadioSX1272Interface + + +class LoRaWANApplet(GlasgowApplet, name="radio-lorawan"): + logger = logging.getLogger(__name__) + help = "operate as a LoRaWAN node or gateway" + description = """ + Set up a LoRaWAN node or gateway using SX1272 PHY + + The gateway uses the Semtech UDP forwarder protocol. Uplink and downlink + operations are multiplexed using the same PHY. The node should join the + network before transmitting any data + + RF parameters are set by specifying the region and channel. + """ + + __pins = ("ss", "sck", "mosi", "miso") + + @classmethod + def add_build_arguments(cls, parser, access): + access.add_build_arguments(parser) + + access.add_pin_argument(parser, "ss", default=True) + access.add_pin_argument(parser, "mosi", default=True) + access.add_pin_argument(parser, "miso", default=True) + access.add_pin_argument(parser, "sck", default=True) + + parser.add_argument( + "-f", "--frequency", metavar="FREQ", type=int, default=500, + help="set SPI frequency to FREQ kHz (default: %(default)s)") + + def build(self, target, args): + self.mux_interface = iface = target.multiplexer.claim_interface(self, args) + pads = iface.get_pads(args, pins=self.__pins) + iface.add_subtarget(SPIMasterSubtarget( + pads=pads, + out_fifo=iface.get_out_fifo(), + in_fifo=iface.get_in_fifo(), + period_cyc=math.ceil(target.sys_clk_freq / (args.frequency * 1000)), + delay_cyc=math.ceil(target.sys_clk_freq / 1e6), + sck_idle=0, + sck_edge="rising", + ss_active=0, + )) + + async def run(self, device, args): + iface = await device.demultiplexer.claim_interface(self, self.mux_interface, args) + spi_iface = SPIMasterInterface(iface, self.logger) + sx1272_iface = RadioSX1272Interface(spi_iface, self.logger) + + return sx1272_iface + + @classmethod + def add_interact_arguments(cls, parser): + p_role = parser.add_subparsers(dest="role", metavar="ROLE", required=True) + p_node = p_role.add_parser("node", help="LoRaWAN Node") + p_gateway = p_role.add_parser("gateway", help="LoRaWAN Gateway (Semtech UDP forwarder)") + ServerEndpoint.add_argument(p_node, "endpoint", default='tcp:localhost:9999') + + def eui(value): + eui = int(value, 16) + return eui + + def port(value): + value = int(value, 10) + if value not in range(1, 224): + raise argparse.ArgumentTypeError( + "invalid port: {} (choose from 1..223)".format(value)) + return value + + def add_common_args(p): + p.add_argument( + "--region", metavar="REGION", type=str, default="EU863870", + help="set the LoRaWAN region" + ) + p.add_argument( + "--chn", metavar="CHN", type=int, default=0, + help="set the region channel to use" + ) + p.add_argument( + "--data-rate", metavar="DATR", type=int, default=0, + choices=(range(0, 6)), + help="set the datarate" + ) + + add_common_args(p_node) + add_common_args(p_gateway) + + p_node.add_argument( + "--dev-eui", metavar="DEVEUI", type=eui, required=True, + help="set the device EUI" + ) + p_node.add_argument( + "--app-eui", metavar="APPEUI", type=eui, required=True, + help="set the application EUI" + ) + p_node.add_argument( + "-K", "--app-key", metavar="APPKEY", type=eui, required=True, + help="set the application key" + ) + p_node.add_argument( + "-P", "--tx-port", metavar="TXPORT", type=port, default=1, + help="Set the transmission port" + ) + p_node.add_argument( + "-C", "--tx-confirmed", default=False, action="store_true", + help="Set to true if transmissions are confirmed" + ) + + p_gateway.add_argument( + "--gw-eui", metavar="GWEUI", type=eui, required=True, + help="set the gateway EUI" + ) + p_gateway.add_argument( + "-S", "--gw-server", metavar="GWSRV", type=str, default="router.eu.thethings.network", + help="set the gateway server" + ) + p_gateway.add_argument( + "-P", "--gw-port", metavar="GWPORT", type=int, default=1700, + help="set the gateway server port" + ) + p_gateway.add_argument( + "--downlink-only", default=False, action="store_true", + help="retransmit application packets, do not listen to devices" + ) + p_gateway.add_argument( + "--uplink-only", default=False, action="store_true", + help="retransmit nodes packets, do not listen to gateway" + ) + + async def _interact_socket(self, args, dev): + endpoint = await ServerEndpoint("socket", self.logger, args.endpoint) + while True: + try: + data = await asyncio.shield(endpoint.recv()) + self.logger.info("Sending payload {}".format(data)) + await dev.transmit(args.tx_port, data, args.tx_confirmed) + except asyncio.CancelledError: + pass + + def node_frame_cb(self, fport, fpl): + self.logger.info("Received payload {} from port {}".format(fpl, fport)) + + async def interact(self, device, args, sx1272_iface): + region_params = { + "EU863870": EU863870_PARAMETERS + }[args.region] + sx1272 = SX1272_LoRa_Device_API(sx1272_iface, self.logger) + + if args.role == "node": + dev = node = LoRaWAN_Node(sx1272, region_params, args.app_key, args.dev_eui, args.app_eui, self.logger, self.node_frame_cb) + elif args.role == "gateway": + dev = gw = LoRaWAN_Gateway(sx1272, region_params, args.gw_server, args.gw_port, args.gw_eui, args.downlink_only, args.uplink_only, self.logger) + + await dev.configure_by_channel(args.chn, args.data_rate) + + if args.role == "node": + joined = await node.join_network() + if not joined: + self.logger.error("Error joinning network") + else: + self.logger.info("Joined network") + await node.configure_by_channel(args.chn, args.data_rate) + await self._interact_socket(args, node) + elif args.role == "gateway": + await gw.main() + +# ------------------------------------------------------------------------------------------------- + +class LoRaWANAppletTestCase(GlasgowAppletTestCase, applet=LoRaWANApplet): + @synthesis_test + def test_build(self): + self.assertBuilds() diff --git a/software/glasgow/applet/radio/sx1272/__init__.py b/software/glasgow/applet/radio/sx1272/__init__.py new file mode 100644 index 000000000..24b40a0fd --- /dev/null +++ b/software/glasgow/applet/radio/sx1272/__init__.py @@ -0,0 +1,188 @@ +# Reference: https://semtech.my.salesforce.com/sfc/p/#E0000000JelG/a/440000001NCE/v_VBhk1IolDgxwwnOpcS_vTFxPfSEPQbuneK3mWsXlU +# Accession: G00051 + +import math +import asyncio +import logging +import argparse +from time import sleep +from nmigen.compat import * + +from ....support.logging import * +from ....support.bits import * +from ....support.endpoint import * +from ....arch.sx1272 import regs_common +from ....arch.sx1272 import regs_xxk +from ....arch.sx1272 import regs_lora +from ....arch.sx1272.apis import * +from ....protocol.lora import * +from ...interface.spi_master import SPIMasterSubtarget, SPIMasterInterface +from ... import * + + +class RadioSX1272Interface: + def __init__(self, interface, logger): + self.lower = interface + self._logger = logger + self._level = logging.DEBUG if self._logger.name == __name__ else logging.TRACE + + def _log(self, message, *args): + self._logger.log(self._level, message, *args) + + async def read_register_wide(self, address, length): + assert address in range(0x70 + 1) + await self.lower.write([regs_common.OP_R_REGISTER|address], hold_ss=True) + value = await self.lower.read(length) + self._log("read register [%02x]=<%s>", address, dump_hex(value)) + return value + + async def write_register_wide(self, address, value): + assert address in range(0x70 + 1) + self._log("write register [%02x]=<%s>", address, dump_hex(value)) + await self.lower.write([regs_common.OP_W_REGISTER|address, *value]) + + async def read_register(self, address): + value, = await self.read_register_wide(address, 1) + return value + + async def write_register(self, address, value): + await self.write_register_wide(address, [value]) + + +class RadioSX1272Applet(GlasgowApplet, name="radio-sx1272"): + logger = logging.getLogger(__name__) + help = "transmit and receive using SX1272 RF PHY" + description = """ + Transmit and receive packets using the SX1272 RF PHY. + + This applet allows setting only LoRa modulation parameters, FSK/OOK + modem is not implemented in high level APIs. Transmit and receive + operations are implemented. The monitor command will listen and dump all + messages received while running. + + For LoRaWAN operation, see radio-lorawan applet + """ + + __pins = ("ss", "sck", "mosi", "miso") + + @classmethod + def add_build_arguments(cls, parser, access): + access.add_build_arguments(parser) + + access.add_pin_argument(parser, "ss", default=True) + access.add_pin_argument(parser, "mosi", default=True) + access.add_pin_argument(parser, "miso", default=True) + access.add_pin_argument(parser, "sck", default=True) + + parser.add_argument( + "--spi-freq", metavar="FREQ", type=int, default=500, + help="set SPI frequency to FREQ kHz (default: %(default)s)") + + def build(self, target, args): + self.mux_interface = iface = target.multiplexer.claim_interface(self, args) + pads = iface.get_pads(args, pins=self.__pins) + iface.add_subtarget(SPIMasterSubtarget( + pads=pads, + out_fifo=iface.get_out_fifo(), + in_fifo=iface.get_in_fifo(), + period_cyc=math.ceil(target.sys_clk_freq / (args.spi_freq * 1000)), + delay_cyc=math.ceil(target.sys_clk_freq / 1e6), + sck_idle=0, + sck_edge="rising", + ss_active=0, + )) + + async def run(self, device, args): + iface = await device.demultiplexer.claim_interface(self, self.mux_interface, args) + spi_iface = SPIMasterInterface(iface, self.logger) + sx1272_iface = RadioSX1272Interface(spi_iface, self.logger) + + return sx1272_iface + + @classmethod + def add_interact_arguments(cls, parser): + p_operation = parser.add_subparsers(dest="operation", metavar="OP", required=True) + p_receive = p_operation.add_parser("receive", help="receive packets") + p_monitor = p_operation.add_parser("monitor", help="monitor packets") + p_transmit = p_operation.add_parser("transmit", help="transmit packets") + p_socket = p_operation.add_parser("socket", help="connect tx to a socket") + ServerEndpoint.add_argument(p_socket, "endpoint", default='tcp:localhost:9999') + + def payload(value): + pl = int(value, 16) + pl = pl.to_bytes(math.ceil(pl.bit_length()/8), 'little') + return pl + + def freq(value): + f = float(value) + return f + + parser.add_argument( + "-f", "--freq", metavar="FREQ", type=freq, required=True, + help="set the transmission frequency in MHz" + ) + parser.add_argument( + "--bw", metavar="BW", type=int, default=125, + choices=(125, 250, 500), + help="set the bandwidth in kHz" + ) + parser.add_argument( + "--crate", metavar="CODR", type=int, default=5, + choices=(5, 6, 7, 8), + help="set the coding rate (in 4/x)" + ) + parser.add_argument( + "--sf", metavar="SF", type=int, default=12, + choices=(6, 7, 8, 9, 10, 11, 12), + help="set the spreading factor" + ) + + p_transmit.add_argument( + "--pwr", metavar="PWR", type=int, default=13, + choices=range(1, 14), + help="set the transmit power in dBm" + ) + p_transmit.add_argument( + "--payload", metavar="PAYLOAD", type=payload, required=True, + help="set the payload to be sent" + ) + + async def _interact_socket(self, dev, endpoint): + endpoint = await ServerEndpoint("socket", self.logger, endpoint) + while True: + try: + data = await asyncio.shield(endpoint.recv()) + await dev.transmit(data) + except asyncio.CancelledError: + pass + + async def interact(self, device, args, sx1272_iface): + dev = SX1272_LoRa_Device_API(sx1272_iface, self.logger) + + if args.operation != "transmit": + args.pwr = 13 + + await dev.configure(args.freq*1e6, args.bw*1e3, args.sf, args.pwr, args.crate) + + if args.operation == "transmit": + await dev.transmit(args.payload) + self.logger.info("Packet sent") + if args.operation == "receive": + data, _, snr, rssi, codr = await dev.receive() + if data != None: + self.logger.info("Received packet: {} with SNR = {}, RSSI = {}, coding rate = {}".format(data, snr, rssi, codr)) + else: + self.logger.error("No packet received") + if args.operation == "monitor": + def onpayload(data, crcerr, snr, rssi, codr): + self.logger.info("Received packet: {} with SNR = {}, RSSI = {}, coding rate = {}".format(data, snr, rssi, codr)) + await dev.listen(onpayload) + if args.operation == "socket": + await self._interact_socket(dev, args.endpoint) + +# ------------------------------------------------------------------------------------------------- + +class RadioSX1272AppletTestCase(GlasgowAppletTestCase, applet=RadioSX1272Applet): + @synthesis_test + def test_build(self): + self.assertBuilds() diff --git a/software/glasgow/arch/sx1272/__init__.py b/software/glasgow/arch/sx1272/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/software/glasgow/arch/sx1272/apis.py b/software/glasgow/arch/sx1272/apis.py new file mode 100644 index 000000000..8e052c2c5 --- /dev/null +++ b/software/glasgow/arch/sx1272/apis.py @@ -0,0 +1,1266 @@ +import logging +import math +import asyncio + +from . import regs_common +from . import regs_xxk +from . import regs_lora + +__all__ = [ + "RadioSX1272APIXXK", "RadioSX1272APILoRa" +] + +class RadioSX1272APICommon: + def __init__(self, interface, logger): + self.lower = interface + self._logger = logger + self._level = logging.DEBUG if self._logger.name == __name__ else logging.TRACE + + def _log(self, message, *args): + self._logger.log(self._level, "SX1272 API: " + message, *args) + + async def _get_register(self, cls, addr): + reg = await self.lower.read_register(addr) + return cls.from_int(reg) + + async def _set_register(self, cls, addr, reg): + assert isinstance(reg, cls) + await self.lower.write_register(addr, reg.to_int()) + + async def get_fifo(self, size): + return await self.lower.read_register_wide(regs_common.ADDR_FIFO, size) + + async def set_fifo(self, data): + await self.lower.write_register_wide(regs_common.ADDR_FIFO, data) + + async def get_frf(self): + frf = await self.lower.read_register_wide(regs_common.ADDR_F_RF_MSB, 3) + frf = int.from_bytes(frf, byteorder='big') + return frf + + async def set_frf(self, frf): + assert frf <= 2**24-1 + return await self.lower.write_register_wide(regs_common.ADDR_F_RF_MSB, [frf >> 16, (frf >> 8) & 0xFF, frf & 0xFF]) + + async def get_pa_config(self): + return await self._get_register(regs_common.REG_PA_CONFIG, regs_common.ADDR_PA_CONFIG) + + async def set_pa_config(self, paconfig): + await self._set_register(regs_common.REG_PA_CONFIG, regs_common.ADDR_PA_CONFIG, paconfig) + + async def set_pa_config_outpower(self, outpower): + reg = await self.get_pa_config() + reg.OUT_POWER = outpower + + async def get_pa_ramp(self): + return await self._get_register(regs_common.REG_PA_RAMP, regs_common.ADDR_PA_RAMP) + + async def set_pa_ramp(self, paramp): + await self._set_register(regs_common.REG_PA_RAMP, regs_common.ADDR_PA_RAMP, paramp) + + async def set_pa_ramp_parmap(self, paramp): + assert isinstance(paramp, regs_common.PARAMP) + reg = await self.get_pa_ramp() + reg.PA_RAMP = paramp + await self.set_pa_ramp(reg) + + async def set_pa_ramp_pll(self, pll): + assert isinstance(pll, regs_common.LOWPLLTX) + reg = await self.get_pa_ramp() + reg.LOW_PN_TX_PLL_OFF = pll + await self.set_pa_ramp(reg) + + async def get_ocp(self): + return await self._get_register(regs_common.REG_OCP, regs_common.ADDR_OCP) + + async def set_ocp(self, ocp): + await self._set_register(regs_common.REG_OCP, regs_common.ADDR_OCP, ocp) + + async def set_ocp_on(self, onoff): + reg = await self.get_ocp() + reg.OCP_ON = onoff + await self.set_ocp(reg) + + async def set_ocp_trim(self, trim): + reg = await self.get_ocp() + reg.OCP_TRIM = trim + await self.set_ocp(reg) + + async def get_lna(self): + return await self._get_register(regs_common.REG_LNA, regs_common.ADDR_LNA) + + async def set_lna(self, lna): + await self._set_register(regs_common.REG_LNA, regs_common.ADDR_LNA, lna) + + async def set_lna_boost(self, boost): + assert isinstance(boost, regs_common.LNABOOST) + reg = await self.get_lna() + reg.LNA_BOOST = boost + self.set_lna(reg) + + async def set_lna_gain(self, gain): + assert isinstance(gain, regs_common.LNAGAIN) + reg = await self.get_lna() + reg.LNA_GAIN = gain + self.set_lna(reg) + + async def get_dio_mapping_1(self): + return await self._get_register(regs_common.REG_DIO_MAPPING_1, regs_common.ADDR_DIO_MAPPING_1) + + async def set_dio_mapping_1(self, mapping): + await self._set_register(regs_common.REG_DIO_MAPPING_1, regs_common.ADDR_DIO_MAPPING_1, mapping) + + async def get_dio_mapping_2(self): + return await self._get_register(regs_common.REG_DIO_MAPPING_2, regs_common.ADDR_DIO_MAPPING_2) + + async def set_dio_mapping_2(self, mapping): + await self._set_register(regs_common.REG_DIO_MAPPING_2, regs_common.ADDR_DIO_MAPPING_2, mapping) + + async def set_dio_mapping_2_preambledetect(self, pdetect): + assert isinstance(pdetect, regs_common.MAPPREAMBLEDETECT) + reg = await self.get_dio_mapping_2() + reg.MAP_PREAMBLE_DETECT = pdetect + self.set_dio_mapping_2(reg) + + async def get_version(self): + return await self._get_register(regs_common.REG_VERSION, regs_common.ADDR_VERSION) + + async def get_agc_ref(self): + return await self._get_register(regs_common.REG_AGC_REF, regs_common.ADDR_AGC_REF) + + async def set_agc_ref_level(self, agcreflevel): + reg = await self.get_agc_ref() + reg.AGC_REF_LEVEL = agcreflevel + await self._set_register(regs_common.REG_AGC_REF, regs_common.ADDR_AGC_REF, reg) + + async def get_agc_thres_1(self): + return await self._get_register(regs_common.REG_AGC_THRESH_1, regs_common.ADDR_AGC_THRESH_1) + + async def set_agc_step_1(self, step): + reg = await self.get_agc_thres_1() + reg.AGC_STEP_1 = step + await self._set_register(regs_common.REG_AGC_THRESH_1, regs_common.ADDR_AGC_THRESH_1, reg) + + async def get_agc_thres_2(self): + return await self._get_register(regs_common.REG_AGC_THRESH_2, regs_common.ADDR_AGC_THRESH_2) + + async def set_agc_thres_2(self, thres): + await self._set_register(regs_common.REG_AGC_THRESH_2, regs_common.ADDR_AGC_THRESH_2, thres) + + async def set_agc_step_2(self, step): + reg = await self.get_agc_thres_2() + reg.AGC_STEP_2 = step + await self.set_agc_thres_2(reg) + + async def set_agc_step_3(self, step): + reg = await self.get_agc_thres_2() + reg.AGC_STEP_3 = step + await self.set_agc_thres_2(reg) + + async def get_agc_thres_3(self): + return await self._get_register(regs_common.REG_AGC_THRESH_3, regs_common.ADDR_AGC_THRESH_3) + + async def set_agc_thres_3(self, thres): + await self._set_register(regs_common.REG_AGC_THRESH_3, regs_common.ADDR_AGC_THRESH_3, thres) + + async def set_agc_step_4(self, step): + reg = await self.get_agc_thres_3() + reg.AGC_STEP_4 = step + await self.set_agc_thres_3(reg) + + async def set_agc_step_5(self, step): + reg = await self.get_agc_thres_3() + reg.AGC_STEP_5 = step + await self.set_agc_thres_3(reg) + + async def get_pll_hop(self): + return await self._get_register(regs_common.REG_PLL_HOP, regs_common.ADDR_PLL_HOP) + + async def set_pll_hop(self, pllhop): + await self._set_register(regs_common.REG_PLL_HOP, regs_common.ADDR_PLL_HOP, pllhop) + + async def set_pll_hop_dutycycle(self, dcycle): + reg = await self.get_pll_hop() + reg.PA_MANUAL_DUTY_CYCLE = dcycle + await self.set_pll_hop(reg) + + async def set_pll_hop_fasthop(self, fasthop): + assert isinstance(fasthop, regs_common.FASTHOP) + reg = await self.get_pll_hop() + reg.FAST_HOP_ON = fasthop + await self.set_pll_hop(reg) + + async def get_tcxo(self): + return await self._get_register(regs_common.REG_TCXO, regs_common.ADDR_TCXO) + + async def set_tcxo(self, tcxoon): + assert isinstance(tcxoon, regs_common.TCXOINPUT) + reg = regs_common.REG_TCXO() + reg.TCXO_INPUT_ON = tcxoon + return await self._set_register(regs_common.REG_TCXO, regs_common.ADDR_TCXO, reg) + + async def get_pa_dac(self): + return await self._get_register(regs_common.REG_PA_DAC, regs_common.ADDR_PA_DAC) + + async def set_pa_dac(self, padac): + assert isinstance(padac, regs_common.PADAC) + reg = regs_common.REG_PA_DAC + reg.PA_DAC = padac + await self._set_register(regs_common.REG_PA_DAC, regs_common.ADDR_PA_DAC, reg) + + async def get_pll(self): + reg = await self._get_register(regs_common.REG_PLL, regs_common.ADDR_PLL) + return reg.PLL_BANDWIDTH + + async def set_pll(self, pllbw): + assert isinstance(pllbw, regs_common.PLLBW) + reg = regs_common.REG_PLL + reg.PLL_BANDWIDTH = pllbw + await self._set_register(regs_common.REG_PLL, regs_common.ADDR_PLL, reg) + + async def get_pll_low_pn(self): + reg = await self._get_register(regs_common.REG_PLL_LOW_PN, regs_common.ADDR_PLL_LOW_PN) + return reg.PLL_BANDWIDTH + + async def set_pll_low_pn(self, pllbw): + assert isinstance(pllbw, regs_common.PLLBW) + reg = regs_common.REG_PLL_LOW_PN + reg.PLL_BANDWIDTH = pllbw + await self._set_register(regs_common.REG_PLL_LOW_PN, regs_common.ADDR_PLL_LOW_PN, reg) + + async def get_pa_manual(self): + reg = await self._get_register(regs_common.REG_PA_MANUAL, regs_common.ADDR_PA_MANUAL) + return reg.MANUAL_PA_CONTROL + + async def set_pa_manual(self, onoff): + reg = regs_common.REG_PA_MANUAL + reg.MANUAL_PA_CONTROL = onoff + await self._set_register(regs_common.REG_PA_MANUAL, regs_common.ADDR_PA_MANUAL, reg) + + async def get_former_temp(self): + return await self.lower.read_register(regs_common.ADDR_FORMER_TEMP) + + async def get_bitrate_frac(self): + reg = await self._get_register(regs_common.REG_BIT_RATE_FRAC, regs_common.ADDR_BIT_RATE_FRAC) + return reg.BIT_RATE_FRAC + + async def set_bit_rate_frac(self, frac): + reg = regs_common.REG_BIT_RATE_FRAC + reg.BIT_RATE_FRAC = frac + await self._set_register(regs_common.REG_BIT_RATE_FRAC, regs_common.ADDR_BIT_RATE_FRAC, reg) + + +class RadioSX1272APIXXK(RadioSX1272APICommon): + def __init__(self, interface, logger): + super(RadioSX1272APIXXK, self).__init__(interface, logger) + + async def get_opmode(self): + return await self._get_register(regs_xxk.REG_OP_MODE, regs_xxk.ADDR_OP_MODE) + + async def set_opmode(self, opmode): + await self._set_register(regs_xxk.REG_OP_MODE, regs_xxk.ADDR_OP_MODE, opmode) + + async def set_opmode_mode(self, mode): + assert isinstance(mode, regs_xxk.MODE) + reg = await self.get_opmode() + reg.MODE = mode + await self.set_opmode(reg) + + async def set_opmode_modshape(self, shape): + assert isinstance(shape, regs_xxk.MODULATIONSHAPINGFSK) or isinstance(shape, regs_xxk.MODULATIONSHAPINGOOK) + reg = await self.get_opmode() + reg.MODULATION_SHAPING = shape + await self.set_opmode(reg) + + async def set_opmode_modtype(self, modtype): + assert isinstance(modtype, regs_xxk.MODULATIONTYPE) + reg = await self.get_opmode() + reg.MODULATION_TYPE = modtype + await self.set_opmode(reg) + + async def set_opmode_lora(self, lrmode): + assert isinstance(lrmode, regs_xxk.LONGRANGEMODE) + reg = await self.get_opmode() + reg.LONG_RANGE_MODE = lrmode + await self.set_opmode(reg) + + async def get_bitrate(self): + bitrate = await self.lower.read_register_wide(regs_xxk.ADDR_BITRATE_MSB, 2) + bitrate = int.from_bytes(bitrate, byteorder='big') + return bitrate + + async def set_bitrate(self, bitrate): + assert bitrate <= 2**16-1 + await self.lower.write_register_wide(regs_xxk.ADDR_BITRATE_MSB, [bitrate >> 8, bitrate & 0xFF]) + + async def get_fdev(self): + fdev = await self.lower.read_register_wide(regs_xxk.ADDR_F_DEV_MSB, 2) + fdev = int.from_bytes(fdev, byteorder='big') + return fdev + + async def set_fdev(self, fdev): + assert fdev <= 2**14-1 + await self.lower.write_register_wide(regs_xxk.ADDR_F_DEV_MSB, [fdev >> 8, fdev & 0xFF], 2) + + async def get_rx_config(self): + return await self._get_register(regs_xxk.REG_RX_CONFIG, regs_xxk.ADDR_RX_CONFIG) + + async def set_rx_config(self, config): + await self._set_register(regs_xxk.REG_RX_CONFIG, regs_xxk.ADDR_RX_CONFIG, config) + + async def set_rx_config_restartrxoncollision(self, onoff): + reg = await self.get_rx_config() + reg.RESTART_RX_ON_COLLISION = onoff + await self.set_rx_config(reg) + + async def set_rx_config_restartrxwithoutplllock(self, onoff): + reg = await self.get_rx_config() + reg.RESTART_RX_WITHOUT_PLL_LOCK = onoff + await self.set_rx_config(reg) + + async def set_rx_config_restartrxwithplllock(self, onoff): + reg = await self.get_rx_config() + reg.RESTART_RX_WITH_PLL_LOCK = onoff + await self.set_rx_config(reg) + + async def set_rx_config_afcautoon(self, onoff): + reg = await self.get_rx_config() + reg.AFC_AUTO_ON = onoff + await self.set_rx_config(reg) + + async def set_rx_config_agcautoon(self, onoff): + reg = await self.get_rx_config() + reg.AGC_AUTO_ON = onoff + await self.set_rx_config(reg) + + async def set_rx_config_rxtrigger(self, trigger): + reg = await self.get_rx_config() + reg.RX_TRIGGER = trigger + await self.set_rx_config(reg) + + async def get_rssi_config(self): + return await self._get_register(regs_xxk.REG_RSSI_CONFIG, regs_xxk.ADDR_RSSI_CONFIG) + + async def set_rssi_config(self, config): + await self._set_register(regs_xxk.REG_RSSI_CONFIG, regs_xxk.ADDR_RSSI_CONFIG, config) + + async def set_rssi_config_offset(self, offset): + reg = await self.get_rssi_config() + reg.RSSI_OFFSET = offset + await self.set_rssi_config(reg) + + async def set_rssi_config_smoothing(self, smoothing): + reg = await self.get_rssi_config() + reg.RSSI_SMOOTHING = smoothing + await self.set_rssi_config(reg) + + async def get_rssi_collision_threshold(self): + return await self.lower.read_register(regs_xxk.ADDR_RSSI_COLLISION) + + async def set_rssi_collision_threshold(self, threshold): + assert threshold <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_RSSI_COLLISION, threshold) + + async def get_rssi_threshold(self): + return await self.lower.read_register(regs_xxk.ADDR_RSSI_THRESH) + + async def set_rssi_threshold(self, threshold): + assert threshold <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_RSSI_THRESH, threshold) + + async def get_rssi(self): + return await self.lower.read_register(regs_xxk.ADDR_RSSI_VALUE) + + async def get_rx_bw(self): + return await self._get_register(regs_xxk.REG_RX_BW, regs_xxk.ADDR_RX_BW) + + async def set_rx_bw(self, reg): + await self._set_register(regs_xxk.REG_RX_BW, regs_xxk.ADDR_RX_BW, reg) + + async def set_rx_bw_exp(self, value): + reg = await self.get_rx_bw() + reg.RX_BW_EXP = value + self.set_rx_bw(reg) + + async def set_rx_bw_mant(self, mant): + assert isinstance(mant, regs_xxk.RXBWMANT) + reg = await self.get_rx_bw() + reg.RX_BW_MANT = mant + self.set_rx_bw(reg) + + async def get_afc_bw(self): + return await self._get_register(regs_xxk.REG_AFC_BW, regs_xxk.ADDR_AFC_BW) + + async def set_afc_bw(self, reg): + await self._set_register(regs_xxk.REG_AFC_BW, regs_xxk.ADDR_AFC_BW, reg) + + async def set_afc_bw_exp(self, value): + reg = await self.get_afc_bw() + reg.RX_BW_EXP_AFC = value + self.set_afc_bw(reg) + + async def set_afc_bw_mant(self, mant): + assert isinstance(mant, regs_xxk.RXBWMANT) + reg = await self.get_afc_bw() + reg.RX_BW_MANT_AFC = mant + self.set_afc_bw(reg) + + async def get_ook_peak(self): + return await self._get_register(regs_xxk.REG_OOK_PEAK, regs_xxk.ADDR_OOK_PEAK) + + async def set_ook_peak(self, reg): + await self._set_register(regs_xxk.REG_OOK_PEAK, regs_xxk.ADDR_OOK_PEAK, reg) + + async def set_ook_peak_bitsync(self, onoff): + reg = await self.get_ook_peak() + reg.BIT_SYNC_ON = onoff + await self.set_ook_peak(reg) + + async def set_ook_peak_threshtype(self, thtype): + assert isinstance(thtype, regs_xxk.OOKTHRESHTYPE) + reg = await self.get_ook_peak() + reg.OOK_THRESH_TYPE = thtype + await self.set_ook_peak(reg) + + async def set_ook_peak_threshstep(self, thstep): + assert isinstance(thstep, regs_xxk.OOKPEAKTHRESHSTEP) + reg = await self.get_ook_peak() + reg.OOK_PEAK_THRESH_STEP = thstep + await self.set_ook_peak(reg) + + async def get_ook_fix(self): + return await self.lower.read_register(regs_xxk.ADDR_OOK_FIX) + + async def set_ook_fix(self, value): + assert value <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_OOK_FIX, value) + + async def get_ook_avg(self): + return await self._get_register(regs_xxk.REG_OOK_AVG, regs_xxk.ADDR_OOK_AVG) + + async def set_ook_avg(self, reg): + await self._set_register(regs_xxk.REG_OOK_AVG, regs_xxk.ADDR_OOK_AVG, reg) + + async def set_ook_avg_peakthreshdec(self, value): + assert isinstance(value, regs_xxk.OOKPEAKTHREASHDEC) + reg = await self.get_ook_avg() + reg.OOK_PEAK_THRESH_DEC = value + await self.set_ook_avg(reg) + + async def set_ook_avg_offset(self, value): + assert isinstance(value, regs_xxk.OOKAVGOFFSET) + reg = await self.get_ook_avg() + reg.OOK_AVG_OFFSET = value + await self.set_ook_avg(reg) + + async def set_ook_avg_threshfilt(self, value): + assert isinstance(value, regs_xxk.OOKAVGTHRESHFILT) + reg = await self.get_ook_avg() + reg.OOK_AVG_THRESH_FILT = value + await self.set_ook_avg(reg) + + async def get_afc_fei(self): + return await self._get_register(regs_xxk.REG_AFC_FEI, regs_xxk.ADDR_AFC_FEI) + + async def set_afc_fei(self, fei): + await self._set_register(regs_xxk.REG_AFC_FEI, regs_xxk.ADDR_AFC_FEI, fei) + + async def set_afc_fei_autoclear(self, onoff): + reg = await self.get_afc_fei() + reg.AFC_AUTO_CLEAR_ON = onoff + await self.set_afc_fei(reg) + + async def set_afc_fei_agcclear(self, onoff): + reg = await self.get_afc_fei() + reg.AFC_CLEAR = onoff + await self.set_afc_fei(reg) + + async def set_afc_fei_agcstart(self, onoff): + reg = await self.get_afc_fei() + reg.AFC_START = onoff + await self.set_afc_fei(reg) + + async def get_afc(self): + return await self.lower.get_register_wide(regs_xxk.ADDR_AFC_MSB, 2) + + async def set_afc(self, value): + await self.lower.write_register_wide(regs_xxk.ADDR_AFC_MSB, [value >> 8, value & 0xFF]) + + async def get_fei(self): + return await self.lower.get_register_wide(regs_xxk.ADDR_FEI_MSB, 2) + + async def set_fei(self, value): + await self.lower.write_register_wide(regs_xxk.ADDR_FEI_MSB, [value >> 8, value & 0xFF]) + + async def get_preamble_detect(self): + return await self._get_register(regs_xxk.REG_PREAMBLE_DETECT, regs_xxk.ADDR_PREAMBLE_DETECT) + + async def set_preamble_detect(self, pdetect): + await self._set_register(regs_xxk.REG_PREAMBLE_DETECT, regs_xxk.ADDR_PREAMBLE_DETECT, pdetect) + + async def set_preamble_detect_tol(self, tol): + reg = await self.get_preamble_detect() + reg.PREAMBLE_DETECTOR_TOL = tol + await self.set_preamble_detect(reg) + + async def set_preamble_detect_size(self, size): + assert isinstance(size, regs_xxk.PREAMBLEDETECTORSIZE) + reg = await self.get_preamble_detect() + reg.PREAMBLE_DETECTOR_SIZE = size + await self.set_preamble_detect(reg) + + async def set_preamble_detect_on(self, onoff): + reg = await self.get_preamble_detect() + reg.PREAMBLE_DETECTOR_ON = onoff + await self.set_preamble_detect(reg) + + async def get_rx_timeout_rssi(self): + return await self.lower.read_register(regs_xxk.ADDR_RX_TIMEOUT_1) + + async def set_rx_timeout_rssi(self, timeout): + assert timeout <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_RX_TIMEOUT_1, timeout) + + async def get_rx_timeout_preamble(self): + return await self.lower.read_register(regs_xxk.ADDR_RX_TIMEOUT_2) + + async def set_rx_timeout_preamble(self, timeout): + assert timeout <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_RX_TIMEOUT_2, timeout) + + async def get_rx_timeout_sync(self): + return await self.lower.read_register(regs_xxk.ADDR_RX_TIMEOUT_3) + + async def set_rx_timeout_sync(self, timeout): + assert timeout <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_RX_TIMEOUT_3, timeout) + + async def get_rx_delay(self): + return await self.lower.read_register(regs_xxk.ADDR_RX_DELAY) + + async def set_rx_delay(self, value): + assert value <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_RX_DELAY, value) + + async def get_osc(self): + return await self._get_register(regs_xxk.REG_OSC, regs_xxk.ADDR_OSC) + + async def set_osc(self, osc): + await self._set_register(regs_xxk.REG_OSC, regs_xxk.ADDR_OSC, osc) + + async def set_osc_clkout(self, value): + assert isinstance(value, regs_xxk.CLKOUT) + reg = await self.get_osc() + reg.CLK_OUT = value + await self.set_osc(reg) + + async def set_osc_calstart(self): + reg = await self.get_osc() + reg.RC_CAL_START = 1 + await self.set_osc(reg) + + async def get_preamble_size(self): + size = await self.lower.read_register_wide(regs_xxk.ADDR_PREAMBLE_MSB, 2) + size = int.from_bytes(size, byteorder='big') + return size + + async def set_preamble_size(self, size): + assert size <= 2**16 - 1 + await self.lower.write_register_wide(regs_xxk.ADDR_PREAMBLE_MSB, [size >> 8, size & 0xFF]) + + async def get_sync_config(self): + return await self._get_register(regs_xxk.REG_SYNC_CONFIG, regs_xxk.ADDR_SYNC_CONFIG) + + async def set_sync_config(self, config): + await self._set_register(regs_xxk.REG_SYNC_CONFIG, regs_xxk.ADDR_SYNC_CONFIG, config) + + async def set_sync_config_size(self, size): + reg = await self.get_sync_config() + reg.SYNC_SIZE = size + await self.set_sync_config(reg) + + async def set_sync_config_fillcond(self, cond): + assert isinstance(cond, regs_xxk.FIFOFILLCONDITION) + reg = await self.get_sync_config() + reg.FIFO_FILL_CONDITION = cond + await self.set_sync_config(reg) + + async def set_sync_config_on(self, onoff): + reg = await self.get_sync_config() + reg.SYNC_ON = onoff + await self.set_sync_config(reg) + + async def set_sync_config_prepolarity(self, pol): + assert isinstance(pol, regs_xxk.PREAMBLEPOLARITY) + reg = await self.get_sync_config() + reg.PREAMBLE_POLARITY = pol + await self.set_sync_config(reg) + + async def set_sync_config_restartmode(self, mode): + assert isinstance(mode, regs_xxk.AUTORESTARTRXMODE) + reg = await self.get_sync_config() + reg.AUTORESTART_RX_MODE = mode + await self.set_sync_config(reg) + + async def get_sync_value(self): + value = await self.lower.read_register_wide(regs_xxk.ADDR_SYNC_VALUE_1, 8) + value = int.from_bytes(value, byteorder='big') + return value + + async def set_sync_value(self, value): + assert value <= 2**64 - 1 + await self.lower.write_register_wide(regs_xxk.ADDR_SYNC_VALUE_1, [value >> 56, (value >> 48) & 0xFF, (value >> 40) & 0xFF, (value >> 32) & 0xFF, (value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF]) + + async def get_packet_config_1(self): + return self._get_register(regs_xxk.REG_PACKET_CONFIG_1, regs_xxk.ADDR_PACKET_CONFIG_1) + + async def set_packet_config_1(self, config): + await self._set_register(regs_xxk.REG_PACKET_CONFIG_1, regs_xxk.ADDR_PACKET_CONFIG_1, config) + + async def set_packet_config_1_whitening(self, wtype): + assert isinstance(wtype, regs_xxk.WHITENINGTYPE) + reg = await self.get_packet_config_1() + reg.CRC_WHITENING_TYPE = wtype + await self.set_packet_config_1(reg) + + async def set_packet_config_1_filtering(self, config): + assert isinstance(config, regs_xxk.ADDRESSFILTERING) + reg = await self.get_packet_config_1() + reg.ADDRESS_FILTERING = config + await self.set_packet_config_1(reg) + + async def set_packet_config_1_crcclear(self, offon): + reg = await self.get_packet_config_1() + reg.CRC_AUTO_CLEAR_OFF = offon + await self.set_packet_config_1(reg) + + async def set_packet_config_1_crcon(self, onoff): + reg = await self.get_packet_config_1() + reg.CRC_ON = onoff + await self.set_packet_config_1(reg) + + async def set_packet_config_1_dcfree(self, config): + assert isinstance(config, regs_xxk.DCFREEENCODING) + reg = await self.get_packet_config_1() + reg.DC_FREE = config + await self.set_packet_config_1(reg) + + async def set_packet_config_1_packetformat(self, format): + assert isinstance(format, regs_xxk.PACKETFORMAT) + reg = await self.get_packet_config_1() + reg.PACKET_FORMAT = format + await self.set_packet_config_1(reg) + + async def get_packet_config_2(self): + return self._get_register(regs_xxk.REG_PACKET_CONFIG_2, regs_xxk.ADDR_PACKET_CONFIG_2) + + async def set_packet_config_2(self, config): + await self._set_register(regs_xxk.REG_PACKET_CONFIG_2, regs_xxk.ADDR_PACKET_CONFIG_2, config) + + async def set_packet_config_2_beaconon(self, onoff): + reg = await self.get_packet_config_2() + reg.BEACON_ON = onoff + await self.set_packet_config_2(reg) + + async def set_packet_config_2_iohomeon(self, onoff): + reg = await self.get_packet_config_2() + reg.IO_HOME_ON = onoff + await self.set_packet_config_2(reg) + + async def set_packet_config_2_datamode(self, mode): + assert isinstance(mode, regs_xxk.DATAMODE) + reg = await self.get_packet_config_2() + reg.DATA_MODE = mode + await self.set_packet_config_2(reg) + + async def get_payload_length(self): + pconf2 = await self.get_packet_config_2() + msb = pconf2.PAYLOAD_LENGTH_10_8 + lsb = await self.lower.read_register(regs_xxk.ADDR_PAYLOAD_LENGTH) + return (msb << 8) + lsb + + async def set_payload_length(self, value): + msb = (value >> 8) + lsb = value & 0xFF + pconf2 = await self.get_packet_config_2() + pconf2.PAYLOAD_LENGTH_10_8 = msb + await self.lower.write_register_wide(regs_xxk.ADDR_PACKET_CONFIG_2, [pconf2, lsb]) + + async def get_node_adrs(self): + return self.lower.read_register(regs_xxk.ADDR_NODE_ADRS) + + async def set_node_adrs(self, adrs): + assert adrs <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_NODE_ADRS, adrs) + + async def get_bcast_adrs(self): + return self.lower.read_register(regs_xxk.ADDR_BROADCAST_ADRS) + + async def set_bcast_adrs(self, adrs): + assert adrs <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_BROADCAST_ADRS, adrs) + + async def get_fifo_thresh(self): + return await self._get_register(regs_xxk.REG_FIFO_THRESH, regs_xxk.ADDR_FIFO_THRESH) + + async def set_fifo_thresh(self, value): + await self._set_register(regs_xxk.REG_FIFO_THRESH, regs_xxk.ADDR_FIFO_THRESH, value) + + async def set_fifo_thresh_thresh(self, thresh): + assert thresh <= 2**6 - 1 + reg = await self.get_fifo_thresh() + reg.FIFO_THRESHOLD = thresh + await self.set_fifo_thresh(reg) + + async def set_fifo_thresh_txcond(self, cond): + assert isinstance(cond, regs_xxk.TXSTARTCONDITION) + reg = await self.get_fifo_thresh() + reg.TX_START_CONDITION = cond + await self.set_fifo_thresh(reg) + + async def get_seq_config_1(self): + return await self._get_register(regs_xxk.REG_SEQ_CONFIG_1, regs_xxk.ADDR_SEQ_CONFIG_1) + + async def set_seq_config_1(self, conf): + await self._set_register(regs_xxk.REG_SEQ_CONFIG_1, regs_xxk.ADDR_SEQ_CONFIG_1, conf) + + async def set_seq_config_1_fromtx(self, value): + assert isinstance(value, regs_xxk.FROMTRANSMIT) + reg = await self.get_seq_config_1() + reg.FROM_TRANSMIT = value + await self.set_seq_config_1(reg) + + async def set_seq_config_1_fromidle(self, value): + assert isinstance(value, regs_xxk.FROMIDLE) + reg = await self.get_seq_config_1() + reg.FROM_IDLE = value + await self.set_seq_config_1(reg) + + async def set_seq_config_1_lpsel(self, value): + assert isinstance(value, regs_xxk.LOWPOWERSELECTION) + reg = await self.get_seq_config_1() + reg.LOW_POWER_SELECTION = value + await self.set_seq_config_1(reg) + + async def set_seq_config_1_fromstart(self, value): + assert isinstance(value, regs_xxk.FROMSTART) + reg = await self.get_seq_config_1() + reg.FROM_START = value + await self.set_seq_config_1(reg) + + async def set_seq_config_1_idlemode(self, value): + assert isinstance(value, regs_xxk.IDLEMODE) + reg = await self.get_seq_config_1() + reg.IDLE_MODE = value + await self.set_seq_config_1(reg) + + async def set_sequencer_stop(self): + reg = await self.get_seq_config_1() + reg.SEQUENCER_STOP = 1 + await self.set_seq_config_1(reg) + + async def set_sequencer_start(self): + reg = await self.get_seq_config_1() + reg.SEQUENCER_START = 1 + await self.set_seq_config_1(reg) + + async def get_seq_config_2(self): + return await self._get_register(regs_xxk.REG_SEQ_CONFIG_2, regs_xxk.ADDR_SEQ_CONFIG_2) + + async def set_seq_config_2(self, conf): + await self._set_register(regs_xxk.REG_SEQ_CONFIG_2, regs_xxk.ADDR_SEQ_CONFIG_2, conf) + + async def set_seq_config_2_frompktrcvd(self, value): + assert isinstance(value, regs_xxk.FROMPACKETRECEIVED) + reg = await self.get_seq_config_2() + reg.FROM_PACKET_RECEIVED = value + await self.set_seq_config_2(reg) + + async def set_seq_config_2_fromrxtmout(self, value): + assert isinstance(value, regs_xxk.FROMRXTIMEOUT) + reg = await self.get_seq_config_2() + reg.FROM_RX_TIMEOUT = value + await self.set_seq_config_2(reg) + + async def set_seq_config_2_fromreceive(self, value): + assert isinstance(value, regs_xxk.FROMRECEIVE) + reg = await self.get_seq_config_2() + reg.FROM_RECEIVE = value + await self.set_seq_config_2(reg) + + async def get_timer_resol(self): + return await self._get_register(regs_xxk.REG_TIMER_RESOL, regs_xxk.ADDR_TIMER_RESOL) + + async def set_timer_resol(self, value): + await self._set_register(regs_xxk.REG_TIMER_RESOL, regs_xxk.ADDR_TIMER_RESOL, value) + + async def set_timer_resol_timer1(self, value): + assert isinstance(value, regs_xxk.TIMERRES) + reg = await self.get_timer_resol() + reg.TIMER_1_RESOLUTION = value + await self.set_timer_resol(reg) + + async def set_timer_resol_timer2(self, value): + assert isinstance(value, regs_xxk.TIMERRES) + reg = await self.get_timer_resol() + reg.TIMER_2_RESOLUTION = value + await self.set_timer_resol(reg) + + async def get_timer1_coef(self): + return await self.lower.read_register(regs_xxk.ADDR_TIMER_1_COEF) + + async def set_timer1_coef(self, value): + assert value <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_TIMER_1_COEF, value) + + async def get_timer2_coef(self): + return await self.lower.read_register(regs_xxk.ADDR_TIMER_2_COEF) + + async def set_timer2_coef(self, value): + assert value <= 2**8 - 1 + await self.lower.write_register(regs_xxk.ADDR_TIMER_2_COEF, value) + + async def get_image_cal(self): + return await self._get_register(regs_xxk.REG_IMAGE_CAL, regs_xxk.ADDR_IMAGE_CAL) + + async def set_image_cal(self, cal): + await self._set_register(regs_xxk.REG_IMAGE_CAL, regs_xxk.ADDR_IMAGE_CAL, cal) + + async def set_image_cal_tempmonoff(self, offon): + reg = await self.get_image_cal() + reg.TEMP_MONITOR_OFF = offon + await self.set_image_cal(reg) + + async def set_image_cal_tempthresh(self, thresh): + assert isinstance(thresh, regs_xxk.TEMPTHRESHOLD) + reg = await self.get_image_cal() + reg.TEMP_THRESHOLD = thresh + await self.set_image_cal(reg) + + async def set_image_cal_tempchange(self, change): + assert isinstance(change, regs_xxk.TEMPCHANGE) + reg = await self.get_image_cal() + reg.TEMP_CHANGE = change + await self.set_image_cal(reg) + + async def get_image_cal_running(self): + reg = await self.get_image_cal() + return reg.IMAGE_CAL_RUNNING + + async def set_image_cal_start(self): + reg = await self.get_image_cal() + reg.IMAGE_CAL_START = 1 + await self.set_image_cal(reg) + + async def set_image_cal_autoon(self, onoff): + reg = await self.get_image_cal() + reg.AUTO_IMAGE_CAL_ON = onoff + await self.set_image_cal(reg) + + async def get_temp(self): + return await self.lower.read_register(regs_xxk.ADDR_TEMP) + + async def get_low_bat(self): + return await self._get_register(regs_xxk.REG_LOW_BAT, regs_xxk.ADDR_LOW_BAT) + + async def set_low_bat(self, value): + await self._set_register(regs_xxk.REG_LOW_BAT, regs_xxk.ADDR_LOW_BAT, value) + + async def set_low_bat_trim(self, value): + assert isinstance(value, regs_xxk.LOWBATTTRIM) + reg = await self.get_low_bat() + reg.LOW_BAT_TRIM = value + await self.set_low_bat(reg) + + async def set_low_bat_on(self, onoff): + reg = await self.get_low_bat() + reg.LOW_BAT_ON = onoff + await self.set_low_bat(reg) + + async def get_irq_flags_1(self): + return await self._get_register(regs_xxk.REG_IRQ_FLAGS_1, regs_xxk.ADDR_IRQ_FLAGS_1) + + async def set_irq_flags_1(self, flags): + await self._set_register(regs_xxk.REG_IRQ_FLAGS_1, regs_xxk.ADDR_IRQ_FLAGS_1, flags) + + async def set_irq_flags_1_syncmatch(self): + reg = regs_xxk.REG_IRQ_FLAGS_1 + reg.SYNC_ADDRESS_MATCH = 1 + await self.set_irq_flags_1(reg) + + async def set_irq_flags_1_predetect(self): + reg = regs_xxk.REG_IRQ_FLAGS_1 + reg.PREAMBLE_DETECT = 1 + await self.set_irq_flags_1(reg) + + async def set_irq_flags_1_rssi(self): + reg = regs_xxk.REG_IRQ_FLAGS_1 + reg.RSSI = 1 + await self.set_irq_flags_1(reg) + + async def get_irq_flags_2(self): + return await self._get_register(regs_xxk.REG_IRQ_FLAGS_2, regs_xxk.ADDR_IRQ_FLAGS_2) + + async def set_irq_flags_2(self, flags): + await self._set_register(regs_xxk.REG_IRQ_FLAGS_2, regs_xxk.ADDR_IRQ_FLAGS_2, flags) + + async def set_irq_flags_2_lowbat(self): + reg = regs_xxk.REG_IRQ_FLAGS_2 + reg.LOW_BAT = 1 + await self.set_irq_flags_2(reg) + + async def set_irq_flags_1_fifoovr(self): + reg = regs_xxk.REG_IRQ_FLAGS_2 + reg.FIFO_OVERRUN = 1 + await self.set_irq_flags_2(reg) + + +class RadioSX1272APILoRa(RadioSX1272APICommon): + def __init__(self, interface, logger): + super(RadioSX1272APILoRa, self).__init__(interface, logger) + + async def get_opmode(self): + return await self._get_register(regs_lora.REG_OP_MODE, regs_lora.ADDR_OP_MODE) + + async def set_opmode(self, opmode): + await self._set_register(regs_lora.REG_OP_MODE, regs_lora.ADDR_OP_MODE, opmode) + + async def set_opmode_mode(self, mode): + assert isinstance(mode, regs_lora.MODE) + reg = await self.get_opmode() + reg.MODE = mode + await self.set_opmode(reg) + + async def set_opmode_sharedreg(self, mode): + assert isinstance(mode, regs_lora.ACCESSSHAREDREG) + reg = await self.get_opmode() + reg.ACCESS_SHARED_REG = mode + await self.set_opmode(reg) + + async def set_opmode_lora(self, lrmode): + assert isinstance(lrmode, regs_lora.LONGRANGEMODE) + reg = await self.get_opmode() + reg.LONG_RANGE_MODE = lrmode + await self.set_opmode(reg) + + async def get_fifo_addr_ptr(self): + return await self.lower.read_register(regs_lora.ADDR_FIFO_ADDR_PTR) + + async def set_fifo_addr_ptr(self, value): + assert value <= 2**8 -1 + await self.lower.write_register(regs_lora.ADDR_FIFO_ADDR_PTR, value) + + async def get_fifo_tx_base_addr(self): + return await self.lower.read_register(regs_lora.ADDR_FIFO_TX_BASE_ADDR) + + async def set_fifo_tx_base_addr(self, value): + assert value <= 2**8 -1 + await self.lower.write_register(regs_lora.ADDR_FIFO_TX_BASE_ADDR, value) + + async def get_fifo_rx_base_addr(self): + return await self.lower.read_register(regs_lora.ADDR_FIFO_RX_BASE_ADDR) + + async def set_fifo_rx_base_addr(self, value): + assert value <= 2**8 -1 + await self.lower.write_register(regs_lora.ADDR_FIFO_RX_BASE_ADDR, value) + + async def get_fifo_rx_curr_addr(self): + return await self.lower.read_register(regs_lora.ADDR_FIFO_RX_CURRENT_ADDR) + + async def get_irq_flags_mask(self): + return await self._get_register(regs_lora.REG_IRQ_FLAGS_MASK, regs_lora.ADDR_IRQ_FLAGS_MASK) + + async def set_irq_flags_mask(self, mask): + await self._set_register(regs_lora.REG_IRQ_FLAGS_MASK, regs_lora.ADDR_IRQ_FLAGS_MASK, mask) + + async def set_irq_flags_mask_caddet(self, onoff): + reg = await self.get_irq_flags_mask() + reg.CAD_DETECTED_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def set_irq_flags_mask_fhsschn(self, onoff): + reg = await self.get_irq_flags_mask() + reg.FHSS_CHANGE_CHANNEL_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def set_irq_flags_mask_caddone(self, onoff): + reg = await self.get_irq_flags_mask() + reg.CAD_DONE_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def set_irq_flags_mask_txdone(self, onoff): + reg = await self.get_irq_flags_mask() + reg.TX_DONE_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def set_irq_flags_mask_validheader(self, onoff): + reg = await self.get_irq_flags_mask() + reg.VALID_HEADER_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def set_irq_flags_mask_crcerr(self, onoff): + reg = await self.get_irq_flags_mask() + reg.PAYLOAD_CRC_ERROR_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def set_irq_flags_mask_rxdone(self, onoff): + reg = await self.get_irq_flags_mask() + reg.RX_DONE_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def set_irq_flags_mask_rxtimeout(self, onoff): + reg = await self.get_irq_flags_mask() + reg.RX_TIMEOUT_MASK = onoff + await self.set_irq_flags_mask(reg) + + async def get_irq_flags(self): + return await self._get_register(regs_lora.REG_IRQ_FLAGS, regs_lora.ADDR_IRQ_FLAGS) + + async def set_irq_flags(self, flags): + await self._set_register(regs_lora.REG_IRQ_FLAGS, regs_lora.ADDR_IRQ_FLAGS, flags) + + async def clear_irq_flags(self): + reg = regs_lora.REG_IRQ_FLAGS.from_int(0xFF) + await self.set_irq_flags(reg) + + async def clear_irq_flag_caddet(self): + reg = await self.get_irq_flags() + reg.CAD_DETECTED = 1 + await self.set_irq_flags(reg) + + async def clear_irq_flag_fhsschn(self): + reg = await self.get_irq_flags() + reg.FHSS_CHANGE_CHANNEL = 1 + await self.set_irq_flags(reg) + + async def clear_irq_flag_caddone(self): + reg = await self.get_irq_flags() + reg.CAD_DONE = 1 + await self.set_irq_flags(reg) + + async def clear_irq_flag_txdone(self): + reg = await self.get_irq_flags() + reg.TX_DONE = 1 + await self.set_irq_flags(reg) + + async def clear_irq_flag_validheader(self): + reg = await self.get_irq_flags() + reg.VALID_HEADER = 1 + await self.set_irq_flags(reg) + + async def clear_irq_flag_crcerr(self): + reg = await self.get_irq_flags() + reg.PAYLOAD_CRC_ERROR = 1 + await self.set_irq_flags(reg) + + async def clear_irq_flag_rxdone(self): + reg = await self.get_irq_flags() + reg.RX_DONE = 1 + await self.set_irq_flags(reg) + + async def clear_irq_flag_rxtimeout(self): + reg = await self.get_irq_flags() + reg.RX_TIMEOUT = 1 + await self.set_irq_flags(reg) + + async def get_rx_nb_bytes(self): + return await self.lower.read_register(regs_lora.ADDR_RX_NB_BYTES) + + async def get_rx_header_cnt(self): + cnt = await self.lower.read_register_wide(regs_lora.ADDR_RX_HEADER_CNT_VALUE_MSB, 2) + return int.from_bytes(cnt, byteorder='big') + + async def get_rx_packet_cnt(self): + cnt = await self.lower.read_register_wide(regs_lora.ADDR_RX_PACKET_CNT_VALUE_MSB, 2) + return int.from_bytes(cnt, byteorder='big') + + async def get_modem_stat(self): + return await self._get_register(regs_lora.REG_MODEM_STAT, regs_lora.ADDR_MODEM_STAT) + + async def get_pkt_snr(self): + return await self.lower.read_register(regs_lora.ADDR_PKT_SNR_VALUE) + + async def get_pkt_rssi(self): + return await self.lower.read_register(regs_lora.ADDR_PKT_RSSI_VALUE) + + async def get_rssi(self): + return await self.lower.read_register(regs_lora.ADDR_RSSI_VALUE) + + async def get_hop_channel(self): + return await self._get_register(regs_lora.REG_HOP_CHANNEL, regs_lora.ADDR_HOP_CHANNEL) + + async def get_modem_config_1(self): + return await self._get_register(regs_lora.REG_MODEM_CONFIG_1, regs_lora.ADDR_MODEM_CONFIG_1) + + async def set_modem_config_1(self, config): + await self._set_register(regs_lora.REG_MODEM_CONFIG_1, regs_lora.ADDR_MODEM_CONFIG_1, config) + + async def set_modem_config_1_ldoptim(self, onoff): + reg = await self.get_modem_config_1() + reg.LOW_DATA_RATE_OPTIMIZE = onoff + await self.set_modem_config_1(reg) + + async def set_modem_config_1_rxcrcon(self, onoff): + reg = await self.get_modem_config_1() + reg.RX_PAYLOAD_CRC_ON = onoff + await self.set_modem_config_1(reg) + + async def set_modem_config_1_headermode(self, mode): + assert isinstance(mode, regs_lora.HEADERMODE) + reg = await self.get_modem_config_1() + reg.IMPLICIT_HEADER_MODE_ON = mode + await self.set_modem_config_1(reg) + + async def set_modem_config_1_codingrate(self, rate): + assert isinstance(rate, regs_lora.CODINGRATE) + reg = await self.get_modem_config_1() + reg.CODING_RATE = rate + await self.set_modem_config_1(reg) + + async def set_modem_config_1_bw(self, bw): + assert isinstance(bw, regs_lora.MODEMBW) + reg = await self.get_modem_config_1() + reg.BW = bw + await self.set_modem_config_1(reg) + + async def get_modem_config_2(self): + return await self._get_register(regs_lora.REG_MODEM_CONFIG_2, regs_lora.ADDR_MODEM_CONFIG_2) + + async def set_modem_config_2(self, config): + await self._set_register(regs_lora.REG_MODEM_CONFIG_2, regs_lora.ADDR_MODEM_CONFIG_2, config) + + async def set_modem_config_2_agcon(self, config): + assert isinstance(config, regs_lora.LNAGAINSOURCE) + reg = await self.get_modem_config_2() + reg.AGC_AUTO_ON = config + await self.set_modem_config_2(reg) + + async def set_modem_config_2_txmode(self, mode): + assert isinstance(mode, regs_lora.TXMODE) + reg = await self.get_modem_config_2() + reg.TX_CONTINUOUS_MODE = mode + await self.set_modem_config_2(reg) + + async def set_modem_config_2_spreading(self, factor): + assert isinstance(factor, regs_lora.SPREADINGFACTOR) + reg = await self.get_modem_config_2() + reg.SPREADING_FACTOR = factor + await self.set_modem_config_2(reg) + + async def get_symbol_timeout(self): + mconfig2 = await self.get_modem_config_2() + msb = mconfig2.SYMB_TIMEOUT_MSB + lsb = await self.lower.read_register(regs_lora.ADDR_SYMB_TIMEOUT_LSB) + return (msb << 8) + lsb + + async def set_symbol_timeout(self, timeout): + assert timeout <= 2**10 - 1 + msb = (timeout >> 8) + lsb = timeout & 0xFF + mconfig2 = await self.get_modem_config_2() + mconfig2.SYMB_TIMEOUT_MSB = msb + await self.lower.write_register_wide(regs_lora.ADDR_MODEM_CONFIG_2, [mconfig2.to_int(), lsb]) + + async def get_preamble_length(self): + preamble = await self.lower.read_register_wide(regs_lora.ADDR_PREAMBLE_MSB, 2) + return int.from_bytes(preamble, byteorder='big') + + async def set_preamble_length(self, preamble): + assert preamble <= 2**16 - 1 + await self.lower.write_register_wide(regs_lora.ADDR_PREAMBLE_MSB, [preamble >> 8, preamble & 0xFF]) + + async def get_payload_length(self): + return await self.lower.read_register(regs_lora.ADDR_PAYLOAD_LENGTH) + + async def set_payload_length(self, value): + assert value != 0 and value <= 2**8 - 1 + await self.lower.write_register(regs_lora.ADDR_PAYLOAD_LENGTH, value) + + async def get_payload_max_length(self): + return await self.lower.read_register(regs_lora.ADDR_MAX_PAYLOAD_LENGTH) + + async def set_payload_max_length(self, value): + assert value <= 2**8 - 1 + await self.lower.write_register(regs_lora.ADDR_MAX_PAYLOAD_LENGTH, value) + + async def get_hop_period(self): + return await self.lower.read_register(regs_lora.ADDR_HOP_PERIOD) + + async def set_hop_period(self, value): + assert value <= 2**8 - 1 + await self.lower.write_register(regs_lora.ADDR_HOP_PERIOD, value) + + async def get_fifo_rx_byte_addr(self): + return await self.lower.read_register(regs_lora.ADDR_FIFO_RX_BYTE_ADDR) + + async def get_fei(self): + msb = await self.lower.read_register(regs_lora.ADDR_FEI_MSB) + lsb = await self.lower.read_register_wide(regs_lora.ADDR_FEI_MID, 2) + return ((msb & 0x0F) << 16) + int.from_bytes(lsb, byteorder='big') + + async def get_rssi_wideband(self): + return await self.lower.read_register(regs_lora.ADDR_RSSI_WIDEBAND) + + async def get_detect_optimize(self): + return await self._get_register(regs_lora.REG_DETECT_OPTIMIZE, regs_lora.ADDR_DETECT_OPTIMIZE) + + async def set_detect_optimize(self, value): + await self._set_register(regs_lora.REG_DETECT_OPTIMIZE, regs_lora.ADDR_DETECT_OPTIMIZE, value) + + async def set_detect_optimize_optim(self, optim): + assert isinstance(optim, regs_lora.DETECTOPTIMIZE) + reg = await self.get_detect_optimize() + reg.DETECTION_OPTIMIZE = optim + await self.set_detect_optimize(reg) + + async def set_detect_optimize_if(self, onoff): + reg = await self.get_detect_optimize() + reg.AUTOMATIC_IF_ON = onoff + await self.set_detect_optimize(reg) + + async def get_invert_iq(self): + return await self._get_register(regs_lora.REG_INVERT_IQ, regs_lora.ADDR_INVERT_IQ) + + async def set_invert_iq(self, reg): + await self._set_register(regs_lora.REG_INVERT_IQ, regs_lora.ADDR_INVERT_IQ, reg) + + async def set_invert_iq_tx(self, onoff): + reg = await self.get_invert_iq() + reg.INVERT_IQTX = onoff + await self.set_invert_iq(reg) + + async def set_invert_iq_rx(self, onoff): + reg = await self.get_invert_iq() + reg.INVERT_IQRX = onoff + await self.set_invert_iq(reg) + # Set to 0x19 when RX inverted IQ is set. c.f. AN1200.24 + if onoff == 1: + await self.lower.write_register(regs_lora.ADDR_INVERT_IQ_2, 0x19) + else: + await self.lower.write_register(regs_lora.ADDR_INVERT_IQ_2, 0x1D) + + async def get_detection_threshold(self): + thresh = regs_lora.DETECTIONTHRESHOLD + thresh = await self.lower.read_register(regs_lora.ADDR_DETECTION_THRESHOLD) + return thresh + + async def set_detection_threshold(self, value): + assert isinstance(value, regs_lora.DETECTIONTHRESHOLD) + await self.lower.write_register(regs_lora.ADDR_DETECTION_THRESHOLD, value) + + async def get_sync_word(self): + return await self.lower.read_register(regs_lora.ADDR_SYNC_WORD) + + async def set_sync_word(self, word): + assert word <= 2**8 - 1 + await self.lower.write_register(regs_lora.ADDR_SYNC_WORD, word) + + async def get_invert_iq_2(self): + return await self.lower.read_register(regs_lora.ADDR_INVERT_IQ_2) + + async def get_chirp_filter(self): + return await self.lower.read_register(regs_lora.ADDR_CHIRP_FILTER) + + async def set_chirp_filter(self, value): + assert value == 0xA0 or value == 0x31 + await self.lower.write_register(regs_lora.ADDR_CHIRP_FILTER, value) diff --git a/software/glasgow/arch/sx1272/regs_common.py b/software/glasgow/arch/sx1272/regs_common.py new file mode 100644 index 000000000..49f4af0a8 --- /dev/null +++ b/software/glasgow/arch/sx1272/regs_common.py @@ -0,0 +1,221 @@ +# Ref: SX1272 Datasheet +# Accession: G00051 + +import enum + +from ...support.bitstruct import * + + +__all__ = [ + # Command opcodes + "OP_R_REGISTER", "OP_W_REGISTER", + # Register addresses + "ADDR_FIFO", "ADDR_F_RF_MSB", "ADDR_F_RF_MID", + "ADDR_F_RF_LSB", "ADDR_PA_CONFIG", "ADDR_PA_RAMP", "ADDR_OCP", + "ADDR_LNA", "ADDR_DIO_MAPPING_1", "ADDR_DIO_MAPPING_2", "ADDR_VERSION", + "ADDR_AGC_REF", "ADDR_AGC_THRESH_1", "ADDR_AGC_THRESH_2", + "ADDR_AGC_THRESH_3", "ADDR_PLL_HOP", "ADDR_TCXO", "ADDR_PA_DAC", + "ADDR_PLL", "ADDR_PLL_LOW_PN", "ADDR_PA_MANUAL", "ADDR_FORMER_TEMP", + "ADDR_BIT_RATE_FRAC", + # Registers + "REG_PA_CONFIG", "REG_PA_RAMP", "REG_OCP", "REG_LNA", + "REG_DIO_MAPPING_1", "REG_DIO_MAPPING_2", "REG_VERSION", "REG_AGC_REF", + "REG_AGC_THRESH_1", "REG_AGC_THRESH_2", "REG_AGC_THRESH_3", + "REG_PLL_HOP", "REG_TCXO", "REG_PA_DAC", "REG_PLL", "REG_PLL_LOW_PN", + "REG_PA_MANUAL", "REG_BIT_RATE_FRAC", + # Enumerations + "PASELECT", "LOWPLLTX", "PARAMP", "LNAGAIN", "LNABOOST", + "MAPPREAMBLEDETECT", "FASTHOP", "TCXOINPUT", "PADAC", "PLLBW" +] + +# Command opcodes + +OP_R_REGISTER = 0b0_0000000 +OP_W_REGISTER = 0b1_0000000 + +# Register addresses + +ADDR_FIFO = 0x00 +ADDR_F_RF_MSB = 0x06 +ADDR_F_RF_MID = 0x07 +ADDR_F_RF_LSB = 0x08 +ADDR_PA_CONFIG = 0x09 +ADDR_PA_RAMP = 0x0A +ADDR_OCP = 0x0B +ADDR_LNA = 0x0C +ADDR_DIO_MAPPING_1 = 0x40 +ADDR_DIO_MAPPING_2 = 0x41 +ADDR_VERSION = 0x42 +ADDR_AGC_REF = 0x43 +ADDR_AGC_THRESH_1 = 0x44 +ADDR_AGC_THRESH_2 = 0x45 +ADDR_AGC_THRESH_3 = 0x46 +ADDR_PLL_HOP = 0x4B +ADDR_TCXO = 0x58 +ADDR_PA_DAC = 0x5A +ADDR_PLL = 0x5C +ADDR_PLL_LOW_PN = 0x5E +ADDR_PA_MANUAL = 0x63 +ADDR_FORMER_TEMP = 0x6C +ADDR_BIT_RATE_FRAC = 0x70 + +class PASELECT(enum.IntEnum): + _RFO = 0b0 + _PA_BOOST = 0b1 + +REG_PA_CONFIG = bitstruct("REG_PA_CONFIG", 8, [ + ("OUT_POWER", 4), + (None, 3), + ("PA_SELECT", 1) +]) + +class LOWPLLTX(enum.IntEnum): + _LOW_TX_STD_RX = 0b0 + _STD_TX_RX = 0b1 + +class PARAMP(enum.IntEnum): + _3400_us = 0b0000 + _2000_us = 0b0001 + _1000_us = 0b0010 + _500_us = 0b0011 + _250_us = 0b0100 + _125_us = 0b0101 + _100_us = 0b0110 + _62_us = 0b0111 + _50_us = 0b1000 + _42_us = 0b1001 + _31_us = 0b1010 + _25_us = 0b1011 + _20_us = 0b1100 + _15_us = 0b1101 + _12_us = 0b1110 + _10_us = 0b1111 + +REG_PA_RAMP = bitstruct("REG_PA_RAMP", 8, [ + ("PA_RAMP", 4), + ("LOW_PN_TX_PLL_OFF", 1), + (None, 3) +]) + +REG_OCP = bitstruct("REG_OCP", 8, [ + ("OCP_TRIM", 5), + ("OCP_ON", 1), + (None, 2) +]) + +class LNAGAIN(enum.IntEnum): + _G1 = 0b001 + _G2 = 0b010 + _G3 = 0b011 + _G4 = 0b100 + _G5 = 0b101 + _G6 = 0b110 + +class LNABOOST(enum.IntEnum): + _DEFAULT = 0b00 + _IMPROVED = 0b11 + +REG_LNA = bitstruct("REG_LNA", 8, [ + ("LNA_BOOST", 2), + (None, 3), + ("LNA_GAIN", 3) +]) + +REG_DIO_MAPPING_1 = bitstruct("REG_DIO_MAPPING_1", 8, [ + ("DIO_3_MAPPING", 2), + ("DIO_2_MAPPING", 2), + ("DIO_1_MAPPING", 2), + ("DIO_0_MAPPING", 2) +]) + +class MAPPREAMBLEDETECT(enum.IntEnum): + _RSSI_INT = 0b0 + _PREAMBLE_INT = 0b1 + +REG_DIO_MAPPING_2 = bitstruct("REG_DIO_MAPPING_2", 8, [ + ("MAP_PREAMBLE_DETECT", 1), + (None, 3), + ("DIO_5_MAPPING", 2), + ("DIO_4_MAPPING", 2) +]) + +REG_VERSION = bitstruct("REG_VERSION", 8, [ + ("METAL_MASK_REVISION", 4), + ("FULL_REVISION", 4) +]) + +REG_AGC_REF = bitstruct("REG_AGC_REF", 8, [ + ("AGC_REF_LEVEL", 6), + (None, 2) +]) + +REG_AGC_THRESH_1 = bitstruct("REG_AGC_THRESH_1", 8, [ + ("AGC_STEP_1", 5), + (None, 3) +]) + +REG_AGC_THRESH_2 = bitstruct("REG_AGC_THRESH_2", 8, [ + ("AGC_STEP_3", 4), + ("AGC_STEP_2", 4) +]) + +REG_AGC_THRESH_3 = bitstruct("REG_AGC_THRESH_3", 8, [ + ("AGC_STEP_5", 4), + ("AGC_STEP_4", 4) +]) + +class FASTHOP(enum.IntEnum): + _FSTX_FSRX = 0b0 + _FRF_LSB = 0b1 + +REG_PLL_HOP = bitstruct("REG_PLL_HOP", 8, [ + ("PA_MANUAL_DUTY_CYCLE", 4), + (None, 3), + ("FAST_HOP_ON", 1) +]) + +class TCXOINPUT(enum.IntEnum): + _EXT_XTAL = 0b0 + _EXT_SINE = 0b1 + +REG_TCXO = bitstruct("REG_TCXO", 8, [ + (None, 4), + ("TCXO_INPUT_ON", 1), + (None, 3) +]) + +class PADAC(enum.IntEnum): + _DEFAULT = 0x04 + _20_dBm = 0x07 + +REG_PA_DAC = bitstruct("REG_PA_DAC", 8, [ + ("PA_DAC", 3), + (None, 5) +]) + +class PLLBW(enum.IntEnum): + _75_kHz = 0b00 + _150_kHz = 0b01 + _225_kHz = 0b10 + _300_kHz = 0b11 + +REG_PLL = bitstruct("REG_PLL", 8, [ + (None, 6), + ("PLL_BANDWIDTH", 2) +]) + +REG_PLL_LOW_PN = bitstruct("REG_PLL_LOW_PN", 8, [ + (None, 6), + ("PLL_BANDWIDTH", 2) +]) + +REG_PA_MANUAL = bitstruct("REG_PA_MANUAL", 8, [ + (None, 4), + ("MANUAL_PA_CONTROL", 1), + (None, 3) +]) + +REG_BIT_RATE_FRAC = bitstruct("REG_BIT_RATE_FRAC", 8, [ + ("BIT_RATE_FRAC", 4), + (None, 4) +]) \ No newline at end of file diff --git a/software/glasgow/arch/sx1272/regs_lora.py b/software/glasgow/arch/sx1272/regs_lora.py new file mode 100644 index 000000000..bbccc8e7c --- /dev/null +++ b/software/glasgow/arch/sx1272/regs_lora.py @@ -0,0 +1,209 @@ +# Ref: SX1272 Datasheet +# Accession: G00051 + +import enum + +from ...support.bitstruct import * + + +__all__ = [ + # Register addresses + "ADDR_OP_MODE", "ADDR_FIFO_ADDR_PTR", "ADDR_FIFO_TX_BASE_ADDR", + "ADDR_FIFO_RX_BASE_ADDR", "ADDR_FIFO_RX_CURRENT_ADDR", "ADDR_IRQ_FLAGS_MASK", + "ADDR_IRQ_FLAGS", "ADDR_RX_NB_BYTES", "ADDR_RX_HEADER_CNT_VALUE_MSB", + "ADDR_RX_HEADER_CNT_VALUE_LSB", "ADDR_RX_PACKET_CNT_VALUE_MSB", + "ADDR_RX_PACKET_CNT_VALUE_LSB", "ADDR_MODEM_STAT", "ADDR_PKT_SNR_VALUE", + "ADDR_PKT_RSSI_VALUE", "ADDR_RSSI_VALUE", "ADDR_HOP_CHANNEL", + "ADDR_MODEM_CONFIG_1", "ADDR_MODEM_CONFIG_2", "ADDR_SYMB_TIMEOUT_LSB", + "ADDR_PREAMBLE_MSB", "ADDR_PREAMBLE_LSB", "ADDR_PAYLOAD_LENGTH", + "ADDR_MAX_PAYLOAD_LENGTH", "ADDR_HOP_PERIOD", "ADDR_FIFO_RX_BYTE_ADDR", + "ADDR_FEI_MSB", "ADDR_FEI_MIB", "ADDR_FEI_LSB", "ADDR_RSSI_WIDEBAND", + "ADDR_DETECT_OPTIMIZE", "ADDR_INVERT_IQ", "ADDR_DETECTION_THRESHOLD", + "ADDR_SYNC_WORD", "ADDR_INVERT_IQ_2", "ADDR_CHIRP_FILTER" + # Registers + "REG_OP_MODE", "REG_IRQ_FLAGS_MASK", "REG_IRQ_FLAGS", "REG_MODEM_STAT", + "REG_HOP_CHANNEL", "REG_MODEM_CONFIG_1", "REG_MODEM_CONFIG_2", + "REG_FEI_MSB", "REG_DETECT_OPTIMIZE", "REG_INVERT_IQ", + # Enumerations + "PLLTIMEOUT", "MODEMBW", "CODINGRATE", "HEADERMODE", "SPREADINGFACTOR", + "TXMODE", "LNAGAINSOURCE", "DETECTOPTIMIZE", "DETECTIONTHRESHOLD", "LONGRANGEMODE", + "ACCESSSHAREDREG", "MODE" +] + +# Register addresses + +ADDR_OP_MODE = 0x01 +ADDR_FIFO_ADDR_PTR = 0x0D +ADDR_FIFO_TX_BASE_ADDR = 0x0E +ADDR_FIFO_RX_BASE_ADDR = 0x0F +ADDR_FIFO_RX_CURRENT_ADDR = 0x10 +ADDR_IRQ_FLAGS_MASK = 0x11 +ADDR_IRQ_FLAGS = 0x12 +ADDR_RX_NB_BYTES = 0x13 +ADDR_RX_HEADER_CNT_VALUE_MSB = 0x14 +ADDR_RX_HEADER_CNT_VALUE_LSB = 0x15 +ADDR_RX_PACKET_CNT_VALUE_MSB = 0x16 +ADDR_RX_PACKET_CNT_VALUE_LSB = 0x17 +ADDR_MODEM_STAT = 0x18 +ADDR_PKT_SNR_VALUE = 0x19 +ADDR_PKT_RSSI_VALUE = 0x1A +ADDR_RSSI_VALUE = 0x1B +ADDR_HOP_CHANNEL = 0x1C +ADDR_MODEM_CONFIG_1 = 0x1D +ADDR_MODEM_CONFIG_2 = 0x1E +ADDR_SYMB_TIMEOUT_LSB = 0x1F +ADDR_PREAMBLE_MSB = 0x20 +ADDR_PREAMBLE_LSB = 0x21 +ADDR_PAYLOAD_LENGTH = 0x22 +ADDR_MAX_PAYLOAD_LENGTH = 0x23 +ADDR_HOP_PERIOD = 0x24 +ADDR_FIFO_RX_BYTE_ADDR = 0x25 +ADDR_FEI_MSB = 0x28 +ADDR_FEI_MID = 0x29 +ADDR_FEI_LSB = 0x2A +ADDR_RSSI_WIDEBAND = 0x2C +ADDR_DETECT_OPTIMIZE = 0x31 +ADDR_INVERT_IQ = 0x33 +ADDR_DETECTION_THRESHOLD = 0x37 +ADDR_SYNC_WORD = 0x39 +ADDR_INVERT_IQ_2 = 0x3B +ADDR_CHIRP_FILTER = 0x3D + +class LONGRANGEMODE(enum.IntEnum): + _FSK_OOK = 0b0 + _LORA = 0b1 + +class ACCESSSHAREDREG(enum.IntEnum): + _LoRa = 0b0 + _XXK = 0b1 + +class MODE(enum.IntEnum): + _SLEEP = 0b000 + _STDBY = 0b001 + _FSTX = 0b010 + _TX = 0b011 + _FSRX = 0b100 + _RXCONT = 0b101 + _RXSINGLE = 0b110 + _CAD = 0b111 + +REG_OP_MODE = bitstruct("REG_OP_MODE", 8, [ + ("MODE", 3), + (None, 3), + ("ACCESS_SHARED_REG", 1), + ("LONG_RANGE_MODE", 1) +]) + +REG_IRQ_FLAGS_MASK = bitstruct("REG_IRQ_FLAGS_MASK", 8, [ + ("CAD_DETECTED_MASK", 1), + ("FHSS_CHANGE_CHANNEL_MASK", 1), + ("CAD_DONE_MASK", 1), + ("TX_DONE_MASK", 1), + ("VALID_HEADER_MASK", 1), + ("PAYLOAD_CRC_ERROR_MASK", 1), + ("RX_DONE_MASK", 1), + ("RX_TIMEOUT_MASK", 1) +]) + +REG_IRQ_FLAGS = bitstruct("REG_IRQ_FLAGS", 8, [ + ("CAD_DETECTED", 1), + ("FHSS_CHANGE_CHANNEL", 1), + ("CAD_DONE", 1), + ("TX_DONE", 1), + ("VALID_HEADER", 1), + ("PAYLOAD_CRC_ERROR", 1), + ("RX_DONE", 1), + ("RX_TIMEOUT", 1) +]) + +REG_MODEM_STAT = bitstruct("REG_MODEM_STAT", 8, [ + ("SIGNAL_DETECTED", 1), + ("SIGNAL_SYNCHRONIZED", 1), + ("RX_ONGOING", 1), + ("HEADER_INFO_VALID", 1), + ("MODEM_CLEAR", 1), + ("RX_CODING_RATE", 3) +]) + +class PLLTIMEOUT(enum.IntEnum): + _PLL_LOCK = 0b0 + _PLL_NO_LOCK = 0b1 + +REG_HOP_CHANNEL = bitstruct("REG_HOP_CHANNEL", 8, [ + ("FHSS_PRESENT_CHANNEL", 6), + ("CRC_ON_PAYLOAD", 1), + ("PLL_TIMEOUT", 1) +]) + +class MODEMBW(enum.IntEnum): + _BW_125kHz = 0b00 + _BW_250kHz = 0b01 + _BW_500kHz = 0b10 + +class CODINGRATE(enum.IntEnum): + _4_OVER_5 = 0b001 + _4_OVER_6 = 0b010 + _4_OVER_7 = 0b011 + _4_OVER_8 = 0b100 + +class HEADERMODE(enum.IntEnum): + _EXPLICIT_HEADER = 0b0 + _IMPLICIT_HEADER = 0b1 + +REG_MODEM_CONFIG_1 = bitstruct("REG_MODEM_CONFIG_1", 8, [ + ("LOW_DATA_RATE_OPTIMIZE", 1), + ("RX_PAYLOAD_CRC_ON", 1), + ("IMPLICIT_HEADER_MODE_ON", 1), + ("CODING_RATE", 3), + ("BW", 2) +]) + +class SPREADINGFACTOR(enum.IntEnum): + _SPREAD_6 = 6 + _SPREAD_7 = 7 + _SPREAD_8 = 8 + _SPREAD_9 = 9 + _SPREAD_10 = 10 + _SPREAD_11 = 11 + _SPREAD_12 = 12 + +class TXMODE(enum.IntEnum): + _NORMAL = 0b0 + _CONTINUOUS = 0b1 + +class LNAGAINSOURCE(enum.IntEnum): + _LNA_GAIN_REG = 0b0 + _LNA_GAIN_AGC = 0b1 + +REG_MODEM_CONFIG_2 = bitstruct("REG_MODEM_CONFIG_2", 8, [ + ("SYMB_TIMEOUT_MSB", 2), + ("AGC_AUTO_ON", 1), + ("TX_CONTINUOUS_MODE_ON", 1), + ("SPREADING_FACTOR", 4) +]) + +REG_FEI_MSB = bitstruct("REG_FEI_MSB", 8, [ + ("FREQ_ERROR_MSB", 4), + (None, 4) +]) + +class DETECTOPTIMIZE(enum.IntEnum): + _SF7_TO_12 = 0x03 + _SF6 = 0x05 + +REG_DETECT_OPTIMIZE = bitstruct("REG_DETECT_OPTIMIZE", 8, [ + ("DETECTION_OPTIMIZE", 3), + (None, 4), + ("AUTOMATIC_IF_ON", 1) +]) + +REG_INVERT_IQ = bitstruct("REG_INVERT_IQ", 8, [ + ("INVERT_IQTX", 1), + (None, 5), + ("INVERT_IQRX", 1), + (None, 1) +]) + +class DETECTIONTHRESHOLD(enum.IntEnum): + _SF7_TO_12 = 0x0A + _SF6 = 0x0C \ No newline at end of file diff --git a/software/glasgow/arch/sx1272/regs_xxk.py b/software/glasgow/arch/sx1272/regs_xxk.py new file mode 100644 index 000000000..aa08f27fe --- /dev/null +++ b/software/glasgow/arch/sx1272/regs_xxk.py @@ -0,0 +1,469 @@ +# Ref: SX1272 Datasheet +# Accession: G00051 + +import enum + +from ...support.bitstruct import * + + +__all__ = [ + # Register addresses + "ADDR_OP_MODE", "ADDR_BITRATE_MSB", "ADDR_BITRATE_LSB", "ADDR_F_DEV_MSB", + "ADDR_F_DEV_LSB", "ADDR_RX_CONFIG", "ADDR_RSSI_CONFIG", + "ADDR_RSSI_COLLISION", "ADDR_RSSI_THRESH", "ADDR_RSSI_VALUE", + "ADDR_RX_BW", "ADDR_AFC_BW", "ADDR_OOK_PEAK", "ADDR_OOK_FIX", + "ADDR_OOK_AVG", "ADDR_AFC_FEI", "ADDR_AFC_MSB", "ADDR_AFC_LSB", + "ADDR_FEI_MSB", "ADDR_FEI_LSB", "ADDR_PREAMBLE_DETECT", + "ADDR_RX_TIMEOUT_1", "ADDR_RX_TIMEOUT_2", "ADDR_RX_TIMEOUT_3", + "ADDR_RX_DELAY", "ADDR_OSC", "ADDR_PREAMBLE_MSB", "ADDR_PREAMBLE_LSB", + "ADDR_SYNC_CONFIG", "ADDR_SYNC_VALUE_1", "ADDR_SYNC_VALUE_2", + "ADDR_SYNC_VALUE_3", "ADDR_SYNC_VALUE_4", "ADDR_SYNC_VALUE_5", + "ADDR_SYNC_VALUE_6", "ADDR_SYNC_VALUE_7", "ADDR_SYNC_VALUE_8", + "ADDR_PACKET_CONFIG_1", "ADDR_PACKET_CONFIG_2", "ADDR_PAYLOAD_LENGTH", + "ADDR_NODE_ADRS", "ADDR_BROADCAST_ADRS", "ADDR_FIFO_THRESH", + "ADDR_SEQ_CONFIG_1", "ADDR_SEQ_CONFIG_2", "ADDR_TIMER_RESOL", + "ADDR_TIMER_1_COEF", "ADDR_TIMER_2_COEF", "ADDR_IMAGE_CAL", "ADDR_TEMP", + "ADDR_LOW_BAT", "ADDR_IRQ_FLAGS_1", "ADDR_IRQ_FLAGS_2", + # Registers + "REG_OP_MODE", "REG_RX_CONFIG", "REG_RSSI_CONFIG", "REG_RX_BW", + "REG_AFC_BW", "REG_OOK_PEAK", "REG_OOK_AVG", "REG_AFC_FEI", + "REG_PREAMBLE_DETECT", "REG_OSC", "REG_SYNC_CONFIG", + "REG_PACKET_CONFIG_1", "REG_PACKET_CONFIG_2", "REG_FIFO_THRESH", + "REG_SEQ_CONFIG_1", "REG_SEQ_CONFIG_2", "REG_TIMER_RESOL", + "REG_IMAGE_CAL", "REG_LOW_BAT", "REG_IRQ_FLAGS_1", "REG_IRQ_FLAGS_2", + # Enumerations + "RSSISMOOTHING", "RXBWMANT", "OOKPEAKTHRESHSTEP", "OOKTHRESHTYPE", + "OOKPEAKTHREASHDEC", "OOKAVGOFFSET", "OOKAVGTHRESHFILT", + "AFCAUTOCLEARON", "PREAMBLEDETECTORON", "PREAMBLEDETECTORSIZE", "CLKOUT", + "AUTORESTARTRXMODE", "PREAMBLEPOLARITY", "SYNCON", "FIFOFILLCONDITION", + "PACKETFORMAT", "DCFREEENCODING", "CRCON", "CRCAUTOCLEAR", + "ADDRESSFILTERING", "WHITENINGTYPE", "DATAMODE", "TXSTARTCONDITION", + "IDLEMODE", "FROMSTART", "LOWPOWERSELECTION", "FROMIDLE", "FROMTRANSMIT", + "FROMPACKETRECEIVED", "FROMRXTIMEOUT", "FROMRECEIVE", "TIMERRES", + "TEMPCHANGE", "TEMPTHRESHOLD", "LOWBATTTRIM", "LONGRANGEMODE", + "MODULATIONTYPE", "MODULATIONSHAPINGFSK", "MODULATIONSHAPINGOOK", "MODE" +] + +# Register addresses + +ADDR_OP_MODE = 0x01 +ADDR_BITRATE_MSB = 0x02 +ADDR_BITRATE_LSB = 0x03 +ADDR_F_DEV_MSB = 0x04 +ADDR_F_DEV_LSB = 0x05 +ADDR_RX_CONFIG = 0x0D +ADDR_RSSI_CONFIG = 0x0E +ADDR_RSSI_COLLISION = 0x0F +ADDR_RSSI_THRESH = 0x10 +ADDR_RSSI_VALUE = 0x11 +ADDR_RX_BW = 0x12 +ADDR_AFC_BW = 0x13 +ADDR_OOK_PEAK = 0x14 +ADDR_OOK_FIX = 0x15 +ADDR_OOK_AVG = 0x16 +ADDR_AFC_FEI = 0x1A +ADDR_AFC_MSB = 0x1B +ADDR_AFC_LSB = 0x1C +ADDR_FEI_MSB = 0x1D +ADDR_FEI_LSB = 0x1E +ADDR_PREAMBLE_DETECT = 0x1F +ADDR_RX_TIMEOUT_1 = 0x20 +ADDR_RX_TIMEOUT_2 = 0x21 +ADDR_RX_TIMEOUT_3 = 0x22 +ADDR_RX_DELAY = 0x23 +ADDR_OSC = 0x24 +ADDR_PREAMBLE_MSB = 0x25 +ADDR_PREAMBLE_LSB = 0x26 +ADDR_SYNC_CONFIG = 0x27 +ADDR_SYNC_VALUE_1 = 0x28 +ADDR_SYNC_VALUE_2 = 0x29 +ADDR_SYNC_VALUE_3 = 0x2A +ADDR_SYNC_VALUE_4 = 0x2B +ADDR_SYNC_VALUE_5 = 0x2C +ADDR_SYNC_VALUE_6 = 0x2D +ADDR_SYNC_VALUE_7 = 0x2E +ADDR_SYNC_VALUE_8 = 0x2F +ADDR_PACKET_CONFIG_1 = 0x30 +ADDR_PACKET_CONFIG_2 = 0x31 +ADDR_PAYLOAD_LENGTH = 0x32 +ADDR_NODE_ADRS = 0x33 +ADDR_BROADCAST_ADRS = 0x34 +ADDR_FIFO_THRESH = 0x35 +ADDR_SEQ_CONFIG_1 = 0x36 +ADDR_SEQ_CONFIG_2 = 0x37 +ADDR_TIMER_RESOL = 0x38 +ADDR_TIMER_1_COEF = 0x39 +ADDR_TIMER_2_COEF = 0x3A +ADDR_IMAGE_CAL = 0x3B +ADDR_TEMP = 0x3C +ADDR_LOW_BAT = 0x3D +ADDR_IRQ_FLAGS_1 = 0x3E +ADDR_IRQ_FLAGS_2 = 0x3F + + +# Registers + +class LONGRANGEMODE(enum.IntEnum): + _FSK_OOK = 0b0 + _LORA = 0b1 + +class MODULATIONTYPE(enum.IntEnum): + _FSK = 0b00 + _OOK = 0b01 + +class MODULATIONSHAPINGFSK(enum.IntEnum): + _NONE = 0b00 + _GAUS_1_0 = 0b01 + _GAUS_0_5 = 0b10 + _GAUS_0_3 = 0b11 + +class MODULATIONSHAPINGOOK(enum.IntEnum): + _NONE = 0b00 + _F_BITRATE = 0b01 + _F_2_BITRATE = 0b10 + +class MODE(enum.IntEnum): + _SLEEP = 0b000 + _STDBY = 0b001 + _FSTX = 0b010 + _TX = 0b011 + _FSRX = 0b100 + _RX = 0b101 + +REG_OP_MODE = bitstruct("REG_OP_MODE", 8, [ + ("MODE", 3), + ("MODULATION_SHAPING", 2), + ("MODULATION_TYPE", 2), + ("LONG_RANGE_MODE", 1) +]) + +REG_RX_CONFIG = bitstruct("REG_RX_CONFIG", 8, [ + ("RX_TRIGGER", 3), + ("AGC_AUTO_ON", 1), + ("AFC_AUTO_ON", 1), + ("RESTART_RX_WITH_PLL_LOCK", 1), + ("RESTART_RX_WITHOUT_PLL_LOCK", 1), + ("RESTART_RX_ON_COLLISION", 1) +]) + +class RSSISMOOTHING(enum.IntEnum): + _2_SAMPLES = 0b000 + _4_SAMPLES = 0b001 + _8_SAMPLES = 0b010 + _16_SAMPLES = 0b011 + _32_SAMPLES = 0b100 + _64_SAMPLES = 0b101 + _128_SAMPLES = 0b110 + _256_SAMPLES = 0b111 + +REG_RSSI_CONFIG = bitstruct("REG_RSSI_CONFIG", 8, [ + ("RSSI_SMOOTHING", 3), + ("RSSI_OFFSET", 5) +]) + +class RXBWMANT(enum.IntEnum): + _RX_BW_MANT_16 = 0b00 + _RX_BW_MANT_20 = 0b01 + _RX_BW_MANT_24 = 0b10 + +REG_RX_BW = bitstruct("REG_RX_BW", 8, [ + ("RX_BW_EXP", 3), + ("RX_BW_MANT", 2), + (None, 3) +]) + +REG_AFC_BW = bitstruct("REG_AFC_BW", 8, [ + ("RX_BW_EXP_AFC", 3), + ("RX_BW_MANT_AFC", 2), + (None, 3) +]) + +class OOKPEAKTHRESHSTEP(enum.IntEnum): + _0_5_dB = 0b000 + _1_0_dB = 0b001 + _1_5_dB = 0b010 + _2_0_dB = 0b011 + _3_0_dB = 0b100 + _4_0_dB = 0b101 + _5_0_dB = 0b110 + _6_0_dB = 0b111 + +class OOKTHRESHTYPE(enum.IntEnum): + _THRESH_FIXED = 0b00 + _THRESH_PEAK = 0b01 + _THRESH_AVG = 0b10 + +REG_OOK_PEAK = bitstruct("REG_OOK_PEAK", 8, [ + ("OOK_PEAK_THRESH_STEP", 3), + ("OOK_THRESH_TYPE", 2), + ("BIT_SYNC_ON", 1), + (None, 2) +]) + +class OOKPEAKTHREASHDEC(enum.IntEnum): + _ONCE_PER_CHIP = 0b000 + _ONCE_EVERY_2_CHIPS = 0b001 + _ONCE_EVERY_4_CHIPS = 0b010 + _ONCE_EVERY_8_CHIPS = 0b011 + _TWICE_EACH_CHIP = 0b100 + _4_TIMES_EACH_CHIP = 0b101 + _8_TIMES_EACH_CHIP = 0b110 + _16_TIMES_EACH_CHIP = 0b111 + +class OOKAVGOFFSET(enum.IntEnum): + _0_dB = 0b00 + _2_dB = 0b01 + _4_dB = 0b10 + _6_dB = 0b11 + +class OOKAVGTHRESHFILT(enum.IntEnum): + _CHRATE_OVER_32_PI = 0b00 + _CHRATE_OVER_8_PI = 0b01 + _CHRATE_OVER_4_PI = 0b10 + _CHRATE_OVER_2_PI = 0b11 + +REG_OOK_AVG = bitstruct("REG_OOK_AVG", 8, [ + ("OOK_AVG_THRESH_FILT", 2), + ("OOK_AVG_OFFSET", 2), + (None, 1), + ("OOK_PEAK_THRESH_DEC", 3) +]) + +REG_AFC_FEI = bitstruct("REG_AFC_FEI", 8, [ + ("AFC_AUTO_CLEAR_ON", 1), + ("AFC_CLEAR", 1), + (None, 2), + ("AFC_START", 1), + (None, 3) +]) + +class PREAMBLEDETECTORSIZE(enum.IntEnum): + _1_BYTE = 0b00 + _2_BYTE = 0b01 + _3_BYTE = 0b10 + +REG_PREAMBLE_DETECT = bitstruct("REG_PREAMBLE_DETECT", 8, [ + ("PREAMBLE_DETECTOR_TOL", 5), + ("PREAMBLE_DETECTOR_SIZE", 2), + ("PREAMBLE_DETECTOR_ON", 1) +]) + +class CLKOUT(enum.IntEnum): + _FXOSC = 0b000 + _FXOSC_OVER_2 = 0b001 + _FXOSC_OVER_4 = 0b010 + _FXOSC_OVER_8 = 0b011 + _FXOSC_OVER_16 = 0b100 + _FXOSC_OVER_32 = 0b101 + _RC = 0b110 + _OFF = 0b111 + +REG_OSC = bitstruct("REG_OSC", 8, [ + ("CLK_OUT", 3), + ("RC_CAL_START", 1), + (None, 4) +]) + +class AUTORESTARTRXMODE(enum.IntEnum): + _MODE_OFF = 0b00 + _MODE_ON_WAIT_RELOCK = 0b01 + _MODE_ON_WAIT_LOCK = 0b10 + +class PREAMBLEPOLARITY(enum.IntEnum): + _AA = 0b0 + _55 = 0b1 + +class SYNCON(enum.IntEnum): + _SYNC_OFF = 0b0 + _SYNC_ON = 0b1 + +class FIFOFILLCONDITION(enum.IntEnum): + _COND_SYNC_INT = 0b0 + _COND_FILL = 0b1 + +REG_SYNC_CONFIG = bitstruct("REG_SYNC_CONFIG", 8, [ + ("SYNC_SIZE", 3), + ("FIFO_FILL_CONDITION", 1), + ("SYNC_ON", 1), + ("PREAMBLE_POLARITY", 1), + ("AUTOSTART_RX_MODE", 2) +]) + +class PACKETFORMAT(enum.IntEnum): + _FIXED_LENGTH = 0b0 + _VARIABLE_LENGTH = 0b1 + +class DCFREEENCODING(enum.IntEnum): + _NONE = 0b00 + _MANCHESTER = 0b01 + _WHITENING = 0b10 + +class ADDRESSFILTERING(enum.IntEnum): + _NONE = 0b00 + _NODE_ONLY = 0b01 + _NODE_OR_BROADCAST = 0b10 + +class WHITENINGTYPE(enum.IntEnum): + _CCITT = 0b0 + _IBM = 0b1 + +REG_PACKET_CONFIG_1 = bitstruct("REG_PACKET_CONFIG_1", 8, [ + ("CRC_WHITENING_TYPE", 1), + ("ADDRESS_FILTERING", 2), + ("CRC_AUTO_CLEAR_OFF", 1), + ("CRC_ON", 1), + ("DC_FREE", 2), + ("PACKET_FORMAT", 1) +]) + +class DATAMODE(enum.IntEnum): + _CONTINUOUS = 0b0 + _PACKET = 0b1 + +REG_PACKET_CONFIG_2 = bitstruct("REG_PACKET_CONFIG_2", 8, [ + ("PAYLOAD_LENGTH_10_8", 3), + ("BEACON_ON", 1), + ("IO_HOME_POWER_FRAME", 1), + ("IO_HOME_ON", 1), + ("DATA_MODE", 1), + (None, 1) +]) + +class TXSTARTCONDITION(enum.IntEnum): + _FIFO_LEVEL = 0b0 + _FIFO_EMPTY = 0b1 + +REG_FIFO_THRESH = bitstruct("REG_FIFO_THRESH", 8, [ + ("FIFO_THRESHOLD", 6), + (None, 1), + ("TX_START_CONDITION", 1) +]) + +class IDLEMODE(enum.IntEnum): + _STANDBY = 0b0 + _SLEEP = 0b1 + +class FROMSTART(enum.IntEnum): + _TO_LOWPOWER = 0b00 + _TO_RECEIVE = 0b01 + _TO_TRANSMIT = 0b10 + _TO_TX_ON_FIFO = 0b11 + +class LOWPOWERSELECTION(enum.IntEnum): + _SEQUENCER_OFF = 0b0 + _IDLE = 0b1 + +class FROMIDLE(enum.IntEnum): + _TO_TRANSMIT = 0b0 + _TO_RECEIVE = 0b1 + +class FROMTRANSMIT(enum.IntEnum): + _TO_LOWPOWER = 0b0 + _TO_RECEIVE = 0b1 + +REG_SEQ_CONFIG_1 = bitstruct("REG_SEQ_CONFIG_1", 8, [ + ("FROM_TRANSMIT", 1), + ("FROM_IDLE", 1), + ("LOW_POWER_SELECTION", 1), + ("FROM_START", 2), + ("IDLE_MODE", 1), + ("SEQUENCER_STOP", 1), + ("SEQUENCER_START", 1) +]) + +class FROMPACKETRECEIVED(enum.IntEnum): + _TO_SEQUENCER_OFF = 0b000 + _TO_TRANSMIT = 0b001 + _TO_LOWPOWER = 0b010 + _TO_RECEIVE_FS = 0b011 + _TO_RECEIVE = 0b100 + +class FROMRXTIMEOUT(enum.IntEnum): + _TO_RECEIVE = 0b00 + _TO_TRANSMIT = 0b01 + _TO_LOWPOWER = 0b10 + _TO_SEQUENCER_OFF = 0b11 + +class FROMRECEIVE(enum.IntEnum): + _TO_PACKET_RECEIVED_PAYLOAD_READY = 0b001 + _TO_LOWPOWER = 0b010 + _TO_PACKET_RECEIVED_CRC_OK = 0b011 + _TO_SEQ_OFF_RSSI = 0b100 + _TO_SEQ_OFF_SYNC = 0b101 + _TO_SEQ_OFF_PREAMBLE = 0b110 + +REG_SEQ_CONFIG_2 = bitstruct("REG_SEQ_CONFIG_2", 8, [ + ("FROM_PACKET_RECEIVED", 3), + ("FROM_RX_TIMEOUT", 2), + ("FROM_RECEIVE", 3) +]) + +class TIMERRES(enum.IntEnum): + _DISABLED = 0b00 + _64_us = 0b01 + _4100_us = 0b10 + _262_ms = 0b11 + +REG_TIMER_RESOL = bitstruct("REG_TIMER_RESOL", 8, [ + ("TIMER_2_RESOLUTION", 2), + ("TIMER_1_RESOLUTION", 2), + (None, 4) +]) + +class TEMPCHANGE(enum.IntEnum): + _TEMP_LOWER = 0b0 + _TEMP_HIGHER = 0b1 + +class TEMPTHRESHOLD(enum.IntEnum): + _5_DEG = 0b00 + _10_DEG = 0b01 + _15_DEG = 0b10 + _20_DEG = 0b11 + +REG_IMAGE_CAL = bitstruct("REG_IMAGE_CAL", 8, [ + ("TEMP_MONITOR_OFF", 1), + ("TEMP_THRESHOLD", 2), + ("TEMP_CHANGE", 1), + (None, 1), + ("IMAGE_CAL_RUNNING", 1), + ("IMAGE_CAL_START", 1), + ("AUTO_IMAGE_CAL_ON", 1) +]) + +class LOWBATTTRIM(enum.IntEnum): + _1695_mV = 0b000 + _1764_mV = 0b001 + _1835_mV = 0b010 + _1905_mV = 0b011 + _1976_mV = 0b100 + _2045_mV = 0b101 + _2116_mV = 0b110 + _2185_mV = 0b111 + +REG_LOW_BAT = bitstruct("REG_TEMP", 8, [ + ("LOW_BAT_TRIM", 3), + ("LOW_BAT_ON", 1), + (None, 4) +]) + +REG_IRQ_FLAGS_1 = bitstruct("REG_IRQ_FLAGS_1", 8, [ + ("SYNC_ADDRESS_MATCH", 1), + ("PREAMBLE_DETECT", 1), + ("TIMEOUT", 1), + ("RSSI", 1), + ("PLL_LOCK", 1), + ("TX_READY", 1), + ("RX_READY", 1), + ("MODE_READY", 1) +]) + +REG_IRQ_FLAGS_2 = bitstruct("REG_IRQ_FLAGS_2", 8, [ + ("LOW_BAT", 1), + ("CRC_OK", 1), + ("PAYLOAD_READY", 1), + ("PACKET_SENT", 1), + ("FIFO_OVERRUN", 1), + ("FIFO_LEVEL", 1), + ("FIFO_EMPTY", 1), + ("FIFO_FULL", 1) +]) diff --git a/software/glasgow/protocol/lora.py b/software/glasgow/protocol/lora.py new file mode 100644 index 000000000..7801d2de0 --- /dev/null +++ b/software/glasgow/protocol/lora.py @@ -0,0 +1,734 @@ +# Ref: LoRaWAN 1.0.3 Specification +# Accession: G00052 + +from collections import namedtuple +import time +import enum +import random +import struct +from Crypto.Hash import CMAC +from Crypto.Cipher import AES +import datetime +import re +import asyncio +import math + +from ..support.bitstruct import bitstruct +from .semtech_udp_forwarder import SemtechPacketForwarder, SemtechPacket +from ..arch.sx1272.apis import RadioSX1272APILoRa +from ..arch.sx1272 import regs_lora + +__all__ = [ + "EU863870_PARAMETERS", + "LoRa_Device_API", "LoRaWAN_Node", "LoRaWAN_Gateway", "SX1272_LoRa_Device_API" +] + +DevAddr = bitstruct("DevAddr", 32, [ + ("NwkAddr", 25), + ("NwkID", 7) +]) + +class MTYPE(enum.IntEnum): + _JOIN_REQUEST = 0b000 + _JOIN_ACCEPT = 0b001 + _UNCONFIRMED_DATA_UP = 0b010 + _UNCONFIRMED_DATA_DOWN = 0b011 + _CONFIRMED_DATA_UP = 0b100 + _CONFIRMED_DATA_DOWN = 0b101 + _RFU = 0b110 + _PROPIETARY = 0b111 + +class MAJOR(enum.IntEnum): + _LORAWAN_R1 = 0b00 + _RFUa = 0b01 + _RFUb = 0b10 + _RFUc = 0b11 + +MacHDR = bitstruct("MacHDR", 8, [ + ("Major", 2), + ("RFU", 3), + ("MType", 3) +]) + +DLsettings = bitstruct("DLsettings", 8, [ + ("RX2DataRate", 4), + ("RX1DRoffset", 3), + ("RFU", 1) +]) + +PhyPayload = namedtuple('PhyPayload', 'MacHDR MacPayload MIC') +MacPayload = namedtuple('MacPayload', 'FrameHDR FPort FramePayload') +FrameHeader = namedtuple('FrameHeader', 'DevAddr FCtrl FCnt FOpts') +JoinRequest = namedtuple('JoinRequest', 'AppEUI DevEUI DevNonce') +JoinAccept = namedtuple('JoinAccept', 'AppNonce NetID DevAddr DLSettings RxDelay CFList') + +FCtrlDownlink = bitstruct("FCtrlDownlink", 8, [ + ("FOptsLen", 4), + ("FPending", 1), + ("ACK", 1), + ("RFU", 1), + ("ADR", 1) +]) + +FCtrlUplink = bitstruct("FCtrlUplink", 8, [ + ("FOptsLen", 4), + ("ClassB", 1), + ("ACK", 1), + ("ADRACKReq", 1), + ("ADR", 1) +]) + +class MODULATION(enum.IntEnum): + _LoRa = 0 + _GFSK = 1 + +class BITRATE(enum.IntEnum): + _DR0 = 0 + _DR1 = 1 + _DR2 = 2 + _DR3 = 3 + _DR4 = 4 + _DR5 = 5 + _DR6 = 6 + _DR7 = 7 + _DR8 = 8 + _DR9 = 9 + _DR10 = 10 + _DR11 = 11 + _DR12 = 12 + _DR13 = 13 + _DR14 = 14 + _DR15 = 15 + +DataRate = namedtuple('DataRate', 'Modulation SpreadingFactor Bandwidth') +PreambleFormat = namedtuple('PreambleFormat', 'Modulation SyncWord PreambleLength') +ChannelConfiguration = namedtuple('ChannelConfiguration', 'Modulation Frequency Datarates MaxDuty') + +class REGION_PARAMETERS: + def __init__(self): + self.PREAMBLE_FORMATS = None + self.DATARATES = None + self.CHANNEL_CONF = None + self.MAX_MAC_PALOAD_SIZE = None + self.RX1_DL_DATARATE = None + self.RECEIVE_DELAY1_S = None + self.RECEIVE_DELAY2_S = None + self.JOIN_ACCEPT_DELAY1_S = None + self.JOIN_ACCEPT_DELAY2_S = None + self.MAX_FCNT_GAP = None + self.ADR_ACK_LIMIT = None + self.ADR_ACK_DELAY = None + + def get_ack_timeout(self): + pass + + def get_join_req_conf(self, chn, datr): + pass + + def get_rx1_conf(self, up_chn, up_datr): + pass + + def get_rx2_conf(self, up_chn, up_datr): + pass + + +class EU863870_PARAMETERS(REGION_PARAMETERS): + def __init__(self): + self.PREAMBLE_FORMATS = [ + PreambleFormat(MODULATION._LoRa, 0x34, 8), + PreambleFormat(MODULATION._GFSK, 0xC194C1, 5) + ] + self.DATARATES = [ + DataRate(MODULATION._LoRa, 12, 125e3), + DataRate(MODULATION._LoRa, 11, 125e3), + DataRate(MODULATION._LoRa, 10, 125e3), + DataRate(MODULATION._LoRa, 9, 125e3), + DataRate(MODULATION._LoRa, 8, 125e3), + DataRate(MODULATION._LoRa, 7, 125e3), + DataRate(MODULATION._LoRa, 7, 250e3), + DataRate(MODULATION._GFSK, 0, 50e3) + ] + self.CHANNEL_CONF = [ + ChannelConfiguration(MODULATION._LoRa, 868.10e6, [BITRATE._DR0, BITRATE._DR1, BITRATE._DR2, BITRATE._DR3, BITRATE._DR4, BITRATE._DR5], 0.01), + ChannelConfiguration(MODULATION._LoRa, 868.30e6, [BITRATE._DR0, BITRATE._DR1, BITRATE._DR2, BITRATE._DR3, BITRATE._DR4, BITRATE._DR5], 0.01), + ChannelConfiguration(MODULATION._LoRa, 868.50e6, [BITRATE._DR0, BITRATE._DR1, BITRATE._DR2, BITRATE._DR3, BITRATE._DR4, BITRATE._DR5], 0.01), + ChannelConfiguration(MODULATION._LoRa, 869.525e6, [BITRATE._DR0, BITRATE._DR1, BITRATE._DR2, BITRATE._DR3, BITRATE._DR4, BITRATE._DR5], 0.01) + ] + self.MAX_MAC_PALOAD_SIZE = [59, 59, 59, 123, 230, 230, 230, 230] + self.RX1_DL_DATARATE = [ + [BITRATE._DR0, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0], + [BITRATE._DR1, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0], + [BITRATE._DR2, BITRATE._DR1, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0], + [BITRATE._DR3, BITRATE._DR2, BITRATE._DR1, BITRATE._DR0, BITRATE._DR0, BITRATE._DR0], + [BITRATE._DR4, BITRATE._DR3, BITRATE._DR2, BITRATE._DR1, BITRATE._DR0, BITRATE._DR0], + [BITRATE._DR5, BITRATE._DR4, BITRATE._DR3, BITRATE._DR2, BITRATE._DR1, BITRATE._DR0], + [BITRATE._DR6, BITRATE._DR5, BITRATE._DR4, BITRATE._DR3, BITRATE._DR2, BITRATE._DR1], + [BITRATE._DR7, BITRATE._DR6, BITRATE._DR5, BITRATE._DR4, BITRATE._DR3, BITRATE._DR2], + ] + self.RECEIVE_DELAY1_S = 1 + self.RECEIVE_DELAY2_S = 2 + self.JOIN_ACCEPT_DELAY1_S = 5 + self.JOIN_ACCEPT_DELAY2_S = 6 + self.MAX_FCNT_GAP = 16384 + self.ADR_ACK_LIMIT = 64 + self.ADR_ACK_DELAY = 32 + + def get_ack_timeout(self): + return 2 # TODO +/- 1s random + + def get_join_req_conf(self, chn, datr): + return (chn, datr) + + def get_rx1_conf(self, up_chn, up_datr): + return (up_chn, up_datr) + + def get_rx2_conf(self, up_chn, up_datr): + return (3, 0) + +class LoRa_Device_API: + async def configure(self, freq, bw, sf, pwr, codr, syncword, prelength): + pass + + async def transmit(self, payload): + pass + + async def receive(self, symTimeout, onpayload): + pass + + async def listen(self, onpayload): + pass + + async def sleep(self): + pass + + async def wakeup(self): + pass + +class LoRaWAN_Device: + def __init__(self, api, region): + assert isinstance(api, LoRa_Device_API) + self.api = api + self.rfparams = region() + self.rfconf = None + self.initTmst = int(datetime.datetime.utcnow().timestamp() * 1e6) + + def _get_symbol_period(self, datr): + bw = self.rfparams.DATARATES[datr].Bandwidth + sf = self.rfparams.DATARATES[datr].SpreadingFactor + return (2 ** sf) / bw + + def _get_symbol_timeout(self, timeout_s, datr): + ts = self._get_symbol_period(datr) + return math.ceil(timeout_s / ts) + + def _get_timestamp(self): + return int(datetime.datetime.utcnow().timestamp() * 1e6) - self.initTmst + + def _pad_16(self, data): + n = len(data) % 16 + if n: + data += bytes(16 - n) + return data + + def _byte_xor(self, ba1, ba2): + return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)]) + + def validate_rf_params(self, freq, bw, sf, pwr, codr): + channel_conf = None + + for chn in self.rfparams.CHANNEL_CONF: + if chn.Frequency == freq: + channel_conf = chn + break + + _bw = None + _sf = None + if channel_conf != None: + for drate in channel_conf.Datarates: + drate = self.rfparams.DATARATES[drate] + if drate.SpreadingFactor == sf and drate.Bandwidth == bw: + _bw = bw + _sf = sf + break + else: + freq = None + + if pwr > 13: + pwr = None # TODO PA_BOOST 20 dBm + + if codr not in range(5, 9): + codr = None + return (freq, _bw, _sf, pwr, codr) + + async def configure_by_channel(self, channel, drate): + freq = self.rfparams.CHANNEL_CONF[channel].Frequency + bw = self.rfparams.DATARATES[drate].Bandwidth + sf = self.rfparams.DATARATES[drate].SpreadingFactor + pwr = 13 # dBm + codr = 5 # 4/5 + self.rfconf = (channel, drate) + await self.configure(freq, bw, sf, pwr, codr) + + async def configure(self, freq, bw, sf, pwr, codr): + freq, bw, sf, pwr, codr = self.validate_rf_params(freq, bw, sf, pwr, codr) + if freq == None or bw == None or sf == None or pwr == None or codr == None: + return False + + sword = self.rfparams.PREAMBLE_FORMATS[0].SyncWord + plength = self.rfparams.PREAMBLE_FORMATS[0].PreambleLength + await self.api.configure(freq, bw, sf, pwr, codr, sword, plength) + return True + + +class LoRaWAN_Node(LoRaWAN_Device): + def __init__(self, api, region, appkey, deveui, appeui, logger, frame_cb): + super().__init__(api, region) + self.__AppKey = appkey.to_bytes(16, byteorder='big') + self.__DevEUI = deveui + self.__AppEUI = appeui + self.logger = logger + self.__fcnt = 0 + self.__ackflagup = False + self.__ackflagdwn = False + self.__frame_cb = frame_cb + random.seed() + + def __gen_join_request(self): + machdr = MacHDR(MTYPE._JOIN_REQUEST, 0, MAJOR._LORAWAN_R1) + nonce = random.getrandbits(16) + reqpl = struct.pack('QQH', self.__AppEUI, self.__DevEUI, nonce) + phypl = machdr.to_bytes() + phypl += reqpl + cmac = CMAC.new(self.__AppKey, ciphermod=AES) + cmac.update(phypl) + cmac = cmac.digest()[0:4] + phypl += cmac + return phypl + + def __process_join_accept(self, data, crcerr, snr, rssi, codr): + if data == None: + return False + + aes = AES.new(self.__AppKey, AES.MODE_ECB) + + tmp = aes.encrypt(data[1:]) + cmac = CMAC.new(self.__AppKey, ciphermod=AES) + mic = cmac.update(bytes([data[0]]) + tmp[0:-4]).digest()[0:4] + if mic != tmp[-4:]: + return False + + jacc = tmp[0:-4] + appNonce = tmp[0:3] + netId = tmp[3:6] + one = (1).to_bytes(1, 'little') + two = (2).to_bytes(1, 'little') + self.__DevAddr = DevAddr.from_int(int.from_bytes(jacc[6:10], 'little')) + self.__DLsettings = DLsettings.from_int(int(jacc[10])) + + self.logger.debug("Download Settings: {}".format(self.__DLsettings)) + + nkey = one + appNonce + netId + self.__devNonce + nkey = self._pad_16(nkey) + self.__NwkSKey = aes.encrypt(nkey) + + akey = two + appNonce + netId + self.__devNonce + akey = self._pad_16(akey) + self.__AppSKey = aes.encrypt(akey) + + self.logger.debug("Joined network. DevAddr: {}".format(self.__DevAddr)) + + return True + + async def join_network(self): + joinreq = self.__gen_join_request() + self.__devNonce = joinreq[17:19] + chn, datr = self.rfparams.get_join_req_conf(*self.rfconf) + await self.configure_by_channel(chn, datr) + + self.logger.debug("Joining network ...") + await self.api.transmit(joinreq) + await asyncio.sleep(self.rfparams.JOIN_ACCEPT_DELAY1_S) + chn1, datr1 = self.rfparams.get_rx1_conf(chn, datr) + # Time Between Windows + tbw = self.rfparams.JOIN_ACCEPT_DELAY2_S - self.rfparams.JOIN_ACCEPT_DELAY1_S + # Symbol period + symTimeout_s = 0.7 * tbw + symTimeout = self._get_symbol_timeout(symTimeout_s, datr1) + await self.configure_by_channel(chn1, datr1) + self.logger.debug("Listen RX1 ...") + joined = await self.api.receive(symTimeout, self.__process_join_accept) + + if joined: + return joined + + await asyncio.sleep(self.rfparams.JOIN_ACCEPT_DELAY2_S - self.rfparams.JOIN_ACCEPT_DELAY1_S - symTimeout_s) + chn2, datr2 = self.rfparams.get_rx2_conf(chn, datr) + # Symbol period + symTimeout = self._get_symbol_timeout(symTimeout_s, datr2) + await self.configure_by_channel(chn2, datr2) + self.logger.debug("Listen RX2 ...") + joined = await self.api.receive(symTimeout, self.__process_join_accept) + return joined + + def __create_encryption_sequence(self, payload, dir, fcnt, key): + aes = AES.new(key, AES.MODE_ECB) + k = math.ceil(len(payload)/16) + S = bytes(0) + for i in range(1, k + 1): + A = struct.pack(' 0: + fport = macpayload[7 + optslen] + key = self.__NwkSKey if fport == 0 else self.__AppSKey + fpl = macpayload[8 + optslen:] + fpl = self.__decrypt_downlink(fpl, fCnt, key) + + self.__frame_cb(fport, fpl) + return True + + +class LoRaWAN_Gateway(LoRaWAN_Device): + def __init__(self, api, region, server, port, eui, downlink_only, uplink_only, logger): + super().__init__(api, region) + self.pkt_fwd = SemtechPacketForwarder(server, port, eui, self.get_txack_error, self.initTmst, logger) + self.logger = logger + self.uplink_only = uplink_only + self.downlink_only = downlink_only + + def get_txack_error(self, pkt): + # Packets are sometime late-ish from the UDP forwarder, here we give a 0.5 s margin + # These two lines can be commented out to receive all packets + #if pkt.tmst < self._get_timestamp() - 1 * 1e6: + # return 'TOO_LATE' + freq = pkt.freq * 1e6 + m = re.match(r'SF(\d+)BW(\d+)', pkt.datr) + sf = int(m.groups()[0]) + bw = int(m.groups()[1]) * 1e3 + codr = int(pkt.codr[-1]) + pwr = pkt.pwr + freq, bw, sf, _, codr = self.validate_rf_params(freq, bw, sf, pwr, codr) + if bw == None or freq == None: + return 'TX_FREQ' + return 'NONE' + + async def main(self): + # Save initial configuration + chn1, datr1 = self.rfconf + # Create parameters to configure each time we listen + rxfreq = self.rfparams.CHANNEL_CONF[chn1].Frequency + rxbw = self.rfparams.DATARATES[datr1].Bandwidth + rxsf = self.rfparams.DATARATES[datr1].SpreadingFactor + rxpwr = 13 # Not used in rx, but we need a param to configure + # Timeout for each receive + symTimeout_s = 0.25 + fastTimeout_s = 7 + self.logger.info("Listening ...") + while True: + if not self.downlink_only: + # 1. Configure RF parameters + symTimeout = self._get_symbol_timeout(symTimeout_s, datr1) + await self.configure_by_channel(chn1, datr1) + # 2. Listen with a given symbol timeout + if self.uplink_only: + payload, crcerr, snr, rssi, codr = await self.api.listen(None) + else: + payload, crcerr, snr, rssi, codr = await self.api.receive(symTimeout) + if None != payload: + datr = 'SF{}BW{}'.format(rxsf, round(rxbw/1e3)) + codr = '4/{}'.format(codr) + chan = 0 + freq = rxfreq + pkt = SemtechPacket(0, freq, chan, -1 if crcerr == 1 else 1, datr, rxpwr, codr, rssi, snr, payload) + self.logger.info("Device Uplink : {} {}".format(self._get_timestamp(), pkt)) + # 3. Send packet to UDP forwarder + self.pkt_fwd.put_dev_uplink(pkt) + # Set fast timeout to send gateway downlink packets more precisely + symTimeout_s = 0.05 + + if fastTimeout_s <= 0: + fastTimeout_s = 7 + symTimeout_s = 0.25 + else: + fastTimeout_s -= symTimeout_s + + # 4. Execute forwarder process + await self.pkt_fwd.main() + + # 5. Get any downlink packet + appData = self.pkt_fwd.get_dev_downlink() + later = [] + now = [] + # Only transmit nodes packets, ignore downlinks + if self.uplink_only: + continue + + while appData != None: + # 6. Send packet now or later based on timestamp + if appData.tmst <= self._get_timestamp(): + now.append(appData) + else: + later.append(appData) + appData = self.pkt_fwd.get_dev_downlink() + + for pkt in now: + self.logger.info("Gateway Downlink : {} {}".format(self._get_timestamp(), pkt)) + freq = pkt.freq * 1e6 + m = re.match(r'SF(\d+)BW(\d+)', pkt.datr) + sf = int(m.groups()[0]) + bw = int(m.groups()[1]) * 1e3 + codr = int(pkt.codr[-1]) + await self.api.configure(freq, bw, sf, 13, codr) + await self.api.transmit(pkt.payload) + + for pkt in later: + # 7. If the packet is to be sent later, put in back in the queue + self.pkt_fwd.put_dev_downlink(pkt) + + +class SX1272_LoRa_Device_API(LoRa_Device_API): + def __init__(self, iface, logger): + self.lower = RadioSX1272APILoRa(iface, logger) + self.logger = logger + + async def __get_pkt_snr(self): + regsnr = await self.lower.get_pkt_snr() + return regsnr/4 + + async def __get_pkt_rssi(self, snr): + regrssi = await self.lower.get_pkt_rssi() + if snr >= 0: + return -139 + regrssi + else: + return -139 + regrssi + snr + + async def configure(self, freq, bw, sf, pwr, codr, syncword=0x34, prelength=8): + await self.lower.set_opmode_mode(regs_lora.MODE._SLEEP) + await self.lower.set_opmode_lora(regs_lora.LONGRANGEMODE._LORA) + await self.lower.set_modem_config_1_rxcrcon(1) + await self.lower.set_sync_word(syncword) + await self.lower.set_preamble_length(prelength) + bw = { + 125e3: regs_lora.MODEMBW._BW_125kHz, + 250e3: regs_lora.MODEMBW._BW_250kHz + }[bw] + sf = { + 7: regs_lora.SPREADINGFACTOR._SPREAD_7, + 8: regs_lora.SPREADINGFACTOR._SPREAD_8, + 9: regs_lora.SPREADINGFACTOR._SPREAD_9, + 10: regs_lora.SPREADINGFACTOR._SPREAD_10, + 11: regs_lora.SPREADINGFACTOR._SPREAD_11, + 12: regs_lora.SPREADINGFACTOR._SPREAD_12, + }[sf] + frf = math.floor((freq * (2**19)) / 32e6) + await self.lower.set_modem_config_1_bw(bw) + codr = { + 5: regs_lora.CODINGRATE._4_OVER_5, + 6: regs_lora.CODINGRATE._4_OVER_6, + 7: regs_lora.CODINGRATE._4_OVER_7, + 8: regs_lora.CODINGRATE._4_OVER_8, + }[codr] + await self.lower.set_modem_config_1_codingrate(codr) + if sf == regs_lora.SPREADINGFACTOR._SPREAD_12 or sf == regs_lora.SPREADINGFACTOR._SPREAD_11: + await self.lower.set_modem_config_1_ldoptim(1) + else: + await self.lower.set_modem_config_1_ldoptim(0) + await self.lower.set_modem_config_2_spreading(sf) + await self.lower.set_frf(frf) + await self.lower.set_pa_config_outpower(pwr + 1) # RFIO pin + await self.lower.set_opmode_mode(regs_lora.MODE._STDBY) + + async def transmit(self, payload): + addr = await self.lower.get_fifo_tx_base_addr() + await self.lower.set_fifo_addr_ptr(addr) + await self.lower.set_fifo(payload) + await self.lower.set_payload_length(len(payload)) + await self.lower.set_opmode_mode(regs_lora.MODE._TX) + irqs = await self.lower.get_irq_flags() + while irqs.TX_DONE != 1: + await asyncio.sleep(0.1) + irqs = await self.lower.get_irq_flags() + self.logger.debug("Sent payload {}".format(payload)) + + async def receive(self, symTimeout = 15, onpayload = None): + data = None + crcerr = None + snr = None + rssi = None + codr = None + + await self.lower.set_symbol_timeout(symTimeout) + await self.lower.clear_irq_flags() + addr = await self.lower.get_fifo_rx_base_addr() + await self.lower.set_fifo_addr_ptr(addr) + await self.lower.set_opmode_mode(regs_lora.MODE._RXSINGLE) + irqs = await self.lower.get_irq_flags() + while irqs.RX_DONE == 0 and irqs.RX_TIMEOUT == 0: + await asyncio.sleep(0.1) + irqs = await self.lower.get_irq_flags() + + nb = await self.lower.get_rx_nb_bytes() + + if irqs.RX_TIMEOUT != 1 and nb != 0: + addr = await self.lower.get_fifo_rx_curr_addr() + await self.lower.set_fifo_addr_ptr(addr) + data = await self.lower.get_fifo(nb) + crcerr = irqs.PAYLOAD_CRC_ERROR + snr = await self.__get_pkt_snr() + rssi = await self.__get_pkt_rssi(snr) + codr = await self.lower.get_modem_stat() + codr = { + regs_lora.CODINGRATE._4_OVER_5: 5, + regs_lora.CODINGRATE._4_OVER_6: 6, + regs_lora.CODINGRATE._4_OVER_7: 7, + regs_lora.CODINGRATE._4_OVER_8: 8, + }[codr.RX_CODING_RATE] + + if onpayload != None: + return onpayload(data, crcerr, snr, rssi, codr) + else: + return data, crcerr, snr, rssi, codr + + async def listen(self, onpayload): + await self.lower.clear_irq_flags() + addr = await self.lower.get_fifo_rx_base_addr() + await self.lower.set_fifo_addr_ptr(addr) + await self.lower.set_opmode_mode(regs_lora.MODE._RXCONT) + while True: + await self.lower.clear_irq_flags() + irqs = await self.lower.get_irq_flags() + while irqs.RX_DONE == 0: + await asyncio.sleep(0.1) + irqs = await self.lower.get_irq_flags() + nb = await self.lower.get_rx_nb_bytes() + addr = await self.lower.get_fifo_rx_curr_addr() + await self.lower.set_fifo_addr_ptr(addr) + + data = await self.lower.get_fifo(nb) + crcerr = irqs.PAYLOAD_CRC_ERROR + snr = await self.__get_pkt_snr() + rssi = await self.__get_pkt_rssi(snr) + codr = await self.lower.get_modem_stat() + codr = { + regs_lora.CODINGRATE._4_OVER_5: 5, + regs_lora.CODINGRATE._4_OVER_6: 6, + regs_lora.CODINGRATE._4_OVER_7: 7, + regs_lora.CODINGRATE._4_OVER_8: 8, + }[codr.RX_CODING_RATE] + + if onpayload is not None: + onpayload(data, crcerr, snr, rssi, codr) + else: + return data, crcerr, snr, rssi, codr + + async def sleep(self): + await self.lower.set_opmode_mode(regs_lora.MODE._SLEEP) + + async def wakeup(self): + await self.lower.set_opmode_mode(regs_lora.MODE._STDBY) \ No newline at end of file diff --git a/software/glasgow/protocol/semtech_udp_forwarder.py b/software/glasgow/protocol/semtech_udp_forwarder.py new file mode 100644 index 000000000..ce047b797 --- /dev/null +++ b/software/glasgow/protocol/semtech_udp_forwarder.py @@ -0,0 +1,204 @@ +from collections import namedtuple +import socket +import random +import struct +import datetime +import math +import base64 +import asyncio +import queue +import json + +PROTOCOL_VERSION = 2 + +PKT_PUSH_DATA = 0 +PKT_PUSH_ACK = 1 +PKT_PULL_DATA = 2 +PKT_PULL_RESP = 3 +PKT_PULL_ACK = 4 +PKT_TX_ACK = 5 + +SemtechPacket = namedtuple('SemtechPacket', 'tmst freq chan crc datr pwr codr rssi snr payload') + +class SemtechPacketForwarder: + def __init__(self, server, port, gw_eui, pkt_verif, initTmst, logger): + self.ip = socket.gethostbyname(server) + self.port = port + self.gw_eui = gw_eui + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.setblocking(False) + self.sock.connect((self.ip, self.port)) + self.initTmst = initTmst + self.logger = logger + self.pkt_verif = pkt_verif + + self.rcv_pkt_cnt = 0 # All packets + self.rcv_pkt_crc_cnt = 0 # Valid CRC + self.fwd_pkt_cnt = 0 + self.fwd_pkt_cnt_ack = 0 + self.pct_pkt_ack = 100 + self.rcv_downlinks = 0 + self.emitted = 0 + + self.packets_dict = {} + + self.__devDownlinkQueue = queue.SimpleQueue() + self.__devUplinkQueue = queue.SimpleQueue() + + def __create_pkt(self, pkt): + tmst = '"tmst":{}'.format(int(datetime.datetime.utcnow().timestamp() * 1e6) - self.initTmst) + freq = '"freq":{}'.format(pkt.freq/1e6) + chan = '"chan":{}'.format(pkt.chan) + rfch = '"rfch":0' + stat = '"stat":{}'.format(pkt.crc) + modu = '"modu":"LORA"' + datr = '"datr":"{}"'.format(pkt.datr) + codr = '"codr":"{}"'.format(pkt.codr) + rssi = '"rssi":{}'.format(math.floor(pkt.rssi)) + lsnr = '"lsnr":{}'.format(pkt.snr) + size = '"size":{}'.format(len(pkt.payload)) + data = '"data":"{}"'.format(base64.b64encode(pkt.payload).decode()) + + buf = ','.join((tmst, freq, chan, rfch, stat, modu, datr, codr, rssi, lsnr, size, data)) + return '{'+buf+'}' + + def __create_stat(self): + time = '"time":"{}"'.format(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S GMT')) + rxnb = '"rxnb":{}'.format(self.rcv_pkt_cnt) + rxok = '"rxok":{}'.format(self.rcv_pkt_crc_cnt) + rxfw = '"rxfw":{}'.format(self.fwd_pkt_cnt) + ackr = '"ackr":{}'.format(self.pct_pkt_ack) + dwnb = '"dwnb":{}'.format(self.rcv_downlinks) + txnb = '"txnb":{}'.format(self.emitted) + + buf = ','.join((time, rxnb, rxok, rxfw, ackr, dwnb, txnb)) + return '"stat":{'+buf+'}' + + def __create_rxpk(self, pkts): + if len(pkts) == 0: + return '' + + buf = '"rxpk":[' + for pkt in pkts: + buf += self.__create_pkt(pkt) + ',' + buf = buf[:-1] + return buf+']' + + def __create_msg(self, pkts, stat): + buf = '{' + buf += self.__create_rxpk(pkts) + if stat and len(pkts) > 0: + buf += ',' + if stat: + buf += self.__create_stat() + buf += '}' + return buf + + def __create_txack(self, nonce, error): + data = struct.pack('>BHBQ', + PROTOCOL_VERSION, + nonce, + PKT_TX_ACK, self.gw_eui) + return data + '{{"tkpk_ack":{{"error":"{}"}}}}'.format(error).encode() + + def __process_app_downlink(self, payload): + data = struct.unpack_from('>BBBB%ds' % (len(payload)-4), payload) + header = data[0:4] + nonce = int.from_bytes(header[1:3], 'little') + snonce = format(nonce, 'X') + + if header[0] != PROTOCOL_VERSION: + return + + if header[3] == PKT_PUSH_ACK and snonce in self.packets_dict: + self.logger.debug("Semtech FWD Push Ack") + del self.packets_dict[snonce] + self.fwd_pkt_cnt_ack += 1 + return + + if header[3] == PKT_PULL_ACK and snonce in self.packets_dict: + self.logger.debug("Semtech FWD Pull Ack") + del self.packets_dict[snonce] + return + + if header[3] == PKT_PULL_RESP: + self.logger.debug("Semtech FWD Pull Resp") + pkt = json.loads(data[4].decode('utf-8')) + pkt = pkt['txpk'] + if 'imme' in pkt and pkt['imme']: + pkt['tmst'] = int(datetime.datetime.utcnow().timestamp() * 1e6) - self.initTmst + pkt = SemtechPacket(pkt['tmst'], pkt['freq'], 0, not pkt['ncrc'], pkt['datr'], pkt['powe'], pkt['codr'], 0, 0, base64.b64decode(pkt['data'])) + error = self.pkt_verif(pkt) + self.logger.debug("Pkt {}, error {}".format(pkt, error)) + if error == 'NONE': + self.__devDownlinkQueue.put(pkt) + resp = self.__create_txack(nonce, error) + self.sock.send(resp) + + def __process_dev_uplink(self, pkt): + msg = self.__create_msg([pkt], True) + data = struct.pack('>BBBBQ', + PROTOCOL_VERSION, + random.randint(0, 0xFF), random.randint(0, 0xFF), + PKT_PUSH_DATA, self.gw_eui) + msg.encode() + nonce = int.from_bytes(data[1:3], 'little') + nonce = format(nonce, 'X') + self.packets_dict[nonce] = 0 + self.logger.debug("App uplink: {}".format(data)) + self.sock.send(data) + + def __pull_data(self): + data = struct.pack('>BBBBQ', + PROTOCOL_VERSION, + random.randint(0, 0xFF), random.randint(0, 0xFF), + PKT_PULL_DATA, self.gw_eui + ) + nonce = int.from_bytes(data[1:3], 'little') + nonce = format(nonce, 'X') + self.packets_dict[nonce] = 0 + self.sock.send(data) + + async def main(self): + appDownlink = None + try: + appDownlink = self.__app_downlink_task() + except: + pass + if None != appDownlink: + self.__process_app_downlink(appDownlink) + + devUplink = None + try: + devUplink = self.__devUplinkQueue.get_nowait() + except: + pass + if None != devUplink: + self.__process_dev_uplink(devUplink) + + self.__pull_data() + + def __app_downlink_task(self): + data = [] + try: + data = self.sock.recv(2048) + except: + pass + + if len(data) > 0: + return data + else: + return None + + def put_dev_uplink(self, data): + self.__devUplinkQueue.put(data) + + def get_dev_downlink(self): + data = None + try: + data = self.__devDownlinkQueue.get_nowait() + except: + pass + return data + + def put_dev_downlink(self, pkt): + self.__devDownlinkQueue.put(pkt)