Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Continue refactor and testing of the code #15

Open
rkompass opened this issue Oct 25, 2023 · 54 comments
Open

Continue refactor and testing of the code #15

rkompass opened this issue Oct 25, 2023 · 54 comments

Comments

@rkompass
Copy link

@davefes: I continue here from our micropython discussion:

I got the module connected. No SPI problems. Had to correct my code again (still little flaws:)

lora_rfm95.py:

from ucollections import namedtuple
from urandom import getrandbits
from umachine import Pin
from utime import time, sleep_ms

# -----  Constants -----
MODE_SLEEP  = const(0x00)
MODE_STDBY  = const(0x01)
MODE_TX     = const(0x03)
MODE_RXCONT = const(0x05)
MODE_CAD    = const(0x07)
LONG_RANGE_MODE = const(0x80)


class LoRa(object):

    Bw125Cr45Sf128 = (0x72, 0x74, 0x04)   # Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
    Bw500Cr45Sf128 = (0x92, 0x74, 0x04)   # Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
    Bw31_25Cr48Sf512 = (0x48, 0x94, 0x04) # Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
    Bw125Cr48Sf4096 = (0x78, 0xc4, 0x0c)  # Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, low data rate, CRC on. Slow+long range
    Bw125Cr45Sf2048 = (0x72, 0xb4, 0x04)  # Bw = 125 kHz, Cr = 4/5, Sf = 2048chips/symbol, CRC on. Slow+long range

    def __init__(self, spi, cs, pirq, addr, res=None, freq=868.0, tx_power=14,
                 modem_config=Bw125Cr45Sf128, rcv_all=False, acks=False, crypto=None):
        """
        LoRa(spi, cs, itr, res=None, addr, freq=868.0, tx_power=14,
                 modem_conf=Bw125Cr45Sf128, receive_all=False, acks=False, crypto=None)
        spi: spi object
        cs: chip select (NSS) pin (mode = Pin.OUT, value=1)
        pirq: interrupt pin (mode = Pin.IN)
        res: reset pin (optional, mode = Pin.OUT, value=1)
        addr: address of this device [0-254]
        freq: frequency in MHz (default: 868.0)
        tx_power: transmit power in dBm (default: 14)
        modem_config: modem configuration, (default: LoRa.Bw125Cr45Sf128 is compatible with the Radiohead library)
        rcv_all: if True, don't filter packets on address
        acks: if True, request acknowledgments
        crypto: if desired, an instance of ucrypto AES (https://docs.pycom.io/firmwareapi/micropython/ucrypto/) - not tested
        """
        
        self.spi = spi              # spi object
        cs(1); self.cs = cs         # chip select (NSS) pin
        self.pirq = pirq            # interrupt pin
        self.res = res              # optional reset pin

        self.addr = addr            # other arguments
        self.freq = freq
        self.tx_power = max(min(tx_power, 23), 5)
        self.modem_config = modem_config
        self.rcv_all = rcv_all
        self.acks = acks
        self.crypto = crypto

        self.mode = None
        self._last_header_id = 0
        self._last_payload = None
        self._cad = None
        self.cad_timeout = 0
        self.send_retries = 2
        self.wait_packet_sent_timeout = 0.2
        self.retry_timeout = 0.2
        
        pirq.irq(trigger=Pin.IRQ_RISING, handler=self._handle_interrupt)   # setup interrupt

        self.reset()                                                       # reset the board
        self.set_mode(MODE_SLEEP | LONG_RANGE_MODE)                        # set mode
        sleep_ms(100)         # v-- REG_01_OP_MODE,  check if mode is set, also tests SPI communication
        assert self._spi_read(0x01) == (MODE_SLEEP | LONG_RANGE_MODE), "LoRa initialization failed" 

        self._spi_write(0x0e, 0)                                           # REG_0E_FIFO_TX_BASE_ADDR
        self._spi_write(0x0f, 0)                                           # REG_0F_FIFO_RX_BASE_ADDR
        
        self.set_mode(MODE_STDBY)
        self.set_modem_config(self.modem_config)                           # set modem config

        # set preamble length (8)
        self._spi_write(0x20, 0)                                           # REG_20_PREAMBLE_MSB
        self._spi_write(0x21, 8)                                           # REG_21_PREAMBLE_LSB

        # set frequency
        self.set_frequency(freq)
        
        # Set tx power
        if self.tx_power > 20:
            self._spi_write(0x4d, 0x07)                                    # REG_4D_PA_DAC,  0x07 = PA_DAC_ENABLE
            self.tx_power -= 3
        else:
            self._spi_write(0x4d, 0x04)                                    # REG_4D_PA_DAC,  PA_DAC_DISABLE = 0x04
        self._spi_write(0x09, 0x80 | (self.tx_power - 5))                  # REG_09_PA_CONFIG,  PA_SELECT = 0x80
    
    def on_recv(self, message):
        pass   # This should be overridden by the user

    def reset(self):
        if isinstance(self.res, Pin):
            self.res(0)
            sleep_ms(1)   # 100 us at least
            self.res(1)
            sleep_ms(5)   # 5 ms at least

    def set_mode(self, mode): # mode .. MODE_SLEEP, MODE_STDBY, MODE_TX, MODE_RXCONT or MODE_CAD
        if self.mode != mode:
            self._spi_write(0x01, mode)                                    # REG_01_OP_MODE
            if mode in (MODE_TX, MODE_RXCONT, MODE_CAD):
                self._spi_write(0x40, {MODE_TX:0x40, MODE_RXCONT:0x00, MODE_CAD:0x80}[mode])  # REG_40_DIO_MAPPING1
            self.mode = mode            #  ^--- Interrupt on TxDone, RxDone or CadDone

    def set_modem_config(self, config):
        self._spi_write(0x1d, config[0])                                   # REG_1D_MODEM_CONFIG1
        self._spi_write(0x1e, config[1])                                   # REG_1E_MzDEM_CONFIG2
        self._spi_write(0x26, config[2])                                   # REG_26_MODEM_CONFIG3

    def set_frequency(self, freq):
        frf = int((self.freq * 1000000.0) / 61.03516)    # 61.03516 = (FXOSC / 524288); FXOSC = 32000000.0
        self._spi_write(0x06, (frf >> 16) & 0xff)                          # REG_06_FRF_MSB
        self._spi_write(0x07, (frf >> 8) & 0xff)                           # REG_07_FRF_MID
        self._spi_write(0x08, frf & 0xff)                                  # REG_08_FRF_LSB

    def _is_channel_active(self):
        self.set_mode(MODE_CAD)

        while self.mode == MODE_CAD:
            yield

        return self._cad
    
    def wait_cad(self):
        if not self.cad_timeout:
            return True

        start = time()
        for status in self._is_channel_active():
            if time() - start < self.cad_timeout:
                return False

            if status is None:
                sleep_ms(100)
                continue
            else:
                return status

    def wait_packet_sent(self):
        # wait for `_handle_interrupt` to switch the mode back
        start = time()
        while time() - start < self.wait_packet_sent_timeout:
            if self.mode != MODE_TX:
                return True

        return False

    def send(self, data, header_to, header_id=0, header_flags=0):
        self.wait_packet_sent()
        self.set_mode(MODE_STDBY)
        self.wait_cad()

        header = [header_to, self.addr, header_id, header_flags]
        if type(data) == int:
            data = [data]
        elif type(data) == bytes:
            data = [p for p in data]
        elif type(data) == str:
            data = [ord(s) for s in data]

        if self.crypto:
            data = [b for b in self._encrypt(bytes(data))]

        payload = header + data
        self._spi_write(0x0d, 0)                            # REG_0D_FIFO_ADDR_PTR
        self._spi_write(0x00, payload)                      # REG_00_FIFO
        self._spi_write(0x22, len(payload))                 # REG_22_PAYLOAD_LENGTH
        self.set_mode(MODE_TX)
        
        return True

    def send_to_wait(self, data, header_to, header_flags=0, retries=3):
        self._last_header_id = (self._last_header_id + 1) % 256

        for _ in range(retries + 1):
            if self._acks:
                header_flags |= 0x40                        # 0x40 = FLAGS_REQ_ACK
            self.send(data, header_to, header_id=self._last_header_id, header_flags=header_flags)
            self.set_mode(MODE_RXCONT)

            if (not self._acks) or header_to == 255:  # Don't wait for acks from a broadcast message,  255 = BROADCAST_ADDRESS
                return True

            start = time()
            while time() - start < self.retry_timeout + (self.retry_timeout * (getrandbits(16) / (2**16 - 1))):
                if self._last_payload:
                    if self._last_payload.header_to == self.addr and \
                            self._last_payload.header_flags & 0x80 and \
                            self._last_payload.header_id == self._last_header_id:   # 0x80 = FLAGS_ACK
                        return True   # We got an ACK
        return False

    def send_ack(self, header_to, header_id):
        self.send(b'!', header_to, header_id, 0x80)                     # 0x80 = FLAGS_ACK
        self.wait_packet_sent()

    def _spi_write(self, register, payload):
        if type(payload) == int:
            payload = [payload]
        elif type(payload) == bytes:
            payload = [p for p in payload]
        elif type(payload) == str:
            payload = [ord(s) for s in payload]
        self.cs.value(0)
        self.spi.write(bytes([register | 0x80] + payload))
        self.cs.value(1)

    def _spi_read(self, register, length=1):
        self.cs.value(0)
        if length == 1:
            data = self.spi.read(length + 1, register)[1]
        else:
            data = self.spi.read(length + 1, register)[1:]
        self.cs.value(1)
        return data

