pycom / pycom-micropython-sigfox

A fork of MicroPython with the ESP32 port customized to run on Pycom's IoT multi-network modules.
MIT License
198 stars 167 forks source link

LoRa RX2 issue #154

Closed jingai closed 6 years ago

jingai commented 6 years ago

We are evaluating Pycom products for a project we are working on and I happen to have access to a RedwoodComm 5020A LoRa Tester.

We're currently using a Pycom LoPy with firmware 1.17.3b1.

On the tester, I've selected US Certification Test v1.2 for US915 region. In test 11, RX2 Receive Window Test, it is failing because it appears the LoPy is echoing back the response to the last command it saw in the RX1 window rather than the data it received in the RX2 window.

Output from the tester:

[ LINK MESSAGE ]
L CH SF  BW    POW  TIME  FCNT Adr Ack B FP  M  CMD        CNTS             
U  2  7 125  -21.1  60.1s 007A  1   0 0 224 U DownlinkCounter    Cnt=4 
D  2  7 500  -30.0   ---- 0008  1   0 - 000 U RXParamSetReq      RX1DROffset=0,RX2DR=8 
U  6  7 125  -23.5  8.08s 007C  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
D R2 12 500  -85.0   ---- 0009  1   0 - 100 U DataDown           ByteLen=10 
U  0  7 125  -19.1  10.1s 007E  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
D R2 12 500  -85.0   ---- 000A  1   0 - 100 U DataDown           ByteLen=10 
U  0  7 125  -23.6  15.1s 0081  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
D R2 12 500  -85.0   ---- 000B  1   0 - 100 U DataDown           ByteLen=10 
U  1  7 125  -21.6  5.01s 0082  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
D R2 12 500  -85.0   ---- 000C  1   0 - 100 U DataDown           ByteLen=10 
U  6  7 125  -19.8  45.1s 008B  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
D R2 12 500  -85.0   ---- 000D  1   0 - 100 U DataDown           ByteLen=10 
U  7  7 125  -23.1  25.1s 0090  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
D R2 12 500  -85.0   ---- 000E  1   0 - 100 U DataDown           ByteLen=10 
U  2  7 125  -20.0  15.1s 0093  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
D R2 12 500  -85.0   ---- 000F  1   0 - 100 U DataDown           ByteLen=10
jingai commented 6 years ago

Some more info: it seems once it gets into this state, it is 'stuck'. The RX1 Receive Window Test also fails at this point in the same way. If I reboot the LoPy and avoid the RX2 test, the RX1 test will succeed.

edit: I take it back. RX1 test has the same problem (though it did work once when I tried it). Maybe a problem with the handling of RXParamSetReq?

robert-hh commented 6 years ago

Just for interest: What is the test set-up of the LoPy, and especially: which python script are you using at the LoPy?

jingai commented 6 years ago

It's just a LoPy on the expansion board powered over USB with one of Pycom's 915 antennas.

I'm using the certification.py script from Pycom:

#!/usr/bin/env python
#
# Copyright (c) 2016, Pycom Limited.
#
# This software is licensed under the GNU GPL version 3 or any
# later version, with permitted additional terms. For more information
# see the Pycom Licence v1.0 document supplied with this file, or
# available at https://www.pycom.io/opensource/licensing
#

from network import LoRa
import time
import binascii
import socket
import struct

DEV_EUI = '3C CA 00 E6 9C 84 44 9A'
APP_EUI = 'AD A4 DA E3 AC 12 67 6B'
APP_KEY = '11 B0 28 2A 18 9B 75 B0 B4 D2 D8 C7 FA 38 54 8B'

DEV_ADDR = '00 00 00 0A'
NWK_SWKEY = '2B 7E 15 16 28 AE D2 A6 AB F7 15 88 09 CF 4F 3C'
APP_SWKEY = '2B 7E 15 16 28 AE D2 A6 AB F7 15 88 09 CF 4F 3C'

