<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.tarpn.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=KV4P</id>
	<title>TARPN Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.tarpn.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=KV4P"/>
	<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php/Special:Contributions/KV4P"/>
	<updated>2026-04-12T23:35:27Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=226</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=226"/>
		<updated>2023-09-24T18:31:32Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777,wait-slave tcp:192.168.1.90:10001&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree ====&lt;br /&gt;
[[File:PXL 20230915 204140878.MP.jpg|alt=LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.|thumb|LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.]]&lt;br /&gt;
&lt;br /&gt;
For the next test, I put the LoRa + pi &amp;quot;port&amp;quot; about 60 feet up in a tree, with a homebrew groundplane antenna instead of the quarter wave vertical (i.e. piece of wire). The idea was to give the test a better chance at success. I also added a [https://www.amazon.com/dp/B095JTW6XM 5dBi rubber duck antenna] to my portable TARPN node&#039;s LoRa port, so it was a little better than the piece of wire as well.&lt;br /&gt;
&lt;br /&gt;
Observations:&lt;br /&gt;
* I ran my first set of tests at 64.5kHz bandwidth and 8 spreadingFactor.&lt;br /&gt;
* My house is surrounded by hills on most sides (I live in a valley), and I could not get any signal to the south, southwest, or southeast. I tried many different spots, from about 2 miles away, all the way down to about a half mile down the road. None worked, no packets received.&lt;br /&gt;
* On a lark, I also tried 500kHz bandwidth and 7 spreadingFactor. This did not help, which was not surprising since the same power was more spread out.&lt;br /&gt;
* I was FINALLY able to get packets to arrive when I was 1/3 mile north of my house. There are no hills to the north of my house.&lt;br /&gt;
&lt;br /&gt;
Overall, 33cm is clearly heavily blocked by hills (all VHF and UHF is to some extent, but 33cm is clearly far more sensitive than either 2m or 70cm to hills, I have other TARPN links on those bands that work OK for several miles).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree, avoiding hills ====&lt;br /&gt;
&lt;br /&gt;
In the next test, I drove north / northeast from my house, where there are no hills higher than my house&#039;s elevation. I drove to one location at 1.5 miles, and another at 4 miles.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, neither of these locations could receive the 33cm LoRa signal (8 spreadingFactor, 62.5kHz bandwidth, 6 coding rate). In fact, the only location I&#039;ve been able to receive the signal (other than literally at my home QTH) is 1/3 mile straight north. This tells me two things:&lt;br /&gt;
&lt;br /&gt;
# Trees / forest likely attenuate 33cm signals fairly strongly, since other locations within ~1/3 mile of my home QTH did not work&lt;br /&gt;
# Hills are a complete no-go for 33cm signals&lt;br /&gt;
&lt;br /&gt;
So a LoRa link for TARPN could be an excellent inexpensive option if you live in a flat area and/or have a clear line-of-site to your nearest TARPN neighbor. For example, I&#039;d expect it to work pretty well in a suburban housing district without hills, letting neighbors link up. Or if you&#039;re in a part of the US that just isn&#039;t very hilly (e.g. farmland area). Where I live is hills everywhere, and I&#039;m in a valley!&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=225</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=225"/>
		<updated>2023-09-20T15:55:29Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777,wait-slave tcp:192.168.1.90:10001&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree ====&lt;br /&gt;
[[File:PXL 20230915 204140878.MP.jpg|alt=LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.|thumb|LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.]]&lt;br /&gt;
&lt;br /&gt;
For the next test, I put the LoRa + pi &amp;quot;port&amp;quot; about 60 feet up in a tree, with a homebrew groundplane antenna instead of the quarter wave vertical (i.e. piece of wire). The idea was to give the test a better chance at success. I also added a [https://www.amazon.com/dp/B095JTW6XM 5dBi rubber duck antenna] to my portable TARPN node&#039;s LoRa port, so it was a little better than the piece of wire as well.&lt;br /&gt;
&lt;br /&gt;
Observations:&lt;br /&gt;
* I ran my first set of tests at 64.5kHz bandwidth and 8 spreadingFactor.&lt;br /&gt;
* My house is surrounded by hills on most sides (I live in a valley), and I could not get any signal to the south, southwest, or southeast. I tried many different spots, from about 2 miles away, all the way down to about a half mile down the road. None worked, no packets received.&lt;br /&gt;
* On a lark, I also tried 500kHz bandwidth and 7 spreadingFactor. This did not help, which was not surprising since the same power was more spread out.&lt;br /&gt;
* I was FINALLY able to get packets to arrive when I was 1/3 mile north of my house. There are no hills to the north of my house.&lt;br /&gt;
&lt;br /&gt;
Overall, 33cm is clearly heavily blocked by hills (all VHF and UHF is to some extent, but 33cm is clearly far more sensitive than either 2m or 70cm to hills, I have other TARPN links on those bands that work OK for several miles).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree, avoiding hills ====&lt;br /&gt;
&lt;br /&gt;
UPCOMING: I will run another test driving north / northeast from my house, where there are no hills higher than my house&#039;s elevation. I have one location at 1.5 miles, and another at 4 miles that I plan to try. Will update this wiki after testing.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=224</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=224"/>
		<updated>2023-09-19T02:18:33Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777,wait-slave tcp:192.168.1.90:10001&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree ====&lt;br /&gt;
[[File:PXL 20230915 204140878.MP.jpg|alt=LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.|thumb|LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.]]&lt;br /&gt;
&lt;br /&gt;
For the next test, I put the LoRa + pi &amp;quot;port&amp;quot; about 60 feet up in a tree, with a homebrew groundplane antenna instead of the quarter wave vertical (i.e. piece of wire). The idea was to give the test a better chance at success. I also added a [https://www.amazon.com/dp/B095JTW6XM 5dBi rubber duck antenna] to my portable TARPN node&#039;s LoRa port, so it was a little better than the piece of wire as well.&lt;br /&gt;
&lt;br /&gt;
Observations:&lt;br /&gt;
* I ran my first set of tests at 64.5kHz bandwidth and 8 spreadingFactor.&lt;br /&gt;
* My house is surrounded by hills on most sides (I live in a valley), and I could not get any signal to the south, southwest, or southeast. I tried many different spots, from about 2 miles away, all the way down to about a half mile down the road. None worked, no packets received.&lt;br /&gt;
* On a lark, I also tried 500kHz bandwidth and 7 spreadingFactor, which in my bench tests was sometimes MORE reliable (inexplicably, since 64.5kHz should be concentrating more signal in less bandwidth). This did not help.&lt;br /&gt;
* I was FINALLY able to get packets to arrive when I was 1/3 mile north of my house. There are no hills to the north of my house.&lt;br /&gt;
&lt;br /&gt;
Overall, 33cm is clearly heavily blocked by hills (all VHF and UHF is to some extent, but 33cm is clearly far more sensitive than either 2m or 70cm to hills, I have other TARPN links on those bands that work OK for several miles).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree, avoiding hills ====&lt;br /&gt;
&lt;br /&gt;
UPCOMING: I will run another test driving north / northeast from my house, where there are no hills higher than my house&#039;s elevation. I have one location at 1.5 miles, and another at 4 miles that I plan to try. Will update this wiki after testing.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=223</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=223"/>
		<updated>2023-09-19T02:17:25Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777,wait-slave tcp:192.168.1.90:10001&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree ====&lt;br /&gt;
[[File:PXL 20230915 204140878.MP.jpg|alt=LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.|thumb|LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.]]&lt;br /&gt;
&lt;br /&gt;
For the next test, I put the LoRa + pi &amp;quot;port&amp;quot; about 60 feet up in a tree, with a homebrew groundplane antenna instead of the quarter wave vertical (i.e. piece of wire). The idea was to give the test a better chance at success. I also added a [https://www.amazon.com/dp/B095JTW6XM 5dBi rubber duck antenna] to my portable TARPN node&#039;s LoRa port, so it was a little better than the piece of wire as well.&lt;br /&gt;
&lt;br /&gt;
Observations:&lt;br /&gt;
* I ran my first set of tests at 64.5kHz bandwidth and 8 spreadingFactor.&lt;br /&gt;
* My house is surrounded by hills on most sides (I live in a valley), and I could not get any signal to the south, southwest, or southeast.&lt;br /&gt;
* On a lark, I also tried 500kHz bandwidth and 7 spreadingFactor, which in my bench tests was sometimes MORE reliable (inexplicably, since 64.5kHz should be concentrating more signal in less bandwidth). This did not help.&lt;br /&gt;
* I was FINALLY able to get packets to arrive when I was 1/3 mile north of my house. There are no hills to the north of my house.&lt;br /&gt;
&lt;br /&gt;
Overall, 33cm is clearly heavily blocked by hills (all VHF and UHF is to some extent, but 33cm is clearly far more sensitive than either 2m or 70cm to hills, I have other TARPN links on those bands that work OK for several miles).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree, avoiding hills ====&lt;br /&gt;
&lt;br /&gt;
UPCOMING: I will run another test driving north / northeast from my house, where there are no hills higher than my house&#039;s elevation. I have one location at 1.5 miles, and another at 4 miles that I plan to try. Will update this wiki after testing.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=File:PXL_20230915_204140878.MP.jpg&amp;diff=222</id>
		<title>File:PXL 20230915 204140878.MP.jpg</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=File:PXL_20230915_204140878.MP.jpg&amp;diff=222"/>
		<updated>2023-09-19T02:17:08Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;LoRa &amp;quot;port&amp;quot; enclosed in a waterproof project box with a homebrew groundplane antenna.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=221</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=221"/>
		<updated>2023-09-19T02:15:33Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777,wait-slave tcp:192.168.1.90:10001&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree ====&lt;br /&gt;
&lt;br /&gt;
For the next test, I put the LoRa + pi &amp;quot;port&amp;quot; about 60 feet up in a tree, with a homebrew groundplane antenna instead of the quarter wave vertical (i.e. piece of wire). The idea was to give the test a better chance at success. I also added a [https://www.amazon.com/dp/B095JTW6XM 5dBi rubber duck antenna] to my portable TARPN node&#039;s LoRa port, so it was a little better than the piece of wire as well.&lt;br /&gt;
&lt;br /&gt;
Observations:&lt;br /&gt;
* I ran my first set of tests at 64.5kHz bandwidth and 8 spreadingFactor.&lt;br /&gt;
* My house is surrounded by hills on most sides (I live in a valley), and I could not get any signal to the south, southwest, or southeast.&lt;br /&gt;
* On a lark, I also tried 500kHz bandwidth and 7 spreadingFactor, which in my bench tests was sometimes MORE reliable (inexplicably, since 64.5kHz should be concentrating more signal in less bandwidth). This did not help.&lt;br /&gt;
* I was FINALLY able to get packets to arrive when I was 1/3 mile north of my house. There are no hills to the north of my house.&lt;br /&gt;
&lt;br /&gt;
Overall, 33cm is clearly heavily blocked by hills (all VHF and UHF is to some extent, but 33cm is clearly far more sensitive than either 2m or 70cm to hills, I have other TARPN links on those bands that work OK for several miles).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, transceiver 60&#039; up a tree, avoiding hills ====&lt;br /&gt;
&lt;br /&gt;
UPCOMING: I will run another test driving north / northeast from my house, where there are no hills higher than my house&#039;s elevation. I have one location at 1.5 miles, and another at 4 miles that I plan to try. Will update this wiki after testing.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=220</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=220"/>
		<updated>2023-09-12T12:29:35Z</updated>

		<summary type="html">&lt;p&gt;KV4P: Tweaks to socat, to try to stop BPQ from hanging (after a while) when the TCP connection to the LoRa device goes down.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777,wait-slave tcp:192.168.1.90:10001&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, longer distances (IN PROGRESS) ====&lt;br /&gt;
&lt;br /&gt;
Will update this section once testing is complete. I plan to test with a groundplane antenna 60ft up in a tree, to see how far this can go with better conditions.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=219</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=219"/>
		<updated>2023-09-11T00:21:37Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 wave vertical antenna made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, longer distances (IN PROGRESS) ====&lt;br /&gt;
&lt;br /&gt;
Will update this section once testing is complete. I plan to test with a groundplane antenna 60ft up in a tree, to see how far this can go with better conditions.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=218</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=218"/>
		<updated>2023-09-11T00:20:49Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
==== 915MHz LoRa, bench test ====&lt;br /&gt;
With 500kHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 vertical made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, quarter mile ====&lt;br /&gt;
With 62.5kHz bandwidth, spreadingFactor of 8, and coding of 6, the module worked very well at a distance of about 1/4 mile. I live in a valley, and put one LoRa transceiver in my attic (3rd floor) and I tested it from a nearby hill. That worked well. I also walked down a flat road that&#039;s level with my house (no intervening hills) and that also worked well. I did not do a throughput test, but at these settings it&#039;s roughly equivalent to a 1200 baud typical TNC link.&lt;br /&gt;
&lt;br /&gt;
Both LoRa nodes used a 1/4 vertical made of magnet wire (no counterpoise or ground plane).&lt;br /&gt;
&lt;br /&gt;
==== 915MHz LoRa, longer distances (IN PROGRESS) ====&lt;br /&gt;
&lt;br /&gt;
Will update this section once testing is complete. I plan to test with a groundplane antenna 60ft up in a tree, to see how far this can go with better conditions.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=217</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=217"/>
		<updated>2023-09-09T14:00:06Z</updated>

		<summary type="html">&lt;p&gt;KV4P: Add timeout to socat so buffer doesn&amp;#039;t overflow when the LoRa device can&amp;#039;t be contacted.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat -T 600 pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With 500MHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=215</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=215"/>
		<updated>2023-09-01T00:37:32Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With 500MHz bandwidth and spreadingFactor of 7 in config.py, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=214</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=214"/>
		<updated>2023-09-01T00:35:50Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would.&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=Community&amp;diff=213</id>
		<title>Community</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=Community&amp;diff=213"/>
		<updated>2023-09-01T00:32:25Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TARPN Community ==&lt;br /&gt;
&lt;br /&gt;
* Email groups&lt;br /&gt;
** [https://tarpn.net/t/builder/builders_tarpn_email.html TARPN groups.io]&lt;br /&gt;
** [https://groups.io/g/ninotnc NinoTNC groups.io]&lt;br /&gt;
** [https://groups.io/g/ncpacket NCPacket groups.io]&lt;br /&gt;
* Discord&lt;br /&gt;
** There is a TARPN discord.  You&#039;ll need to get the link from somebody who is already logged into the TARPN channel.  If you are already subscribed to the TARPN groups.io, send a request there.  &lt;br /&gt;
* GitHub&lt;br /&gt;
** There are projects for and about TARPN on GITHUB.  Some of these are official, some are candidate projects that are not part of the project, yet.  [https://github.com/search?q=tarpn&amp;amp;type=repositories Click to Search]&lt;br /&gt;
** As of 2013, the TARPN node stack uses LINBPQ, written by John, G8BPQ.  This link goes to one of the GITHUB mirrors of LINBPQ or other elements of John&#039;s software:  [https://github.com/g8bpq?tab=repositories G8BPQ (network routing layer)].  John maintains his own software repository and has links to his project code [https://www.cantab.net/users/john.wiseman/Documents/index.html here].  The GITHUB copies are by other-than-John and are not necessarily up to date.&lt;br /&gt;
* On-the-air nets&lt;br /&gt;
** NCPacket UHF net: Thursday @ 7pm Eastern on the [https://www.repeaterbook.com/repeaters/details.php?ID=314&amp;amp;state_id=37 442.150 repeater]&lt;br /&gt;
** NCPacket UHF net: Friday @ 12pm Eastern on the [https://www.repeaterbook.com/repeaters/details.php?ID=464&amp;amp;state_id=37 444.525 repeater]&lt;br /&gt;
** TARPN 75m net: Sunday @ 9pm Eastern around 3.973 MHz LSB (+/- depending on conflicting QSOs on freq).  An email is sent to the TARPN groups.io each week updating us on what is going on with the HF net.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=Community&amp;diff=212</id>
		<title>Community</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=Community&amp;diff=212"/>
		<updated>2023-09-01T00:31:53Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TARPN Community ==&lt;br /&gt;
&lt;br /&gt;
* Email groups&lt;br /&gt;
** [https://tarpn.net/t/builder/builders_tarpn_email.html TARPN groups.io]&lt;br /&gt;
** [https://groups.io/g/ninotnc NinoTNC groups.io]&lt;br /&gt;
** [https://groups.io/g/ncpacket NCPacket groups.io]&lt;br /&gt;
* Discord&lt;br /&gt;
** There is a TARPN discord.  You&#039;ll need to get the link from somebody who is already logged into the TARPN channel.  If you are already subscribed to the TARPN groups.io, send a request there.  &lt;br /&gt;
* GitHub&lt;br /&gt;
** There are projects for and about TARPN on GITHUB.  Some of these are official, some are candidate projects that are not part of the project, yet.  [https://github.com/search?q=tarpn&amp;amp;type=repositories Click to Search]&lt;br /&gt;
** As of 2013, the TARPN node stack uses LINBPQ, written by John, G8BPQ.  This link goes to one of the GITHUB mirrors of LINBPQ or other elements of John&#039;s software:  [https://github.com/g8bpq?tab=repositories G8BPQ (network routing layer)].  John maintains his own software repository and has links to his project code [https://www.cantab.net/users/john.wiseman/Documents/index.html here].  The GITHUB copies are by other-than-John and are not necessarily up to date.&lt;br /&gt;
* On-the-air nets&lt;br /&gt;
** NCPacket UHF net: Thursday @ 7pm Eastern on the [https://www.repeaterbook.com/repeaters/details.php?ID=314&amp;amp;state_id=37 442.150 repeater]&lt;br /&gt;
** NCPacket UHF net: Friday @ 12pm Eastern on the [https://www.repeaterbook.com/repeaters/details.php?ID=464&amp;amp;state_id=37 444.525 repeater]&lt;br /&gt;
** TARPN 75m net: Sunday @ 9pm Eastern around 3.968 MHz LSB (+/- depending on conflicting QSOs on freq).  An email is sent to the TARPN groups.io each week updating us on what is going on with the HF net.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=Community&amp;diff=211</id>
		<title>Community</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=Community&amp;diff=211"/>
		<updated>2023-09-01T00:31:15Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== TARPN Community ==&lt;br /&gt;
&lt;br /&gt;
* Email groups&lt;br /&gt;
** [https://tarpn.net/t/builder/builders_tarpn_email.html TARPN groups.io]&lt;br /&gt;
** [https://groups.io/g/ninotnc NinoTNC groups.io]&lt;br /&gt;
** [https://groups.io/g/ncpacket NCPacket groups.io]&lt;br /&gt;
* Discord&lt;br /&gt;
** There is a TARPN discord.  You&#039;ll need to get the link from somebody who is already logged into the TARPN channel.  If you are already subscribed to the TARPN groups.io, send a request there.  &lt;br /&gt;
* GitHub&lt;br /&gt;
** There are projects for and about TARPN on GITHUB.  Some of these are official, some are candidate projects that are not part of the project, yet.  [https://github.com/search?q=tarpn&amp;amp;type=repositories Click to Search]&lt;br /&gt;
** As of 2013, the TARPN node stack uses LINBPQ, written by John, G8BPQ.  This link goes to one of the GITHUB mirrors of LINBPQ or other elements of John;s software:  [https://github.com/g8bpq?tab=repositories G8BPQ (network routing layer)].  John maintains his own software repository and has links to his project code [https://www.cantab.net/users/john.wiseman/Documents/index.html here].  The GITHUB copies are by other-than-John and are not necessarily up to date.&lt;br /&gt;
* On-the-air nets&lt;br /&gt;
** NCPacket UHF net: Thursday @ 7pm Eastern on the [https://www.repeaterbook.com/repeaters/details.php?ID=314&amp;amp;state_id=37 442.150 repeater]&lt;br /&gt;
** NCPacket UHF net: Friday @ 12pm Eastern on the [https://www.repeaterbook.com/repeaters/details.php?ID=464&amp;amp;state_id=37 444.525 repeater]&lt;br /&gt;
** TARPN 75m net: Sunday @ 9pm Eastern around 3.968 MHz LSB (+/- depending on conflicting QSOs on freq).  An email is sent to the TARPN groups.io each week updating us on what is going on with the HF net.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=210</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=210"/>
		<updated>2023-08-31T14:56:01Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=209</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=209"/>
		<updated>2023-08-31T14:51:12Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 8&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 15&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=208</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=208"/>
		<updated>2023-08-31T14:47:48Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=207</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=207"/>
		<updated>2023-08-31T14:46:58Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this to start every time the LoRa pi reboots, so follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
&lt;br /&gt;
You&#039;ll want this LoRa virtual device to appear on device reboot, you can do this with crontab (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file, and save:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot nohup /home/pi/lora-port-up.sh &amp;amp;&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=206</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=206"/>
		<updated>2023-08-31T14:45:06Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want this to start every time the LoRa pi reboots, you can follow these steps (TODO replace this crontab approach with systemd):&lt;br /&gt;
# sudo crontab -e (If you&#039;ve never used crontab it will ask you to select you preferred text editor, I use nano)&lt;br /&gt;
# Paste this line at the end of the crontab file:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
@reboot /usr/bin/python3 /home/pi/RPi-LoRa-KISS-TNC-2ndgen/Start_lora-tnc.py &amp;amp;&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=205</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=205"/>
		<updated>2023-08-31T00:02:01Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* chmod +x lora-port-up.sh&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=204</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=204"/>
		<updated>2023-08-31T00:01:24Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,nonblock,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=203</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=203"/>
		<updated>2023-08-27T21:44:20Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for balanced speed and range. If your connection is strong, you can increase bandwidth up to 500000 (500kHz) which will significantly improve speed. 500kHz is roughly equivalent to 9600 baud, and 62500 is roughly equivalent to 1200 baud. If you need even more distance, you can also increase spreadingFactor above 7 (to say 8 or 9), which will make it transmit more slowly but be received much better. There are other settings in config.py that can affect range and speed as well, but this is a good starting point.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=202</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=202"/>
		<updated>2023-08-27T16:05:08Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but have not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for maximum throughput. If you need longer range (at slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and double the spreading time). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=201</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=201"/>
		<updated>2023-08-27T16:04:53Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; [https://www.raspberrypi.com/software/operating-systems/ Raspberry Pi OS image], not either of the &amp;quot;with desktop&amp;quot; images. They might work, but has not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for maximum throughput. If you need longer range (at slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and double the spreading time). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=200</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=200"/>
		<updated>2023-08-27T16:04:25Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
A few notes:&lt;br /&gt;
* This has only been tested with the &amp;quot;Lite&amp;quot; Raspberry Pi OS image, not either of the &amp;quot;with desktop&amp;quot; images. They might work, but has not been tested.&lt;br /&gt;
* &#039;&#039;&#039;This definitely does not work on a Pi that has TARPN installed.&#039;&#039;&#039; Something in the TARPN setup script is incompatible with the LoRa bonnet. We may be able to fix this in the future, but it would require significant debugging to figure out what TARPN setup step breaks the LoRa bonnet functionality.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for maximum throughput. If you need longer range (at slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and double the spreading time). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=199</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=199"/>
		<updated>2023-08-27T15:46:20Z</updated>

		<summary type="html">&lt;p&gt;KV4P: /* Turn it into a TCP KISS TNC */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for maximum throughput. If you need longer range (at slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and double the spreading time). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=198</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=198"/>
		<updated>2023-08-27T15:46:04Z</updated>

		<summary type="html">&lt;p&gt;KV4P: /* Turn it into a TCP KISS TNC */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the original github project] this is forked from for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for maximum throughput. If you need longer range (at slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and double the spreading time). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=197</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=197"/>
		<updated>2023-08-27T15:45:33Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
https://github.com/VanceVagell/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# Edit config.py and update the frequency (search for &amp;quot;frequency&amp;quot;). You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge).&lt;br /&gt;
# Optional: The default config.py settings are for maximum throughput. If you need longer range (at slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and double the spreading time). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
Create log placeholders that the library expects to exist:&lt;br /&gt;
&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=196</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=196"/>
		<updated>2023-08-27T13:16:37Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet, and segments too-large packets):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
MAX_SEGMENT_LENGTH = 200 # for segmented packets&lt;br /&gt;
segments = []&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    global segments&lt;br /&gt;
&lt;br /&gt;
    if len(frame) &amp;gt; MAX_SEGMENT_LENGTH + 1:&lt;br /&gt;
        logf(&amp;quot;Warning: Discarded very long frame, even longer than a segmented frame. Likely corrupted.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    # If this is a data segment, process it as such.&lt;br /&gt;
    if len(frame) &amp;gt;= MAX_SEGMENT_LENGTH + 1 or len(segments) &amp;gt; 0:&lt;br /&gt;
        if frame[0:1] == b&#039;0&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Start of segmented data, caching to recombine.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;Continuation of segmented data, adding to cache.&amp;quot;)&lt;br /&gt;
            segments.append(frame[1:MAX_SEGMENT_LENGTH + 1])&lt;br /&gt;
            return&lt;br /&gt;
        elif frame[0:1] == b&#039;1&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Warning, corrupt segmentation, received end segment with no prior segments. Discarding.&amp;quot;)&lt;br /&gt;
                segments = []&lt;br /&gt;
                return&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;End of segmented data, adding to cache and returning complete packet via KISS.&amp;quot;)&lt;br /&gt;
                segments.append(frame[1:])&lt;br /&gt;
                frame = bytearray()&lt;br /&gt;
                for segment in segments:&lt;br /&gt;
                    for b in segment:&lt;br /&gt;
                        frame.append(b)&lt;br /&gt;
                segments = []&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
            logf(str(e))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, replace the entire queue_frame function in TCPServer.py with this:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
    def segment_ax25_packet(self, packet):&lt;br /&gt;
        # The maximum size for the data in each segment.&lt;br /&gt;
        MAX_SEGMENT_SIZE = 200&lt;br /&gt;
&lt;br /&gt;
        # If packet is small enough to transmit in one go, return as is&lt;br /&gt;
        if len(packet) &amp;lt;= MAX_SEGMENT_SIZE:&lt;br /&gt;
            return [packet]&lt;br /&gt;
&lt;br /&gt;
        segments = []&lt;br /&gt;
&lt;br /&gt;
        # Start breaking the data into segments.&lt;br /&gt;
        while packet:&lt;br /&gt;
            segment_data = packet[:MAX_SEGMENT_SIZE]&lt;br /&gt;
            packet = packet[MAX_SEGMENT_SIZE:]&lt;br /&gt;
&lt;br /&gt;
            # Mark the first or continuation segments with a &amp;quot;0&amp;quot; in first byte, and the final segment with &amp;quot;1&amp;quot;&lt;br /&gt;
            segment = (b&#039;0&#039; if packet else b&#039;1&#039;) + segment_data&lt;br /&gt;
            segments.append(segment)&lt;br /&gt;
&lt;br /&gt;
        return segments&lt;br /&gt;
&lt;br /&gt;
    def queue_frame(self, frame, verbose=True):&lt;br /&gt;
        try:&lt;br /&gt;
            logf(&amp;quot;Received from IP: &amp;quot;+str(client_address[0])+&amp;quot; KISS Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in queue_frame.&amp;quot;)&lt;br /&gt;
        if config.TX_OE_Style:&lt;br /&gt;
            decoded_data = KissHelper.decode_kiss_OE(frame)&lt;br /&gt;
        else:&lt;br /&gt;
            decoded_data = KissHelper.decode_kiss_AX25(frame)&lt;br /&gt;
        #logf(&amp;quot;Decapsulated Kiss Frame :&amp;quot;+ repr(decoded_data))&lt;br /&gt;
&lt;br /&gt;
        # subdivide any packets that are too large into &amp;lt;255 bytes to fit in LoRa module tx register&lt;br /&gt;
        segments = self.segment_ax25_packet(decoded_data)&lt;br /&gt;
        for segment in segments:&lt;br /&gt;
            self.txQueue.put(segment, block=True)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=195</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=195"/>
		<updated>2023-08-27T02:20:23Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet, and segments too-large packets):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
MAX_SEGMENT_LENGTH = 200 # for segmented packets&lt;br /&gt;
segments = []&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    global segments&lt;br /&gt;
&lt;br /&gt;
    if len(frame) &amp;gt; MAX_SEGMENT_LENGTH + 1:&lt;br /&gt;
        logf(&amp;quot;Warning: Discarded very long frame, even longer than a segmented frame. Likely corrupted.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    # If this is a data segment, process it as such.&lt;br /&gt;
    if len(frame) &amp;gt;= MAX_SEGMENT_LENGTH + 1 or len(segments) &amp;gt; 0:&lt;br /&gt;
        if frame[0:1] == b&#039;0&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Start of segmented data, caching to recombine.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;Continuation of segmented data, adding to cache.&amp;quot;)&lt;br /&gt;
            segments.append(frame[1:MAX_SEGMENT_LENGTH + 1])&lt;br /&gt;
            return&lt;br /&gt;
        elif frame[0:1] == b&#039;1&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Warning, corrupt segmentation, received end segment with no prior segments. Discarding.&amp;quot;)&lt;br /&gt;
                segments = []&lt;br /&gt;
                return&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;End of segmented data, adding to cache and returning complete packet via KISS.&amp;quot;)&lt;br /&gt;
                segments.append(frame[1:])&lt;br /&gt;
                frame = bytearray()&lt;br /&gt;
                for segment in segments:&lt;br /&gt;
                    for b in segment:&lt;br /&gt;
                        frame.append(b)&lt;br /&gt;
                segments = []&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
            logf(str(e))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, add this function to TCPServer.py (right above queue_frame would be fine):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
    def segment_ax25_packet(self, packet):&lt;br /&gt;
        # The maximum size for the data in each segment.&lt;br /&gt;
        MAX_SEGMENT_SIZE = 200&lt;br /&gt;
&lt;br /&gt;
        # If packet is small enough to transmit in one go, return as is&lt;br /&gt;
        if len(packet) &amp;lt;= MAX_SEGMENT_SIZE:&lt;br /&gt;
            return [packet]&lt;br /&gt;
&lt;br /&gt;
        segments = []&lt;br /&gt;
&lt;br /&gt;
        # Start breaking the data into segments.&lt;br /&gt;
        while packet:&lt;br /&gt;
            segment_data = packet[:MAX_SEGMENT_SIZE]&lt;br /&gt;
            packet = packet[MAX_SEGMENT_SIZE:]&lt;br /&gt;
&lt;br /&gt;
            # Mark the first or continuation segments with a &amp;quot;0&amp;quot; in first byte, and the final segment with &amp;quot;1&amp;quot;&lt;br /&gt;
            segment = (b&#039;0&#039; if packet else b&#039;1&#039;) + segment_data&lt;br /&gt;
            segments.append(segment)&lt;br /&gt;
&lt;br /&gt;
        return segments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=194</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=194"/>
		<updated>2023-08-27T02:19:53Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet, and segments too-large packets):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
MAX_SEGMENT_LENGTH = 200 # for segmented packets&lt;br /&gt;
segments = []&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    global segments&lt;br /&gt;
&lt;br /&gt;
    if len(frame) &amp;gt; MAX_SEGMENT_LENGTH + 1:&lt;br /&gt;
        logf(&amp;quot;Warning: Discarded very long frame, even longer than a segmented frame. Likely corrupted.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    # If this is a data segment, process it as such.&lt;br /&gt;
    if len(frame) &amp;gt;= MAX_SEGMENT_LENGTH + 1 or len(segments) &amp;gt; 0:&lt;br /&gt;
        if frame[0:1] == b&#039;0&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Start of segmented data, caching to recombine.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;Continuation of segmented data, adding to cache.&amp;quot;)&lt;br /&gt;
            segments.append(frame[1:MAX_SEGMENT_LENGTH + 1])&lt;br /&gt;
            return&lt;br /&gt;
        elif frame[0:1] == b&#039;1&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Warning, corrupt segmentation, received end segment with no prior segments. Discarding.&amp;quot;)&lt;br /&gt;
                segments = []&lt;br /&gt;
                return&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;End of segmented data, adding to cache and returning complete packet via KISS.&amp;quot;)&lt;br /&gt;
                segments.append(frame[1:])&lt;br /&gt;
                frame = bytearray()&lt;br /&gt;
                for segment in segments:&lt;br /&gt;
                    for b in segment:&lt;br /&gt;
                        frame.append(b)&lt;br /&gt;
                segments = []&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
            logf(str(e))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, add this function to TCPServer.py (write before queue_frame would be fine):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
    def segment_ax25_packet(self, packet):&lt;br /&gt;
        # The maximum size for the data in each segment.&lt;br /&gt;
        MAX_SEGMENT_SIZE = 200&lt;br /&gt;
&lt;br /&gt;
        # If packet is small enough to transmit in one go, return as is&lt;br /&gt;
        if len(packet) &amp;lt;= MAX_SEGMENT_SIZE:&lt;br /&gt;
            return [packet]&lt;br /&gt;
&lt;br /&gt;
        segments = []&lt;br /&gt;
&lt;br /&gt;
        # Start breaking the data into segments.&lt;br /&gt;
        while packet:&lt;br /&gt;
            segment_data = packet[:MAX_SEGMENT_SIZE]&lt;br /&gt;
            packet = packet[MAX_SEGMENT_SIZE:]&lt;br /&gt;
&lt;br /&gt;
            # Mark the first or continuation segments with a &amp;quot;0&amp;quot; in first byte, and the final segment with &amp;quot;1&amp;quot;&lt;br /&gt;
            segment = (b&#039;0&#039; if packet else b&#039;1&#039;) + segment_data&lt;br /&gt;
            segments.append(segment)&lt;br /&gt;
&lt;br /&gt;
        return segments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=193</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=193"/>
		<updated>2023-08-27T02:17:56Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet, and segments too-large packets):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
MAX_SEGMENT_LENGTH = 200 # for segmented packets&lt;br /&gt;
segments = []&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    global segments&lt;br /&gt;
&lt;br /&gt;
    if len(frame) &amp;gt; MAX_SEGMENT_LENGTH + 1:&lt;br /&gt;
        logf(&amp;quot;Warning: Discarded very long frame, even longer than a segmented frame. Likely corrupted.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    # If this is a data segment, process it as such.&lt;br /&gt;
    if len(frame) &amp;gt;= MAX_SEGMENT_LENGTH + 1 or len(segments) &amp;gt; 0:&lt;br /&gt;
        if frame[0:1] == b&#039;0&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Start of segmented data, caching to recombine.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;Continuation of segmented data, adding to cache.&amp;quot;)&lt;br /&gt;
            segments.append(frame[1:MAX_SEGMENT_LENGTH + 1])&lt;br /&gt;
            return&lt;br /&gt;
        elif frame[0:1] == b&#039;1&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Warning, corrupt segmentation, received end segment with no prior segments. Discarding.&amp;quot;)&lt;br /&gt;
                segments = []&lt;br /&gt;
                return&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;End of segmented data, adding to cache and returning complete packet via KISS.&amp;quot;)&lt;br /&gt;
                segments.append(frame[1:])&lt;br /&gt;
                frame = bytearray()&lt;br /&gt;
                for segment in segments:&lt;br /&gt;
                    for b in segment:&lt;br /&gt;
                        frame.append(b)&lt;br /&gt;
                segments = []&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
            logf(str(e))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=192</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=192"/>
		<updated>2023-08-27T02:17:24Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
MAX_SEGMENT_LENGTH = 200 # for segmented packets&lt;br /&gt;
segments = []&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    global segments&lt;br /&gt;
&lt;br /&gt;
    if len(frame) &amp;gt; MAX_SEGMENT_LENGTH + 1:&lt;br /&gt;
        logf(&amp;quot;Warning: Discarded very long frame, even longer than a segmented frame. Likely corrupted.&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
&lt;br /&gt;
    # If this is a data segment, process it as such.&lt;br /&gt;
    if len(frame) &amp;gt;= MAX_SEGMENT_LENGTH + 1 or len(segments) &amp;gt; 0:&lt;br /&gt;
        if frame[0:1] == b&#039;0&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Start of segmented data, caching to recombine.&amp;quot;)&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;Continuation of segmented data, adding to cache.&amp;quot;)&lt;br /&gt;
            segments.append(frame[1:MAX_SEGMENT_LENGTH + 1])&lt;br /&gt;
            return&lt;br /&gt;
        elif frame[0:1] == b&#039;1&#039;:&lt;br /&gt;
            if len(segments) == 0:&lt;br /&gt;
                logf(&amp;quot;Warning, corrupt segmentation, received end segment with no prior segments. Discarding.&amp;quot;)&lt;br /&gt;
                segments = []&lt;br /&gt;
                return&lt;br /&gt;
            else:&lt;br /&gt;
                logf(&amp;quot;End of segmented data, adding to cache and returning complete packet via KISS.&amp;quot;)&lt;br /&gt;
                segments.append(frame[1:])&lt;br /&gt;
                frame = bytearray()&lt;br /&gt;
                for segment in segments:&lt;br /&gt;
                    for b in segment:&lt;br /&gt;
                        frame.append(b)&lt;br /&gt;
                segments = []&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
            logf(str(e))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=191</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=191"/>
		<updated>2023-08-22T13:17:55Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            #Call parse with a string of one or more characters&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:1&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=190</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=190"/>
		<updated>2023-08-21T13:59:40Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            #Call parse with a string of one or more characters&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:5&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=189</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=189"/>
		<updated>2023-08-21T13:16:00Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be building a LoRa radio that shows up on your network as a TCP-based KISS TNC, which we&#039;ll wrap in a virtual serial port so TARPN ports 11 or 12 can be configured to access it like a normal TNC.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            #Call parse with a string of one or more characters&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:20&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=188</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=188"/>
		<updated>2023-08-21T01:30:40Z</updated>

		<summary type="html">&lt;p&gt;KV4P: Increase TX delay to avoid race conditions between two nodes with long packets&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            #Call parse with a string of one or more characters&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:20&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=187</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=187"/>
		<updated>2023-08-20T18:46:09Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            #Call parse with a string of one or more characters&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:5&lt;br /&gt;
frack11:2000&lt;br /&gt;
kissoptions11:disable&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=186</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=186"/>
		<updated>2023-08-20T18:37:16Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;The default settings above are for maximum throughput shorter-range links.&#039;&#039;&#039; If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
Note that if you&#039;re using a ham band other than 33cm you may need to use lower bandwidth settings to adhere to [https://www.ecfr.gov/current/title-47/part-97#p-97.307(f)(6) FCC requirements for spread spectrum transmissions] (&amp;lt;100kHz bandwidth below 33cm). You can use the full 500kHz 33cm and above.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            #Call parse with a string of one or more characters&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:5&lt;br /&gt;
frack11:2000&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=185</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=185"/>
		<updated>2023-08-20T18:33:11Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The default settings above are for maximum throughput shorter-range links. If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        try:&lt;br /&gt;
            #Call parse with a string of one or more characters&lt;br /&gt;
            for c in data:&lt;br /&gt;
                if self.state == self.STATE_IDLE:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_FEND&lt;br /&gt;
                elif self.state == self.STATE_FEND:&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        self.reset()&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.cur_frame.append(c)&lt;br /&gt;
                        self.state = self.STATE_DATA&lt;br /&gt;
                elif self.state == self.STATE_DATA:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    if c == self.KISS_FEND:&lt;br /&gt;
                        # frame complete&lt;br /&gt;
                        if self.frame_cb:&lt;br /&gt;
                            self.frame_cb(self.cur_frame)&lt;br /&gt;
                        self.reset()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            logf(&amp;quot;Exception in SerialParser.parse(), callback NOT called.&amp;quot;)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service, or you can start it in crontab.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:5&lt;br /&gt;
frack11:2000&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=184</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=184"/>
		<updated>2023-08-20T14:53:56Z</updated>

		<summary type="html">&lt;p&gt;KV4P: Added photo of complete node&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:PXL 20230820 144254009.MP.jpg|thumb|A complete TARPN 1-port node using LoRa. The smaller device is the LoRa KISS TNC Transceiver, and the larger Raspberry Pi is the TARPN node. It requires about 1.5 watts to run, just plug it into a USB power source.]]&lt;br /&gt;
KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The default settings above are for maximum throughput shorter-range links. If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# This program provides basic KISS AX.25 APRS frame encoding and decoding.&lt;br /&gt;
# Note that only APRS relevant structures are tested. It might not work&lt;br /&gt;
# for generic AX.25 frames.&lt;br /&gt;
#&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
# APRS data types&lt;br /&gt;
DATA_TYPES_POSITION = b&amp;quot;!&#039;/@`&amp;quot;&lt;br /&gt;
DATA_TYPE_MESSAGE = b&amp;quot;:&amp;quot;&lt;br /&gt;
DATA_TYPE_THIRD_PARTY = b&amp;quot;}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
# from LoRa OE_Style to KISS&lt;br /&gt;
# Addresses must be 6 bytes plus the SSID byte, each character shifted left by 1&lt;br /&gt;
# If it&#039;s the final address in the header, set the low bit to 1&lt;br /&gt;
# If it has been digipeated, set the H bit to 1&lt;br /&gt;
# Ignoring command/response for simple example&lt;br /&gt;
def encode_address(s, final):&lt;br /&gt;
    H=0b00000000 #default H bit low&lt;br /&gt;
    if chr(s[-1])==&#039;*&#039;: #example: WIDE1-1* or WIDE1*&lt;br /&gt;
        s=s[:-1]&lt;br /&gt;
        H=0b10000000&lt;br /&gt;
    if b&amp;quot;-&amp;quot; not in s:&lt;br /&gt;
        s = s + b&amp;quot;-0&amp;quot;  # default to SSID 0&lt;br /&gt;
    call, ssid = s.split(b&#039;-&#039;)&lt;br /&gt;
    if len(call) &amp;lt; 6:&lt;br /&gt;
        call = call + b&amp;quot; &amp;quot;*(6 - len(call)) # pad with spaces&lt;br /&gt;
    encoded_call = [x &amp;lt;&amp;lt; 1 for x in call[0:6]]&lt;br /&gt;
    encoded_ssid = (int(ssid) &amp;lt;&amp;lt; 1) | H | 0b01100000 | (0b00000001 if final else 0)&lt;br /&gt;
    return encoded_call + [encoded_ssid]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def decode_address(data, cursor):&lt;br /&gt;
    (a1, a2, a3, a4, a5, a6, a7) = struct.unpack(&amp;quot;&amp;lt;BBBBBBB&amp;quot;, data[cursor:cursor + 7])&lt;br /&gt;
    hrr = a7 &amp;gt;&amp;gt; 5&lt;br /&gt;
    ssid = (a7 &amp;gt;&amp;gt; 1) &amp;amp; 0xf&lt;br /&gt;
    ext = a7 &amp;amp; 0x1&lt;br /&gt;
    addr = struct.pack(&amp;quot;&amp;lt;BBBBBB&amp;quot;, a1 &amp;gt;&amp;gt; 1, a2 &amp;gt;&amp;gt; 1, a3 &amp;gt;&amp;gt; 1, a4 &amp;gt;&amp;gt; 1, a5 &amp;gt;&amp;gt; 1, a6 &amp;gt;&amp;gt; 1)&lt;br /&gt;
    if ssid != 0:&lt;br /&gt;
        call = addr.strip() + &amp;quot;-{}&amp;quot;.format(ssid).encode()&lt;br /&gt;
    else:&lt;br /&gt;
        call = addr&lt;br /&gt;
    return (call, hrr, ext)&lt;br /&gt;
&lt;br /&gt;
def ax25parser(frame): #extracts fields from ax25 frames and add signal report do payload, if specified&lt;br /&gt;
    try:&lt;br /&gt;
        pos = 0&lt;br /&gt;
&lt;br /&gt;
        # DST&lt;br /&gt;
        (dest_addr, dest_hrr, dest_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;DST: &amp;quot;, dest_addr)&lt;br /&gt;
&lt;br /&gt;
        # SRC&lt;br /&gt;
        (src_addr, src_hrr, src_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;SRC: &amp;quot;, src_addr)&lt;br /&gt;
&lt;br /&gt;
        # REPEATERS&lt;br /&gt;
        ext = src_ext&lt;br /&gt;
        rpt_list = b&amp;quot;&amp;quot;&lt;br /&gt;
        while ext == 0:&lt;br /&gt;
            rpt_addr, rpt_hrr, ext = decode_address(frame, pos)&lt;br /&gt;
            if rpt_hrr==b&#039;111&#039;: # H bit high--&amp;gt;packet has been digipeated&lt;br /&gt;
               rpt_addr+=b&#039;*&#039;&lt;br /&gt;
            rpt_list += b&amp;quot;,&amp;quot;+rpt_addr&lt;br /&gt;
            print(&amp;quot;RPT: &amp;quot;, rpt_addr)&lt;br /&gt;
            pos += 7&lt;br /&gt;
&lt;br /&gt;
        # CTRL&lt;br /&gt;
        ctrl = frame[pos]&lt;br /&gt;
        pos += 1&lt;br /&gt;
        if (ctrl &amp;amp; 0x3) == 0x3:&lt;br /&gt;
            #(pid,) = struct.unpack(&amp;quot;&amp;lt;B&amp;quot;, frame[pos])&lt;br /&gt;
            try:&lt;br /&gt;
                pid = frame[pos]&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pid = 240&lt;br /&gt;
            print(&amp;quot;PID: &amp;quot;+str(pid))&lt;br /&gt;
            pos += 1&lt;br /&gt;
        elif (ctrl &amp;amp; 0x3) == 0x1:&lt;br /&gt;
            # decode_sframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;SFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        elif (ctrl &amp;amp; 0x1) == 0x0:&lt;br /&gt;
            # decode_iframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;IFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        payload=frame[pos:]&lt;br /&gt;
        print(&amp;quot;payload: &amp;quot;, payload)&lt;br /&gt;
        try:&lt;br /&gt;
            dti=payload[0]&lt;br /&gt;
        except Exception:&lt;br /&gt;
            dti=&amp;quot;:&amp;quot;&lt;br /&gt;
        logf(&amp;quot;Extracted AX25 parameters from AX25 Frame&amp;quot;)&lt;br /&gt;
        logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+repr(rpt_list)[3:-1]+&amp;quot; PID: &amp;quot;+str(hex(pid))+&amp;quot; Payload: &amp;quot;+str(payload)[str(payl&lt;br /&gt;
oad).find(&amp;quot;&#039;&amp;quot;):-1])&lt;br /&gt;
&lt;br /&gt;
        return src_addr,dest_addr,rpt_list,payload,dti&lt;br /&gt;
    except Exception:&lt;br /&gt;
        return None,None,None,None,None&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        frame += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to aprx and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_OE(frame,signalreport): #from Lora to Kiss, OE_Style&lt;br /&gt;
    # Ugly frame disassembling&lt;br /&gt;
    if not b&amp;quot;:&amp;quot; in frame:&lt;br /&gt;
        logf(&amp;quot;Can&#039;t decode OE LoRa Frame&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    path = frame.split(b&amp;quot;:&amp;quot;)[0]&lt;br /&gt;
    payload = frame[frame.find(b&amp;quot;:&amp;quot;)+1:]&lt;br /&gt;
    dti = payload[0]&lt;br /&gt;
    src_addr = path.split(b&amp;quot;&amp;gt;&amp;quot;)[0]&lt;br /&gt;
    digis = path[path.find(b&amp;quot;&amp;gt;&amp;quot;) + 1:].split(b&amp;quot;,&amp;quot;)&lt;br /&gt;
    dest_addr = digis.pop(0)&lt;br /&gt;
&lt;br /&gt;
    # destination address&lt;br /&gt;
    packet = encode_address(dest_addr.upper(), False)&lt;br /&gt;
    # source address&lt;br /&gt;
    packet += encode_address(src_addr.upper(), len(digis) == 0)&lt;br /&gt;
    # digipeaters&lt;br /&gt;
    for digi in digis:&lt;br /&gt;
        final_addr = digis.index(digi) == len(digis) - 1&lt;br /&gt;
        packet += encode_address(digi.upper(), final_addr)&lt;br /&gt;
    # control field&lt;br /&gt;
    packet += [0x03]  # This is an UI frame&lt;br /&gt;
    # protocol ID&lt;br /&gt;
    packet += [0xF0]  # No protocol&lt;br /&gt;
    # information field&lt;br /&gt;
    logf(&amp;quot;Extracted AX25 parameters from OE LoRa Frame&amp;quot;)&lt;br /&gt;
    logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+str(digis)[1:-1].replace(&amp;quot;b&amp;quot;,&amp;quot;&amp;quot;).replace(&amp;quot;&#039;&amp;quot;,&amp;quot;&amp;quot;)+&amp;quot; Payload: &amp;quot;+repr(payload)[2:-1])&lt;br /&gt;
    packet += payload&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        #some SW (es OE5BPA) append newline character at the end of packet. Must be cut for appending signal report&lt;br /&gt;
        if chr(packet[-1])==&amp;quot;\n&amp;quot;:&lt;br /&gt;
          packet=packet[:-1]&lt;br /&gt;
        packet += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in packet:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to Dire Wolf and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
    try:&lt;br /&gt;
        #print(bytearray(kiss_frame).hex())&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_OE(frame): #From Kiss to LoRa, OE_Style&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    #build OE_style frame piece by piece&lt;br /&gt;
    result = src_addr.strip()+b&amp;quot;&amp;gt;&amp;quot;+dest_addr.strip()+rpt_list+b&amp;quot;:&amp;quot;+payload&lt;br /&gt;
    return result&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        #Call parse with a string of one or more characters&lt;br /&gt;
        for c in data:&lt;br /&gt;
            if self.state == self.STATE_IDLE:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_FEND&lt;br /&gt;
            elif self.state == self.STATE_FEND:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.reset()&lt;br /&gt;
                else:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_DATA&lt;br /&gt;
            elif self.state == self.STATE_DATA:&lt;br /&gt;
                self.cur_frame.append(c)&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    # frame complete&lt;br /&gt;
                    if self.frame_cb:&lt;br /&gt;
                        self.frame_cb(self.cur_frame)&lt;br /&gt;
                    self.reset()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    # Playground for testing&lt;br /&gt;
&lt;br /&gt;
    kissframe = b&amp;quot;\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V&lt;br /&gt;
\xc0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;OE&lt;br /&gt;
    print(decode_kiss_OE(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;AX25&lt;br /&gt;
    print(decode_kiss_AX25(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test encode OE-&amp;gt;KISS&lt;br /&gt;
    OE_frame = b&amp;quot;OE9TKH-8&amp;gt;APRS,digi-3,digi-2:!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    signalreport=&amp;quot;Level:-115dBm, SNR:0dB&amp;quot;&lt;br /&gt;
    print(encode_kiss_OE(OE_frame,signalreport))&lt;br /&gt;
&lt;br /&gt;
    #test encode AX25-&amp;gt;KISS&lt;br /&gt;
    ax25_frame = b&amp;quot;\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    print(encode_kiss_AX25(ax25_frame,signalreport))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: The above KissHelper.py has not been optimized at all, it was just made to work with all KISS packets and made fault-tolerant so it doens&#039;t crash. The OE-based encoding can be removed, and many simplifications of the code can be made! This is the quick and dirty version.&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:5&lt;br /&gt;
frack11:2000&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=File:PXL_20230820_144254009.MP.jpg&amp;diff=183</id>
		<title>File:PXL 20230820 144254009.MP.jpg</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=File:PXL_20230820_144254009.MP.jpg&amp;diff=183"/>
		<updated>2023-08-20T14:52:46Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A LoRa KISS TNC and a 2nd raspberry pi running TARPN.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=182</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=182"/>
		<updated>2023-08-20T11:49:02Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 8 #valid preable length is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 7 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The default settings above are for maximum throughput shorter-range links. If you want to go farther (at much slower speeds), try decreasing the bandwidth to 62500 and increasing the spreadingFactor to 8 (this will concentrate more power in a narrower signal, and slightly increase the encoding spread which takes longer to transmit the signal). These are the 2 most important values (bandwidth and spreadingFactor) in terms of throughput and range.&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# This program provides basic KISS AX.25 APRS frame encoding and decoding.&lt;br /&gt;
# Note that only APRS relevant structures are tested. It might not work&lt;br /&gt;
# for generic AX.25 frames.&lt;br /&gt;
#&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
# APRS data types&lt;br /&gt;
DATA_TYPES_POSITION = b&amp;quot;!&#039;/@`&amp;quot;&lt;br /&gt;
DATA_TYPE_MESSAGE = b&amp;quot;:&amp;quot;&lt;br /&gt;
DATA_TYPE_THIRD_PARTY = b&amp;quot;}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
# from LoRa OE_Style to KISS&lt;br /&gt;
# Addresses must be 6 bytes plus the SSID byte, each character shifted left by 1&lt;br /&gt;
# If it&#039;s the final address in the header, set the low bit to 1&lt;br /&gt;
# If it has been digipeated, set the H bit to 1&lt;br /&gt;
# Ignoring command/response for simple example&lt;br /&gt;
def encode_address(s, final):&lt;br /&gt;
    H=0b00000000 #default H bit low&lt;br /&gt;
    if chr(s[-1])==&#039;*&#039;: #example: WIDE1-1* or WIDE1*&lt;br /&gt;
        s=s[:-1]&lt;br /&gt;
        H=0b10000000&lt;br /&gt;
    if b&amp;quot;-&amp;quot; not in s:&lt;br /&gt;
        s = s + b&amp;quot;-0&amp;quot;  # default to SSID 0&lt;br /&gt;
    call, ssid = s.split(b&#039;-&#039;)&lt;br /&gt;
    if len(call) &amp;lt; 6:&lt;br /&gt;
        call = call + b&amp;quot; &amp;quot;*(6 - len(call)) # pad with spaces&lt;br /&gt;
    encoded_call = [x &amp;lt;&amp;lt; 1 for x in call[0:6]]&lt;br /&gt;
    encoded_ssid = (int(ssid) &amp;lt;&amp;lt; 1) | H | 0b01100000 | (0b00000001 if final else 0)&lt;br /&gt;
    return encoded_call + [encoded_ssid]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def decode_address(data, cursor):&lt;br /&gt;
    (a1, a2, a3, a4, a5, a6, a7) = struct.unpack(&amp;quot;&amp;lt;BBBBBBB&amp;quot;, data[cursor:cursor + 7])&lt;br /&gt;
    hrr = a7 &amp;gt;&amp;gt; 5&lt;br /&gt;
    ssid = (a7 &amp;gt;&amp;gt; 1) &amp;amp; 0xf&lt;br /&gt;
    ext = a7 &amp;amp; 0x1&lt;br /&gt;
    addr = struct.pack(&amp;quot;&amp;lt;BBBBBB&amp;quot;, a1 &amp;gt;&amp;gt; 1, a2 &amp;gt;&amp;gt; 1, a3 &amp;gt;&amp;gt; 1, a4 &amp;gt;&amp;gt; 1, a5 &amp;gt;&amp;gt; 1, a6 &amp;gt;&amp;gt; 1)&lt;br /&gt;
    if ssid != 0:&lt;br /&gt;
        call = addr.strip() + &amp;quot;-{}&amp;quot;.format(ssid).encode()&lt;br /&gt;
    else:&lt;br /&gt;
        call = addr&lt;br /&gt;
    return (call, hrr, ext)&lt;br /&gt;
&lt;br /&gt;
def ax25parser(frame): #extracts fields from ax25 frames and add signal report do payload, if specified&lt;br /&gt;
    try:&lt;br /&gt;
        pos = 0&lt;br /&gt;
&lt;br /&gt;
        # DST&lt;br /&gt;
        (dest_addr, dest_hrr, dest_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;DST: &amp;quot;, dest_addr)&lt;br /&gt;
&lt;br /&gt;
        # SRC&lt;br /&gt;
        (src_addr, src_hrr, src_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;SRC: &amp;quot;, src_addr)&lt;br /&gt;
&lt;br /&gt;
        # REPEATERS&lt;br /&gt;
        ext = src_ext&lt;br /&gt;
        rpt_list = b&amp;quot;&amp;quot;&lt;br /&gt;
        while ext == 0:&lt;br /&gt;
            rpt_addr, rpt_hrr, ext = decode_address(frame, pos)&lt;br /&gt;
            if rpt_hrr==b&#039;111&#039;: # H bit high--&amp;gt;packet has been digipeated&lt;br /&gt;
               rpt_addr+=b&#039;*&#039;&lt;br /&gt;
            rpt_list += b&amp;quot;,&amp;quot;+rpt_addr&lt;br /&gt;
            print(&amp;quot;RPT: &amp;quot;, rpt_addr)&lt;br /&gt;
            pos += 7&lt;br /&gt;
&lt;br /&gt;
        # CTRL&lt;br /&gt;
        ctrl = frame[pos]&lt;br /&gt;
        pos += 1&lt;br /&gt;
        if (ctrl &amp;amp; 0x3) == 0x3:&lt;br /&gt;
            #(pid,) = struct.unpack(&amp;quot;&amp;lt;B&amp;quot;, frame[pos])&lt;br /&gt;
            try:&lt;br /&gt;
                pid = frame[pos]&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pid = 240&lt;br /&gt;
            print(&amp;quot;PID: &amp;quot;+str(pid))&lt;br /&gt;
            pos += 1&lt;br /&gt;
        elif (ctrl &amp;amp; 0x3) == 0x1:&lt;br /&gt;
            # decode_sframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;SFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        elif (ctrl &amp;amp; 0x1) == 0x0:&lt;br /&gt;
            # decode_iframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;IFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        payload=frame[pos:]&lt;br /&gt;
        print(&amp;quot;payload: &amp;quot;, payload)&lt;br /&gt;
        try:&lt;br /&gt;
            dti=payload[0]&lt;br /&gt;
        except Exception:&lt;br /&gt;
            dti=&amp;quot;:&amp;quot;&lt;br /&gt;
        logf(&amp;quot;Extracted AX25 parameters from AX25 Frame&amp;quot;)&lt;br /&gt;
        logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+repr(rpt_list)[3:-1]+&amp;quot; PID: &amp;quot;+str(hex(pid))+&amp;quot; Payload: &amp;quot;+str(payload)[str(payl&lt;br /&gt;
oad).find(&amp;quot;&#039;&amp;quot;):-1])&lt;br /&gt;
&lt;br /&gt;
        return src_addr,dest_addr,rpt_list,payload,dti&lt;br /&gt;
    except Exception:&lt;br /&gt;
        return None,None,None,None,None&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        frame += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to aprx and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_OE(frame,signalreport): #from Lora to Kiss, OE_Style&lt;br /&gt;
    # Ugly frame disassembling&lt;br /&gt;
    if not b&amp;quot;:&amp;quot; in frame:&lt;br /&gt;
        logf(&amp;quot;Can&#039;t decode OE LoRa Frame&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    path = frame.split(b&amp;quot;:&amp;quot;)[0]&lt;br /&gt;
    payload = frame[frame.find(b&amp;quot;:&amp;quot;)+1:]&lt;br /&gt;
    dti = payload[0]&lt;br /&gt;
    src_addr = path.split(b&amp;quot;&amp;gt;&amp;quot;)[0]&lt;br /&gt;
    digis = path[path.find(b&amp;quot;&amp;gt;&amp;quot;) + 1:].split(b&amp;quot;,&amp;quot;)&lt;br /&gt;
    dest_addr = digis.pop(0)&lt;br /&gt;
&lt;br /&gt;
    # destination address&lt;br /&gt;
    packet = encode_address(dest_addr.upper(), False)&lt;br /&gt;
    # source address&lt;br /&gt;
    packet += encode_address(src_addr.upper(), len(digis) == 0)&lt;br /&gt;
    # digipeaters&lt;br /&gt;
    for digi in digis:&lt;br /&gt;
        final_addr = digis.index(digi) == len(digis) - 1&lt;br /&gt;
        packet += encode_address(digi.upper(), final_addr)&lt;br /&gt;
    # control field&lt;br /&gt;
    packet += [0x03]  # This is an UI frame&lt;br /&gt;
    # protocol ID&lt;br /&gt;
    packet += [0xF0]  # No protocol&lt;br /&gt;
    # information field&lt;br /&gt;
    logf(&amp;quot;Extracted AX25 parameters from OE LoRa Frame&amp;quot;)&lt;br /&gt;
    logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+str(digis)[1:-1].replace(&amp;quot;b&amp;quot;,&amp;quot;&amp;quot;).replace(&amp;quot;&#039;&amp;quot;,&amp;quot;&amp;quot;)+&amp;quot; Payload: &amp;quot;+repr(payload)[2:-1])&lt;br /&gt;
    packet += payload&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        #some SW (es OE5BPA) append newline character at the end of packet. Must be cut for appending signal report&lt;br /&gt;
        if chr(packet[-1])==&amp;quot;\n&amp;quot;:&lt;br /&gt;
          packet=packet[:-1]&lt;br /&gt;
        packet += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in packet:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to Dire Wolf and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
    try:&lt;br /&gt;
        #print(bytearray(kiss_frame).hex())&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_OE(frame): #From Kiss to LoRa, OE_Style&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    #build OE_style frame piece by piece&lt;br /&gt;
    result = src_addr.strip()+b&amp;quot;&amp;gt;&amp;quot;+dest_addr.strip()+rpt_list+b&amp;quot;:&amp;quot;+payload&lt;br /&gt;
    return result&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        #Call parse with a string of one or more characters&lt;br /&gt;
        for c in data:&lt;br /&gt;
            if self.state == self.STATE_IDLE:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_FEND&lt;br /&gt;
            elif self.state == self.STATE_FEND:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.reset()&lt;br /&gt;
                else:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_DATA&lt;br /&gt;
            elif self.state == self.STATE_DATA:&lt;br /&gt;
                self.cur_frame.append(c)&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    # frame complete&lt;br /&gt;
                    if self.frame_cb:&lt;br /&gt;
                        self.frame_cb(self.cur_frame)&lt;br /&gt;
                    self.reset()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    # Playground for testing&lt;br /&gt;
&lt;br /&gt;
    kissframe = b&amp;quot;\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V&lt;br /&gt;
\xc0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;OE&lt;br /&gt;
    print(decode_kiss_OE(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;AX25&lt;br /&gt;
    print(decode_kiss_AX25(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test encode OE-&amp;gt;KISS&lt;br /&gt;
    OE_frame = b&amp;quot;OE9TKH-8&amp;gt;APRS,digi-3,digi-2:!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    signalreport=&amp;quot;Level:-115dBm, SNR:0dB&amp;quot;&lt;br /&gt;
    print(encode_kiss_OE(OE_frame,signalreport))&lt;br /&gt;
&lt;br /&gt;
    #test encode AX25-&amp;gt;KISS&lt;br /&gt;
    ax25_frame = b&amp;quot;\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    print(encode_kiss_AX25(ax25_frame,signalreport))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: The above KissHelper.py has not been optimized at all, it was just made to work with all KISS packets and made fault-tolerant so it doens&#039;t crash. The OE-based encoding can be removed, and many simplifications of the code can be made! This is the quick and dirty version.&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:5&lt;br /&gt;
frack11:2000&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=181</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=181"/>
		<updated>2023-08-19T18:56:30Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge) and &#039;&#039;&#039;if you have poor link conditions you can increase &amp;quot;spreadingfactor&amp;quot; above 7&#039;&#039;&#039;, and 7 is the fastest this bonnet can handle (12 is slowest):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 32 #valid preable lenght is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 5 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# This program provides basic KISS AX.25 APRS frame encoding and decoding.&lt;br /&gt;
# Note that only APRS relevant structures are tested. It might not work&lt;br /&gt;
# for generic AX.25 frames.&lt;br /&gt;
#&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
# APRS data types&lt;br /&gt;
DATA_TYPES_POSITION = b&amp;quot;!&#039;/@`&amp;quot;&lt;br /&gt;
DATA_TYPE_MESSAGE = b&amp;quot;:&amp;quot;&lt;br /&gt;
DATA_TYPE_THIRD_PARTY = b&amp;quot;}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
# from LoRa OE_Style to KISS&lt;br /&gt;
# Addresses must be 6 bytes plus the SSID byte, each character shifted left by 1&lt;br /&gt;
# If it&#039;s the final address in the header, set the low bit to 1&lt;br /&gt;
# If it has been digipeated, set the H bit to 1&lt;br /&gt;
# Ignoring command/response for simple example&lt;br /&gt;
def encode_address(s, final):&lt;br /&gt;
    H=0b00000000 #default H bit low&lt;br /&gt;
    if chr(s[-1])==&#039;*&#039;: #example: WIDE1-1* or WIDE1*&lt;br /&gt;
        s=s[:-1]&lt;br /&gt;
        H=0b10000000&lt;br /&gt;
    if b&amp;quot;-&amp;quot; not in s:&lt;br /&gt;
        s = s + b&amp;quot;-0&amp;quot;  # default to SSID 0&lt;br /&gt;
    call, ssid = s.split(b&#039;-&#039;)&lt;br /&gt;
    if len(call) &amp;lt; 6:&lt;br /&gt;
        call = call + b&amp;quot; &amp;quot;*(6 - len(call)) # pad with spaces&lt;br /&gt;
    encoded_call = [x &amp;lt;&amp;lt; 1 for x in call[0:6]]&lt;br /&gt;
    encoded_ssid = (int(ssid) &amp;lt;&amp;lt; 1) | H | 0b01100000 | (0b00000001 if final else 0)&lt;br /&gt;
    return encoded_call + [encoded_ssid]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def decode_address(data, cursor):&lt;br /&gt;
    (a1, a2, a3, a4, a5, a6, a7) = struct.unpack(&amp;quot;&amp;lt;BBBBBBB&amp;quot;, data[cursor:cursor + 7])&lt;br /&gt;
    hrr = a7 &amp;gt;&amp;gt; 5&lt;br /&gt;
    ssid = (a7 &amp;gt;&amp;gt; 1) &amp;amp; 0xf&lt;br /&gt;
    ext = a7 &amp;amp; 0x1&lt;br /&gt;
    addr = struct.pack(&amp;quot;&amp;lt;BBBBBB&amp;quot;, a1 &amp;gt;&amp;gt; 1, a2 &amp;gt;&amp;gt; 1, a3 &amp;gt;&amp;gt; 1, a4 &amp;gt;&amp;gt; 1, a5 &amp;gt;&amp;gt; 1, a6 &amp;gt;&amp;gt; 1)&lt;br /&gt;
    if ssid != 0:&lt;br /&gt;
        call = addr.strip() + &amp;quot;-{}&amp;quot;.format(ssid).encode()&lt;br /&gt;
    else:&lt;br /&gt;
        call = addr&lt;br /&gt;
    return (call, hrr, ext)&lt;br /&gt;
&lt;br /&gt;
def ax25parser(frame): #extracts fields from ax25 frames and add signal report do payload, if specified&lt;br /&gt;
    try:&lt;br /&gt;
        pos = 0&lt;br /&gt;
&lt;br /&gt;
        # DST&lt;br /&gt;
        (dest_addr, dest_hrr, dest_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;DST: &amp;quot;, dest_addr)&lt;br /&gt;
&lt;br /&gt;
        # SRC&lt;br /&gt;
        (src_addr, src_hrr, src_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;SRC: &amp;quot;, src_addr)&lt;br /&gt;
&lt;br /&gt;
        # REPEATERS&lt;br /&gt;
        ext = src_ext&lt;br /&gt;
        rpt_list = b&amp;quot;&amp;quot;&lt;br /&gt;
        while ext == 0:&lt;br /&gt;
            rpt_addr, rpt_hrr, ext = decode_address(frame, pos)&lt;br /&gt;
            if rpt_hrr==b&#039;111&#039;: # H bit high--&amp;gt;packet has been digipeated&lt;br /&gt;
               rpt_addr+=b&#039;*&#039;&lt;br /&gt;
            rpt_list += b&amp;quot;,&amp;quot;+rpt_addr&lt;br /&gt;
            print(&amp;quot;RPT: &amp;quot;, rpt_addr)&lt;br /&gt;
            pos += 7&lt;br /&gt;
&lt;br /&gt;
        # CTRL&lt;br /&gt;
        ctrl = frame[pos]&lt;br /&gt;
        pos += 1&lt;br /&gt;
        if (ctrl &amp;amp; 0x3) == 0x3:&lt;br /&gt;
            #(pid,) = struct.unpack(&amp;quot;&amp;lt;B&amp;quot;, frame[pos])&lt;br /&gt;
            try:&lt;br /&gt;
                pid = frame[pos]&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pid = 240&lt;br /&gt;
            print(&amp;quot;PID: &amp;quot;+str(pid))&lt;br /&gt;
            pos += 1&lt;br /&gt;
        elif (ctrl &amp;amp; 0x3) == 0x1:&lt;br /&gt;
            # decode_sframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;SFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        elif (ctrl &amp;amp; 0x1) == 0x0:&lt;br /&gt;
            # decode_iframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;IFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        payload=frame[pos:]&lt;br /&gt;
        print(&amp;quot;payload: &amp;quot;, payload)&lt;br /&gt;
        try:&lt;br /&gt;
            dti=payload[0]&lt;br /&gt;
        except Exception:&lt;br /&gt;
            dti=&amp;quot;:&amp;quot;&lt;br /&gt;
        logf(&amp;quot;Extracted AX25 parameters from AX25 Frame&amp;quot;)&lt;br /&gt;
        logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+repr(rpt_list)[3:-1]+&amp;quot; PID: &amp;quot;+str(hex(pid))+&amp;quot; Payload: &amp;quot;+str(payload)[str(payl&lt;br /&gt;
oad).find(&amp;quot;&#039;&amp;quot;):-1])&lt;br /&gt;
&lt;br /&gt;
        return src_addr,dest_addr,rpt_list,payload,dti&lt;br /&gt;
    except Exception:&lt;br /&gt;
        return None,None,None,None,None&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        frame += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to aprx and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_OE(frame,signalreport): #from Lora to Kiss, OE_Style&lt;br /&gt;
    # Ugly frame disassembling&lt;br /&gt;
    if not b&amp;quot;:&amp;quot; in frame:&lt;br /&gt;
        logf(&amp;quot;Can&#039;t decode OE LoRa Frame&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    path = frame.split(b&amp;quot;:&amp;quot;)[0]&lt;br /&gt;
    payload = frame[frame.find(b&amp;quot;:&amp;quot;)+1:]&lt;br /&gt;
    dti = payload[0]&lt;br /&gt;
    src_addr = path.split(b&amp;quot;&amp;gt;&amp;quot;)[0]&lt;br /&gt;
    digis = path[path.find(b&amp;quot;&amp;gt;&amp;quot;) + 1:].split(b&amp;quot;,&amp;quot;)&lt;br /&gt;
    dest_addr = digis.pop(0)&lt;br /&gt;
&lt;br /&gt;
    # destination address&lt;br /&gt;
    packet = encode_address(dest_addr.upper(), False)&lt;br /&gt;
    # source address&lt;br /&gt;
    packet += encode_address(src_addr.upper(), len(digis) == 0)&lt;br /&gt;
    # digipeaters&lt;br /&gt;
    for digi in digis:&lt;br /&gt;
        final_addr = digis.index(digi) == len(digis) - 1&lt;br /&gt;
        packet += encode_address(digi.upper(), final_addr)&lt;br /&gt;
    # control field&lt;br /&gt;
    packet += [0x03]  # This is an UI frame&lt;br /&gt;
    # protocol ID&lt;br /&gt;
    packet += [0xF0]  # No protocol&lt;br /&gt;
    # information field&lt;br /&gt;
    logf(&amp;quot;Extracted AX25 parameters from OE LoRa Frame&amp;quot;)&lt;br /&gt;
    logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+str(digis)[1:-1].replace(&amp;quot;b&amp;quot;,&amp;quot;&amp;quot;).replace(&amp;quot;&#039;&amp;quot;,&amp;quot;&amp;quot;)+&amp;quot; Payload: &amp;quot;+repr(payload)[2:-1])&lt;br /&gt;
    packet += payload&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        #some SW (es OE5BPA) append newline character at the end of packet. Must be cut for appending signal report&lt;br /&gt;
        if chr(packet[-1])==&amp;quot;\n&amp;quot;:&lt;br /&gt;
          packet=packet[:-1]&lt;br /&gt;
        packet += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in packet:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to Dire Wolf and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
    try:&lt;br /&gt;
        #print(bytearray(kiss_frame).hex())&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_OE(frame): #From Kiss to LoRa, OE_Style&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    #build OE_style frame piece by piece&lt;br /&gt;
    result = src_addr.strip()+b&amp;quot;&amp;gt;&amp;quot;+dest_addr.strip()+rpt_list+b&amp;quot;:&amp;quot;+payload&lt;br /&gt;
    return result&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        #Call parse with a string of one or more characters&lt;br /&gt;
        for c in data:&lt;br /&gt;
            if self.state == self.STATE_IDLE:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_FEND&lt;br /&gt;
            elif self.state == self.STATE_FEND:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.reset()&lt;br /&gt;
                else:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_DATA&lt;br /&gt;
            elif self.state == self.STATE_DATA:&lt;br /&gt;
                self.cur_frame.append(c)&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    # frame complete&lt;br /&gt;
                    if self.frame_cb:&lt;br /&gt;
                        self.frame_cb(self.cur_frame)&lt;br /&gt;
                    self.reset()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    # Playground for testing&lt;br /&gt;
&lt;br /&gt;
    kissframe = b&amp;quot;\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V&lt;br /&gt;
\xc0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;OE&lt;br /&gt;
    print(decode_kiss_OE(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;AX25&lt;br /&gt;
    print(decode_kiss_AX25(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test encode OE-&amp;gt;KISS&lt;br /&gt;
    OE_frame = b&amp;quot;OE9TKH-8&amp;gt;APRS,digi-3,digi-2:!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    signalreport=&amp;quot;Level:-115dBm, SNR:0dB&amp;quot;&lt;br /&gt;
    print(encode_kiss_OE(OE_frame,signalreport))&lt;br /&gt;
&lt;br /&gt;
    #test encode AX25-&amp;gt;KISS&lt;br /&gt;
    ax25_frame = b&amp;quot;\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    print(encode_kiss_AX25(ax25_frame,signalreport))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: The above KissHelper.py has not been optimized at all, it was just made to work with all KISS packets and made fault-tolerant so it doens&#039;t crash. The OE-based encoding can be removed, and many simplifications of the code can be made! This is the quick and dirty version.&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
TARPN only supports serial TNCs, so first we need to &amp;quot;fake&amp;quot; a serial TNC that actually connects to our TCP-bsaed LoRa KISS TNC using the &amp;quot;socat&amp;quot; command.&lt;br /&gt;
&lt;br /&gt;
* sudo apt install socat&lt;br /&gt;
* sudo nano lora-port-up.sh&lt;br /&gt;
* Use these contents but replace the IP address to your LoRa KISS TNC&#039;s address:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
printf &amp;quot;Bringing up LoRa port as /dev/ttyS0\n&amp;quot;&lt;br /&gt;
while true&lt;br /&gt;
    do&lt;br /&gt;
      sudo socat pty,link=/dev/ttyS0,b115200,raw,echo=0,mode=777 tcp:192.168.1.90:10001,forever,interval=10&lt;br /&gt;
      printf &amp;quot;LoRa port /dev/ttyS0 disconnected, waiting 1 second and bringing it up again.\n&amp;quot;&lt;br /&gt;
      sleep 1&lt;br /&gt;
    done&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Start the fake serial device with &amp;quot;./lora-port-up.sh &amp;amp;&amp;quot; and it will show up as /dev/ttyS0 like a serial TNC would be. (You might want to do this in crontab or something, so it always runs on node restart.)&lt;br /&gt;
&lt;br /&gt;
Then you can edit your TARPN node.ini to configure port 11 as follows:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
usb-port11:ENABLE&lt;br /&gt;
portdev11:/dev/ttyS0&lt;br /&gt;
speed11:115200&lt;br /&gt;
txdelay11:5&lt;br /&gt;
frack11:2000&lt;br /&gt;
neighbor11:KV4P-3&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, assign &amp;quot;neighbor11&amp;quot; to the callsign/ssid of the node you plan to connect to via LoRa on your new port 11.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=180</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=180"/>
		<updated>2023-08-14T23:29:16Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge) and &#039;&#039;&#039;if you have poor link conditions you can increase &amp;quot;spreadingfactor&amp;quot; above 7&#039;&#039;&#039;, which is the fastest this bonnet can handle:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 32 #valid preable lenght is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 5 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# This program provides basic KISS AX.25 APRS frame encoding and decoding.&lt;br /&gt;
# Note that only APRS relevant structures are tested. It might not work&lt;br /&gt;
# for generic AX.25 frames.&lt;br /&gt;
#&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
# APRS data types&lt;br /&gt;
DATA_TYPES_POSITION = b&amp;quot;!&#039;/@`&amp;quot;&lt;br /&gt;
DATA_TYPE_MESSAGE = b&amp;quot;:&amp;quot;&lt;br /&gt;
DATA_TYPE_THIRD_PARTY = b&amp;quot;}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
# from LoRa OE_Style to KISS&lt;br /&gt;
# Addresses must be 6 bytes plus the SSID byte, each character shifted left by 1&lt;br /&gt;
# If it&#039;s the final address in the header, set the low bit to 1&lt;br /&gt;
# If it has been digipeated, set the H bit to 1&lt;br /&gt;
# Ignoring command/response for simple example&lt;br /&gt;
def encode_address(s, final):&lt;br /&gt;
    H=0b00000000 #default H bit low&lt;br /&gt;
    if chr(s[-1])==&#039;*&#039;: #example: WIDE1-1* or WIDE1*&lt;br /&gt;
        s=s[:-1]&lt;br /&gt;
        H=0b10000000&lt;br /&gt;
    if b&amp;quot;-&amp;quot; not in s:&lt;br /&gt;
        s = s + b&amp;quot;-0&amp;quot;  # default to SSID 0&lt;br /&gt;
    call, ssid = s.split(b&#039;-&#039;)&lt;br /&gt;
    if len(call) &amp;lt; 6:&lt;br /&gt;
        call = call + b&amp;quot; &amp;quot;*(6 - len(call)) # pad with spaces&lt;br /&gt;
    encoded_call = [x &amp;lt;&amp;lt; 1 for x in call[0:6]]&lt;br /&gt;
    encoded_ssid = (int(ssid) &amp;lt;&amp;lt; 1) | H | 0b01100000 | (0b00000001 if final else 0)&lt;br /&gt;
    return encoded_call + [encoded_ssid]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def decode_address(data, cursor):&lt;br /&gt;
    (a1, a2, a3, a4, a5, a6, a7) = struct.unpack(&amp;quot;&amp;lt;BBBBBBB&amp;quot;, data[cursor:cursor + 7])&lt;br /&gt;
    hrr = a7 &amp;gt;&amp;gt; 5&lt;br /&gt;
    ssid = (a7 &amp;gt;&amp;gt; 1) &amp;amp; 0xf&lt;br /&gt;
    ext = a7 &amp;amp; 0x1&lt;br /&gt;
    addr = struct.pack(&amp;quot;&amp;lt;BBBBBB&amp;quot;, a1 &amp;gt;&amp;gt; 1, a2 &amp;gt;&amp;gt; 1, a3 &amp;gt;&amp;gt; 1, a4 &amp;gt;&amp;gt; 1, a5 &amp;gt;&amp;gt; 1, a6 &amp;gt;&amp;gt; 1)&lt;br /&gt;
    if ssid != 0:&lt;br /&gt;
        call = addr.strip() + &amp;quot;-{}&amp;quot;.format(ssid).encode()&lt;br /&gt;
    else:&lt;br /&gt;
        call = addr&lt;br /&gt;
    return (call, hrr, ext)&lt;br /&gt;
&lt;br /&gt;
def ax25parser(frame): #extracts fields from ax25 frames and add signal report do payload, if specified&lt;br /&gt;
    try:&lt;br /&gt;
        pos = 0&lt;br /&gt;
&lt;br /&gt;
        # DST&lt;br /&gt;
        (dest_addr, dest_hrr, dest_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;DST: &amp;quot;, dest_addr)&lt;br /&gt;
&lt;br /&gt;
        # SRC&lt;br /&gt;
        (src_addr, src_hrr, src_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;SRC: &amp;quot;, src_addr)&lt;br /&gt;
&lt;br /&gt;
        # REPEATERS&lt;br /&gt;
        ext = src_ext&lt;br /&gt;
        rpt_list = b&amp;quot;&amp;quot;&lt;br /&gt;
        while ext == 0:&lt;br /&gt;
            rpt_addr, rpt_hrr, ext = decode_address(frame, pos)&lt;br /&gt;
            if rpt_hrr==b&#039;111&#039;: # H bit high--&amp;gt;packet has been digipeated&lt;br /&gt;
               rpt_addr+=b&#039;*&#039;&lt;br /&gt;
            rpt_list += b&amp;quot;,&amp;quot;+rpt_addr&lt;br /&gt;
            print(&amp;quot;RPT: &amp;quot;, rpt_addr)&lt;br /&gt;
            pos += 7&lt;br /&gt;
&lt;br /&gt;
        # CTRL&lt;br /&gt;
        ctrl = frame[pos]&lt;br /&gt;
        pos += 1&lt;br /&gt;
        if (ctrl &amp;amp; 0x3) == 0x3:&lt;br /&gt;
            #(pid,) = struct.unpack(&amp;quot;&amp;lt;B&amp;quot;, frame[pos])&lt;br /&gt;
            try:&lt;br /&gt;
                pid = frame[pos]&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pid = 240&lt;br /&gt;
            print(&amp;quot;PID: &amp;quot;+str(pid))&lt;br /&gt;
            pos += 1&lt;br /&gt;
        elif (ctrl &amp;amp; 0x3) == 0x1:&lt;br /&gt;
            # decode_sframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;SFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        elif (ctrl &amp;amp; 0x1) == 0x0:&lt;br /&gt;
            # decode_iframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;IFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        payload=frame[pos:]&lt;br /&gt;
        print(&amp;quot;payload: &amp;quot;, payload)&lt;br /&gt;
        try:&lt;br /&gt;
            dti=payload[0]&lt;br /&gt;
        except Exception:&lt;br /&gt;
            dti=&amp;quot;:&amp;quot;&lt;br /&gt;
        logf(&amp;quot;Extracted AX25 parameters from AX25 Frame&amp;quot;)&lt;br /&gt;
        logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+repr(rpt_list)[3:-1]+&amp;quot; PID: &amp;quot;+str(hex(pid))+&amp;quot; Payload: &amp;quot;+str(payload)[str(payl&lt;br /&gt;
oad).find(&amp;quot;&#039;&amp;quot;):-1])&lt;br /&gt;
&lt;br /&gt;
        return src_addr,dest_addr,rpt_list,payload,dti&lt;br /&gt;
    except Exception:&lt;br /&gt;
        return None,None,None,None,None&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        frame += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to aprx and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_OE(frame,signalreport): #from Lora to Kiss, OE_Style&lt;br /&gt;
    # Ugly frame disassembling&lt;br /&gt;
    if not b&amp;quot;:&amp;quot; in frame:&lt;br /&gt;
        logf(&amp;quot;Can&#039;t decode OE LoRa Frame&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    path = frame.split(b&amp;quot;:&amp;quot;)[0]&lt;br /&gt;
    payload = frame[frame.find(b&amp;quot;:&amp;quot;)+1:]&lt;br /&gt;
    dti = payload[0]&lt;br /&gt;
    src_addr = path.split(b&amp;quot;&amp;gt;&amp;quot;)[0]&lt;br /&gt;
    digis = path[path.find(b&amp;quot;&amp;gt;&amp;quot;) + 1:].split(b&amp;quot;,&amp;quot;)&lt;br /&gt;
    dest_addr = digis.pop(0)&lt;br /&gt;
&lt;br /&gt;
    # destination address&lt;br /&gt;
    packet = encode_address(dest_addr.upper(), False)&lt;br /&gt;
    # source address&lt;br /&gt;
    packet += encode_address(src_addr.upper(), len(digis) == 0)&lt;br /&gt;
    # digipeaters&lt;br /&gt;
    for digi in digis:&lt;br /&gt;
        final_addr = digis.index(digi) == len(digis) - 1&lt;br /&gt;
        packet += encode_address(digi.upper(), final_addr)&lt;br /&gt;
    # control field&lt;br /&gt;
    packet += [0x03]  # This is an UI frame&lt;br /&gt;
    # protocol ID&lt;br /&gt;
    packet += [0xF0]  # No protocol&lt;br /&gt;
    # information field&lt;br /&gt;
    logf(&amp;quot;Extracted AX25 parameters from OE LoRa Frame&amp;quot;)&lt;br /&gt;
    logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+str(digis)[1:-1].replace(&amp;quot;b&amp;quot;,&amp;quot;&amp;quot;).replace(&amp;quot;&#039;&amp;quot;,&amp;quot;&amp;quot;)+&amp;quot; Payload: &amp;quot;+repr(payload)[2:-1])&lt;br /&gt;
    packet += payload&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        #some SW (es OE5BPA) append newline character at the end of packet. Must be cut for appending signal report&lt;br /&gt;
        if chr(packet[-1])==&amp;quot;\n&amp;quot;:&lt;br /&gt;
          packet=packet[:-1]&lt;br /&gt;
        packet += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in packet:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to Dire Wolf and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
    try:&lt;br /&gt;
        #print(bytearray(kiss_frame).hex())&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_OE(frame): #From Kiss to LoRa, OE_Style&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    #build OE_style frame piece by piece&lt;br /&gt;
    result = src_addr.strip()+b&amp;quot;&amp;gt;&amp;quot;+dest_addr.strip()+rpt_list+b&amp;quot;:&amp;quot;+payload&lt;br /&gt;
    return result&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        #Call parse with a string of one or more characters&lt;br /&gt;
        for c in data:&lt;br /&gt;
            if self.state == self.STATE_IDLE:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_FEND&lt;br /&gt;
            elif self.state == self.STATE_FEND:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.reset()&lt;br /&gt;
                else:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_DATA&lt;br /&gt;
            elif self.state == self.STATE_DATA:&lt;br /&gt;
                self.cur_frame.append(c)&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    # frame complete&lt;br /&gt;
                    if self.frame_cb:&lt;br /&gt;
                        self.frame_cb(self.cur_frame)&lt;br /&gt;
                    self.reset()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    # Playground for testing&lt;br /&gt;
&lt;br /&gt;
    kissframe = b&amp;quot;\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V&lt;br /&gt;
\xc0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;OE&lt;br /&gt;
    print(decode_kiss_OE(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;AX25&lt;br /&gt;
    print(decode_kiss_AX25(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test encode OE-&amp;gt;KISS&lt;br /&gt;
    OE_frame = b&amp;quot;OE9TKH-8&amp;gt;APRS,digi-3,digi-2:!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    signalreport=&amp;quot;Level:-115dBm, SNR:0dB&amp;quot;&lt;br /&gt;
    print(encode_kiss_OE(OE_frame,signalreport))&lt;br /&gt;
&lt;br /&gt;
    #test encode AX25-&amp;gt;KISS&lt;br /&gt;
    ax25_frame = b&amp;quot;\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    print(encode_kiss_AX25(ax25_frame,signalreport))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: The above KissHelper.py has not been optimized at all, it was just made to work with all KISS packets and made fault-tolerant so it doens&#039;t crash. The OE-based encoding can be removed, and many simplifications of the code can be made! This is the quick and dirty version.&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
On your TARPN node, you won&#039;t be able to run it with &amp;quot;tarpn service start&amp;quot; until TARPN is updated to allow TCP-based KISS TNCs on port 11 and 12. For now, you can force this by &amp;quot;tarpn service stop&amp;quot;, then editing bpq/bpq32.cfg.&lt;br /&gt;
&lt;br /&gt;
# nano bpq/bpq32.cfg&lt;br /&gt;
# Search for &amp;quot;PORTNUM=11&amp;quot; to find the right section&lt;br /&gt;
# Paste this in (change IPADDR to point at the Raspberry Pi your LoRa bonnet is attached to):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
PORT&lt;br /&gt;
PORTNUM=11                                ; Optional but sets port number if stated&lt;br /&gt;
ID=LoRa link to KV4P-3     ; Displayed by PORTS command&lt;br /&gt;
TYPE=ASYNC                                ; Port is RS232 Com&lt;br /&gt;
PROTOCOL=KISS                         ; TNC is used in KISS (or JKISS) mode&lt;br /&gt;
;; See ..\RelatedFiles\KissRoms\KissRoms.zip.&lt;br /&gt;
FULLDUP=0                      ; Only meaningful for KISS (or JKISS) devices&lt;br /&gt;
;COMPORT=/dev/ttyUSB0&lt;br /&gt;
;SPEED=57600                    ; RS232 COM PORT SPEED&lt;br /&gt;
CHANNEL=A                      ; A for single channel TNC, A or B for multichannel&lt;br /&gt;
PERSIST=180                    ; PERSIST=256/(# of transmitters-1)&lt;br /&gt;
SLOTTIME=50                    ; CMSA interval timer in milliseconds&lt;br /&gt;
TXDELAY=5          ; Transmit keyup delay in milliseconds&lt;br /&gt;
TXTAIL=1                       ; TX key down, in milliseconds, at packet end&lt;br /&gt;
QUALITY=1                      ; Quality factor applied to node broadcasts heard on&lt;br /&gt;
;                               ; this port, unless overridden by a locked route&lt;br /&gt;
;                               ; entry. Setting to 0 stops node broadcasts&lt;br /&gt;
MINQUAL=81                     ; Entries in the nodes table with qualities greater or&lt;br /&gt;
;                               ; equal to MINQUAL will be sent on this port. A value&lt;br /&gt;
;                               ; of 0 sends everything.&lt;br /&gt;
MAXFRAME=1                     ; Max outstanding frames (1 thru 7)&lt;br /&gt;
FRACK=2000              ; Level 2 timout in milliseconds&lt;br /&gt;
RESPTIME=40                    ; Level 2 delayed ack timer in milliseconds&lt;br /&gt;
RETRIES=20                     ; Level 2 maximum retry value&lt;br /&gt;
PACLEN=136                      ; Default max packet length for this port&lt;br /&gt;
UNPROTO=ID                     ; BTEXT broadcast addrs format: DEST[,digi1[,digi2]]&lt;br /&gt;
L3ONLY=0                       ; 1=No user downlink connects on this port&lt;br /&gt;
DIGIFLAG=0                     ; Digipeat: 0=OFF, 1=ALL, 255=UI Only&lt;br /&gt;
DIGIPORT=0                     ; Port on which to send digi&#039;d frames (0 = same port)&lt;br /&gt;
USERS=0                        ; Maximum number of L2 sessions, 0 = no limit&lt;br /&gt;
IGNOREUNLOCKEDROUTES=0  ; ignore node broadcasts from other than locked routes&lt;br /&gt;
IPADDR=192.168.1.90       ; Address of PC runnning the TNC. Usually this is the same machine as BPQ, so use 127.0.0.1 but can be on anoth&amp;gt;&lt;br /&gt;
TCPPORT=10001           ; Port TNC listens on. Default for UZ7HO is 8100 and Direwolf 8001&lt;br /&gt;
KISSOPTIONS=NOPARAMS&lt;br /&gt;
ENDPORT&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You also need to add a fixed route for port 11 or 12 in bpq32.cfg. The appropriate section looks like this, and has commented-out port 11 and 12 routes already listed:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
ROUTES: ; Locked routes (31 maximum)&lt;br /&gt;
KV4P-3,200,11&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, replace &amp;quot;KV4P-3&amp;quot; with whatever neighbor you plan to link to with that LoRa link.&lt;br /&gt;
&lt;br /&gt;
Start TARPN with ./linbpq (instead of &amp;quot;tarpn service start&amp;quot;). You won&#039;t be able to use TARPN HOME until TARPN is updated to handle TCP-based KISS TNCs natively. But you can manually connect to your node (e.g. with QtTerm), and get into chat that way, and test out your new LoRa link, etc.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=179</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=179"/>
		<updated>2023-08-14T23:27:59Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge) and &#039;&#039;&#039;if you have poor link conditions you can increase &amp;quot;spreadingfactor&amp;quot; above 7&#039;&#039;&#039;, which is the fastest this bonnet can handle:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 32 #valid preable lenght is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 5 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# This program provides basic KISS AX.25 APRS frame encoding and decoding.&lt;br /&gt;
# Note that only APRS relevant structures are tested. It might not work&lt;br /&gt;
# for generic AX.25 frames.&lt;br /&gt;
#&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
# APRS data types&lt;br /&gt;
DATA_TYPES_POSITION = b&amp;quot;!&#039;/@`&amp;quot;&lt;br /&gt;
DATA_TYPE_MESSAGE = b&amp;quot;:&amp;quot;&lt;br /&gt;
DATA_TYPE_THIRD_PARTY = b&amp;quot;}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
# from LoRa OE_Style to KISS&lt;br /&gt;
# Addresses must be 6 bytes plus the SSID byte, each character shifted left by 1&lt;br /&gt;
# If it&#039;s the final address in the header, set the low bit to 1&lt;br /&gt;
# If it has been digipeated, set the H bit to 1&lt;br /&gt;
# Ignoring command/response for simple example&lt;br /&gt;
def encode_address(s, final):&lt;br /&gt;
    H=0b00000000 #default H bit low&lt;br /&gt;
    if chr(s[-1])==&#039;*&#039;: #example: WIDE1-1* or WIDE1*&lt;br /&gt;
        s=s[:-1]&lt;br /&gt;
        H=0b10000000&lt;br /&gt;
    if b&amp;quot;-&amp;quot; not in s:&lt;br /&gt;
        s = s + b&amp;quot;-0&amp;quot;  # default to SSID 0&lt;br /&gt;
    call, ssid = s.split(b&#039;-&#039;)&lt;br /&gt;
    if len(call) &amp;lt; 6:&lt;br /&gt;
        call = call + b&amp;quot; &amp;quot;*(6 - len(call)) # pad with spaces&lt;br /&gt;
    encoded_call = [x &amp;lt;&amp;lt; 1 for x in call[0:6]]&lt;br /&gt;
    encoded_ssid = (int(ssid) &amp;lt;&amp;lt; 1) | H | 0b01100000 | (0b00000001 if final else 0)&lt;br /&gt;
    return encoded_call + [encoded_ssid]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def decode_address(data, cursor):&lt;br /&gt;
    (a1, a2, a3, a4, a5, a6, a7) = struct.unpack(&amp;quot;&amp;lt;BBBBBBB&amp;quot;, data[cursor:cursor + 7])&lt;br /&gt;
    hrr = a7 &amp;gt;&amp;gt; 5&lt;br /&gt;
    ssid = (a7 &amp;gt;&amp;gt; 1) &amp;amp; 0xf&lt;br /&gt;
    ext = a7 &amp;amp; 0x1&lt;br /&gt;
    addr = struct.pack(&amp;quot;&amp;lt;BBBBBB&amp;quot;, a1 &amp;gt;&amp;gt; 1, a2 &amp;gt;&amp;gt; 1, a3 &amp;gt;&amp;gt; 1, a4 &amp;gt;&amp;gt; 1, a5 &amp;gt;&amp;gt; 1, a6 &amp;gt;&amp;gt; 1)&lt;br /&gt;
    if ssid != 0:&lt;br /&gt;
        call = addr.strip() + &amp;quot;-{}&amp;quot;.format(ssid).encode()&lt;br /&gt;
    else:&lt;br /&gt;
        call = addr&lt;br /&gt;
    return (call, hrr, ext)&lt;br /&gt;
&lt;br /&gt;
def ax25parser(frame): #extracts fields from ax25 frames and add signal report do payload, if specified&lt;br /&gt;
    try:&lt;br /&gt;
        pos = 0&lt;br /&gt;
&lt;br /&gt;
        # DST&lt;br /&gt;
        (dest_addr, dest_hrr, dest_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;DST: &amp;quot;, dest_addr)&lt;br /&gt;
&lt;br /&gt;
        # SRC&lt;br /&gt;
        (src_addr, src_hrr, src_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;SRC: &amp;quot;, src_addr)&lt;br /&gt;
&lt;br /&gt;
        # REPEATERS&lt;br /&gt;
        ext = src_ext&lt;br /&gt;
        rpt_list = b&amp;quot;&amp;quot;&lt;br /&gt;
        while ext == 0:&lt;br /&gt;
            rpt_addr, rpt_hrr, ext = decode_address(frame, pos)&lt;br /&gt;
            if rpt_hrr==b&#039;111&#039;: # H bit high--&amp;gt;packet has been digipeated&lt;br /&gt;
               rpt_addr+=b&#039;*&#039;&lt;br /&gt;
            rpt_list += b&amp;quot;,&amp;quot;+rpt_addr&lt;br /&gt;
            print(&amp;quot;RPT: &amp;quot;, rpt_addr)&lt;br /&gt;
            pos += 7&lt;br /&gt;
&lt;br /&gt;
        # CTRL&lt;br /&gt;
        ctrl = frame[pos]&lt;br /&gt;
        pos += 1&lt;br /&gt;
        if (ctrl &amp;amp; 0x3) == 0x3:&lt;br /&gt;
            #(pid,) = struct.unpack(&amp;quot;&amp;lt;B&amp;quot;, frame[pos])&lt;br /&gt;
            try:&lt;br /&gt;
                pid = frame[pos]&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pid = 240&lt;br /&gt;
            print(&amp;quot;PID: &amp;quot;+str(pid))&lt;br /&gt;
            pos += 1&lt;br /&gt;
        elif (ctrl &amp;amp; 0x3) == 0x1:&lt;br /&gt;
            # decode_sframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;SFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        elif (ctrl &amp;amp; 0x1) == 0x0:&lt;br /&gt;
            # decode_iframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;IFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        payload=frame[pos:]&lt;br /&gt;
        print(&amp;quot;payload: &amp;quot;, payload)&lt;br /&gt;
        try:&lt;br /&gt;
            dti=payload[0]&lt;br /&gt;
        except Exception:&lt;br /&gt;
            dti=&amp;quot;:&amp;quot;&lt;br /&gt;
        logf(&amp;quot;Extracted AX25 parameters from AX25 Frame&amp;quot;)&lt;br /&gt;
        logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+repr(rpt_list)[3:-1]+&amp;quot; PID: &amp;quot;+str(hex(pid))+&amp;quot; Payload: &amp;quot;+str(payload)[str(payl&lt;br /&gt;
oad).find(&amp;quot;&#039;&amp;quot;):-1])&lt;br /&gt;
&lt;br /&gt;
        return src_addr,dest_addr,rpt_list,payload,dti&lt;br /&gt;
    except Exception:&lt;br /&gt;
        return None,None,None,None,None&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        frame += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to aprx and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_OE(frame,signalreport): #from Lora to Kiss, OE_Style&lt;br /&gt;
    # Ugly frame disassembling&lt;br /&gt;
    if not b&amp;quot;:&amp;quot; in frame:&lt;br /&gt;
        logf(&amp;quot;Can&#039;t decode OE LoRa Frame&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    path = frame.split(b&amp;quot;:&amp;quot;)[0]&lt;br /&gt;
    payload = frame[frame.find(b&amp;quot;:&amp;quot;)+1:]&lt;br /&gt;
    dti = payload[0]&lt;br /&gt;
    src_addr = path.split(b&amp;quot;&amp;gt;&amp;quot;)[0]&lt;br /&gt;
    digis = path[path.find(b&amp;quot;&amp;gt;&amp;quot;) + 1:].split(b&amp;quot;,&amp;quot;)&lt;br /&gt;
    dest_addr = digis.pop(0)&lt;br /&gt;
&lt;br /&gt;
    # destination address&lt;br /&gt;
    packet = encode_address(dest_addr.upper(), False)&lt;br /&gt;
    # source address&lt;br /&gt;
    packet += encode_address(src_addr.upper(), len(digis) == 0)&lt;br /&gt;
    # digipeaters&lt;br /&gt;
    for digi in digis:&lt;br /&gt;
        final_addr = digis.index(digi) == len(digis) - 1&lt;br /&gt;
        packet += encode_address(digi.upper(), final_addr)&lt;br /&gt;
    # control field&lt;br /&gt;
    packet += [0x03]  # This is an UI frame&lt;br /&gt;
    # protocol ID&lt;br /&gt;
    packet += [0xF0]  # No protocol&lt;br /&gt;
    # information field&lt;br /&gt;
    logf(&amp;quot;Extracted AX25 parameters from OE LoRa Frame&amp;quot;)&lt;br /&gt;
    logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+str(digis)[1:-1].replace(&amp;quot;b&amp;quot;,&amp;quot;&amp;quot;).replace(&amp;quot;&#039;&amp;quot;,&amp;quot;&amp;quot;)+&amp;quot; Payload: &amp;quot;+repr(payload)[2:-1])&lt;br /&gt;
    packet += payload&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        #some SW (es OE5BPA) append newline character at the end of packet. Must be cut for appending signal report&lt;br /&gt;
        if chr(packet[-1])==&amp;quot;\n&amp;quot;:&lt;br /&gt;
          packet=packet[:-1]&lt;br /&gt;
        packet += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in packet:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to Dire Wolf and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
    try:&lt;br /&gt;
        #print(bytearray(kiss_frame).hex())&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_OE(frame): #From Kiss to LoRa, OE_Style&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    #build OE_style frame piece by piece&lt;br /&gt;
    result = src_addr.strip()+b&amp;quot;&amp;gt;&amp;quot;+dest_addr.strip()+rpt_list+b&amp;quot;:&amp;quot;+payload&lt;br /&gt;
    return result&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        #Call parse with a string of one or more characters&lt;br /&gt;
        for c in data:&lt;br /&gt;
            if self.state == self.STATE_IDLE:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_FEND&lt;br /&gt;
            elif self.state == self.STATE_FEND:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.reset()&lt;br /&gt;
                else:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_DATA&lt;br /&gt;
            elif self.state == self.STATE_DATA:&lt;br /&gt;
                self.cur_frame.append(c)&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    # frame complete&lt;br /&gt;
                    if self.frame_cb:&lt;br /&gt;
                        self.frame_cb(self.cur_frame)&lt;br /&gt;
                    self.reset()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    # Playground for testing&lt;br /&gt;
&lt;br /&gt;
    kissframe = b&amp;quot;\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V&lt;br /&gt;
\xc0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;OE&lt;br /&gt;
    print(decode_kiss_OE(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;AX25&lt;br /&gt;
    print(decode_kiss_AX25(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test encode OE-&amp;gt;KISS&lt;br /&gt;
    OE_frame = b&amp;quot;OE9TKH-8&amp;gt;APRS,digi-3,digi-2:!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    signalreport=&amp;quot;Level:-115dBm, SNR:0dB&amp;quot;&lt;br /&gt;
    print(encode_kiss_OE(OE_frame,signalreport))&lt;br /&gt;
&lt;br /&gt;
    #test encode AX25-&amp;gt;KISS&lt;br /&gt;
    ax25_frame = b&amp;quot;\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    print(encode_kiss_AX25(ax25_frame,signalreport))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: The above KissHelper.py has not been optimized at all, it was just made to work with all KISS packets and made fault-tolerant so it doens&#039;t crash. The OE-based encoding can be removed, and many simplifications of the code can be made! This is the quick and dirty version.&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
This is what you should see:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
########################&lt;br /&gt;
#LORA KISS TNC STARTING#&lt;br /&gt;
########################&lt;br /&gt;
#Lora parameters:&lt;br /&gt;
frequency= 910300000&lt;br /&gt;
preamble= 32&lt;br /&gt;
spreadingFactor= 7&lt;br /&gt;
bandwidth= 500000&lt;br /&gt;
codingrate= 5&lt;br /&gt;
APPEND_SIGNAL_REPORT= False&lt;br /&gt;
outputPower= 17&lt;br /&gt;
TX_OE_Style= False&lt;br /&gt;
sync_word= 0x12&lt;br /&gt;
crc= True&lt;br /&gt;
########################&lt;br /&gt;
2023/08/15 00:27:18 - KISS-Server: Started. Listening on IP 0.0.0.0 Port: 10001&lt;br /&gt;
&lt;br /&gt;
2023/08/15 00:27:18 - LoRa radio initialized. Waiting for LoRa Spots...&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
On your TARPN node, you won&#039;t be able to run it with &amp;quot;tarpn service start&amp;quot; until TARPN is updated to allow TCP-based KISS TNCs on port 11 and 12. For now, you can force this by &amp;quot;tarpn service stop&amp;quot;, then editing bpq/bpq32.cfg.&lt;br /&gt;
&lt;br /&gt;
# nano bpq/bpq32.cfg&lt;br /&gt;
# Search for &amp;quot;PORTNUM=11&amp;quot; to find the right section&lt;br /&gt;
# Paste this in:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
PORT&lt;br /&gt;
PORTNUM=11                                ; Optional but sets port number if stated&lt;br /&gt;
ID=LoRa link to KV4P-3     ; Displayed by PORTS command&lt;br /&gt;
TYPE=ASYNC                                ; Port is RS232 Com&lt;br /&gt;
PROTOCOL=KISS                         ; TNC is used in KISS (or JKISS) mode&lt;br /&gt;
;; See ..\RelatedFiles\KissRoms\KissRoms.zip.&lt;br /&gt;
FULLDUP=0                      ; Only meaningful for KISS (or JKISS) devices&lt;br /&gt;
;COMPORT=/dev/ttyUSB0&lt;br /&gt;
;SPEED=57600                    ; RS232 COM PORT SPEED&lt;br /&gt;
CHANNEL=A                      ; A for single channel TNC, A or B for multichannel&lt;br /&gt;
PERSIST=180                    ; PERSIST=256/(# of transmitters-1)&lt;br /&gt;
SLOTTIME=50                    ; CMSA interval timer in milliseconds&lt;br /&gt;
TXDELAY=50          ; Transmit keyup delay in milliseconds&lt;br /&gt;
TXTAIL=1                       ; TX key down, in milliseconds, at packet end&lt;br /&gt;
QUALITY=1                      ; Quality factor applied to node broadcasts heard on&lt;br /&gt;
;                               ; this port, unless overridden by a locked route&lt;br /&gt;
;                               ; entry. Setting to 0 stops node broadcasts&lt;br /&gt;
MINQUAL=81                     ; Entries in the nodes table with qualities greater or&lt;br /&gt;
;                               ; equal to MINQUAL will be sent on this port. A value&lt;br /&gt;
;                               ; of 0 sends everything.&lt;br /&gt;
MAXFRAME=1                     ; Max outstanding frames (1 thru 7)&lt;br /&gt;
FRACK=2000              ; Level 2 timout in milliseconds&lt;br /&gt;
RESPTIME=40                    ; Level 2 delayed ack timer in milliseconds&lt;br /&gt;
RETRIES=20                     ; Level 2 maximum retry value&lt;br /&gt;
PACLEN=136                      ; Default max packet length for this port&lt;br /&gt;
UNPROTO=ID                     ; BTEXT broadcast addrs format: DEST[,digi1[,digi2]]&lt;br /&gt;
L3ONLY=0                       ; 1=No user downlink connects on this port&lt;br /&gt;
DIGIFLAG=0                     ; Digipeat: 0=OFF, 1=ALL, 255=UI Only&lt;br /&gt;
DIGIPORT=0                     ; Port on which to send digi&#039;d frames (0 = same port)&lt;br /&gt;
USERS=0                        ; Maximum number of L2 sessions, 0 = no limit&lt;br /&gt;
IGNOREUNLOCKEDROUTES=0  ; ignore node broadcasts from other than locked routes&lt;br /&gt;
IPADDR=127.0.0.1       ; Address of PC runnning the TNC. Usually this is the same machine as BPQ, so use 127.0.0.1 but can be on anoth&amp;gt;&lt;br /&gt;
TCPPORT=10001           ; Port TNC listens on. Default for UZ7HO is 8100 and Direwolf 8001&lt;br /&gt;
KISSOPTIONS=NOPARAMS&lt;br /&gt;
ENDPORT&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You also need to add a fixed route for port 11 or 12 in bpq32.cfg. The appropriate section looks like this, and has commented-out port 11 and 12 routes already listed:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
ROUTES: ; Locked routes (31 maximum)&lt;br /&gt;
KV4P-3,200,11&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, replace &amp;quot;KV4P-3&amp;quot; with whatever neighbor you plan to link to with that LoRa link.&lt;br /&gt;
&lt;br /&gt;
Start TARPN with ./linbpq (instead of &amp;quot;tarpn service start&amp;quot;). You won&#039;t be able to use TARPN HOME until TARPN is updated to handle TCP-based KISS TNCs natively. But you can manually connect to your node (e.g. with QtTerm), and get into chat that way, and test out your new LoRa link, etc.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=178</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=178"/>
		<updated>2023-08-14T23:26:51Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge) and &#039;&#039;&#039;if you have poor link conditions you can increase &amp;quot;spreadingfactor&amp;quot; above 7&#039;&#039;&#039;, which is the fastest this bonnet can handle:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 32 #valid preable lenght is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 5 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
#!/usr/bin/python3&lt;br /&gt;
# This program is free software: you can redistribute it and/or modify&lt;br /&gt;
# it under the terms of the GNU General Public License as published by&lt;br /&gt;
# the Free Software Foundation, either version 3 of the License, or&lt;br /&gt;
# (at your option) any later version.&lt;br /&gt;
#&lt;br /&gt;
# This program is distributed in the hope that it will be useful,&lt;br /&gt;
# but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
# GNU General Public License for more details.&lt;br /&gt;
#&lt;br /&gt;
# You should have received a copy of the GNU General Public License&lt;br /&gt;
# along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# This program provides basic KISS AX.25 APRS frame encoding and decoding.&lt;br /&gt;
# Note that only APRS relevant structures are tested. It might not work&lt;br /&gt;
# for generic AX.25 frames.&lt;br /&gt;
#&lt;br /&gt;
# Inspired by:&lt;br /&gt;
# * Python script to decode AX.25 from KISS frames over a serial TNC&lt;br /&gt;
#   https://gist.github.com/mumrah/8fe7597edde50855211e27192cce9f88&lt;br /&gt;
#&lt;br /&gt;
# * Sending a raw AX.25 frame with Python&lt;br /&gt;
#   https://thomask.sdf.org/blog/2018/12/15/sending-raw-ax25-python.html&lt;br /&gt;
#&lt;br /&gt;
#   KISS-TNC for LoRa radio modem&lt;br /&gt;
#   https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC&lt;br /&gt;
&lt;br /&gt;
import struct&lt;br /&gt;
import datetime&lt;br /&gt;
import config&lt;br /&gt;
&lt;br /&gt;
KISS_FEND = 0xC0  # Frame start/end marker&lt;br /&gt;
KISS_FESC = 0xDB  # Escape character&lt;br /&gt;
KISS_TFEND = 0xDC  # If after an escape, means there was an 0xC0 in the source message&lt;br /&gt;
KISS_TFESC = 0xDD  # If after an escape, means there was an 0xDB in the source message&lt;br /&gt;
&lt;br /&gt;
# APRS data types&lt;br /&gt;
DATA_TYPES_POSITION = b&amp;quot;!&#039;/@`&amp;quot;&lt;br /&gt;
DATA_TYPE_MESSAGE = b&amp;quot;:&amp;quot;&lt;br /&gt;
DATA_TYPE_THIRD_PARTY = b&amp;quot;}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
def logf(message):&lt;br /&gt;
    timestamp = datetime.datetime.now().strftime(&#039;%Y/%m/%d %H:%M:%S - &#039;)&lt;br /&gt;
    if config.log_enable:&lt;br /&gt;
       fileLog = open(config.logpath,&amp;quot;a&amp;quot;)&lt;br /&gt;
       fileLog.write(timestamp + message+&amp;quot;\n&amp;quot;)&lt;br /&gt;
       fileLog.close()&lt;br /&gt;
    print(timestamp + message)&lt;br /&gt;
&lt;br /&gt;
# from LoRa OE_Style to KISS&lt;br /&gt;
# Addresses must be 6 bytes plus the SSID byte, each character shifted left by 1&lt;br /&gt;
# If it&#039;s the final address in the header, set the low bit to 1&lt;br /&gt;
# If it has been digipeated, set the H bit to 1&lt;br /&gt;
# Ignoring command/response for simple example&lt;br /&gt;
def encode_address(s, final):&lt;br /&gt;
    H=0b00000000 #default H bit low&lt;br /&gt;
    if chr(s[-1])==&#039;*&#039;: #example: WIDE1-1* or WIDE1*&lt;br /&gt;
        s=s[:-1]&lt;br /&gt;
        H=0b10000000&lt;br /&gt;
    if b&amp;quot;-&amp;quot; not in s:&lt;br /&gt;
        s = s + b&amp;quot;-0&amp;quot;  # default to SSID 0&lt;br /&gt;
    call, ssid = s.split(b&#039;-&#039;)&lt;br /&gt;
    if len(call) &amp;lt; 6:&lt;br /&gt;
        call = call + b&amp;quot; &amp;quot;*(6 - len(call)) # pad with spaces&lt;br /&gt;
    encoded_call = [x &amp;lt;&amp;lt; 1 for x in call[0:6]]&lt;br /&gt;
    encoded_ssid = (int(ssid) &amp;lt;&amp;lt; 1) | H | 0b01100000 | (0b00000001 if final else 0)&lt;br /&gt;
    return encoded_call + [encoded_ssid]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def decode_address(data, cursor):&lt;br /&gt;
    (a1, a2, a3, a4, a5, a6, a7) = struct.unpack(&amp;quot;&amp;lt;BBBBBBB&amp;quot;, data[cursor:cursor + 7])&lt;br /&gt;
    hrr = a7 &amp;gt;&amp;gt; 5&lt;br /&gt;
    ssid = (a7 &amp;gt;&amp;gt; 1) &amp;amp; 0xf&lt;br /&gt;
    ext = a7 &amp;amp; 0x1&lt;br /&gt;
    addr = struct.pack(&amp;quot;&amp;lt;BBBBBB&amp;quot;, a1 &amp;gt;&amp;gt; 1, a2 &amp;gt;&amp;gt; 1, a3 &amp;gt;&amp;gt; 1, a4 &amp;gt;&amp;gt; 1, a5 &amp;gt;&amp;gt; 1, a6 &amp;gt;&amp;gt; 1)&lt;br /&gt;
    if ssid != 0:&lt;br /&gt;
        call = addr.strip() + &amp;quot;-{}&amp;quot;.format(ssid).encode()&lt;br /&gt;
    else:&lt;br /&gt;
        call = addr&lt;br /&gt;
    return (call, hrr, ext)&lt;br /&gt;
&lt;br /&gt;
def ax25parser(frame): #extracts fields from ax25 frames and add signal report do payload, if specified&lt;br /&gt;
    try:&lt;br /&gt;
        pos = 0&lt;br /&gt;
&lt;br /&gt;
        # DST&lt;br /&gt;
        (dest_addr, dest_hrr, dest_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;DST: &amp;quot;, dest_addr)&lt;br /&gt;
&lt;br /&gt;
        # SRC&lt;br /&gt;
        (src_addr, src_hrr, src_ext) = decode_address(frame, pos)&lt;br /&gt;
        pos += 7&lt;br /&gt;
        print(&amp;quot;SRC: &amp;quot;, src_addr)&lt;br /&gt;
&lt;br /&gt;
        # REPEATERS&lt;br /&gt;
        ext = src_ext&lt;br /&gt;
        rpt_list = b&amp;quot;&amp;quot;&lt;br /&gt;
        while ext == 0:&lt;br /&gt;
            rpt_addr, rpt_hrr, ext = decode_address(frame, pos)&lt;br /&gt;
            if rpt_hrr==b&#039;111&#039;: # H bit high--&amp;gt;packet has been digipeated&lt;br /&gt;
               rpt_addr+=b&#039;*&#039;&lt;br /&gt;
            rpt_list += b&amp;quot;,&amp;quot;+rpt_addr&lt;br /&gt;
            print(&amp;quot;RPT: &amp;quot;, rpt_addr)&lt;br /&gt;
            pos += 7&lt;br /&gt;
&lt;br /&gt;
        # CTRL&lt;br /&gt;
        ctrl = frame[pos]&lt;br /&gt;
        pos += 1&lt;br /&gt;
        if (ctrl &amp;amp; 0x3) == 0x3:&lt;br /&gt;
            #(pid,) = struct.unpack(&amp;quot;&amp;lt;B&amp;quot;, frame[pos])&lt;br /&gt;
            try:&lt;br /&gt;
                pid = frame[pos]&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pid = 240&lt;br /&gt;
            print(&amp;quot;PID: &amp;quot;+str(pid))&lt;br /&gt;
            pos += 1&lt;br /&gt;
        elif (ctrl &amp;amp; 0x3) == 0x1:&lt;br /&gt;
            # decode_sframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;SFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        elif (ctrl &amp;amp; 0x1) == 0x0:&lt;br /&gt;
            # decode_iframe(ctrl, frame, pos)&lt;br /&gt;
            logf(&amp;quot;IFRAME&amp;quot;)&lt;br /&gt;
            return None,None,None,None,None&lt;br /&gt;
        payload=frame[pos:]&lt;br /&gt;
        print(&amp;quot;payload: &amp;quot;, payload)&lt;br /&gt;
        try:&lt;br /&gt;
            dti=payload[0]&lt;br /&gt;
        except Exception:&lt;br /&gt;
            dti=&amp;quot;:&amp;quot;&lt;br /&gt;
        logf(&amp;quot;Extracted AX25 parameters from AX25 Frame&amp;quot;)&lt;br /&gt;
        logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+repr(rpt_list)[3:-1]+&amp;quot; PID: &amp;quot;+str(hex(pid))+&amp;quot; Payload: &amp;quot;+str(payload)[str(payl&lt;br /&gt;
oad).find(&amp;quot;&#039;&amp;quot;):-1])&lt;br /&gt;
&lt;br /&gt;
        return src_addr,dest_addr,rpt_list,payload,dti&lt;br /&gt;
    except Exception:&lt;br /&gt;
        return None,None,None,None,None&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_AX25(frame,signalreport): #from Lora to Kiss, Standard AX25&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        frame += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in frame:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to aprx and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def encode_kiss_OE(frame,signalreport): #from Lora to Kiss, OE_Style&lt;br /&gt;
    # Ugly frame disassembling&lt;br /&gt;
    if not b&amp;quot;:&amp;quot; in frame:&lt;br /&gt;
        logf(&amp;quot;Can&#039;t decode OE LoRa Frame&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    path = frame.split(b&amp;quot;:&amp;quot;)[0]&lt;br /&gt;
    payload = frame[frame.find(b&amp;quot;:&amp;quot;)+1:]&lt;br /&gt;
    dti = payload[0]&lt;br /&gt;
    src_addr = path.split(b&amp;quot;&amp;gt;&amp;quot;)[0]&lt;br /&gt;
    digis = path[path.find(b&amp;quot;&amp;gt;&amp;quot;) + 1:].split(b&amp;quot;,&amp;quot;)&lt;br /&gt;
    dest_addr = digis.pop(0)&lt;br /&gt;
&lt;br /&gt;
    # destination address&lt;br /&gt;
    packet = encode_address(dest_addr.upper(), False)&lt;br /&gt;
    # source address&lt;br /&gt;
    packet += encode_address(src_addr.upper(), len(digis) == 0)&lt;br /&gt;
    # digipeaters&lt;br /&gt;
    for digi in digis:&lt;br /&gt;
        final_addr = digis.index(digi) == len(digis) - 1&lt;br /&gt;
        packet += encode_address(digi.upper(), final_addr)&lt;br /&gt;
    # control field&lt;br /&gt;
    packet += [0x03]  # This is an UI frame&lt;br /&gt;
    # protocol ID&lt;br /&gt;
    packet += [0xF0]  # No protocol&lt;br /&gt;
    # information field&lt;br /&gt;
    logf(&amp;quot;Extracted AX25 parameters from OE LoRa Frame&amp;quot;)&lt;br /&gt;
    logf(&amp;quot;From: &amp;quot;+repr(src_addr)[2:-1]+&amp;quot; To: &amp;quot;+repr(dest_addr)[2:-1]+&amp;quot; Via: &amp;quot;+str(digis)[1:-1].replace(&amp;quot;b&amp;quot;,&amp;quot;&amp;quot;).replace(&amp;quot;&#039;&amp;quot;,&amp;quot;&amp;quot;)+&amp;quot; Payload: &amp;quot;+repr(payload)[2:-1])&lt;br /&gt;
    packet += payload&lt;br /&gt;
    if config.appendSignalReport and str(dti) != DATA_TYPE_MESSAGE:&lt;br /&gt;
        #some SW (es OE5BPA) append newline character at the end of packet. Must be cut for appending signal report&lt;br /&gt;
        if chr(packet[-1])==&amp;quot;\n&amp;quot;:&lt;br /&gt;
          packet=packet[:-1]&lt;br /&gt;
        packet += b&amp;quot; &amp;quot;+str.encode(signalreport,&#039;utf-8&#039;)&lt;br /&gt;
&lt;br /&gt;
    # Escape the packet in case either KISS_FEND or KISS_FESC ended up in our stream&lt;br /&gt;
    packet_escaped = []&lt;br /&gt;
    for x in packet:&lt;br /&gt;
        if x == KISS_FEND:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFEND]&lt;br /&gt;
        elif x == KISS_FESC:&lt;br /&gt;
            packet_escaped += [KISS_FESC, KISS_TFESC]&lt;br /&gt;
        else:&lt;br /&gt;
            packet_escaped += [x]&lt;br /&gt;
&lt;br /&gt;
    # Build the frame that we will send to Dire Wolf and turn it into a string&lt;br /&gt;
    kiss_cmd = 0x00  # Two nybbles combined - TNC 0, command 0 (send data)&lt;br /&gt;
    kiss_frame = [KISS_FEND, kiss_cmd] + packet_escaped + [KISS_FEND]&lt;br /&gt;
    try:&lt;br /&gt;
        #print(bytearray(kiss_frame).hex())&lt;br /&gt;
        output = bytearray(kiss_frame)&lt;br /&gt;
    except ValueError:&lt;br /&gt;
        logf(&amp;quot;Invalid value in frame.&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
    return output&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_OE(frame): #From Kiss to LoRa, OE_Style&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    #build OE_style frame piece by piece&lt;br /&gt;
    result = src_addr.strip()+b&amp;quot;&amp;gt;&amp;quot;+dest_addr.strip()+rpt_list+b&amp;quot;:&amp;quot;+payload&lt;br /&gt;
    return result&lt;br /&gt;
&lt;br /&gt;
def decode_kiss_AX25(frame): #from kiss to LoRA, Standard AX25&lt;br /&gt;
    result = b&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if frame[0] != 0xC0 or frame[len(frame) - 1] != 0xC0:&lt;br /&gt;
        logf(&amp;quot;Kiss Header not found, abort decoding of Frame: &amp;quot;+repr(frame))&lt;br /&gt;
        return None&lt;br /&gt;
    frame=frame[2:len(frame) - 1] #cut kiss delimitator 0xc0 and command 0x00&lt;br /&gt;
&lt;br /&gt;
    src_addr,dest_addr,rpt_list,payload,dti=ax25parser(frame) #only for logging&lt;br /&gt;
&lt;br /&gt;
    return frame&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class SerialParser():&lt;br /&gt;
    &#039;&#039;&#039;Simple parser for KISS frames. It handles multiple frames in one packet&lt;br /&gt;
    and calls the callback function on each frame&#039;&#039;&#039;&lt;br /&gt;
    STATE_IDLE = 0&lt;br /&gt;
    STATE_FEND = 1&lt;br /&gt;
    STATE_DATA = 2&lt;br /&gt;
    KISS_FEND = KISS_FEND&lt;br /&gt;
&lt;br /&gt;
    def __init__(self, frame_cb=None):&lt;br /&gt;
        self.frame_cb = frame_cb&lt;br /&gt;
        self.reset()&lt;br /&gt;
&lt;br /&gt;
    def reset(self):&lt;br /&gt;
        self.state = self.STATE_IDLE&lt;br /&gt;
        self.cur_frame = bytearray()&lt;br /&gt;
&lt;br /&gt;
    def parse(self, data):&lt;br /&gt;
        #Call parse with a string of one or more characters&lt;br /&gt;
        for c in data:&lt;br /&gt;
            if self.state == self.STATE_IDLE:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_FEND&lt;br /&gt;
            elif self.state == self.STATE_FEND:&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    self.reset()&lt;br /&gt;
                else:&lt;br /&gt;
                    self.cur_frame.append(c)&lt;br /&gt;
                    self.state = self.STATE_DATA&lt;br /&gt;
            elif self.state == self.STATE_DATA:&lt;br /&gt;
                self.cur_frame.append(c)&lt;br /&gt;
                if c == self.KISS_FEND:&lt;br /&gt;
                    # frame complete&lt;br /&gt;
                    if self.frame_cb:&lt;br /&gt;
                        self.frame_cb(self.cur_frame)&lt;br /&gt;
                    self.reset()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    # Playground for testing&lt;br /&gt;
&lt;br /&gt;
    kissframe = b&amp;quot;\xc0\x00\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V&lt;br /&gt;
\xc0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;OE&lt;br /&gt;
    print(decode_kiss_OE(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test decode KISS-&amp;gt;AX25&lt;br /&gt;
    print(decode_kiss_AX25(kissframe))&lt;br /&gt;
&lt;br /&gt;
    #test encode OE-&amp;gt;KISS&lt;br /&gt;
    OE_frame = b&amp;quot;OE9TKH-8&amp;gt;APRS,digi-3,digi-2:!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    signalreport=&amp;quot;Level:-115dBm, SNR:0dB&amp;quot;&lt;br /&gt;
    print(encode_kiss_OE(OE_frame,signalreport))&lt;br /&gt;
&lt;br /&gt;
    #test encode AX25-&amp;gt;KISS&lt;br /&gt;
    ax25_frame = b&amp;quot;\x82\xa0\xa4\xa6@@`\x9e\x8ar\xa8\x96\x90p\x88\x92\x8e\x92@@f\x88\x92\x8e\x92@@e\x03\xf0!4725.51N/00939.86E[322/002/A=001306 Batt=3.99V\n&amp;quot;&lt;br /&gt;
    print(encode_kiss_AX25(ax25_frame,signalreport))&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NOTE: The above KissHelper.py has not been optimized at all, it was just made to work with all KISS packets and made fault-tolerant so it doens&#039;t crash. The OE-based encoding can be removed, and many simplifications of the code can be made! This is the quick and dirty version.&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
On your TARPN node, you won&#039;t be able to run it with &amp;quot;tarpn service start&amp;quot; until TARPN is updated to allow TCP-based KISS TNCs on port 11 and 12. For now, you can force this by &amp;quot;tarpn service stop&amp;quot;, then editing bpq/bpq32.cfg.&lt;br /&gt;
&lt;br /&gt;
# nano bpq/bpq32.cfg&lt;br /&gt;
# Search for &amp;quot;PORTNUM=11&amp;quot; to find the right section&lt;br /&gt;
# Paste this in:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
PORT&lt;br /&gt;
PORTNUM=11                                ; Optional but sets port number if stated&lt;br /&gt;
ID=LoRa link to KV4P-3     ; Displayed by PORTS command&lt;br /&gt;
TYPE=ASYNC                                ; Port is RS232 Com&lt;br /&gt;
PROTOCOL=KISS                         ; TNC is used in KISS (or JKISS) mode&lt;br /&gt;
;; See ..\RelatedFiles\KissRoms\KissRoms.zip.&lt;br /&gt;
FULLDUP=0                      ; Only meaningful for KISS (or JKISS) devices&lt;br /&gt;
;COMPORT=/dev/ttyUSB0&lt;br /&gt;
;SPEED=57600                    ; RS232 COM PORT SPEED&lt;br /&gt;
CHANNEL=A                      ; A for single channel TNC, A or B for multichannel&lt;br /&gt;
PERSIST=180                    ; PERSIST=256/(# of transmitters-1)&lt;br /&gt;
SLOTTIME=50                    ; CMSA interval timer in milliseconds&lt;br /&gt;
TXDELAY=50          ; Transmit keyup delay in milliseconds&lt;br /&gt;
TXTAIL=1                       ; TX key down, in milliseconds, at packet end&lt;br /&gt;
QUALITY=1                      ; Quality factor applied to node broadcasts heard on&lt;br /&gt;
;                               ; this port, unless overridden by a locked route&lt;br /&gt;
;                               ; entry. Setting to 0 stops node broadcasts&lt;br /&gt;
MINQUAL=81                     ; Entries in the nodes table with qualities greater or&lt;br /&gt;
;                               ; equal to MINQUAL will be sent on this port. A value&lt;br /&gt;
;                               ; of 0 sends everything.&lt;br /&gt;
MAXFRAME=1                     ; Max outstanding frames (1 thru 7)&lt;br /&gt;
FRACK=2000              ; Level 2 timout in milliseconds&lt;br /&gt;
RESPTIME=40                    ; Level 2 delayed ack timer in milliseconds&lt;br /&gt;
RETRIES=20                     ; Level 2 maximum retry value&lt;br /&gt;
PACLEN=136                      ; Default max packet length for this port&lt;br /&gt;
UNPROTO=ID                     ; BTEXT broadcast addrs format: DEST[,digi1[,digi2]]&lt;br /&gt;
L3ONLY=0                       ; 1=No user downlink connects on this port&lt;br /&gt;
DIGIFLAG=0                     ; Digipeat: 0=OFF, 1=ALL, 255=UI Only&lt;br /&gt;
DIGIPORT=0                     ; Port on which to send digi&#039;d frames (0 = same port)&lt;br /&gt;
USERS=0                        ; Maximum number of L2 sessions, 0 = no limit&lt;br /&gt;
IGNOREUNLOCKEDROUTES=0  ; ignore node broadcasts from other than locked routes&lt;br /&gt;
IPADDR=127.0.0.1       ; Address of PC runnning the TNC. Usually this is the same machine as BPQ, so use 127.0.0.1 but can be on anoth&amp;gt;&lt;br /&gt;
TCPPORT=10001           ; Port TNC listens on. Default for UZ7HO is 8100 and Direwolf 8001&lt;br /&gt;
KISSOPTIONS=NOPARAMS&lt;br /&gt;
ENDPORT&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You also need to add a fixed route for port 11 or 12 in bpq32.cfg. The appropriate section looks like this, and has commented-out port 11 and 12 routes already listed:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
ROUTES: ; Locked routes (31 maximum)&lt;br /&gt;
KV4P-3,200,11&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, replace &amp;quot;KV4P-3&amp;quot; with whatever neighbor you plan to link to with that LoRa link.&lt;br /&gt;
&lt;br /&gt;
Start TARPN with ./linbpq (instead of &amp;quot;tarpn service start&amp;quot;). You won&#039;t be able to use TARPN HOME until TARPN is updated to handle TCP-based KISS TNCs natively. But you can manually connect to your node (e.g. with QtTerm), and get into chat that way, and test out your new LoRa link, etc.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=177</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=177"/>
		<updated>2023-08-14T23:24:39Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of [https://www.amazon.com/gp/product/B07GBM6Y4Z 16AWG magnet wire] (33cm band 1/4 wave antenna), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use) (~$12 for a spool)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge) and &#039;&#039;&#039;if you have poor link conditions you can increase &amp;quot;spreadingfactor&amp;quot; above 7&#039;&#039;&#039;, which is the fastest this bonnet can handle:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 32 #valid preable lenght is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 5 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
TODO get the contents of this file from one of my test devices (even better to put this on github instead)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
On your TARPN node, you won&#039;t be able to run it with &amp;quot;tarpn service start&amp;quot; until TARPN is updated to allow TCP-based KISS TNCs on port 11 and 12. For now, you can force this by &amp;quot;tarpn service stop&amp;quot;, then editing bpq/bpq32.cfg.&lt;br /&gt;
&lt;br /&gt;
# nano bpq/bpq32.cfg&lt;br /&gt;
# Search for &amp;quot;PORTNUM=11&amp;quot; to find the right section&lt;br /&gt;
# Paste this in:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
PORT&lt;br /&gt;
PORTNUM=11                                ; Optional but sets port number if stated&lt;br /&gt;
ID=LoRa link to KV4P-3     ; Displayed by PORTS command&lt;br /&gt;
TYPE=ASYNC                                ; Port is RS232 Com&lt;br /&gt;
PROTOCOL=KISS                         ; TNC is used in KISS (or JKISS) mode&lt;br /&gt;
;; See ..\RelatedFiles\KissRoms\KissRoms.zip.&lt;br /&gt;
FULLDUP=0                      ; Only meaningful for KISS (or JKISS) devices&lt;br /&gt;
;COMPORT=/dev/ttyUSB0&lt;br /&gt;
;SPEED=57600                    ; RS232 COM PORT SPEED&lt;br /&gt;
CHANNEL=A                      ; A for single channel TNC, A or B for multichannel&lt;br /&gt;
PERSIST=180                    ; PERSIST=256/(# of transmitters-1)&lt;br /&gt;
SLOTTIME=50                    ; CMSA interval timer in milliseconds&lt;br /&gt;
TXDELAY=50          ; Transmit keyup delay in milliseconds&lt;br /&gt;
TXTAIL=1                       ; TX key down, in milliseconds, at packet end&lt;br /&gt;
QUALITY=1                      ; Quality factor applied to node broadcasts heard on&lt;br /&gt;
;                               ; this port, unless overridden by a locked route&lt;br /&gt;
;                               ; entry. Setting to 0 stops node broadcasts&lt;br /&gt;
MINQUAL=81                     ; Entries in the nodes table with qualities greater or&lt;br /&gt;
;                               ; equal to MINQUAL will be sent on this port. A value&lt;br /&gt;
;                               ; of 0 sends everything.&lt;br /&gt;
MAXFRAME=1                     ; Max outstanding frames (1 thru 7)&lt;br /&gt;
FRACK=2000              ; Level 2 timout in milliseconds&lt;br /&gt;
RESPTIME=40                    ; Level 2 delayed ack timer in milliseconds&lt;br /&gt;
RETRIES=20                     ; Level 2 maximum retry value&lt;br /&gt;
PACLEN=136                      ; Default max packet length for this port&lt;br /&gt;
UNPROTO=ID                     ; BTEXT broadcast addrs format: DEST[,digi1[,digi2]]&lt;br /&gt;
L3ONLY=0                       ; 1=No user downlink connects on this port&lt;br /&gt;
DIGIFLAG=0                     ; Digipeat: 0=OFF, 1=ALL, 255=UI Only&lt;br /&gt;
DIGIPORT=0                     ; Port on which to send digi&#039;d frames (0 = same port)&lt;br /&gt;
USERS=0                        ; Maximum number of L2 sessions, 0 = no limit&lt;br /&gt;
IGNOREUNLOCKEDROUTES=0  ; ignore node broadcasts from other than locked routes&lt;br /&gt;
IPADDR=127.0.0.1       ; Address of PC runnning the TNC. Usually this is the same machine as BPQ, so use 127.0.0.1 but can be on anoth&amp;gt;&lt;br /&gt;
TCPPORT=10001           ; Port TNC listens on. Default for UZ7HO is 8100 and Direwolf 8001&lt;br /&gt;
KISSOPTIONS=NOPARAMS&lt;br /&gt;
ENDPORT&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You also need to add a fixed route for port 11 or 12 in bpq32.cfg. The appropriate section looks like this, and has commented-out port 11 and 12 routes already listed:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
ROUTES: ; Locked routes (31 maximum)&lt;br /&gt;
KV4P-3,200,11&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, replace &amp;quot;KV4P-3&amp;quot; with whatever neighbor you plan to link to with that LoRa link.&lt;br /&gt;
&lt;br /&gt;
Start TARPN with ./linbpq (instead of &amp;quot;tarpn service start&amp;quot;). You won&#039;t be able to use TARPN HOME until TARPN is updated to handle TCP-based KISS TNCs natively. But you can manually connect to your node (e.g. with QtTerm), and get into chat that way, and test out your new LoRa link, etc.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
	<entry>
		<id>https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=176</id>
		<title>LoRa port for TARPN node</title>
		<link rel="alternate" type="text/html" href="https://wiki.tarpn.net/index.php?title=LoRa_port_for_TARPN_node&amp;diff=176"/>
		<updated>2023-08-14T23:22:23Z</updated>

		<summary type="html">&lt;p&gt;KV4P: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;KV4P has been experimenting with adding LoRa ports to his TARPN node. LoRa is very desirable for TARPN because LoRa transceivers are very inexpensive yet have good range, are very small and low-power, and reduce the overall setup cost of a new link.&lt;br /&gt;
&lt;br /&gt;
Materials:&lt;br /&gt;
&lt;br /&gt;
* Raspberry Pi Zero 2 W (~$15 when in stock), WITH gpio header. You can use any Rapsberry Pi model for this, but this is what I used.&lt;br /&gt;
* [https://www.adafruit.com/product/4074 Adafruit LoRa Radio Bonnet with OLED - RFM95W @ 915MHz - RadioFruit] ($32.50)&lt;br /&gt;
* 78mm of magnet wire (33cm band 1/4 wave), soldered to the radio bonnet antenna center conductor via (right next to the connector we won&#039;t use)&lt;br /&gt;
&lt;br /&gt;
This assumes you already have a TARPN node. With this experimental guide, you&#039;ll be buiding a LoRa radio that shows up on your network as a TCP-based KISS TNC, which port 11 or port 12 of your TARPN node will connect to via the network.&lt;br /&gt;
&lt;br /&gt;
I&#039;m going to assume you already have the Raspberry Pi configured with Raspberry Pi OS, and connected to your local network (the same network as your TARPN node). You&#039;ll want to assign a static IP to it (rather than DHCP), so you can ensure your TARPN node will be able to find it on the network after restarts.&lt;br /&gt;
&lt;br /&gt;
=== Get LoRa bonnet working ===&lt;br /&gt;
You can read more about these steps on [https://learn.adafruit.com/adafruit-radio-bonnets/rfm9x-raspberry-pi-setup this Adafruit page].&lt;br /&gt;
&lt;br /&gt;
# sudo apt install python3-pip&lt;br /&gt;
# sudo pip3 install --upgrade setuptools&lt;br /&gt;
# sudo pip3 install --upgrade adafruit-python-shell&lt;br /&gt;
# wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin&lt;br /&gt;
# wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/raspi-blinka.py&lt;br /&gt;
# sudo python3 raspi-blinka.py &#039;&#039;&#039;(this will require reboot at the end)&#039;&#039;&#039;&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-ssd1306&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-framebuf&lt;br /&gt;
# sudo pip3 install adafruit-circuitpython-rfm9x&lt;br /&gt;
# create this test script rfm9x_check.py, which you should run (&amp;quot;python3 rfm9x_check.py&amp;quot;) to prove your bonnet is properly connected and working: &lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries&lt;br /&gt;
#&lt;br /&gt;
# SPDX-License-Identifier: MIT&lt;br /&gt;
&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Wiring Check, Pi Radio w/RFM9x&lt;br /&gt;
&lt;br /&gt;
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi&lt;br /&gt;
Author: Brent Rubell for Adafruit Industries&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
import time&lt;br /&gt;
import busio&lt;br /&gt;
from digitalio import DigitalInOut, Direction, Pull&lt;br /&gt;
import board&lt;br /&gt;
# Import the SSD1306 module.&lt;br /&gt;
import adafruit_ssd1306&lt;br /&gt;
# Import the RFM9x radio module.&lt;br /&gt;
import adafruit_rfm9x&lt;br /&gt;
&lt;br /&gt;
# Button A&lt;br /&gt;
btnA = DigitalInOut(board.D5)&lt;br /&gt;
btnA.direction = Direction.INPUT&lt;br /&gt;
btnA.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button B&lt;br /&gt;
btnB = DigitalInOut(board.D6)&lt;br /&gt;
btnB.direction = Direction.INPUT&lt;br /&gt;
btnB.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Button C&lt;br /&gt;
btnC = DigitalInOut(board.D12)&lt;br /&gt;
btnC.direction = Direction.INPUT&lt;br /&gt;
btnC.pull = Pull.UP&lt;br /&gt;
&lt;br /&gt;
# Create the I2C interface.&lt;br /&gt;
i2c = busio.I2C(board.SCL, board.SDA)&lt;br /&gt;
&lt;br /&gt;
# 128x32 OLED Display&lt;br /&gt;
reset_pin = DigitalInOut(board.D4)&lt;br /&gt;
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)&lt;br /&gt;
# Clear the display.&lt;br /&gt;
display.fill(0)&lt;br /&gt;
display.show()&lt;br /&gt;
width = display.width&lt;br /&gt;
height = display.height&lt;br /&gt;
&lt;br /&gt;
# Configure RFM9x LoRa Radio&lt;br /&gt;
CS = DigitalInOut(board.CE1)&lt;br /&gt;
RESET = DigitalInOut(board.D25)&lt;br /&gt;
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)&lt;br /&gt;
&lt;br /&gt;
while True:&lt;br /&gt;
    # Clear the image&lt;br /&gt;
    display.fill(0)&lt;br /&gt;
&lt;br /&gt;
    # Attempt to set up the RFM9x Module&lt;br /&gt;
    try:&lt;br /&gt;
        rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)&lt;br /&gt;
        display.text(&#039;RFM9x: Detected&#039;, 0, 0, 1)&lt;br /&gt;
    except RuntimeError as error:&lt;br /&gt;
        # Thrown on version mismatch&lt;br /&gt;
        display.text(&#039;RFM9x: ERROR&#039;, 0, 0, 1)&lt;br /&gt;
        print(&#039;RFM9x Error: &#039;, error)&lt;br /&gt;
&lt;br /&gt;
    # Check buttons&lt;br /&gt;
    if not btnA.value:&lt;br /&gt;
        # Button A Pressed&lt;br /&gt;
        display.text(&#039;Ada&#039;, width-85, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnB.value:&lt;br /&gt;
        # Button B Pressed&lt;br /&gt;
        display.text(&#039;Fruit&#039;, width-75, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
    if not btnC.value:&lt;br /&gt;
        # Button C Pressed&lt;br /&gt;
        display.text(&#039;Radio&#039;, width-65, height-7, 1)&lt;br /&gt;
        display.show()&lt;br /&gt;
        time.sleep(0.1)&lt;br /&gt;
&lt;br /&gt;
    display.show()&lt;br /&gt;
    time.sleep(0.1)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Turn it into a TCP KISS TNC ===&lt;br /&gt;
See [https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen/blob/main/INSTALL.md the github project] for more info.&lt;br /&gt;
# sudo apt install git aprx python3-rpi.gpio python3-spidev python3-pil python3-smbus&lt;br /&gt;
# git clone https://github.com/IZ7BOJ/RPi-LoRa-KISS-TNC-2ndgen.git&lt;br /&gt;
# cd RPi-LoRa-KISS-TNC-2ndgen&lt;br /&gt;
# git clone https://github.com/mayeranalytics/pySX127x.git&lt;br /&gt;
# sudo mv board_config.py pySX127x/SX127x/board_config.py&lt;br /&gt;
# sudo mv LoRa.py pySX127x/SX127x//LoRa.py&lt;br /&gt;
# Replace config.py with these contents. You can/should customize the frequency to anything in the 33cm band (but at least 500kHz from the band edge) and &#039;&#039;&#039;if you have poor link conditions you can increase &amp;quot;spreadingfactor&amp;quot; above 7&#039;&#039;&#039;, which is the fastest this bonnet can handle:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
## Config file for RPi-LoRa-KISS-TNC 2nd generation&lt;br /&gt;
&lt;br /&gt;
## Lora Module selection&lt;br /&gt;
sx127x = True #if True, enables sx127x family, else sx126x&lt;br /&gt;
&lt;br /&gt;
## Display enable and font&lt;br /&gt;
disp_en = False&lt;br /&gt;
font_size = 8&lt;br /&gt;
&lt;br /&gt;
## Log enable and path&lt;br /&gt;
log_enable = True&lt;br /&gt;
logpath=&#039;/var/log/lora/lora.log&#039; #log filename. Give r/w permission!&lt;br /&gt;
&lt;br /&gt;
## KISS Settings&lt;br /&gt;
# Where to listen?&lt;br /&gt;
# TCP_HOST can be &amp;quot;localhost&amp;quot;, &amp;quot;0.0.0.0&amp;quot; or a specific interface address&lt;br /&gt;
# TCP_PORT as configured in aprx.conf &amp;lt;interface&amp;gt; section&lt;br /&gt;
TCP_HOST = &amp;quot;0.0.0.0&amp;quot;&lt;br /&gt;
TCP_PORT = 10001&lt;br /&gt;
&lt;br /&gt;
## Hardware Settings&lt;br /&gt;
# See datasheets for detailed pinout.&lt;br /&gt;
# The default pin assignment refers to PCB designed by I8FUC.&lt;br /&gt;
# The user can wire the module by his own and change pin assignment.&lt;br /&gt;
# assign &amp;quot;-1&amp;quot; if the pin is not used&lt;br /&gt;
&lt;br /&gt;
# Settings valid for both SX126x and SX127x&lt;br /&gt;
busId = 0 #SPI Bus ID. Must be enabled on raspberry (sudo raspi-config). Default is 0&lt;br /&gt;
csId = 1 #SPI Chip Select pin. Valid values are 0 or 1&lt;br /&gt;
irqPin = 22 #DIO0 of sx127x and DIO1 of sx126x, used for IRQ in rx&lt;br /&gt;
&lt;br /&gt;
# Settings valid only for SX126x. Default pin assignment refers to the PCB schematic /doc/LoRa_RPi_Companion_2022.pdf&lt;br /&gt;
resetPin = 6&lt;br /&gt;
busyPin = 4&lt;br /&gt;
# If txen and rxen are disabled (=-1), then DIO2 will be set as RF Switch control&lt;br /&gt;
txenPin = 0 #In Ebyte modules, it&#039;s used for switching on the tx pa.&lt;br /&gt;
rxenPin = 1 #In Ebyte modules, it&#039;s used for switching on the rx lna&lt;br /&gt;
&lt;br /&gt;
# If Lora module has a TCXO, the following parameter must be True&lt;br /&gt;
# If True, the DIO3 line will be set as control voltage of the TCXO&lt;br /&gt;
tcxo=False&lt;br /&gt;
&lt;br /&gt;
## LoRa Settings valid for both SX127x and SX126x modules&lt;br /&gt;
frequency = 910300000 #frequency in Hz&lt;br /&gt;
preamble = 32 #valid preable lenght is 8/16/24/32&lt;br /&gt;
spreadingFactor = 7 #valid spreading factor is between 5 and 12&lt;br /&gt;
bandwidth = 500000 #possible BW values: 7800, 10400,15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000&lt;br /&gt;
codingrate = 5 #valid code rate denominator is between 5 and 8&lt;br /&gt;
appendSignalReport = False #append signal report when packets are forwarded to aprs server&lt;br /&gt;
outputPower = 17 #maximum TX power is 22(22dBm) for SX126x, and 17 (17dBm) for SX127x . Higher values will be forced to max allowed!&lt;br /&gt;
TX_OE_Style = False #if True, tx RF packets are in OE Style, otherwise in standard AX25&lt;br /&gt;
#sync_word = 0x1424 #sync word is x4y4. Es: 0x12 of 1st gen LoRa chip --&amp;gt; 0x1424 of 2nd gen LoRa chip&lt;br /&gt;
sync_word = 0x12&lt;br /&gt;
crc = True #defines if CRC is calculated and transmitted in the header. Note that modem works in explicit mode&lt;br /&gt;
&lt;br /&gt;
#LoRa Settings valid only for SX126x&lt;br /&gt;
RX_GAIN_POWER_SAVING = False #If false, receiver is set in boosted gain mode (needs more power)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This github project needs more modification to work with TARPN, since it was originally written only for APRS packets and will break with any other kind. It also assumed ~400MHz, whereas the Adafruit bonnet is 33cm (902-928MHz). Here is how to update it (TODO these should be in a forked github project to remove these steps):&lt;br /&gt;
&lt;br /&gt;
# nano pySX127x/SX127x/board_config.py &#039;&#039;&#039;(Change low_band = True, to False for 33cm support)&#039;&#039;&#039;&lt;br /&gt;
# nano pySX127x/SX127x/LoRa.py &#039;&#039;&#039;(Search for “43” and change multiple occurences to 915MHz)&#039;&#039;&#039;&lt;br /&gt;
# nano LoraAprsKissTnc_sx127x.py &#039;&#039;&#039;(Ditto, there&#039;s only 1 occurence in this one)&#039;&#039;&#039;&lt;br /&gt;
# sudo mkdir /var/log/lora&lt;br /&gt;
# sudo touch /var/log/lora/lora.log&lt;br /&gt;
# Replace KissHelper.py with this contents (this extends it to handle all KISS packets not just APRS, and makes it a little more tolerant of unexpected failures rather than just stopping the process at the first weird packet):&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
TODO get the contents of this file from one of my test devices (even better to put this on github instead)&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start/test your TCP-based KISS TNC with this command (you should see it print out the config values and say it&#039;s listening for connections, with no errors listed):&lt;br /&gt;
&lt;br /&gt;
* sudo python3 Start_lora-tnc.py&lt;br /&gt;
&lt;br /&gt;
When you run the TNC for long-term use, you&#039;ll want to run it with &amp;quot;nohup sudo python3 Start_lora-tnc.py &amp;amp;&amp;quot; which will disassociate it with your linux account and run it in the background until manually terminated. In the future, we should wrap this in a systemd service.&lt;br /&gt;
&lt;br /&gt;
=== TARPN configuration ===&lt;br /&gt;
&lt;br /&gt;
On your TARPN node, you won&#039;t be able to run it with &amp;quot;tarpn service start&amp;quot; until TARPN is updated to allow TCP-based KISS TNCs on port 11 and 12. For now, you can force this by &amp;quot;tarpn service stop&amp;quot;, then editing bpq/bpq32.cfg.&lt;br /&gt;
&lt;br /&gt;
# nano bpq/bpq32.cfg&lt;br /&gt;
# Search for &amp;quot;PORTNUM=11&amp;quot; to find the right section&lt;br /&gt;
# Paste this in:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
PORT&lt;br /&gt;
PORTNUM=11                                ; Optional but sets port number if stated&lt;br /&gt;
ID=LoRa link to KV4P-3     ; Displayed by PORTS command&lt;br /&gt;
TYPE=ASYNC                                ; Port is RS232 Com&lt;br /&gt;
PROTOCOL=KISS                         ; TNC is used in KISS (or JKISS) mode&lt;br /&gt;
;; See ..\RelatedFiles\KissRoms\KissRoms.zip.&lt;br /&gt;
FULLDUP=0                      ; Only meaningful for KISS (or JKISS) devices&lt;br /&gt;
;COMPORT=/dev/ttyUSB0&lt;br /&gt;
;SPEED=57600                    ; RS232 COM PORT SPEED&lt;br /&gt;
CHANNEL=A                      ; A for single channel TNC, A or B for multichannel&lt;br /&gt;
PERSIST=180                    ; PERSIST=256/(# of transmitters-1)&lt;br /&gt;
SLOTTIME=50                    ; CMSA interval timer in milliseconds&lt;br /&gt;
TXDELAY=50          ; Transmit keyup delay in milliseconds&lt;br /&gt;
TXTAIL=1                       ; TX key down, in milliseconds, at packet end&lt;br /&gt;
QUALITY=1                      ; Quality factor applied to node broadcasts heard on&lt;br /&gt;
;                               ; this port, unless overridden by a locked route&lt;br /&gt;
;                               ; entry. Setting to 0 stops node broadcasts&lt;br /&gt;
MINQUAL=81                     ; Entries in the nodes table with qualities greater or&lt;br /&gt;
;                               ; equal to MINQUAL will be sent on this port. A value&lt;br /&gt;
;                               ; of 0 sends everything.&lt;br /&gt;
MAXFRAME=1                     ; Max outstanding frames (1 thru 7)&lt;br /&gt;
FRACK=2000              ; Level 2 timout in milliseconds&lt;br /&gt;
RESPTIME=40                    ; Level 2 delayed ack timer in milliseconds&lt;br /&gt;
RETRIES=20                     ; Level 2 maximum retry value&lt;br /&gt;
PACLEN=136                      ; Default max packet length for this port&lt;br /&gt;
UNPROTO=ID                     ; BTEXT broadcast addrs format: DEST[,digi1[,digi2]]&lt;br /&gt;
L3ONLY=0                       ; 1=No user downlink connects on this port&lt;br /&gt;
DIGIFLAG=0                     ; Digipeat: 0=OFF, 1=ALL, 255=UI Only&lt;br /&gt;
DIGIPORT=0                     ; Port on which to send digi&#039;d frames (0 = same port)&lt;br /&gt;
USERS=0                        ; Maximum number of L2 sessions, 0 = no limit&lt;br /&gt;
IGNOREUNLOCKEDROUTES=0  ; ignore node broadcasts from other than locked routes&lt;br /&gt;
IPADDR=127.0.0.1       ; Address of PC runnning the TNC. Usually this is the same machine as BPQ, so use 127.0.0.1 but can be on anoth&amp;gt;&lt;br /&gt;
TCPPORT=10001           ; Port TNC listens on. Default for UZ7HO is 8100 and Direwolf 8001&lt;br /&gt;
KISSOPTIONS=NOPARAMS&lt;br /&gt;
ENDPORT&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You also need to add a fixed route for port 11 or 12 in bpq32.cfg. The appropriate section looks like this, and has commented-out port 11 and 12 routes already listed:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
ROUTES: ; Locked routes (31 maximum)&lt;br /&gt;
KV4P-3,200,11&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Of course, replace &amp;quot;KV4P-3&amp;quot; with whatever neighbor you plan to link to with that LoRa link.&lt;br /&gt;
&lt;br /&gt;
Start TARPN with ./linbpq (instead of &amp;quot;tarpn service start&amp;quot;). You won&#039;t be able to use TARPN HOME until TARPN is updated to handle TCP-based KISS TNCs natively. But you can manually connect to your node (e.g. with QtTerm), and get into chat that way, and test out your new LoRa link, etc.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
With the above config.py settings, a throughput test on my own TARPN node with a &amp;quot;bench setup&amp;quot; (2 LoRa transceivers right next to each other), I achieved 135bytes/sec. That&#039;s about as good as a very solid 9600 baud link with a NinoTNC and high quality mobile radios.&lt;br /&gt;
&lt;br /&gt;
It can probably be made to go much faster with additional optimization. It&#039;s unclear if the KISS service running in Python represents a cap on the throughput, or if it&#039;s just the settings.&lt;br /&gt;
&lt;br /&gt;
Although the distance of this link hasn&#039;t been tested yet, the LoRa module claims to work between 4km and 40km (from city to perfectly flat ideal conditions). 33cm has the nice property of easily going through windows and walls, so it should be especially good for suburban links, or between nodes with a fairly clear shot.&lt;/div&gt;</summary>
		<author><name>KV4P</name></author>
	</entry>
</feed>