#     def _spi_read(self, register, length=1):
#         self.cs(0)
#         data = self.spi.read(length + 1, register)[1:]
#         self.cs(1)
#         return data
        
    def _decrypt(self, message):
        decr_msg = self.crypto.decrypt(message)
        l = decr_msg[0]
        return decr_msg[1:l+1]

    def _encrypt(self, message):
        l = len(message)
        padding = bytes(15-l%16)  # same as bytes(((math.ceil((l + 1) / 16) * 16) - (l + 1)) * [0])
        return self.crypto.encrypt(bytes([l]) + message + padding)

    def _handle_interrupt(self, channel):
        irq_flags = self._spi_read(0x12)                          # REG_12_IRQ_FLAGS

        if self.mode == MODE_RXCONT and (irq_flags & 0x40):       # 0x40 = RX_DONE
            packet_len = self._spi_read(0x13)                     # REG_13_RX_NB_BYTES
            self._spi_write(0x0d, self._spi_read(0x10))           # REG_0D_FIFO_ADDR_PTR, REG_10_FIFO_RX_CURRENT_ADDR

            packet = self._spi_read(0x00, packet_len)             # REG_00_FIFO
            self._spi_write(0x12, 0xff)                           # REG_12_IRQ_FLAGS, Clear all IRQ flags

            snr = self._spi_read(0x19) / 4                        # REG_19_PKT_SNR_VALUE
            rssi = self._spi_read(0x1a)                           # REG_1A_PKT_RSSI_VALUE

            if snr < 0:
                rssi = snr + rssi
            else:
                rssi = rssi * 16 / 15

            if self._freq >= 779:
                rssi = round(rssi - 157, 2)
            else:
                rssi = round(rssi - 164, 2)

            if packet_len >= 4:
                header_to = packet[0]
                header_from = packet[1]
                header_id = packet[2]
                header_flags = packet[3]
                message = bytes(packet[4:]) if packet_len > 4 else b''

                if (self.addr != header_to) and (header_to != 255) and (self._receive_all is False):  # 255 = BROADCAST_ADDRESS
                    return

                if self.crypto and len(message) % 16 == 0:
                    message = self._decrypt(message)

                if self._acks and header_to == self.addr and header_flags & 0x40 and not header_flags & 0x80:
                    self.send_ack(header_from, header_id)             # ^-- 0x40 = FLAGS_REQ_ACK,       0x80 = FLAGS_ACK

                self.set_mode(MODE_RXCONT)

                self._last_payload = namedtuple("Payload",
                    ['message', 'header_to', 'header_from', 'header_id', 'header_flags', 'rssi', 'snr']
                )(message, header_to, header_from, header_id, header_flags, rssi, snr)

                if not header_flags & 0x80:     # FLAGS_ACK = 0x80
                    self.on_recv(self._last_payload)

        elif self.mode == MODE_TX and (irq_flags & 0x08):   # 0x08 = TX_DONE
            self.set_mode(MODE_STDBY)

        elif self.mode == MODE_CAD and (irq_flags & 0x04):  # 0x04 = CAD_DONE 
            self._cad = irq_flags & 0x01            # 0x01 = CAD_DETECTED
            self.set_mode(MODE_STDBY)

        self._spi_write(0x12, 0xff)   # REG_12_IRQ_FLAGS,  Clear all IRQ flags