class Compliance:
    def __init__(self, activation=LoRa.OTAA, region=LoRa.US915):
        dr = 3
        if region == LoRa.EU868:
            dr = 5

        self.lora = LoRa(mode=LoRa.LORAWAN, region=region)

        self.lora.compliance_test(True, 0, False)  # enable testing
        self.activation = activation

        self._join()

        self.s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
        self.s.setsockopt(socket.SOL_LORA, socket.SO_DR, dr)
        self.s.setsockopt(socket.SOL_LORA, socket.SO_CONFIRMED, False)

    def _join(self):
        if self.activation == LoRa.OTAA:
            dev_eui = binascii.unhexlify(DEV_EUI.replace(' ',''))
            app_eui = binascii.unhexlify(APP_EUI.replace(' ',''))
            app_key = binascii.unhexlify(APP_KEY.replace(' ',''))
            self.lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0)
        else:
            dev_addr = struct.unpack(">l", binascii.unhexlify(DEV_ADDR.replace(' ','')))[0]
            nwk_swkey = binascii.unhexlify(NWK_SWKEY.replace(' ',''))
            app_swkey = binascii.unhexlify(APP_SWKEY.replace(' ',''))
            self.lora.join(activation=LoRa.ABP, auth=(dev_addr, nwk_swkey, app_swkey))

        # wait until the module has joined the network
        while not self.lora.has_joined():
            time.sleep(5)
            print("Joining...")

        print("Network joined!")

    def run(self):
        while True:
            while not self.lora.compliance_test().running:
                time.sleep(5)
                self.s.send('Ready')

            print('Test running!')

            self.s.setblocking(True)

            self.tx_payload = bytes([(self.lora.compliance_test().downlink_counter >> 8) & 0xFF,
                                      self.lora.compliance_test().downlink_counter & 0xFF])

            while self.lora.compliance_test().running:

                if self.lora.compliance_test().state < 6: # re-join
                    try:
                        self.s.send(self.tx_payload)
                        time.sleep(2)
                    except Exception:
                        time.sleep(1)

                    if self.lora.compliance_test().link_check:
                        self.tx_payload = bytes([5, self.lora.compliance_test().demod_margin,
                                                    self.lora.compliance_test().nbr_gateways])
                        # set the state to 1 and clear the link check flag
                        self.lora.compliance_test(True, 1, False)
                    else:
                        if self.lora.compliance_test().state == 4:
                            rx_payload = self.s.recv(255)
                            if rx_payload:
                                print('RX: {}'.format(rx_payload))
                                self.tx_payload = bytes([rx_payload[0]])
                                for i in range(1, len(rx_payload)):
                                    self.tx_payload += bytes([(rx_payload[i] + 1) & 0xFF])
                                print('TX: {}'.format(self.tx_payload))
                            self.lora.compliance_test(True, 1)  # set the state to 1
                        else:
                            self.tx_payload = bytes([(self.lora.compliance_test().downlink_counter >> 8) & 0xFF,
                                                      self.lora.compliance_test().downlink_counter & 0xFF])
                else:
                    time.sleep(2)
                    self._join()
jingai commented 6 years ago

I have the ability to rebuild the firmware and such if anyone needs me to try anything, by the way.

robert-hh commented 6 years ago

There were a lot of complaints about the LoRa stack on the xxPy devices, but not listening to the RX2 window wasn't one of those. And it seems not to be an issue when working with the TTN. As far as I could tell, TTN always uses the RX1 window.

jingai commented 6 years ago

I'm not entirely sure yet that it's just not listening on the RX2 window. It seems it might be an issue with the handling of RXParamSetReq. I edited my comment above to reflect this, but it seems the RX1 Receive Window Test fails most of the time with the same issue.

jingai commented 6 years ago

It must be timing related. If I move the LoPy right next to the Tester, it appears to be passing. The previous position was just 3 feet away, though.

I'll post back when the test finishes.

jingai commented 6 years ago

It worked for a while and then I started seeing the duplicate RXParamSetAns again.

Good:

        D R2 10 500  -85.0   ---- 00C7  1   0 - 100 U DataDown           ByteLen=10 
        U  7 10 125   -7.3  4.44s 00D8  1   0 0 224 U DownlinkCounter    Cnt=191 
        D R2 10 500  -85.0   ---- 00C8  1   0 - 100 U DataDown           ByteLen=10 
        U  0 10 125   -7.1  4.44s 00D9  1   0 0 224 U DownlinkCounter    Cnt=191 
        D R2 10 500  -85.0   ---- 00C9  1   0 - 100 U DataDown           ByteLen=10 
        U  3 10 125   -7.2  4.44s 00DA  1   0 0 224 U DownlinkCounter    Cnt=192

Followed by bad:

        D  3 10 500  -30.0   ---- 00CA  1   0 - 000 U RXParamSetReq      RX1DROffset=0,RX2DR=11 
        U  4 10 125   -7.2  3.42s 00DB  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00CB  1   0 - 100 U DataDown           ByteLen=10 
        U  6 10 125   -7.3  4.39s 00DC  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00CC  1   0 - 100 U DataDown           ByteLen=10 
        U  1 10 125   -7.1  5.01s 00DD  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00CD  1   0 - 100 U DataDown           ByteLen=10 
        U  2 10 125   -7.2  5.01s 00DE  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00CE  1   0 - 100 U DataDown           ByteLen=10 
        U  5 10 125   -7.3  5.01s 00DF  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00CF  1   0 - 100 U DataDown           ByteLen=10 
        U  0 10 125   -7.1  5.01s 00E0  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00D0  1   0 - 100 U DataDown           ByteLen=10 
        U  5 10 125   -7.3  5.01s 00E1  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00D1  1   0 - 100 U DataDown           ByteLen=10 
        U  3 10 125   -7.2  5.01s 00E2  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        D R2  9 500  -85.0   ---- 00D2  1   0 - 100 U DataDown           ByteLen=10 
        U  4 10 125   -7.2  4.43s 00E3  1   0 0 224 U RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1 
        U                                             RXParamSetAns      RX1DROffset=1, RX2DR=1, CH=1
jingai commented 6 years ago

The basic problem seems to be that the LoPy no longer sees downlinks after responding to a mac command. Because of this, it's retransmitting the mac response repeatedly.

If I issue another mac command even in this state, the downlink counter increments and the LoPy does respond to the command. But, non-mac downlinks are not seen still.

jingai commented 6 years ago

Solved this. Seems it was an issue with the configuration of the tester. Sorry for the noise.