'''
A program supposed to run on two RP2040 connected over two channel of an RS485 bus over TTL-UART-to-RS485 converters, and illustrates a simple ping-pong.
Copyright (C) 2024  Benjamin Burkhardt

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
'''

from machine import UART, Pin
import time

# CHANGE THIS to 'True' on one of the machines (as it
# tells the rp2040 to do the first step in ping-pong communication ;)
INITIALIZER = True


# define the UART in-/outputs, named after their usage as either send or receive channels...
# ...depending on wether this is the INITIALIZER or not.
# sure, the in and out pins could be defined in other ways also, but this allows easy customizability...
# ...as just one variable needs to be changed.
print('[UART] Initializing UART for two channels...')
if INITIALIZER:  # then read on ch0 and write on ch1
    receiver = UART(0, baudrate=115200, tx=Pin(0), rx=Pin(1))
    sender = UART(1, baudrate=115200, tx=Pin(4), rx=Pin(5))
else:
    sender = UART(0, baudrate=115200, tx=Pin(0), rx=Pin(1))
    receiver = UART(1, baudrate=115200, tx=Pin(4), rx=Pin(5))
print('[UART] Initialized UART for two channels.')


# initialize the read/write channels and set their value
print('[UART] Setting output/input for each channel...')
if INITIALIZER:  # then enable read on ch0 and write on ch1
    receiver_channel_mode = Pin(2, Pin.OUT)  # define pinout for the receiver/driver enable pins of channel 0 (shortened together)
    sender_channel_mode = Pin(3, Pin.OUT)  # define pinout for the receiver/driver enable pins of channel 1 (shortened together)
else:
    sender_channel_mode = Pin(2, Pin.OUT)
    receiver_channel_mode = Pin(3, Pin.OUT)
receiver_channel_mode.low()
sender_channel_mode.high()
print(f'[UART] Set output/input for each channel. Pin 2 is now {"low" if INITIALIZER else "high"} (CH0 as {"input" if INITIALIZER else "output"}), Pin 3 is now {"high" if INITIALIZER else "low"} (CH1 as {"output" if INITIALIZER else "input"}).')

# some logging
print('[INFO] Set up RS485 ping-pong.')



# now, the REAL PING-PONG mechanism begins

# the counter variable will be increased with every ping
# received (which is a pong from the other side which resulted out of a ping there ;)
a = 0  
       
# make the first step if INITIALIZER is True (see above at declaration)
if INITIALIZER:
    sender.write(f'{a}\r\n')
    sender.flush()
    print(f'[UART] Sent initial ping: {a}')
print('[INFO] Now waiting for incoming pings/pongs.')

# now listen for a pong from the other side and answer to it
rxDataConcatenated = ''  # to combine partial received data to one full packet (actually just a string that ends with \n :)

while True:
    rxData = receiver.readline()
    if rxData is not None:
        rxDataDecoded = rxData.decode('utf-8')
        if not '\n' in rxDataDecoded:  # if the received data is not complete (does not end with a \n), just save the received part
            rxDataConcatenated += rxDataDecoded
        else:  # if it's complete now, add the received part to the potentially saved parts and send the pong
            rxDataConcatenated += rxDataDecoded
            try:
                a = int(rxDataConcatenated)
                print(f"[UART] Received ping: {a}")
                # send pong after some waiting (could also be left out completely!)
                time.sleep(0.2)
                a += 1
                sender.write(f'{a}\r\n')
                sender.flush()
                print(f'[UART] Sent pong:     {a}')
            except:
                receiver_error_details = repr(rxDataConcatenated)
                print(f'[UART] Received invalid data? Wait until something good comes in! (received "{receiver_error_details}")')
            rxDataConcatenated = ''  # reset the received parts storage (see comments above!)