and

from utime import sleep
from lora_rfm95 import LoRa
from umachine import Pin


# Lora Parameters
LORA_POW = 12
CLIENT_ADDRESS = 1
SERVER_ADDRESS = 2

# WeAct Blackpill STM32F411: We use SPI2 with (NSS, SCK, MISO, MOSI) = (Y5, Y6, Y7, Y8) = (PB12, PB13, PB14, PB15)
from pyb import SPI
spi = SPI(2, SPI.MASTER, baudrate=500_000, polarity=0, phase=0) # SCK=PB13, MISO=PB14, MOSI=PB15

# WeAct Blackpill STM32F411: We use SoftSPI
# from umachine import SoftSPI
# spi = SoftSPI(baudrate=100_000, polarity=0, phase=0, sck=Pin('PB13'), mosi=Pin('PB15'), miso=Pin('PB14'))

cs=Pin('PB12', Pin.OUT, value=1)
pirq=Pin('PA8', Pin.IN)
res=Pin('PA9', Pin.OUT, value=1)

led=Pin('PC13', Pin.OUT, value=0)

lora = LoRa(spi, cs=cs, pirq=pirq, res=res, addr=CLIENT_ADDRESS, modem_config=LoRa.Bw125Cr45Sf128, tx_power=LORA_POW)

# loop and send data
#while True:
#    lora.send_to_wait('Test msg', SERVER_ADDRESS)
lora.send('Test msg', SERVER_ADDRESS)
print ('sent')
sleep(1)
led(1)    # led off
sleep(0.5)

As it seems I have to solder another module (got 5) for receiving.

@davefes
Copy link

davefes commented Oct 25, 2023

Good to re-sync files. Receiver still not working, I will check that I am getting interrupts from the RFM96W.

@davefes
Copy link

davefes commented Oct 25, 2023

if self._freq >= 779: to if self.freq >= 779:

@davefes
Copy link

davefes commented Oct 25, 2023

Getting interrupts from the RFM96W. The callback doesn't seem to be triggered.

@davefes
Copy link

davefes commented Oct 25, 2023

Possibly in this section:

            if packet_len >= 4:
                header_to = packet[0]
                header_from = packet[1]
                header_id = packet[2]
                header_flags = packet[3]
                message = bytes(packet[4:]) if packet_len > 4 else b''

                if (self._this_address != header_to) and ((header_to != BROADCAST_ADDRESS) or (self._receive_all is False)):
                    return

                if self.crypto and len(message) % 16 == 0:
                    message = self._decrypt(message)

                if self._acks and header_to == self._this_address and not header_flags & 0x80:   # FLAGS_ACK = 0x80
                    self.send_ack(header_from, header_id)

                self.set_mode(MODE_RXCONT)

                self._last_payload = namedtuple("Payload",
                    ['message', 'header_to', 'header_from', 'header_id', 'header_flags', 'rssi', 'snr']
                )(message, header_to, header_from, header_id, header_flags, rssi, snr)

                if not header_flags & FLAGS_ACK:
                    self.on_recv(self._last_payload)

@davefes
Copy link

davefes commented Oct 25, 2023

I see logic changes on lines 273 and 279, so over to you!

@rkompass
Copy link
Author

From: 1
Received: b'Test msg'
RSSI: -77.0; SNR: 9.75
From: 1
Received: b'Test msg'
RSSI: -77.0; SNR: 10.25
From: 1
Received: b'Test msg'
RSSI: -75.93; SNR: 10.25
From: 1
Received: b'Test msg'
RSSI: -75.93; SNR: 10.0
.......

with

from utime import sleep
from lora_rfm95 import LoRa
from umachine import Pin

MODE_RXCONT = const(0x05)

# Lora Parameters
LORA_POW = 12
CLIENT_ADDRESS = 1
SERVER_ADDRESS = 2

# This is our callback function that runs when a message is received
def on_recv(payload):
    print("From:", payload.header_from)
    print("Received:", payload.message)
    print("RSSI: {}; SNR: {}".format(payload.rssi, payload.snr))

# WeAct Blackpill STM32F411: We use SPI2 with (NSS, SCK, MISO, MOSI) = (Y5, Y6, Y7, Y8) = (PB12, PB13, PB14, PB15)
from pyb import SPI
spi = SPI(2, SPI.MASTER, baudrate=500_000, polarity=0, phase=0) # SCK=PB13, MISO=PB14, MOSI=PB15

# WeAct Blackpill STM32F411: We use SoftSPI
# from umachine import SoftSPI
# spi = SoftSPI(baudrate=100_000, polarity=0, phase=0, sck=Pin('PB13'), mosi=Pin('PB15'), miso=Pin('PB14'))

cs=Pin('PB12', Pin.OUT, value=1)
pirq=Pin('PA8', Pin.IN)
res=Pin('PA9', Pin.OUT, value=1)

led=Pin('PC13', Pin.OUT, value=0)

lora = LoRa(spi, cs=cs, pirq=pirq, res=res, addr=SERVER_ADDRESS, modem_config=LoRa.Bw125Cr45Sf128, tx_power=LORA_POW)

# set callback
lora.on_recv = on_recv

# set to listen continuously
lora.set_mode(MODE_RXCONT)

# loop and wait for data
while True:
    sleep(0.1)

and not only the _freq had to be replaced by freq, also the _acks by acks.

@rkompass
Copy link
Author

If the client script has

while True:
    led(1)    # led off
    lora.send('Test msg', SERVER_ADDRESS)
    # lora.send_to_wait("This is a test message", SERVER_ADDRESS)
    print("sent")
    sleep(1)
    led(0)    # led off
    sleep(1)

in the loop then I get the received message every time the message is sent.
If I change the comments to lora.send_to_wait("This is a test message", SERVER_ADDRESS) activated
I get the received message only every second time the message is sent. That seems strange.

@davefes
Copy link

davefes commented Oct 25, 2023

changed self._acks' to self.acks` and still not go.

Every second time ... maybe as per Barry Hunter's comments change to retry_timeout say 0.5 to 1 second. Which modem setting?

@davefes
Copy link

davefes commented Oct 25, 2023

Is normal operation for it to return?

                if (self._this_address != header_to) and ((header_to != BROADCAST_ADDRESS) or (self._receive_all is False)):
                    return

@davefes
Copy link

davefes commented Oct 25, 2023

With the original retry_timeout it will take about 1 second to do a send_to_wait(). There was a discussion about this at:
#13

@rkompass
Copy link
Author

I put the driver and server and client file here:
lora_rfm95.zip

Note some cosmetic: Now the modes

    # Operating modes
    MODE_SLEEP  = const(0x00)
    MODE_STDBY  = const(0x01)
    MODE_TX     = const(0x03)
    MODE_RXCONT = const(0x05)
    MODE_CAD    = const(0x07)
    LONG_RANGE_MODE = const(0x80)

may be used from outside as in lora.set_mode(LoRa.MODE_RXCONT) # set to listen continuously.

Hope you have success with that.

@davefes
Copy link

davefes commented Oct 25, 2023

One coding question, what does:
cs(1); self.cs=cs
mean?

@rkompass
Copy link
Author

These are two instructions (may be on two lines without the separating ;).
The first: cs(1) is a shorter and faster version of cs.value(1). The second is the storage of the cs pin object as a class instance variable. As long as we are in __init__() we have cs as argument and may change the level to high, i.e. 1 by using cs(1). In other methods access to cs is only possible through the class instance itself, so we use self.cs(1) or self.cs(0).

@rkompass
Copy link
Author

Barry Hunters changes are all in already.

@davefes
Copy link

davefes commented Oct 25, 2023

Ah, cs.value(1) Thank you.

I had to comment-out the assert for the transmitter to work.

@rkompass
Copy link
Author

Lora_RFM95_Blackpill

@rkompass
Copy link
Author

I had to comment-out the assert for the transmitter to work.

Should not be necessary any longer.

I feel I have to start learning about LoRa by now ...

@davefes
Copy link

davefes commented Oct 25, 2023

O dear, had to comment-out the assert in the receiver and also not seeing any interrupts.

@rkompass
Copy link
Author

In line 260 self._receive_all still is to be replaced by self.rcv_all:
if (self.addr != header_to) and (header_to != 255) and (self.rcv_all is False): # 255 = BROADCAST_ADDRESS

If you have to comment out the assert to avoid the "LoRa initialization failed" message, then there is a SPI communication problem. That may also be responsible for the receiving not happening. ???

@davefes
Copy link

davefes commented Oct 25, 2023

Sorry, I need to modify lora_rfm95.py to 433MHz. I am barely keeping-up!

@rkompass
Copy link
Author

No problem, but how about the SPI issue? That will not change.
I would try to solve this first. Did you try with the latest driver version above? (I sometimes forget to upload the changed driver to the board).

@davefes
Copy link

davefes commented Oct 25, 2023

I needed to re-assign the pins in server and client to what I was actually using!

Also, changed to SoftSPI and "wahoo" the signal appears at the receiver!!!

I believe I have the latest version out of the .zip file. I must get rid of the hundreds of unwanted "ulora" file around.

Assert statement back-in and all seems good. Stress-testing underway while I relax!

Because of strange SNR readings at low signal levels I put this in the server:

def on_recv(payload):
    if payload.rssi < -120:
        print('RSSI is below -120dBm')
    else:
        print('From:', payload.header_from)
        print('Received:', payload.message)
        print('RSSI: {}; SNR: {}'.format(payload.rssi, payload.snr))

Did you read through https://github.com/micropython/micropython-lib/tree/master/micropython/lora

@davefes
Copy link

davefes commented Oct 25, 2023

I still think that there is some critical timing issue before the assert statement.

@rkompass
Copy link
Author

Congratulations. I will have to go to sleep now.

@davefes
Copy link

davefes commented Oct 25, 2023

For 800MHz I would use a 1/10 or 1/4 watt 47 or 56 Ohm resistor with leads about 3mm long, ie as short as possible.

@rkompass
Copy link
Author

We can start testing more functionality tomorrow. I have the feeling the requesting acknowledgements does not work.
Broadcasting has to be tested too.
There should be an init() function, with more settings.

@rkompass
Copy link
Author

If send_to_wait() is used the (listening) server is requested to send an acknowledgement for every packet. This does not work correctly.
The first such acknowledgement sent always reports a timeout. send_to_wait() has to retry again each time.

I guess there is an error due to it happening from within the RX interrupt callback while needing another TX interrupt.
Couldn't figure that out more precisely yet.

If you want to investigate: here are the files with many debugging print statements.
Lora.zip

@davefes
Copy link

davefes commented Oct 26, 2023

I noticed both of his examples had acks=True so I thought that it must be working.

The comment about longer retry_timeout I gather refer to the PICO. I don't know what timing precision that the STM32F411 has.

My testing with sf=2048 has been rock-solid. Unfortunately, I am left wondering why your code runs on both platforms and yet the original ulora failed on one platform.

I will have a look but interrupts are not one of my strengths.

@rkompass
Copy link
Author

I am left wondering why your code runs on both platforms and yest the original ulora failed on one platform.

This is because the SPI configuration is outside the class/module as it should be.
The original ulora failed only because of that.
Now, the error I described only becomes apparent with the debugging by the print statements. Sending everything twice otherwise is not noted because the server in the interrupt routine stays at a point where it sends the acknowledgement back.

@davefes
Copy link

davefes commented Oct 26, 2023

I only have one USB cable that will plug into a WeAct dev board :( So, I can't run two terminal programs. Will try to make-up a cable.

Can you explain why SPI fails when it is in the class?

Is the rule: send() with no acks and send_to_wait() with acks. No other combinations possible?

@davefes
Copy link

davefes commented Oct 26, 2023

Got it connected, modified pin assignments and have both RX and TX running. Things appear to be working to the untrained eye.

I see acks sometimes on the first send_to_wait() but mostly on the 2nd send_to_wait(). I see a lot of `no cad detected'

@davefes
Copy link

davefes commented Oct 26, 2023

TX

sent 112
int_cad_done: no cad detected
send_to_wait attempt 1
int_tx_done
int_cad_done: no cad detected
send_to_wait attempt 2
int_tx_done
int_rxcont packet id:113 from:2 to:1 req_ack:False ack:True
got ack

RX:

int_rxcont packet id:23 from:1 to:2 req_ack:True ack:False
wait_ack_packet_sent: timeout at id 23
From 1:
Received: b'Test message with ack 23'
RSSI: -102.13; SNR: 9.25

@davefes
Copy link

davefes commented Oct 26, 2023

An observation ... I forgot to change to 433MHz in lora_rfm95.py and the transmitter initialises and appears to send data to who knows where. Maybe, having freq in sever/client scripts might be useful.

@rkompass
Copy link
Author

rkompass commented Oct 26, 2023

Same here.
I have to rewrite all uses of time.time() because that has seconds resolution to use of time.ticks_ms() and time.ticks_diff().
Because the timeouts are defined in fractions of seconds.
Tomorrow.

@davefes
Copy link

davefes commented Oct 27, 2023

Just an idea to test with:
make both 'retry_timeoutandwait_packet_sent_timeout' =1

And in client maybe increase the time between sends:

while True:
    led(0)    # led on
    
    # Here we send just a test message. Packets ar>
    # lora.send('Test message {}'.format(i), SERVE>
    
    # Here we send a test message with request for>
    lora.send_to_wait('Test message with ack {}'.f>
    
    print('sent {}'.format(i))
    i += 1
    sleep(1)
    led(1)    # led off
    sleep(4)  # to allow for 4 retries

In testing with Wei's LoRa code https://github.com/Wei1234c/SX127x_driver_for_MicroPython_on_ESP8266/tree/master I found that I had to put say a 1 second delay between sends to allow the receiver to process the message. Maybe, that is what the whole retry timeout thing is for.

Good luck.

@davefes
Copy link

davefes commented Oct 27, 2023

Updated a comment on the original thread about using a resistor for the antenna load.

What I showed in the picture is barely appropriate even for 433MHz, Suggest a 47 to 56Ohm 0805 SMD resistor soldered between the ANT and adjacent ground pin.

Also, run the TX power at minimum until you get a good antenna on the unit.

I use this cable cutting off the IPEX connector. Drop this into Aliexpress search:
1Pcs SMA to IPEX RG178 Cable SMA Male to uFL/u.FL/IPX/IPEX-1 Female
I can take a photo of how to prepare the end that connects to the module.

@rkompass
Copy link
Author

This video shows what I will probabably try in the end.

@davefes
Copy link

davefes commented Oct 27, 2023

That is one way. I cut off about 5-6mm of the outer telfon off, flow solder on the braid, then cut off say 3mm of braid, wrap thin copper wire around the braid (instead of making pig-tails), solder it, and then cut 1-2mm of the inner Teflon off to expose the inner wire and "tin" that.

The braid (with the wire wrapped around it) is soldered directly to ground.

I have been running send_to_wait() most of day and seems to work for me. I have 1 second timeouts and using Sf=2048.

If you are sending a long message at a high Sf do you not need longer timeouts?

  1. Can you explain why SPI fails when it is in the class?
  1. Is the rule: send() with no acks and send_to_wait() with acks. No other combinations possible?

@davefes
Copy link

davefes commented Oct 27, 2023

If you give me a screenshot of the unwanted behaviour that you are seeing it would help to focus my attention. Testing down near the -120dBm level with send_to_wait() I only see correct messages. When I was testing just with send() I would sometimes get corrupted messages.

As per question 2) acks were off. As CRC is on why does the receiver display a corrupted message?

  1. If the software, when using send_to_wait() can tell the transmitter to send the message again, why can't the receiver processing determine whether or not to display a corrupted message?

My typical use case is sending brief messages, say once an hour and over long distances. Power source 1 * 18650 cell and 1W PV panel. 1-4seconds every hour with the STM32F411 going to deepsleep seems a good solution.

@davefes
Copy link

davefes commented Oct 27, 2023

Just another thought ... if you place a probe on DIO0 using a good DSO do you see multiple interrupts during 1 send_to_wait() call?

Here is a picture:
20231028_101258
... with send() you don't get two rising edges, ie only the 5ms pulse.

Even with send_to_wait() there can be another (3rd) interrupt, seen on a NORMAL sweep.

Maybe disable interrupts, although I did not have much success there as they can be disabled only for a "short" time.

@davefes
Copy link

davefes commented Oct 28, 2023

There is a lot of processing in _handle_int, (9 SPI read/write calls) maybe increasing the SPI clock rate.

@rkompass
Copy link
Author

Tomorrow I will give a description. Also my feeling is now that systematic testing is needed.

All this is about send_to_wait(). The receiver switches to sending the requested ACK which is now scheduled, i.e. happens outside the interrupt routine (which is fine). In sending the ACK it does a CAD (carrier detection) like always before before sending (we do not want to send into a just proceeding transmission). But this CAD is not finished (no CAD_DONE interrupt). Instead the CAD times out and transmit is started.
We see a report of the CAD_DONE interrupt in a TX Mode however, which is reported, as I have included a print on any interrupt happening.
You would from this suppose that the CAD phase was not long enough, but setting the cad_timeout longer seems to make the problem not go away. This has to be investigated.

Here is the new version, also with little cosmetics/renaming. Hope you get a grip on the different reports and the problem.
I may go into deeper dialogue about this tomorrow.
Lora.zip

@davefes
Copy link

davefes commented Oct 28, 2023

Wow, I can see you have put a lot of work into this.

Are you wanting to use send_to_wait () to get higher message reliability? I have managed with just testing the received message at the server, even for messages with small value changes in them.

Here is what I see:

Client:

set CAD mode
int_cad_done: no cad detected
send_to_wait attempt 1
int_tx_done
int_rxcont packet id:165 from:2 to:1 req_ack:False ack:True
got ack
sent 165

Server:

other interrupt: mode: 3, irq_flags: 0x4
int_tx_done
int_rxcont packet id:165 from:1 to:2 req_ack:True ack:False
send ack to 1 at id 165
set CAD mode
wait_cad: timeout
From 1:
Received: b'Test message with ack 165'
RSSI: -105.33; SNR: 8.25

So, the problem is the no cad detected on the client`, correct?

I am off to the "Big city" for a few days so things will be quiet from my end.

@davefes
Copy link

davefes commented Oct 28, 2023

Is the real problem:
wait_cad: timeout
at the server?

@davefes
Copy link

davefes commented Oct 29, 2023

Also, the server setting CAD mode after sending an ACK doesn't seem right. CAD mode is only set when doing a send. I am confused. Does the server need to send a dummy TX to get it into CAD-mode?

@rkompass
Copy link
Author

rkompass commented Oct 29, 2023 via email

@davefes
Copy link

davefes commented Oct 29, 2023

My current understanding ... a point to point link returning an ack to the client has good value. Maybe, in complex systems and/or wanting to save receiver current in a duplex system, CAD may have some value.

@rkompass
Copy link
Author

rkompass commented Oct 29, 2023

I just tried the micropython-lib SX1276 version of reliable sending and receiving. Got sender and receiver to run, but there was no communication. I will have to repeat this with the SX1276 boards that I got yesterday (another run of soldering).
Noted that in Angus Grattons code there are a few very interesting comments.
He put a delay of 100ms (at least 25 ms) for sending the ACK. Perhaps this is the culprit? It could be that the sender is not able to switch back fast enough from sending to receiving again.
I like the current code we have - compared to the micropython-lib one it is quite compact. But there is quite some work to be done yet. It takes time to investigate things thoroughly. I would like to have a decent introduction with code examples for the RFM95 but didn't find some.
....
Did more modifications and testing.
The thing is that with switching from TXCONT to doing a CAD the CAD times out and the CAD_DONE interrupt is noted when DR mode is already active.
This seems to be independent from a delay inserted in between TXCONT and SLEEP and CAD.
So the above assumption does not hold.
Sending the ACK alone seems to work, then the CAD_DONE is correctly noted.
I cannot explain this atm. Perhaps an interrupt issue. It needs experimentation.
All nested interrupt stuff is gone now by usage of micropython.schedule - this is the correct way to do it.

@davefes
Copy link

davefes commented Oct 30, 2023

I found when testing Wei's send/receive that I had to put a short delay after a tx. I think 250ms was OK, but I put in 1 second delays.

@davefes
Copy link

davefes commented Nov 1, 2023

Just an observation ... Bw125Cr45Sf2048 seems to work well, but with Bw125Cr48Sf4096 the message comes through 4 times.

The timeouts (wait_packet_sent_timeout_ms, retry_timeout_ms and maybe cad_timeout_ms) seem to need tweaking depending on MODEM_CONFIG. Maybe message length is another variable.

I see in the micropython-lib lora that there is some timeout adjustment around line 729

@davefes
Copy link

davefes commented Nov 12, 2023

@rkompass The main issue I am facing is that my sensor links using Wei's code may send a corrupted message, but at least an hour later it tries again ... so the odd missed data is not a problem. On a LoRa control link "missed messages" are a problem.

In ulora.py , the send_wait() method tries 4 times and the real plus is that you get an ACK sent back to the sender.

Have you made any further progress?

@rkompass
Copy link
Author

I just tried the micropython-lib SX1276 version of reliable sending and receiving. Got sender and receiver to run, but there was no communication. I will have to repeat this with the SX1276 boards that I got yesterday (another run of soldering).

@davefes I saw that you posted the same observation on MP discussions. Cannot solve it at the moment, but will solve it later.

@davefes
Copy link

davefes commented Nov 18, 2023

Appears that any "PA switching " that takes place is turning the whole TX on/off, as well as any possible antenna port selection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants